Skip to content

Commit

Permalink
Merge branch 'main' into HAN-8
Browse files Browse the repository at this point in the history
  • Loading branch information
violetaperezandrade committed Feb 1, 2024
2 parents fea6bad + 86ede75 commit 7dc911c
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 55 deletions.
5 changes: 5 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RABBITMQ_HOST=
QUEUE_NAME=
LOGGING_LEVEL=
WEATHER_API_KEY=
WEATHER_API_URL=
31 changes: 31 additions & 0 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

name: Linters

on: 'push'

jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest

steps:
- name: Check out Git repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.8

- name: Install Python dependencies
run: pip install black flake8

- name: Run linters
uses: wearerequired/lint-action@v1
with:
black: true

- name: flake8 Lint
uses: py-actions/flake8@v1.2.0
with:
max-line-length: "88"
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.9.7-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY src src

CMD ["python", "src/main.py"]
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
default: docker-compose-up

all:

docker-image:
docker build -f Dockerfile -t main-app .
.PHONY: docker-image

docker-compose-up: docker-image
docker-compose -f docker-compose.yaml up -d --build
.PHONY: docker-compose-up

docker-compose-down:
docker-compose -f docker-compose.yaml stop -t 20
docker-compose -f docker-compose.yaml down --remove-orphans
.PHONY: docker-compose-down

docker-compose-logs:
docker-compose -f docker-compose.yaml logs -f
.PHONY: docker-compose-logs
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,17 @@ This parameters are fetched from the [OpenWeatherMap API](https://openweathermap

## Commands
It would be nice to accept commands from TUI to simulate deviations fixes. Something like `<parameter> <increase/decrease> <amount>`

## Usage Instructions
The repository includes a **Makefile** that encapsulates various commands used frequently in the project as targets. The targets are executed by invoking:

* **make \<target\>**:
The essential targets to start and stop the system are **docker-compose-up** and **docker-compose-down**, with the remaining targets being useful for debugging and troubleshooting.

Available targets are:
* **docker-compose-up**: Initializes the development environment (builds docker images for the server and client, initializes the network used by docker, etc.) and starts the containers of the applications that make up the project.
* **docker-compose-down**: Performs a `docker-compose stop` to stop the containers associated with the compose and then performs a `docker-compose down` to destroy all resources associated with the initialized project. It is recommended to execute this command at the end of each run to prevent the host machine's disk from filling up.
* **docker-compose-logs**: Allows viewing the current logs of the project. Use with `grep` to filter messages from a specific application within the compose.
* **docker-image**: Builds the images to be used. This target is used by **docker-compose-up**, so it can be used to test new changes in the images before starting the project.

Important Note: This service assumes a running instance of RabbitMQ and connects to it. Therefore, to run this service, it is necessary to first have the **measurements** service running. Please make sure to also check [measurements repository](https://github.com/Hanagotchi/measurements).
16 changes: 16 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.9'

services:
main-app:
build:
context: .
dockerfile: Dockerfile
image: main-app
env_file:
- .env
networks:
- common_network

networks:
common_network:
external: true
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pika
requests
python-dotenv
52 changes: 52 additions & 0 deletions src/common/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import pika
import os


class Middleware:

def __init__(self):
rabbitmq_host = os.environ.get("RABBITMQ_HOST", "localhost")
self._connection = pika.BlockingConnection(
pika.ConnectionParameters(host=rabbitmq_host)
)
self._channel = self._connection.channel()
self._exit = False
self._remake = False

def create_queue(self, queue_name):
self._channel.queue_declare(queue=queue_name)

def _setup_message_consumption(self, queue_name, user_function):
self._channel.basic_consume(queue=queue_name,
on_message_callback=lambda channel,
method, properties, body:
(user_function(body),
channel.basic_ack
(delivery_tag=method.delivery_tag),
self._verify_connection_end()))
self._channel.start_consuming()

def _verify_connection_end(self):
if self._exit:
self._channel.close()
if self._remake:
self._exit = False
self._channel = self._connection.channel()

def finish(self, open_new_channel=False):
self._exit = True
self._remake = open_new_channel

# Work queue methods
def listen_on(self, queue_name, user_function):
self.create_queue(queue_name)
self._channel.basic_qos(prefetch_count=30)
self._setup_message_consumption(queue_name, user_function)

def send_message(self, queue_name, message):
self._channel.basic_publish(exchange='',
routing_key=queue_name,
body=message)

def __del__(self):
self._connection.close()
175 changes: 129 additions & 46 deletions src/data_packet.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,167 @@
import random as rnd
import requests as req
from requests import Response
from dotenv import load_dotenv
from os import environ
from typing import Tuple
from datetime import datetime
import logging
import math
import uuid

load_dotenv()
logging.getLogger("urllib3").setLevel(logging.WARNING)
UUID = str(uuid.uuid4()).replace("-", "")


def fetch_temperature_and_humidity(location: str) -> Tuple[int, int]:

base_params = {
"q": location,
"APPID": environ['WEATHER_API_KEY']
}
res: Response = req.get(environ["WEATHER_API_URL"], params=base_params)

if not res.ok:
raise Exception(
"Could not fetch temperature and humidity from weather API"
)

result = res.json()
temperature = int(result["main"]["temp"] - 273.15)
humidity = result["main"]["humidity"]

return temperature, humidity


def get_decimal_hour():
current_hour = datetime.now()
return current_hour.hour + (
60 * current_hour.minute + current_hour.second
) / 3600


def solar_irradiation_simulator(x):
if x < 7 or x > 19:
return 0

x += 14
x *= (math.pi / 6)
x = math.sin(x)
x += 1
x *= 500
return x

'''
Creates a data packet with simulated data, validating the data before that.

If all the parameters are empty, returns None. Else, return a dictionary with the data.
def fetch_solar_irradiation():
x = get_decimal_hour()
return round(solar_irradiation_simulator(x))


Constraints:
- Humidity and Watering should be between 0 and 100, since they are percentages.
- Light has to be positive or 0.
'''
def watering_simulator(x):
x %= 6
x /= 6
x *= 100
x = 100 - x
return x


def create_packet(temperature: float = None, humidity: float = None, light: float = None, watering: float = None):
if not (temperature and humidity and light and watering):
def fetch_watering():
x = get_decimal_hour()
return round(watering_simulator(x))


def create_packet(temperature: float = None, humidity: float = None,
light: float = None, watering: float = None):
'''
Creates a data packet with simulated data, validating the data before that.
If all the parameters are empty, returns None. Else, return a dictionary
with the data.
Constraints:
- Humidity and Watering should be between 0 and 100, since they are
percentages.
- Light has to be positive or 0.
'''
if not (temperature or humidity or light or watering):
return None

if humidity < 0 or humidity > 100:
raise Exception(f"Humidity has to be between 0 and 100. Current value: {humidity}")
raise Exception(f"Humidity has to be between 0 and 100."
f"Current value: {humidity}")

if watering < 0 or watering > 100:
raise Exception(f"Watering has to be between 0 and 100. Current value: {watering}")
raise Exception(f"Watering has to be between 0 and 100. "
f"Current value: {watering}")

if light < 0:
raise Exception(f"Light has to be positive or 0. Current value: {light}")
raise Exception(f"Light has to be positive or 0. "
f"Current value: {light}")

return {
"temperature": temperature,
"humidity": humidity,
"light": light,
"watering": watering
"watering": watering,
"time_stamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"id_device": UUID
}


'''
Generates the parameters data: temperature, humidity, light and watering.
def generate_data(location="Pilar, AR") -> Tuple[int, int, int, int]:
'''
Generates the parameters data: temperature, humidity, light and watering.
[Describe the criteria of every parameter here]
-
-
-
'''
[Describe the criteria of every parameter here]
-
-
-
'''


def generate_data():
# TODO
temperature = rnd.randint(-5, 30)
humidity = rnd.randint(0, 100)
light = rnd.randint(0, 400)
watering = rnd.randint(0, 100)
temperature, humidity = fetch_temperature_and_humidity(location)
# esto no va a producir cambios instantaneamente, pero es para probar
light = fetch_solar_irradiation()
watering = fetch_watering()

return temperature, humidity, light, watering


'''
Compares the current packet and the last sent packet, based in the deviations.
If any parameter differs enough from the last sent packet, then the packet
must be sent.
def data_has_changed(current, last_sent, deviations):
'''
Compares the current packet and the last sent packet,
based in the deviations.
In other words: if (|current[parameter] - last_sent[parameter]| > deviations[parameter]), return True.
Else, return False
If any parameter differs enough from the last sent packet, then the packet
must be sent.
If there is no last_sent, then the current must be sent.
In other words:
if (|current[parameter] - last_sent[parameter]| > deviations[parameter]),
return True.
Else, return False
Types:
- current: {temperature: float, humidity: float, light: float, watering: float}
- last_sent: {temperature: float, humidity: float, light: float, watering: float}
- deviations: {temperature: float, humidity: float, light: float, watering: float}
'''
If there is no last_sent, then the current must be sent.
Types:
- current: {temperature: float, humidity: float, light: float,
watering: float}
- last_sent: {temperature: float, humidity: float, light: float,
watering: float}
- deviations: {temperature: float, humidity: float, light: float,
watering: float}
'''

def data_has_changed(current, last_sent, deviations):
if not last_sent:
return True

if parameter_has_changed(current["temperature"], last_sent["temperature"], deviations["temperature"])\
or parameter_has_changed(current["humidity"], last_sent["humidity"], deviations["humidity"])\
or parameter_has_changed(current["light"], last_sent["light"], deviations["light"])\
or parameter_has_changed(current["watering"], last_sent["watering"], deviations["watering"]):
if parameter_has_changed(current["temperature"], last_sent["temperature"],
deviations["temperature"])\
or parameter_has_changed(current["humidity"], last_sent["humidity"],
deviations["humidity"])\
or parameter_has_changed(current["light"], last_sent["light"],
deviations["light"])\
or parameter_has_changed(current["watering"], last_sent["watering"],
deviations["watering"]):
return True

return False
Expand Down
Loading

0 comments on commit 7dc911c

Please sign in to comment.