Skip to content

Commit

Permalink
Merge pull request #5 from AndreWohnsland/dev
Browse files Browse the repository at this point in the history
v1.2 Merge
  • Loading branch information
AndreWohnsland authored Oct 21, 2021
2 parents 07245b9 + 5996b8d commit 30efdcb
Show file tree
Hide file tree
Showing 66 changed files with 5,061 additions and 975 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ venv
*.csv
__pycache__
.env
.venv
.venv
team.db
Cocktail_database.db
failed_data.db
Binary file not shown.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019 Andre Wohnsland
Copyright (c) 2021 Andre Wohnsland

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
10 changes: 10 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
--------------- 2021_10_19 ---------------------------------------------------------------------------------------------------------
- release of v1.2
- Introduced teams to have a competetive point
- Added Dashboard for display of teams
- Added API Service for teams
- Introduced type hints a many places, more docs
- Some refactoring
- Removed global vars and switched to Shared class
- Switched to default db and the creation of local db, that git won't overwrite local db

--------------- 2020_12_20 ---------------------------------------------------------------------------------------------------------
- Introduced microservice to post tasks to
- This includes an endpoint to make a post of the cocktail name and date to an webhook / endpoint
Expand Down
5 changes: 5 additions & 0 deletions cocktail.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Desktop Entry]
Type=Application
Name=CocktailScreen
NoDisplay=false
Exec=/usr/bin/lxterminal -e /home/pi/launcher.sh
60 changes: 45 additions & 15 deletions config/config_manager.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
class ConfigManager:
"""Manager for all static configuration of the machine """

MASTERPASSWORD = "1337"
USEDPINS = [14, 15, 18, 23, 24, 25, 8, 7, 17, 27, 22, 20]
PUMP_VOLUMEFLOW = [30, 30, 25, 30, 30, 30, 25, 30, 30, 23, 30, 30]
NUMBER_BOTTLES = 10
CLEAN_TIME = 20
SLEEP_TIME = 0.05
PARTYMODE = False
LOGGERNAME = "cocktaillogger"
LOGGERNAME_DEBUG = "debuglogger"
USE_MICROSERVICE = True
MICROSERVICE_BASE_URL = "http://127.0.0.1:5000"
DEVENVIRONMENT = True
class ConfigManager:
"""Manager for all static configuration of the machine """

# Password to lock clean, delete and other critical operators
MASTERPASSWORD = "1337"
# RPi pins where pumps (ascending) are connected
USEDPINS = [14, 15, 18, 23, 24, 25, 8, 7, 17, 27, 22, 20]
# Volumeflow for the according pumps
PUMP_VOLUMEFLOW = [30, 30, 25, 30, 30, 30, 25, 30, 30, 23, 30, 30]
# Number of bottles possible at the machine
NUMBER_BOTTLES = 10
# Time in seconds to execute clean programm
CLEAN_TIME = 20
# time between each check loop when making cocktail
SLEEP_TIME = 0.05
# Locks the recipe tab, making it impossible to acesss
PARTYMODE = False
# Names for the according logger files
LOGGERNAME = "cocktaillogger"
LOGGERNAME_DEBUG = "debuglogger"
# If to use microservice (mostly docker on same device) to handle external API calls and according url
USE_MICROSERVICE = False
MICROSERVICE_BASE_URL = "http://127.0.0.1:5000"
# if to use the teams function and according options.
# URL should be 'device_ip:8080' where dashboard container is running and in the same network
# Button names must be two strings in the list
USE_TEAMS = True
TEAM_BUTTON_NAMES = ["Team 1", "Team 2"]
TEAM_API_URL = "http://127.0.0.1:8080"
# Activating some dev features like mouse cursor
DEVENVIRONMENT = True


class Shared:
"""Shared global variables which may dynamically change and are needed on different spaces"""

def __init__(self):
self.cocktail_started = False
self.make_cocktail = True
self.supress_error = False
self.old_ingredient = []
self.selected_team = "Nothing"


shared = Shared()
12 changes: 12 additions & 0 deletions dashboard/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.8-slim-buster

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]
81 changes: 81 additions & 0 deletions dashboard/backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import os
import datetime
from pathlib import Path
import sqlite3
from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

DATABASE_NAME = "team"
DIRPATH = os.path.dirname(__file__)
database_path = os.path.join(DIRPATH, "storage", f"{DATABASE_NAME}.db")

app = FastAPI()


class Teaminfo(BaseModel):
team: str
volume: int


class BoardConfig(BaseModel):
hourrange: Optional[int] = None
limit: Optional[int] = 5
count: Optional[bool] = True


@app.get("/")
def home():
return {"message": "Welcome to dashboard api"}


@app.post("/cocktail")
async def enter_cocktail_for_team(team: Teaminfo):
conn = sqlite3.connect(database_path)
cursor = conn.cursor()
entry_datetime = datetime.datetime.now().replace(microsecond=0)
sql = "INSERT INTO TEAM(Date, Team, Volume) VALUES(?,?,?)"
cursor.execute(sql, (entry_datetime, team.team, team.volume,))
conn.commit()
conn.close()
return {"message": "Team entry was Successfull", "team": team.team, "volume": team.volume}


def get_leaderboard(hourrange=None, limit=2, count=True):
addition = ""
if hourrange is not None:
addition = f" WHERE Date >= datetime('now','-{hourrange} hours')"
agg = "count(*)" if count else "sum(Volume)"
conn = sqlite3.connect(database_path)
cursor = conn.cursor()
sql = f"SELECT Team, {agg} as amount FROM Team{addition} GROUP BY Team ORDER BY {agg} DESC LIMIT ?"
cursor.execute(sql, (limit,))
return_data = dict(cursor.fetchall())
conn.close()
return return_data


@app.get("/leaderboard")
def leaderboard(conf: BoardConfig):
return get_leaderboard(conf.hourrange, conf.limit, conf.count)


def create_tables():
conn = sqlite3.connect(database_path)
cursor = conn.cursor()
cursor.execute(
"""CREATE TABLE IF NOT EXISTS
Team(Date DATETIME NOT NULL,
Team TEXT NOT NULL,
Volume INTEGER NOT NULL);"""
)
conn.commit()
conn.close()


if __name__ == "__main__":
if not Path(database_path).exists():
print("creating Database")
create_tables()
uvicorn.run("main:app", host="0.0.0.0", port=8080)
2 changes: 2 additions & 0 deletions dashboard/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fastapi==0.70.0
uvicorn==0.15.0
Empty file.
21 changes: 21 additions & 0 deletions dashboard/docker-compose.both.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: '3'

services:
frontend:
container_name: cocktail-dashboard-frontend
restart: always
build: frontend
ports:
- 8501:8501
depends_on:
- backend
volumes:
- ./storage:/app/storage
backend:
container_name: cocktail-dashboard-backend
restart: always
build: backend
ports:
- 8080:8080
volumes:
- ./storage:/app/storage
12 changes: 12 additions & 0 deletions dashboard/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: '3'

services:
backend:
image: cocktail-dashboard-backend
container_name: cocktail-dashboard-backend
restart: always
build: backend
ports:
- 8080:8080
volumes:
- ./storage:/app/storage
12 changes: 12 additions & 0 deletions dashboard/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.9-slim-buster

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py", "--theme.base", "dark"]
116 changes: 116 additions & 0 deletions dashboard/frontend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import math
import os
import sqlite3
import streamlit as st
from streamlit_autorefresh import st_autorefresh
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
from pywaffle import Waffle
import warnings

st.set_page_config(
page_title="Cocktail Dashboard",
page_icon="🍸",
layout="wide",
initial_sidebar_state="collapsed",
)
warnings.filterwarnings("ignore", 'Starting a Matplotlib GUI outside of the main thread will likely fail.')
mpl.rcParams.update({'text.color': "white", 'axes.labelcolor': "white"})

DATABASE_NAME = "team"
DIRPATH = os.path.dirname(__file__)
database_path = os.path.join(DIRPATH, "storage", f"{DATABASE_NAME}.db")


def get_leaderboard(hourrange=None, limit=2, count=True):
addition = ""
if hourrange is not None:
addition = f" WHERE Date >= datetime('now','-{hourrange} hours')"
agg = "count(*)" if count else "sum(Volume)"
conn = sqlite3.connect(database_path)
SQL = f"SELECT Team, {agg} as amount FROM Team{addition} GROUP BY Team ORDER BY {agg} DESC LIMIT ?"
board = pd.read_sql(SQL, conn, params=(limit,))
board.reset_index(drop=True, inplace=True)
conn.close()
return board


def sort_dict_items(to_sort: dict):
dictionary_items = to_sort.items()
sorted_items = sorted(dictionary_items)
return {x[0]: x[1] for x in sorted_items}


def extract_data(sort: bool, df: pd.DataFrame):
if df.empty or sum(df.amount.to_list()) < 3:
waffle_data = {"Cocktails trinken zum starten ...": 3}
else:
waffle_data = {f"{x} ({y})": y for x, y in zip(df.Team.to_list(), df.amount.to_list())}
if sort:
waffle_data = sort_dict_items(waffle_data)
return waffle_data


def generate_dimensions(total: float, count=True):
proportion = 3
threshold_upper = 1.2
threshold_lower = 0.77
one_row_until = 9
# use fixed grid for non count variables
if not count:
return {"rows": 10, "columns": 25}
# don't split until given dimension
if total < one_row_until:
return {"rows": 1}
row = max(math.floor(math.sqrt(total / proportion)), 1)
# calculate current proportion, add one if exceeds th
column = math.ceil(total / row)
real_prop = column / row
if real_prop >= proportion * threshold_upper:
row += 1
# calculates adjusted proportion, rolls back if its too extreme
column = math.ceil(total / row)
adjusted_prop = column / row
if adjusted_prop <= proportion * threshold_lower:
row -= 1
return {"rows": row}


def generate_figure(title: str, hourrange: int = None, limit=5, sort=False, count=True):
df = get_leaderboard(hourrange, limit, count)
waffle_data = extract_data(sort, df)
dims = generate_dimensions(sum(df.amount.to_list()), count)
fig = plt.figure(
FigureClass=Waffle,
**dims,
values=waffle_data,
title={
'label': title,
'fontdict': {
'fontsize': 20
}
},
facecolor=(0.054, 0.066, 0.090, 1),
legend={'loc': 'upper center', 'bbox_to_anchor': (0.5, 0.0), 'ncol': 2, 'framealpha': 0}
)
return fig


st_autorefresh(interval=15000, key="autorefresh")
st.markdown(""" <style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
.block-container {padding: 1rem 1rem 1rem 1rem !important;}
</style> """, unsafe_allow_html=True)

st.sidebar.header("Zeit aussuchen")
selected_display = st.sidebar.radio("", ("Heute", "All time"))
st.sidebar.header("Aggregation aussuchen")
selected_type = st.sidebar.radio("", ("Anzahl", "Volumen"))
use_count = selected_type == "Anzahl"

if selected_display == "Heute":
st.pyplot(generate_figure(title=f"Leaderboard ({selected_type}, Heute)", hourrange=24, sort=True, count=use_count))
else:
st.pyplot(generate_figure(title=f"Leaderboard ({selected_type}, All time)", limit=20, count=use_count))
4 changes: 4 additions & 0 deletions dashboard/frontend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
streamlit
streamlit-autorefresh
matplotlib
pywaffle
Empty file.
Loading

0 comments on commit 30efdcb

Please sign in to comment.