From fa34df53e40ed4da8d2d5978808ae7620604b5f9 Mon Sep 17 00:00:00 2001 From: q00 Date: Sun, 1 Dec 2024 00:16:41 +0300 Subject: [PATCH] Gen table via rrdtool and push to tg how photo --- .gitignore | 1 + parser/.~lock.tmp.xlsx# | 1 - vodynoi/create_rrd-bd.sh | 9 +++ vodynoi/gen_graph.py | 62 +++++++++++++++++++ vodynoi/pars_graph.py | 109 ++++++++++++++++++++++++++++++++++ vodynoi/temperature_graph.png | Bin 0 -> 15845 bytes 6 files changed, 181 insertions(+), 1 deletion(-) delete mode 100644 parser/.~lock.tmp.xlsx# create mode 100755 vodynoi/create_rrd-bd.sh create mode 100644 vodynoi/gen_graph.py create mode 100644 vodynoi/pars_graph.py create mode 100644 vodynoi/temperature_graph.png diff --git a/.gitignore b/.gitignore index b88eda0..26c9240 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ pyvenv.cfg share/ kaluga/lib64 */*.xlsx +*/*.rrd diff --git a/parser/.~lock.tmp.xlsx# b/parser/.~lock.tmp.xlsx# deleted file mode 100644 index 13c22c5..0000000 --- a/parser/.~lock.tmp.xlsx# +++ /dev/null @@ -1 +0,0 @@ -,q,q,26.11.2024 20:34,file:///home/q/.config/libreoffice/4; \ No newline at end of file diff --git a/vodynoi/create_rrd-bd.sh b/vodynoi/create_rrd-bd.sh new file mode 100755 index 0000000..05af713 --- /dev/null +++ b/vodynoi/create_rrd-bd.sh @@ -0,0 +1,9 @@ +#!/bin/bash +rrdtool create temperature_data.rrd \ +--step 60 \ +DS:temp_low:GAUGE:120:-40:100 \ +DS:temp_high:GAUGE:120:-40:100 \ +RRA:AVERAGE:0.5:1:1440 \ +RRA:AVERAGE:0.5:60:720 \ +RRA:MAX:0.5:60:720 \ +RRA:MIN:0.5:60:720 diff --git a/vodynoi/gen_graph.py b/vodynoi/gen_graph.py new file mode 100644 index 0000000..e3ad58f --- /dev/null +++ b/vodynoi/gen_graph.py @@ -0,0 +1,62 @@ +import rrdtool +import time +import requests +from dotenv import load_dotenv +import os +load_dotenv() + +def send_telegram(): + token = os.getenv("TELEGRAM_TOKEN") + channel_id = os.getenv("TELEGRAM_CHANNEL_ID") + if not token or not channel_id: + raise ValueError("Telegram токен или ID канала не заданы в .env") + + url = f"https://api.telegram.org/bot{token}/sendPhoto" + with open('temperature_graph.png', 'rb') as file: + r = requests.post(url, data={ + "chat_id": channel_id + }, files={ + "photo": file + }) + print(r.text) + if r.status_code != 200: + raise Exception(f"Ошибка отправки в Telegram: {r.text}") + +def create_graph(rrd_file: str, output_file: str): + # Получаем текущее время + now = int(time.time()) + # Время начала (24 часа назад) + start_time = now - 86400 # 86400 секунд = 1 день + + # Форматируем даты для заголовка + start_date = time.strftime("%d-%m-%Y", time.localtime(start_time)) + end_date = time.strftime("%d-%m-%Y", time.localtime(now)) + + try: + rrdtool.graph( + output_file, + "--start", str(start_time), # Начало графика + "--end", str(now), # Конец графика + "--vertical-label", "Temperature (°C)", + "--title", f"Temperature Readings from {start_date} to {end_date}", + "DEF:low_temp={}:temp_low:AVERAGE".format(rrd_file), # Средние значения нижней температуры + "DEF:high_temp={}:temp_high:AVERAGE".format(rrd_file), # Средние значения верхней температуры + "LINE1:low_temp#0000FF:Low Temperature", # Линия для нижней температуры (синий) + "LINE1:high_temp#FF0000:High Temperature", # Линия для верхней температуры (красный) + r"GPRINT:low_temp:MIN:Min Low Temp\: %2.2lf°C", + r"GPRINT:low_temp:MAX:Max Low Temp\: %2.2lf°C", + r"GPRINT:low_temp:AVERAGE:Avg Low Temp\: %2.2lf°C\n", + r"GPRINT:high_temp:MIN:Min High Temp\: %2.2lf°C", + r"GPRINT:high_temp:MAX:Max High Temp\: %2.2lf°C", + r"GPRINT:high_temp:AVERAGE:Avg High Temp\: %2.2lf°C\n", + ) + print(f"График успешно создан: {output_file}") + send_telegram() + + except Exception as e: + print(f"Ошибка создания графика: {e}") + +# Пример использования +rrd_file = "temperature_data.rrd" +output_file = "temperature_graph.png" +create_graph(rrd_file, output_file) diff --git a/vodynoi/pars_graph.py b/vodynoi/pars_graph.py new file mode 100644 index 0000000..ebb5ec6 --- /dev/null +++ b/vodynoi/pars_graph.py @@ -0,0 +1,109 @@ +import requests +import json +import os +import time +import rrdtool +from dotenv import load_dotenv + +# Конфигурация файла базы данных RRD +rrd_file = "temperature_data.rrd" +load_dotenv() + +# Функция для инициализации базы данных RRDTool +def initialize_rrd_database(rrd_file: str): + if not os.path.exists(rrd_file): + rrdtool.create( + rrd_file, + "--step", "60", # Шаг в 60 секунд + "DS:temp_low:GAUGE:120:-40:100", # Источник данных: нижняя температура + "DS:temp_high:GAUGE:120:-40:100", # Источник данных: верхняя температура + "RRA:AVERAGE:0.5:1:1440", # Средние значения с шагом 1 минута за сутки + "RRA:AVERAGE:0.5:60:720" # Средние значения с шагом 1 час за месяц + ) + print(f"Создана база данных: {rrd_file}") + else: + print(f"База данных уже существует: {rrd_file}") + +# Функция обновления базы данных RRDTool +def update_rrd_database(rrd_file: str, temp_low: float, temp_high: float): + timestamp = int(time.time()) + try: + rrdtool.update(rrd_file, f"{timestamp}:{temp_low}:{temp_high}") + print(f"Data update: Time: {timestamp}, down {temp_low}, Up {temp_high}") + except Exception as e: + print(f"Ошибка обновления базы данных RRD: {e}") + +# Получение токена OwenCloud +def get_token(): + login = os.getenv("LOGIN") + password = os.getenv("PASSWORD") + if not login or not password: + raise ValueError("Логин или пароль не заданы в .env") + + data = {'login': login, 'password': password} + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } + req = requests.post("https://api.owencloud.ru/v1/auth/open/", json=data, headers=headers) + if req.status_code == 200: + answer = req.json() + token = "Bearer " + answer["token"] + return token + else: + print(f"Ошибка авторизации: {req.status_code}, {req.text}") + return None + +# Основная функция +def main(): + initialize_rrd_database(rrd_file) # Инициализируем RRD базу данных + + while True: # Бесконечный цикл для записи данных каждую минуту + token = get_token() + if not token: + print("Не удалось получить токен. Пропускаем итерацию.") + time.sleep(60) # Ждем минуту и пробуем снова + continue + + device_id = "296613" + headers = {'Authorization': token} + data = json.dumps({'filter': ''}) + + req = requests.post(f"https://api.owencloud.ru/v1/device/{device_id}", data=data, headers=headers) + if req.status_code != 200: + print(f"Ошибка запроса устройства: {req.status_code}, {req.text}") + time.sleep(60) + continue + + nswer = req.json() + + if "parameters" not in nswer: + print("Не удалось найти параметры в ответе.") + time.sleep(60) + continue + + parameters = nswer['parameters'] + + # Извлечение температуры + try: + temperature_1 = next(param for param in parameters if param['name'] == "Температура_1 (НИЗ)") + temperature_2 = next(param for param in parameters if param['name'] == "Температура_2 (ВЕРХ)") + except StopIteration: + print("Не удалось найти необходимые параметры.") + time.sleep(60) + continue + + temp_low = float(temperature_1['value']) + temp_high = float(temperature_2['value']) + + # Обновление базы данных RRDTool + try: + update_rrd_database(rrd_file, temp_low, temp_high) + except Exception as e: + print(f"Error with RRDTool: {e}") + + # Ждем минуту перед следующей итерацией + time.sleep(60) + +if __name__ == "__main__": + main() diff --git a/vodynoi/temperature_graph.png b/vodynoi/temperature_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..a6fa95a2b2baafb9e9d50a0eb4127494c2e048fb GIT binary patch literal 15845 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