Skip to content

Commit 729eeb6

Browse files
committed
feat: new command freq implemented to toggle data logging freq
1 parent 37e533e commit 729eeb6

File tree

5 files changed

+252
-28
lines changed

5 files changed

+252
-28
lines changed

src/cli.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
toggle_monitor,
1616
get_pi_ip,
1717
get_system_stats,
18+
set_frequency,
1819
)
1920

2021
import sys
@@ -31,7 +32,7 @@
3132
def get_parser():
3233
parser = argparse.ArgumentParser(
3334
description="Weather data management CLI",
34-
usage="meteorix [-h] {upload, delete, check, head, tail, info, spit, plot, monitor, ifconfig, top, eda, ml, who}",
35+
usage="meteorix [-h] {upload, delete, check, head, tail, info, spit, plot, monitor, freq, ifconfig, top, eda, ml, who}",
3536
)
3637

3738
subparsers = parser.add_subparsers(dest="command", required=True)
@@ -154,6 +155,21 @@ def get_parser():
154155
),
155156
],
156157
},
158+
"freq": {
159+
"help": "Control data logging frequency",
160+
"description": "Set or check the data logging frequency on the Raspberry Pi. Use '0' for high frequency (32Hz) or '1' for low frequency (1Hz).",
161+
"args": [
162+
("action", {"choices": ["set", "status"], "help": "Action to perform"}),
163+
(
164+
"value",
165+
{
166+
"nargs": "?",
167+
"choices": ["0", "1"],
168+
"help": "Frequency value: 0 (32Hz) or 1 (1Hz). Only used with 'set' action",
169+
},
170+
),
171+
],
172+
},
157173
"ifconfig": {
158174
"help": "Show Raspberry Pi network information",
159175
"description": "Display network interface information from the weather station Raspberry Pi.",
@@ -220,6 +236,7 @@ def main():
220236
"monitor": lambda: toggle_monitor(args.action),
221237
"ifconfig": lambda: get_pi_ip(),
222238
"top": lambda: get_system_stats(),
239+
"freq": lambda: handle_freq_command(args),
223240
}
224241

225242
# Date-based command handlers
@@ -284,5 +301,14 @@ def handle_plot_command(start_date, end_date, save_locally=True):
284301
rprint(f"[red]Error creating plot: {str(e)}[/red]")
285302

286303

304+
def handle_freq_command(args):
305+
"""Handle frequency control command."""
306+
if args.action == "set" and args.value is None:
307+
rprint("[red]Error: Frequency value (0 or 1) required for 'set' action[/red]")
308+
return
309+
310+
set_frequency(args.value if args.action == "set" else None)
311+
312+
287313
if __name__ == "__main__":
288314
main()

src/cli_components/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .monitor import toggle_monitor
1414
from .ifconfig import get_pi_ip
1515
from .top import get_system_stats
16+
from .freq import set_frequency
1617

1718
__all__ = [
1819
"check_analysis_results",
@@ -32,4 +33,5 @@
3233
"toggle_monitor",
3334
"get_pi_ip",
3435
"get_system_stats",
36+
"set_frequency",
3537
]

src/cli_components/freq.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from rich import print as rprint
2+
from .ifconfig import SSHClient, find_pi_address
3+
4+
5+
def set_frequency(freq=None):
6+
"""Set or check the data logging frequency on the Raspberry Pi
7+
8+
Args:
9+
freq (str, optional): '1' for high frequency (32Hz), '0' for low frequency (1Hz)
10+
"""
11+
try:
12+
# Find the Pi's current address
13+
pi_address = find_pi_address()
14+
if not pi_address:
15+
raise Exception("Could not locate the Raspberry Pi on the network")
16+
17+
# Get SSH client instance for status check
18+
ssh = SSHClient.get_instance().get_client(pi_address)
19+
20+
# If no frequency specified, check current status
21+
if freq is None:
22+
# Check if the Weather Station logger is running using multiple methods
23+
# First check screen session
24+
stdin, stdout, stderr = ssh.exec_command("sudo screen -ls | grep logger")
25+
screen_output = stdout.read().decode().strip()
26+
27+
if not screen_output:
28+
rprint("[yellow]Weather Station logger is not running[/yellow]")
29+
return
30+
31+
# Check for active high frequency files
32+
stdin, stdout, stderr = ssh.exec_command(
33+
"ls -t /var/tmp/wx/*_weather_station_data.csv 2>/dev/null | head -1"
34+
)
35+
latest_file = stdout.read().decode().strip()
36+
37+
if not latest_file:
38+
rprint("[yellow]No active data files found[/yellow]")
39+
return
40+
41+
# Get file's last modification time
42+
stdin, stdout, stderr = ssh.exec_command(f"stat -c %Y {latest_file}")
43+
file_time = int(stdout.read().decode().strip())
44+
current_time = int(ssh.exec_command("date +%s")[1].read().decode().strip())
45+
46+
# Check the actual frequency by looking at timestamps in real-time
47+
stdin, stdout, stderr = ssh.exec_command(
48+
f"tail -n 1 -f {latest_file} | head -n 2"
49+
)
50+
51+
# Read two consecutive lines
52+
timestamps = []
53+
for _ in range(2):
54+
line = stdout.readline()
55+
if not line:
56+
break
57+
timestamp_str = line.strip().split(",")[0]
58+
try:
59+
stdin2, stdout2, stderr2 = ssh.exec_command(
60+
f"date -d '{timestamp_str}' +%s.%N"
61+
)
62+
timestamp = float(stdout2.read().decode().strip())
63+
timestamps.append(timestamp)
64+
except (ValueError, IndexError):
65+
break
66+
67+
# Calculate time difference and determine frequency
68+
if len(timestamps) == 2:
69+
time_diff = timestamps[1] - timestamps[0]
70+
is_high_freq = time_diff < 0.1
71+
else:
72+
is_high_freq = False
73+
74+
# Display current status
75+
if current_time - file_time < 5 and is_high_freq:
76+
rprint(
77+
"[green]Currently running in HIGH frequency mode (32 Hz)[/green]"
78+
)
79+
rprint(
80+
f"[yellow]High frequency data being logged to: {latest_file}[/yellow]"
81+
)
82+
else:
83+
rprint("[green]Currently running in LOW frequency mode (1 Hz)[/green]")
84+
rprint(f"[yellow]Data being logged to: {latest_file}[/yellow]")
85+
return
86+
87+
# Validate frequency input
88+
if freq not in ["0", "1"]:
89+
raise ValueError("Frequency must be '1' (32Hz) or '0' (1Hz)")
90+
91+
try:
92+
# Run the appropriate script with sudo
93+
if freq == "1":
94+
rprint("[yellow]Starting HIGH frequency logging...[/yellow]")
95+
ssh.exec_command("sudo python3 /home/pi/SDL_Starter.py")
96+
rprint(
97+
"[green]Successfully enabled HIGH frequency logging (32 Hz)[/green]"
98+
)
99+
else:
100+
rprint("[yellow]Stopping HIGH frequency logging...[/yellow]")
101+
ssh.exec_command("sudo python3 /home/pi/SDL_Stopper.py")
102+
rprint(
103+
"[green]Successfully disabled HIGH frequency logging (returning to 1 Hz)[/green]"
104+
)
105+
106+
except Exception as e:
107+
raise Exception(f"Failed to run frequency control script: {str(e)}")
108+
109+
except Exception as e:
110+
rprint(f"[red]Error: {str(e)}[/red]")
111+
# If there's an error, try to close the SSH connection
112+
SSHClient.get_instance().close()
113+
return False
114+
115+
return True

src/cli_components/monitor.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from datetime import datetime, timedelta
22
from pathlib import Path
33
from rich import print as rprint
4-
import pandas as pd
54
import glob
65
from typing import Optional
76
import json
7+
import os
88

99
from src import CSV_DIR
1010

@@ -61,32 +61,41 @@ def get_latest_data_time() -> Optional[datetime]:
6161

6262
latest_file = max(csv_files)
6363

64-
# Get total number of rows
65-
total_rows = sum(1 for _ in open(latest_file)) - 1 # -1 for header
66-
67-
# Define column names (matching tail.py)
68-
columns = [
69-
"tNow",
70-
"u_m_s",
71-
"v_m_s",
72-
"w_m_s",
73-
"2dSpeed_m_s",
74-
"3DSpeed_m_s",
75-
"Azimuth_deg",
76-
"Elev_deg",
77-
"Press_Pa",
78-
"Temp_C",
79-
"Hum_RH",
80-
"SonicTemp_C",
81-
"Error",
82-
]
83-
84-
# Read only the last row (matching tail.py approach)
85-
df = pd.read_csv(latest_file, skiprows=max(0, total_rows - 1), names=columns)
86-
timestamp_str = df.iloc[-1]["tNow"]
87-
88-
# Parse the timestamp string directly using datetime
89-
return datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
64+
# Use tail command to get last line reliably
65+
with open(latest_file, "rb") as f:
66+
try:
67+
f.seek(-2, os.SEEK_END)
68+
while f.read(1) != b"\n":
69+
f.seek(-2, os.SEEK_CUR)
70+
except OSError:
71+
f.seek(0)
72+
last_line = f.readline().decode().strip()
73+
74+
if not last_line:
75+
return None
76+
77+
# Get timestamp from first column
78+
timestamp_str = last_line.split(",")[0]
79+
if timestamp_str == "tNow": # Skip header row
80+
return None
81+
82+
# Try parsing with different formats
83+
try:
84+
# Try with microseconds first
85+
return datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S.%f")
86+
except ValueError:
87+
try:
88+
# Try without microseconds
89+
return datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
90+
except ValueError:
91+
# Handle decimal seconds
92+
base_str = timestamp_str.split(".")[0]
93+
decimal_str = (
94+
timestamp_str.split(".")[1] if "." in timestamp_str else "0"
95+
)
96+
base_time = datetime.strptime(base_str, "%Y-%m-%d %H:%M:%S")
97+
microseconds = int(float("0." + decimal_str) * 1000000)
98+
return base_time.replace(microsecond=microseconds)
9099

91100
except Exception as e:
92101
rprint(f"[red]Error checking latest data: {str(e)}[/red]")
@@ -122,8 +131,10 @@ def show_monitor_status() -> None:
122131
"[green]enabled[/green]" if config["enabled"] else "[yellow]disabled[/yellow]"
123132
)
124133
time_str = latest_time.strftime("%Y-%m-%d %H:%M:%S") if latest_time else "N/A"
134+
data_state = "[green]fresh[/green]" if fresh else "[red]stale[/red]"
125135

126136
rprint(f"Monitor state: {status}")
137+
rprint(f"Data state: {data_state}")
127138
rprint(f"Alert threshold: {config['alert_threshold_minutes']} minutes")
128139
rprint(f"Check interval: {config['check_interval_minutes']} minutes")
129140
rprint(f"Latest data point: {time_str}")

0 commit comments

Comments
 (0)