| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- #!/usr/bin/env python3
- import os
- import sys
- from datetime import datetime
- import calendar
- import time
- import numpy as np
- import matplotlib.pyplot as plt
- from matplotlib.colors import LinearSegmentedColormap
- from matplotlib.patches import Polygon
- import pandas as pd
- marker = 'o'
- markersize = 4
- linestyle = '-'
- linewidth = 2
- colors = {
- 'max': 'red',
- 'min': 'blue'
- }
- def read_file_values(path):
- temp = pressure = humidity = None
- temp_u = pressure_u = humidity_u = ''
- try:
- with open(path, 'r') as f:
- for line in f:
- if 'Temperature' in line:
- try:
- parts = line.split(':', 1)[1].strip().split()
- temp = float(parts[0])
- temp_u = ' '.join(parts[1:]) if len(parts) > 1 else ''
- except:
- pass
- elif 'Pressure' in line:
- try:
- parts = line.split(':', 1)[1].strip().split()
- pressure = float(parts[0])
- pressure_u = ' '.join(parts[1:]) if len(parts) > 1 else ''
- except:
- pass
- elif 'Humidity' in line:
- try:
- parts = line.split(':', 1)[1].strip().split()
- humidity = float(parts[0])
- humidity_u = ' '.join(parts[1:]) if len(parts) > 1 else ''
- except:
- pass
- except FileNotFoundError:
- return (None, '', None, '', None, '')
- return (temp, temp_u, pressure, pressure_u, humidity, humidity_u)
- def read_measurements(data_folder):
- records = []
- year_folder = str(datetime.now().year)
- for root, dirs, files in os.walk(data_folder):
- for filename in files:
- if not filename.endswith('.txt'):
- continue
- if filename == year_folder:
- continue
- filepath = os.path.join(root, filename)
- try:
- timestamp = int(filename[:-4])
- except ValueError:
- print(f"Filename not a timestamp, skipping: {filename} (in {root})")
- continue
- file_time = datetime.fromtimestamp(timestamp)
- temp, temp_u, pressure, pressure_u, humidity, humidity_u = read_file_values(filepath)
- # fallback to year subfolder file if some values missing
- if (temp is None or pressure is None or humidity is None):
- alt_path = os.path.join(data_folder, year_folder, filename)
- if os.path.isfile(alt_path):
- at, at_u, ap, ap_u, ah, ah_u = read_file_values(alt_path)
- if temp is None and at is not None:
- temp, temp_u = at, at_u
- if pressure is None and ap is not None:
- pressure, pressure_u = ap, ap_u
- if humidity is None and ah is not None:
- humidity, humidity_u = ah, ah_u
- if temp is not None and pressure is not None and humidity is not None:
- records.append({
- 'timestamp': file_time,
- 'date': file_time.date(),
- 'day': file_time.day,
- 'temperature': temp,
- 'temperature_unit': temp_u,
- 'pressure': pressure,
- 'pressure_unit': pressure_u,
- 'humidity': humidity,
- 'humidity_unit': humidity_u
- })
- else:
- print(f"Missing data in file: {filename} (in {root}). Skipping.")
- return pd.DataFrame(records)
- def filter_month(df, month, year):
- if df.empty:
- return df
- return df[(df['timestamp'].dt.month == month) & (df['timestamp'].dt.year == year)]
- def daily_min_max(df, col):
- g = df.groupby('date')[col].agg(['min', 'max']).reset_index()
- g['day'] = pd.to_datetime(g['date']).dt.day
- g = g.sort_values('day')
- return g
- def plot_min_max(daily_df, col, out_folder, year, month, unit=''):
- if daily_df.empty:
- print(f"No data to plot for {col}")
- return
- last_day = calendar.monthrange(year, month)[1]
- fig, ax = plt.subplots(figsize=(12, 6))
- ax.plot(daily_df['day'], daily_df['min'],
- marker=marker, markersize=markersize,
- linestyle=linestyle, linewidth=linewidth,
- label='Min', color=colors['min'], zorder=3)
- ax.plot(daily_df['day'], daily_df['max'],
- marker=marker, markersize=markersize,
- linestyle=linestyle, linewidth=linewidth,
- label='Max', color=colors['max'], zorder=4)
- xs = list(daily_df['day'])
- ys_max = list(daily_df['max'])
- ys_min = list(daily_df['min'])
- if len(xs) >= 2:
- verts = [(x, y) for x, y in zip(xs, ys_max)] + [(x, y) for x, y in zip(xs[::-1], ys_min[::-1])]
- cmap = LinearSegmentedColormap.from_list('blue_red', [colors['min'], colors['max']])
- global_min = min(ys_min)
- global_max = max(ys_max)
- if global_min == global_max:
- global_min -= 0.5
- global_max += 0.5
- gradient = np.linspace(0, 1, 256).reshape(256, 1)
- gradient = np.repeat(gradient, 10, axis=1)
- im = ax.imshow(gradient, aspect='auto', cmap=cmap,
- extent=(0.5, last_day + 0.5, global_min, global_max),
- origin='lower', alpha=0.35, zorder=1)
- poly = Polygon(verts, closed=True, transform=ax.transData)
- im.set_clip_path(poly)
- ax.set_xlim(1, last_day)
- xticks = list(range(1, last_day + 1))
- xtick_labels = [f"{d}." for d in xticks]
- ax.set_xticks(xticks)
- ax.set_xticklabels(xtick_labels, rotation=0)
- ylabel = col.capitalize()
- if unit:
- ylabel += f' in {unit}'
- ax.set_ylabel(ylabel)
- ax.set_xlabel(f'Day of Month ({year}-{month:02d})')
- ax.set_title(f'{col.capitalize()} - Daily Min/Max Value for {year}-{month:02d}')
- ax.grid(True, alpha=0.25)
- ax.legend()
- fig.tight_layout()
- out_path = os.path.join(out_folder, f'{col}_daily_min_max.png')
- fig.savefig(out_path)
- plt.close(fig)
- print(f"Saved plot: {out_path}")
- def ensure_folder(path):
- if not os.path.exists(path):
- os.makedirs(path, exist_ok=True)
- def pick_unit(series_unit_col):
- if series_unit_col is None or series_unit_col.empty:
- return ''
- vals = series_unit_col.dropna().astype(str)
- vals = vals[vals != '']
- return vals.mode().iat[0] if not vals.empty else ''
- def main():
- if len(sys.argv) < 2:
- print("Usage: python3 script.py <month_number (1-12)>")
- sys.exit(1)
- try:
- month = int(sys.argv[1])
- assert 1 <= month <= 12
- except:
- print("Month must be an integer between 1 and 12.")
- sys.exit(1)
- data_folder = 'temperature'
- if not os.path.isdir(data_folder):
- print(f"Data folder not found: {data_folder}")
- sys.exit(1)
- start_time = time.time()
- df = read_measurements(data_folder)
- if df.empty:
- print("No valid measurements found.")
- sys.exit(1)
- df['timestamp'] = pd.to_datetime(df['timestamp'])
- year = datetime.now().year
- df_month = filter_month(df, month, year)
- if df_month.empty:
- print(f"No measurements for {month}/{year}.")
- sys.exit(0)
- # pick most common unit per measurement type for the month
- units = {
- 'temperature': pick_unit(df_month.get('temperature_unit')),
- 'pressure': pick_unit(df_month.get('pressure_unit')),
- 'humidity': pick_unit(df_month.get('humidity_unit'))
- }
- out_folder = f'plots_{year}_{month:02d}'
- ensure_folder(out_folder)
- for col in ['temperature', 'pressure', 'humidity']:
- daily = daily_min_max(df_month, col)
- plot_min_max(daily, col, out_folder, year, month, unit=units.get(col, ''))
- elapsed = time.time() - start_time
- print(f"Verarbeitung abgeschlossen in {elapsed:.2f}s. Plots in {out_folder}")
- if __name__ == '__main__':
- main()
|