Skip to content

Commit c8eb202

Browse files
committed
cleanup and document analysis stuff
1 parent ea0c7ab commit c8eb202

1 file changed

Lines changed: 72 additions & 50 deletions

File tree

meshtastic/analysis/__main__.py

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,112 +29,134 @@
2929
pa.string(): pd.StringDtype(),
3030
}
3131

32-
# sdir = '/home/kevinh/.local/share/meshtastic/slogs/20240626-152804'
33-
sdir = "/home/kevinh/.local/share/meshtastic/slogs/latest"
34-
dpwr = feather.read_table(f"{sdir}/power.feather").to_pandas(
35-
types_mapper=dtype_mapping.get
36-
)
37-
dslog = feather.read_table(f"{sdir}/slog.feather").to_pandas(
38-
types_mapper=dtype_mapping.get
39-
)
32+
# Configure panda options
33+
pd.options.mode.copy_on_write = True
4034

35+
def to_pmon_names(arr) -> list[str]:
36+
"""Convert the power monitor state numbers to their corresponding names.
4137
42-
def get_board_info():
43-
"""Get the board information from the slog dataframe.
38+
arr (list): List of power monitor state numbers.
4439
45-
tuple: A tuple containing the board ID and software version.
40+
Returns the List of corresponding power monitor state names.
4641
"""
47-
board_info = dslog[dslog["sw_version"].notnull()]
48-
sw_version = board_info.iloc[0]["sw_version"]
49-
board_id = mesh_pb2.HardwareModel.Name(board_info.iloc[0]["board_id"])
50-
return (board_id, sw_version)
42+
def to_pmon_name(n):
43+
try:
44+
s = powermon_pb2.PowerMon.State.Name(int(n))
45+
return s if s != "None" else None
46+
except ValueError:
47+
return None
5148

49+
return [to_pmon_name(x) for x in arr]
5250

53-
pmon_events = dslog[dslog["pm_mask"].notnull()]
51+
def read_pandas(filepath: str) -> pd.DataFrame:
52+
"""Read a feather file and convert it to a pandas DataFrame.
5453
54+
filepath (str): Path to the feather file.
5555
56-
pm_masks = pd.Series(pmon_events["pm_mask"]).to_numpy()
56+
Returns the pandas DataFrame.
57+
"""
58+
return feather.read_table(filepath).to_pandas(types_mapper=dtype_mapping.get)
5759

58-
# possible to do this with pandas rolling windows if I was smarter?
59-
pm_changes = [(pm_masks[i - 1] ^ x if i != 0 else x) for i, x in enumerate(pm_masks)]
60-
pm_raises = [(pm_masks[i] & x) for i, x in enumerate(pm_changes)]
61-
pm_falls = [(~pm_masks[i] & x if i != 0 else 0) for i, x in enumerate(pm_changes)]
60+
def get_pmon_raises(dslog: pd.DataFrame) -> pd.DataFrame:
61+
"""Get the power monitor raises from the slog DataFrame.
6262
63+
dslog (pd.DataFrame): The slog DataFrame.
6364
64-
def to_pmon_names(arr) -> list[str]:
65-
"""Convert the power monitor state numbers to their corresponding names.
65+
Returns the DataFrame containing the power monitor raises.
6666
"""
67+
pmon_events = dslog[dslog["pm_mask"].notnull()]
6768

68-
def to_pmon_name(n):
69-
try:
70-
s = powermon_pb2.PowerMon.State.Name(int(n))
71-
return s if s != "None" else None
72-
except ValueError:
73-
return None
69+
pm_masks = pd.Series(pmon_events["pm_mask"]).to_numpy()
7470

75-
return [to_pmon_name(x) for x in arr]
71+
# possible to do this with pandas rolling windows if I was smarter?
72+
pm_changes = [(pm_masks[i - 1] ^ x if i != 0 else x) for i, x in enumerate(pm_masks)]
73+
pm_raises = [(pm_masks[i] & x) for i, x in enumerate(pm_changes)]
74+
pm_falls = [(~pm_masks[i] & x if i != 0 else 0) for i, x in enumerate(pm_changes)]
7675

76+
pmon_events["pm_raises"] = to_pmon_names(pm_raises)
77+
pmon_events["pm_falls"] = to_pmon_names(pm_falls)
7778

78-
pd.options.mode.copy_on_write = True
79-
pmon_events["pm_raises"] = to_pmon_names(pm_raises)
80-
pmon_events["pm_falls"] = to_pmon_names(pm_falls)
79+
pmon_raises = pmon_events[pmon_events["pm_raises"].notnull()][["time", "pm_raises"]]
80+
pmon_falls = pmon_events[pmon_events["pm_falls"].notnull()]
81+
82+
def get_endtime(row):
83+
"""Find the corresponding fall event."""
84+
following = pmon_falls[(pmon_falls["pm_falls"] == row["pm_raises"]) &
85+
(pmon_falls["time"] > row["time"])]
86+
return following.iloc[0] if not following.empty else None
87+
88+
# HMM - setting end_time doesn't work yet - leave off for now
89+
# pmon_raises['end_time'] = pmon_raises.apply(get_endtime, axis=1)
90+
91+
return pmon_raises
92+
93+
def get_board_info(dslog: pd.DataFrame) -> tuple:
94+
"""Get the board information from the slog DataFrame.
95+
96+
dslog (pd.DataFrame): The slog DataFrame.
97+
98+
Returns a tuple containing the board ID and software version.
99+
"""
100+
board_info = dslog[dslog["sw_version"].notnull()]
101+
sw_version = board_info.iloc[0]["sw_version"]
102+
board_id = mesh_pb2.HardwareModel.Name(board_info.iloc[0]["board_id"])
103+
return (board_id, sw_version)
81104

82-
pmon_raises = pmon_events[pmon_events["pm_raises"].notnull()]
105+
def create_dash(slog_path: str) -> Dash:
106+
"""Create a Dash application for visualizing power consumption data.
83107
108+
slog_path (str): Path to the slog directory.
84109
85-
def create_dash():
86-
"""Create a Dash application for visualizing power consumption data."""
110+
Returns the Dash application.
111+
"""
87112
app = Dash(
88113
external_stylesheets=[dbc.themes.BOOTSTRAP]
89114
)
90115

116+
dpwr = read_pandas(f"{slog_path}/power.feather")
117+
dslog = read_pandas(f"{slog_path}/slog.feather")
118+
119+
pmon_raises = get_pmon_raises(dslog)
120+
91121
def set_legend(f, name):
92122
f["data"][0]["showlegend"] = True
93123
f["data"][0]["name"] = name
94124
return f
95125

96-
df = dpwr
97-
avg_pwr_lines = px.line(df, x="time", y="average_mW").update_traces(
126+
avg_pwr_lines = px.line(dpwr, x="time", y="average_mW").update_traces(
98127
line_color="red"
99128
)
100129
set_legend(avg_pwr_lines, "avg power")
101-
max_pwr_points = px.scatter(df, x="time", y="max_mW").update_traces(
130+
max_pwr_points = px.scatter(dpwr, x="time", y="max_mW").update_traces(
102131
marker_color="blue"
103132
)
104133
set_legend(max_pwr_points, "max power")
105-
min_pwr_points = px.scatter(df, x="time", y="min_mW").update_traces(
134+
min_pwr_points = px.scatter(dpwr, x="time", y="min_mW").update_traces(
106135
marker_color="green"
107136
)
108137
set_legend(min_pwr_points, "min power")
109138

110-
pmon = pmon_raises
111-
fake_y = np.full(len(pmon), 10.0)
112-
pmon_points = px.scatter(pmon, x="time", y=fake_y, text="pm_raises")
139+
fake_y = np.full(len(pmon_raises), 10.0)
140+
pmon_points = px.scatter(pmon_raises, x="time", y=fake_y, text="pm_raises")
113141

114-
# fig = avg_pwr_lines
115-
# fig.add_trace(max_pwr_points)
116-
# don't show minpower because not that interesting: min_pwr_points.data
117142
fig = go.Figure(data=max_pwr_points.data + avg_pwr_lines.data + pmon_points.data)
118143

119144
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01))
120145

121146
# App layout
122147
app.layout = [
123148
html.Div(children="Early Meshtastic power analysis tool testing..."),
124-
# dash_table.DataTable(data=df.to_dict('records'), page_size=10),
125149
dcc.Graph(figure=fig),
126150
]
127151

128152
return app
129153

130-
131154
def main():
132155
"""Entry point of the script."""
133-
app = create_dash()
156+
app = create_dash(slog_path="/home/kevinh/.local/share/meshtastic/slogs/latest")
134157
port = 8051
135158
logging.info(f"Running Dash visualization webapp on port {port} (publicly accessible)")
136159
app.run_server(debug=True, host='0.0.0.0', port=port)
137160

138-
139161
if __name__ == "__main__":
140162
main()

0 commit comments

Comments
 (0)