DmitrMakeev's picture
Update app.py
fa1a09a verified
raw
history blame
26.4 kB
from flask import Flask, request, jsonify, redirect, url_for, render_template, send_from_directory, send_file
import os
import sqlite3
from datetime import datetime
import pytz
import io
import base64
from dotenv import load_dotenv
import requests
import json
import logging
import uuid
from io import BytesIO
from werkzeug.utils import secure_filename
import globs
from api_logic import api
from urllib.parse import urlencode # Добавлен правильный импорт
load_dotenv()
# Инициализация базы данных
def init_db(db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
# Таблица с системными данными (твоя старая таблица)
cursor.execute('''
CREATE TABLE IF NOT EXISTS system_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date_time TEXT,
dey TEXT,
wek TEXT,
v_hid TEXT,
v_min TEXT,
ph TEXT,
ec TEXT,
tS TEXT,
tA TEXT,
hDm TEXT,
sVen TEXT,
onA TEXT,
onB TEXT,
onC TEXT,
nPh TEXT,
nEC TEXT,
nTa TEXT,
nLon TEXT,
nLoff TEXT
)
''')
# **Новая таблица для пользователей бота**
cursor.execute('''
CREATE TABLE IF NOT EXISTS bot_users (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- Уникальный ID
chat_id INTEGER UNIQUE, -- Telegram ID пользователя
created_at TEXT -- Время добавления (ISO формат)
)
''')
conn.commit()
conn.close()
# Глобальные переменные
api_key_sys = os.getenv('api_key') # Берём значение API-ключа
btg_key = os.getenv('btg_key') # Берём значение
btg_id = os.getenv('btg_id') # Берём значение
btg_on = os.getenv('btg_on') # Берём значение
globs.dey = 0
globs.wek = 0
globs.v_hid = 0
globs.v_min = 0
globs.ph = 0
globs.ec = 0
globs.tS = 0
globs.tA = 0
globs.hDm = 0
globs.sVen = 0
globs.onA = 0
globs.onB = 0
globs.onC = 0
globs.ph_eep = 0
globs.ph_on_eep = 0
globs.ec_eep = 0
globs.ec_A_eep = 0
globs.ec_B_eep = 0
globs.ec_C_eep = 0
globs.l_ON_h_eep = 0
globs.l_ON_m_eep = 0
globs.l_OFF_h_eep = 0
globs.l_OFF_m_eep = 0
globs.t_Voz_eep = 0
# Создаем экземпляр Flask-приложения
app = Flask(__name__, template_folder="./")
app.config['DEBUG'] = True
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'}
# Создаем папку для загрузок если ее нет
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# Глобальная переменная для хранения последнего изображения в памяти
latest_image = {"data": None, "filename": None}
# Настроим логирование
logging.basicConfig(level=logging.DEBUG)
# Функция сохранения в базу данных системы автоматизации гидропоники
def save_data_to_db(db_name, data):
try:
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
# ✅ Устанавливаем московское время (UTC+3)
moscow_tz = pytz.timezone("Europe/Moscow")
current_time = datetime.now(moscow_tz).strftime('%Y-%m-%d %H:%M:%S')
# Вставляем данные в таблицу
cursor.execute('''
INSERT INTO system_data (
date_time, dey, wek, v_hid, v_min, ph, ec, tS, tA, hDm, sVen, onA, onB, onC, nPh, nEC, nTa, nLon, nLoff
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
current_time, # ✅ Дата и время по Москве
data['dey'], data['wek'], data['v_hid'], data['v_min'], data['ph'], data['ec'],
data['tS'], data['tA'], data['hDm'], data['sVen'], data['onA'], data['onB'],
data['onC'], data['nPh'], data['nEC'], data['nTa'], data['nLon'], data['nLoff']
))
conn.commit()
conn.close()
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
# Маршрут сохранения в базу
@app.route('/sav_db_api', methods=['GET'])
def sav_db_api():
# Инициализируем базу данных
init_db('system_data.db')
# Получаем данные из запроса
data = {
'dey': request.args.get('dey'),
'wek': request.args.get('wek'),
'v_hid': request.args.get('v_hid'),
'v_min': request.args.get('v_min'),
'ph': request.args.get('ph'),
'ec': request.args.get('ec'),
'tS': request.args.get('tS'),
'tA': request.args.get('tA'),
'hDm': request.args.get('hDm'),
'sVen': request.args.get('sVen'),
'onA': request.args.get('onA'),
'onB': request.args.get('onB'),
'onC': request.args.get('onC'),
'nPh': request.args.get('nPh'),
'nEC': request.args.get('nEC'),
'nTa': request.args.get('nTa'),
'nLon': request.args.get('nLon'),
'nLoff': request.args.get('nLoff')
}
# Проверяем, что все необходимые параметры переданы
required_params = ['dey', 'wek', 'v_hid', 'v_min', 'ph', 'ec', 'tS', 'tA', 'hDm', 'sVen', 'onA', 'onB', 'onC', 'nPh', 'nEC', 'nTa', 'nLon', 'nLoff']
for param in required_params:
if data[param] is None:
return jsonify({'status': 'error', 'message': f'Отсутствует параметр: {param}'}), 400
# Сохраняем данные в базу
save_data_to_db('system_data.db', data)
# Возвращаем ответ
return jsonify({'status': 'success', 'message': 'Save OK'})
# Проверка входа на страницы
@app.route('/page_key', methods=['GET'])
def check_api_key():
api_sys_param = request.args.get('api_sys') # Получаем параметр из запроса
if api_sys_param == api_key_sys:
return jsonify({"status": "ok"}), 200 # ✅ Совпадает — отправляем "ok"
else:
return jsonify({"status": "error", "message": "Invalid API key"}), 403 # ❌ Ошибка 403
# Тестовый запрос с установки
@app.route('/test_server', methods=['GET'])
def test_server():
api_key_param = request.args.get('api_sys') # Получаем параметр из запроса
err_ser = 1 if api_key_param == api_key_sys else 0 # Проверяем совпадение ключей
return jsonify(err_ser=err_ser)
@app.route('/test_server_str', methods=['GET'])
def test_server_str():
api_key_param = request.args.get('api_sys')
err_ser = "1" if api_key_param == api_key_sys else "0"
return err_ser # Возвращаем строку "1" или "0"
# Тестовый запрос с установки
@app.route('/btg_teleg', methods=['GET'])
def btg_teleg():
api_key_param = request.args.get('api_sys') # Получаем параметр из запроса
return jsonify(btg_key_ser=btg_key,btg_id_ser=btg_id,btg_on_ser=btg_on)
# Маршрут для вывода всех данных из таблицы
@app.route('/get_all_data', methods=['GET'])
def get_all_data():
try:
conn = sqlite3.connect('system_data.db')
cursor = conn.cursor()
# Выполняем запрос для получения всех данных из таблицы
cursor.execute('SELECT * FROM system_data')
rows = cursor.fetchall()
# Получаем названия столбцов
column_names = [description[0] for description in cursor.description]
# Преобразуем данные в формат JSON
data = []
for row in rows:
data.append(dict(zip(column_names, row)))
conn.close()
# Возвращаем данные в формате JSON
return jsonify(data)
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
# Удаление базы
@app.route('/delite_db', methods=['GET'])
def delete_db():
try:
conn = sqlite3.connect("system_data.db") # Используем вашу БД
cursor = conn.cursor()
# ✅ Удаляем все записи из таблицы
cursor.execute("DELETE FROM system_data")
# ✅ Сбрасываем автоинкрементный счётчик ID (для SQLite)
cursor.execute("DELETE FROM sqlite_sequence WHERE name='system_data'")
conn.commit()
conn.close()
return jsonify({'status': 'ok', 'message': 'База данных успешно очищена'})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/plot_week', methods=['GET'])
def plot_week():
try:
# Получаем номер недели из параметров запроса
week_number = request.args.get('week', default=1, type=int)
week_number = max(1, min(30, week_number)) # Ограничиваем диапазон 1-30
# Подключаемся к базе данных
conn = sqlite3.connect('system_data.db')
cursor = conn.cursor()
# Проверяем существование таблицы
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='system_data'")
table_exists = cursor.fetchone()
if not table_exists:
conn.close()
return render_template('plot_week.html', data=None, week_number=week_number, table_exists=False)
# Запрашиваем данные за выбранную неделю
cursor.execute('''
SELECT date_time, dey, ph, ec, tS, tA, hDm, sVen, onA, onB, onC, v_hid, v_min
FROM system_data
WHERE wek = ?
ORDER BY date_time
''', (str(week_number),)) # Приводим week_number к строке, так как wek имеет тип TEXT
rows = cursor.fetchall()
conn.close()
# Если данных нет
if not rows:
return render_template('plot_week.html', data=None, week_number=week_number, table_exists=True)
# Формируем данные для JSON
data = {
'week': week_number,
'dates': [row[0] for row in rows],
'days_of_week': [int(row[1]) if row[1] else 0 for row in rows], # Преобразуем dey в int
'ph': [float(row[2]) if row[2] else 0.0 for row in rows], # pH
'ec': [float(row[3]) if row[3] else 0.0 for row in rows], # EC
'tS': [float(row[4]) if row[4] else 0.0 for row in rows], # Температура раствора
'tA': [float(row[5]) if row[5] else 0.0 for row in rows], # Температура воздуха
'hDm': [float(row[6]) if row[6] else 0.0 for row in rows], # Влажность воздуха
'sVen': [float(row[7]) if row[7] else 0.0 for row in rows], # Обороты вентилятора
'onA': [float(row[8]) if row[8] else 0.0 for row in rows], # Насос A
'onB': [float(row[9]) if row[9] else 0.0 for row in rows], # Насос B
'onC': [float(row[10]) if row[10] else 0.0 for row in rows], # Насос C
'sus': [f"{row[11]}:{row[12]}" if row[11] and row[12] else "0:0" for row in rows] # Объединяем v_hid и v_min
}
# Отправляем данные в HTML-шаблон
return render_template('plot_week.html', data=data, week_number=week_number, table_exists=True)
except Exception as e:
# Логируем ошибку в консоль для отладки
print(f"Ошибка: {str(e)}")
return render_template('plot_week.html', data=None, week_number=week_number, table_exists=True, message=f"Ошибка: {str(e)}")
@app.route("/")
def index():
return flask.render_template('index.html')
@app.route('/online', methods=['GET'])
def online():
return render_template('online.html')
@app.route('/table', methods=['GET'])
def table():
return render_template('table.html')
@app.route('/online_api', methods=['GET'])
def online_api():
# Устанавливаем московское время (UTC+3)
moscow_tz = pytz.timezone("Europe/Moscow")
current_time = datetime.now(moscow_tz)
# Форматируем дату и время отдельно
date = current_time.strftime('%Y-%m-%d') # Например, "2025-03-23"
time = current_time.strftime('%H:%M:%S') # Например, "14:35:42"
return jsonify(
dey=globs.dey,
wek=globs.wek,
v_hid=globs.v_hid,
v_min=globs.v_min,
ph=globs.ph,
ec=globs.ec,
tS=globs.tS,
tA=globs.tA,
hDm=globs.hDm,
sVen=globs.sVen,
rFul=globs.rFul,
rLi=globs.rLi,
rWat=globs.rWat,
rRas=globs.rRas,
rPH=globs.rPH,
rEC=globs.rEC,
rSl=globs.rSl,
rLe=globs.rLe,
alW=globs.alW,
ec_A_eep=globs.ec_A_eep,
ec_B_eep=globs.ec_B_eep,
ec_C_eep=globs.ec_C_eep,
date=date, # Добавляем дату
time=time # Добавляем время
)
@app.route('/settings', methods=['GET'])
def settings():
return render_template('settings.html')
@app.route('/settings_api', methods=['GET'])
def settings_api():
return jsonify(ph_eep=globs.ph_eep,
ph_on_eep=globs.ph_on_eep,
ec_eep=globs.ec_eep,
ec_A_eep=globs.ec_A_eep,
ec_B_eep=globs.ec_B_eep,
ec_C_eep=globs.ec_C_eep,
l_ON_h_eep=globs.l_ON_h_eep,
l_ON_m_eep=globs.l_ON_m_eep,
l_OFF_h_eep=globs.l_OFF_h_eep,
l_OFF_m_eep=globs.l_OFF_m_eep,
t_Voz_eep=globs.t_Voz_eep,
set_st=globs.set_status
)
@app.route('/pH_set', methods=['GET'])
def set_pH_value():
ph_value = request.args.get('value')
globs.ph_set = ph_value
globs.eep_set = 1
return "pH value set successfully"
@app.route('/ph_on_set', methods=['GET'])
def ph_on_value():
ph_on_value = request.args.get('value')
globs.ph_on_set = ph_on_value
globs.eep_set = 2
return "EC value set successfully"
@app.route('/EC_set', methods=['GET'])
def set_EC_value():
ec_value = request.args.get('value')
globs.ec_set = ec_value
globs.eep_set = 3
return "EC value set successfully"
@app.route('/ec_A_set', methods=['GET'])
def ec_A_setValue():
ec_A_setValue = request.args.get('value')
globs.ec_A_set = ec_A_setValue
globs.eep_set = 4
return "EC value set successfully"
@app.route('/ec_B_set', methods=['GET'])
def ec_B_setValue():
ec_B_setValue = request.args.get('value')
globs.ec_B_set = ec_B_setValue
globs.eep_set = 5
return "EC value set successfully"
@app.route('/ec_C_set', methods=['GET'])
def ec_C_setValue():
ec_C_setValue = request.args.get('value')
globs.ec_C_set = ec_C_setValue
globs.eep_set = 6
return "EC value set successfully"
@app.route('/l_ON_set', methods=['GET'])
def l_ON_set():
globs.l_ON_h_set = request.args.get('l_ON_h_set')
globs.l_ON_m_set = request.args.get('l_ON_m_set')
globs.eep_set = 7
return "EC value set successfully"
@app.route('/l_OFF_set', methods=['GET'])
def l_OFF_set():
globs.l_OFF_h_set = request.args.get('l_OFF_h_set')
globs.l_OFF_m_set = request.args.get('l_OFF_m_set')
globs.eep_set = 8
return "EC value set successfully"
@app.route('/t_Voz_eep_set', methods=['GET'])
def t_Voz_eep_set():
t_Voz_eep_set = request.args.get('value')
globs.t_Voz_set = t_Voz_eep_set
globs.eep_set = 9
return "EC value set successfully"
@app.route('/but_start', methods=['GET'])
def but_start():
globs.eep_set = 10
return jsonify(value_set="start")
@app.route('/but_stop', methods=['GET'])
def but_stop():
globs.eep_set = 11
return jsonify(value_set="stop")
@app.route('/but_res', methods=['GET'])
def but_res():
globs.eep_set = 12
return jsonify(value_set="res")
@app.route('/but_sliv', methods=['GET'])
def but_sliv():
globs.eep_set = 13
return jsonify(value_set="sliv")
@app.route("/api", methods=['GET'])
def handle_api():
response = api()
return response
@app.route("/save_db", methods=['GET'])
def handle_save_db():
response = save_db()
return response
@app.route('/set_res')
def set_res():
globs.eep_set = 0
return jsonify(value_set="reset")
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if not allowed_file(file.filename):
return jsonify({"error": "Invalid file type"}), 400
# Генерируем имя для файла с использованием времени
timestamp = datetime.now().strftime('%Y.%m.%d_%H:%M:%S_')
filename = timestamp + file.filename
save_path = os.path.join(UPLOAD_FOLDER, filename)
# Открываем файл для записи и собираем его части
try:
with open(save_path, 'wb') as f:
while chunk := file.read(1024): # Чтение и запись данных частями
f.write(chunk)
return jsonify({
"message": "File uploaded successfully",
"filename": filename,
"path": save_path
}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/uploads/<filename>', methods=['GET'])
def uploaded_file(filename):
return send_from_directory(UPLOAD_FOLDER, filename)
# 🧠 2. Маршрут: сохраняет файл только в память (BytesIO)
# Маршрут для загрузки файла в память
# Загрузка изображения в память (в виде байтов)
@app.route('/upload_memory', methods=['POST'])
def upload_file_to_memory():
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if not file.filename.lower().endswith(('.jpg', '.jpeg', '.png')):
return jsonify({"error": "Invalid file type"}), 400
timestamp = datetime.now().strftime('%Y.%m.%d_%H:%M:%S_')
filename = timestamp + file.filename
try:
# Читаем весь файл в байты
file_bytes = file.read()
# Сохраняем в переменную
latest_image["data"] = file_bytes
latest_image["filename"] = filename
return jsonify({
"message": "Image uploaded successfully",
"filename": filename
}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
# Получение последнего изображения
@app.route('/last_image', methods=['GET'])
def get_last_image():
if not latest_image["data"]:
return jsonify({"error": "No image available"}), 404
# Возвращаем через новый BytesIO каждый раз
return send_file(
BytesIO(latest_image["data"]),
mimetype='image/jpeg',
download_name=latest_image["filename"]
)
@app.route('/view_image', methods=['GET'])
def view_image():
return render_template('show_image.html')
fertilizers_db = {
# Кальциевая селитра (Ca(NO3)2 · 4H2O - тетрагидрат)
"Кальциевая селитра": {
"N (NO3-)": 0.118, # 11.8% азота (правильно)
"Ca": 0.169 # 16.9% кальция (правильно)
},
# Калий азотнокислый (KNO3)
"Калий азотнокислый": {
"N (NO3-)": 0.138, # 13.8% азота (правильно)
"K": 0.387 # 38.7% калия (правильно)
},
# Аммоний азотнокислый (NH4NO3)
"Аммоний азотнокислый": {
"N (NO3-)": 0.175, # 17.5% нитратного азота
"N (NH4+)": 0.175 # 17.5% аммонийного азота
},
# Сульфат магния (MgSO4 · 7H2O - гептагидрат)
"Сульфат магния": {
"Mg": 0.098, # 9.8% магния (правильно)
"S": 0.13 # 13% серы (правильно)
},
# Монофосфат калия (KH2PO4)
"Монофосфат калия": {
"P": 0.227, # 22.7% фосфора (правильно)
"K": 0.287 # 28.7% калия (правильно)
}
}
class HydroCalculator:
def __init__(self, volume_liters=1.0):
self.volume = volume_liters
self.fertilizers = []
def add_fertilizer(self, name, grams):
if name not in fertilizers_db:
raise ValueError(f"Удобрение '{name}' не найдено в базе!")
self.fertilizers.append((name, grams))
def calculate_ppm(self):
ppm = {}
for name, grams in self.fertilizers:
for element, ratio in fertilizers_db[name].items():
ppm[element] = ppm.get(element, 0) + (grams * ratio * 1000) / self.volume
return ppm
def calculate_ec(self):
ppm = self.calculate_ppm()
ec = 0.0
EC_COEFFICIENTS = {
'N (NO3-)': 0.6,
'N (NH4+)': 0.4,
'K': 0.7,
'Ca': 0.75,
'Mg': 0.6,
'S': 0.3,
'P': 0.1,
}
for element, value in ppm.items():
if element in EC_COEFFICIENTS:
ec += value * EC_COEFFICIENTS[element]
return ec / 1000
def calculate_ec_exact(self):
MOLAR_MASSES = {
'NO3-': 62.0,
'NH4+': 18.0,
'K+': 39.1,
'Ca+2': 40.1,
'Mg+2': 24.3,
'SO4-2': 96.1,
'H2PO4-': 97.0,
}
ION_CONDUCTIVITY = {
'NO3-': 71.4,
'NH4+': 73.5,
'K+': 73.5,
'Ca+2': 119.0,
'Mg+2': 106.0,
'SO4-2': 160.0,
'H2PO4-': 33.0,
}
ELEMENT_TO_ION = {
'N (NO3-)': 'NO3-',
'N (NH4+)': 'NH4+',
'K': 'K+',
'Ca': 'Ca+2',
'Mg': 'Mg+2',
'S': 'SO4-2',
'P': 'H2PO4-',
}
ppm = self.calculate_ppm()
ec = 0.0 # µS/cm
for element, value in ppm.items():
if element in ELEMENT_TO_ION:
ion = ELEMENT_TO_ION[element]
molar_mass = MOLAR_MASSES[ion]
conductivity = ION_CONDUCTIVITY[ion]
# ppm = mg/L → mmol/L = (ppm / molar_mass)
mmol_per_liter = value / molar_mass
# µS/cm = mmol/L * µS·cm²/mmol
ec += mmol_per_liter * conductivity
return ec / 100 # переводим в mS/cm
def print_report(self):
ppm = self.calculate_ppm()
ec_simple = self.calculate_ec()
ec_exact = self.calculate_ec_exact()
# Вывод основной таблицы элементов
print("\n" + "="*50)
print("ТАБЛИЦА КОНЦЕНТРАЦИЙ ЭЛЕМЕНТОВ")
print("="*50)
print("{:<15} {:>15} {:>15}".format("Элемент", "Конц. (ppm)", "Конц. (мг/л)"))
print("-"*50)
for element, value in sorted(ppm.items()):
print("{:<15} {:>15.2f} {:>15.2f}".format(element, value, value))
print("="*50)
# Вывод данных по азоту
print("\n" + "="*50)
print("АНАЛИЗ АЗОТНЫХ СОЕДИНЕНИЙ")
print("="*50)
print(f"Общий азот (N): {ppm.get('N (NO3-)', 0) + ppm.get('N (NH4+)', 0):.2f} ppm")
print(f"Нитратный азот (NO3-): {ppm.get('N (NO3-)', 0):.2f} ppm")
print(f"Аммонийный азот (NH4+): {ppm.get('N (NH4+)', 0):.2f} ppm")
print("="*50)
# Вывод электропроводности
print("\n" + "="*50)
print("ЭЛЕКТРОПРОВОДНОСТЬ РАСТВОРА")
print("="*50)
print(f"EC: {ec_simple:.2f} mS/cm")
print("="*50)
# Пример использования с правильными названиями удобрений
calc = HydroCalculator(10) # 10 литров раствора
# Добавляем удобрения с правильными названиями и адекватными количествами
calc.add_fertilizer("Кальциевая селитра", 5) # 10 г
calc.add_fertilizer("Калий азотнокислый", 5) # 5 г
calc.add_fertilizer("Аммоний азотнокислый", 5) # 3 г
# Выводим полный отчёт
calc.print_report()
# Дополнительный вывод только NO3 и NH4
ppm = calc.calculate_ppm()
print("\nСодержание азота:")
print(f"NO3-: {ppm.get('N (NO3-)', 0):.2f} ppm")
print(f"NH4+: {ppm.get('N (NH4+)', 0):.2f} ppm")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))