Compare commits
17 Commits
0c7f46f610
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 71ab797964 | |||
| cbc8b63532 | |||
| 19a90d99c9 | |||
| e22fe1c1a0 | |||
| f5a7b3206a | |||
| 0a966f6430 | |||
| ad33d47794 | |||
| 5dcb617991 | |||
| 1708bdbac4 | |||
| 0ab4c1e22b | |||
| bf8aa6c374 | |||
| f0d691ff8e | |||
| ac733dccc0 | |||
| 4a0f3aaab6 | |||
| 0bc82518d7 | |||
| 552525214b | |||
| 32e8ab0b24 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/.venv
|
||||||
|
/SaveData
|
||||||
|
/__pycache__
|
||||||
3037
001rec_ADC_data.csv
Normal file
3037
001rec_ADC_data.csv
Normal file
File diff suppressed because it is too large
Load Diff
23
001rec_SI.csv
Normal file
23
001rec_SI.csv
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
3601,PING
|
||||||
|
314696,PING
|
||||||
|
397567,SET_ST_SPEED
|
||||||
|
428427,PING
|
||||||
|
509624,START_STREAM
|
||||||
|
878205,ARM
|
||||||
|
878207,ENTER_ARM
|
||||||
|
895800,STATIC_FIRE3
|
||||||
|
895800,EXIT_ARM
|
||||||
|
895803,ENTER_STATIC_FIRE
|
||||||
|
900817,NITROGEN_OPEN_FULL
|
||||||
|
905320,ETHANOL_OPEN
|
||||||
|
905627,LOX_OPEN
|
||||||
|
907331,ETHANOL_OPEN_FULL
|
||||||
|
907337,LOX_OPEN_FULL
|
||||||
|
911840,ETHANOL_FULL_CLOSED
|
||||||
|
911840,LOX_FULL_CLOSED
|
||||||
|
917347,ETHANOL_DRAIN_OPEN
|
||||||
|
917349,LOX_DRAIN_OPEN
|
||||||
|
919351,NITROGEN_FULL_CLOSED
|
||||||
|
920111,EXIT_STATIC_FIRE
|
||||||
|
949806,STOP_STREAM
|
||||||
|
1553500,START_USB
|
||||||
|
20
COMMANDS.txt
Normal file
20
COMMANDS.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
START_STREAM
|
||||||
|
STOP_STREAM
|
||||||
|
ARM
|
||||||
|
DISARM
|
||||||
|
STATIC_FIRE3
|
||||||
|
STATIC_FIRE5
|
||||||
|
STATIC_FIRE7
|
||||||
|
STATIC_FIRE9
|
||||||
|
ABORT
|
||||||
|
SERVO_SWEEP
|
||||||
|
START_FLASH
|
||||||
|
START_USB
|
||||||
|
RESET
|
||||||
|
WIPE_STM
|
||||||
|
UPTIME
|
||||||
|
PING
|
||||||
|
GET_STATE
|
||||||
|
VERSION
|
||||||
|
START_OXYVENT
|
||||||
|
STOP_OXYVENT
|
||||||
3037
Matlab_post_analysis/001rec_ADC_data.csv
Normal file
3037
Matlab_post_analysis/001rec_ADC_data.csv
Normal file
File diff suppressed because it is too large
Load Diff
24
Matlab_post_analysis/001rec_SI.csv
Normal file
24
Matlab_post_analysis/001rec_SI.csv
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Time,EVENT
|
||||||
|
3601,PING
|
||||||
|
314696,PING
|
||||||
|
397567,SET_ST_SPEED
|
||||||
|
428427,PING
|
||||||
|
509624,START_STREAM
|
||||||
|
878205,ARM
|
||||||
|
878207,ENTER_ARM
|
||||||
|
895800,STATIC_FIRE3
|
||||||
|
895800,EXIT_ARM
|
||||||
|
895803,ENTER_STATIC_FIRE
|
||||||
|
900817,NITROGEN_OPEN_FULL
|
||||||
|
905320,ETHANOL_OPEN
|
||||||
|
905627,LOX_OPEN
|
||||||
|
907331,ETHANOL_OPEN_FULL
|
||||||
|
907337,LOX_OPEN_FULL
|
||||||
|
911840,ETHANOL_FULL_CLOSED
|
||||||
|
911840,LOX_FULL_CLOSED
|
||||||
|
917347,ETHANOL_DRAIN_OPEN
|
||||||
|
917349,LOX_DRAIN_OPEN
|
||||||
|
919351,NITROGEN_FULL_CLOSED
|
||||||
|
920111,EXIT_STATIC_FIRE
|
||||||
|
949806,STOP_STREAM
|
||||||
|
1553500,START_USB
|
||||||
|
1387
Matlab_post_analysis/Second_hot.eps
Normal file
1387
Matlab_post_analysis/Second_hot.eps
Normal file
File diff suppressed because it is too large
Load Diff
1145
Matlab_post_analysis/final_sequence.eps
Normal file
1145
Matlab_post_analysis/final_sequence.eps
Normal file
File diff suppressed because it is too large
Load Diff
99
Matlab_post_analysis/final_sequence.m
Normal file
99
Matlab_post_analysis/final_sequence.m
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
close all; clear all;
|
||||||
|
%% Final Static Fire Sequence
|
||||||
|
|
||||||
|
% Time vector
|
||||||
|
dt = 0.0001; % or 0.01, etc.
|
||||||
|
t = -8:dt:14; % colon, not linspace
|
||||||
|
|
||||||
|
|
||||||
|
%% Define sequence for each valve
|
||||||
|
% [ start_time, target_value, ramp_duration ]
|
||||||
|
|
||||||
|
N2_events = [
|
||||||
|
-3 100 0
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
Fuel_events = [
|
||||||
|
1.5 25 0.5
|
||||||
|
3.5 100 1.5
|
||||||
|
8 0 0.5
|
||||||
|
];
|
||||||
|
|
||||||
|
LOX_events = [
|
||||||
|
1.8 25 0.2
|
||||||
|
3.5 100 1.5
|
||||||
|
8 0 0.3
|
||||||
|
];
|
||||||
|
|
||||||
|
Pyro_events = [
|
||||||
|
0 100 0.01
|
||||||
|
1.8 0 0.01
|
||||||
|
];
|
||||||
|
|
||||||
|
%% Generate profiles (initial value = 0)
|
||||||
|
N2 = valve_profile(t, N2_events, 0);
|
||||||
|
Fuel = valve_profile(t, Fuel_events, 0);
|
||||||
|
LOX = valve_profile(t, LOX_events, 0);
|
||||||
|
Pyro = valve_profile(t, Pyro_events, 0);
|
||||||
|
|
||||||
|
%% Plot
|
||||||
|
% Existing
|
||||||
|
figure; hold on; grid on; box on;
|
||||||
|
plot(t, N2, '--', 'LineWidth', 1, 'Color', [0 0 1 0.5]);
|
||||||
|
plot(t, Fuel, '-', 'LineWidth', 1.5);
|
||||||
|
plot(t, LOX, '-', 'LineWidth', 1.5);
|
||||||
|
%plot(t, Pyro, '--', 'LineWidth', 1);
|
||||||
|
plot([0,0],[0,100],'-', 'LineWidth', 2,'Color', [1 0 0 0.5]); %Ignition
|
||||||
|
|
||||||
|
|
||||||
|
ax = gca;
|
||||||
|
ax.XTick = -4:1:10;
|
||||||
|
ax.XMinorTick = 'on';
|
||||||
|
ax.XAxis.MinorTickValues = -4:0.1:10;
|
||||||
|
ax.XMinorGrid = 'on';
|
||||||
|
ax.MinorGridAlpha = 0.15; % transparency
|
||||||
|
ax.MinorGridLineStyle = '-'; % solid but faint
|
||||||
|
ax.MinorGridColor = [0.8 0.8 0.8];
|
||||||
|
|
||||||
|
xlabel('Time [s]');
|
||||||
|
ylabel('Opening[%]');
|
||||||
|
legend('Nitrogen','Ethanol','LOX','Ignition','Location','best');
|
||||||
|
ylim([-5 105]);
|
||||||
|
xlim([-4 10]);
|
||||||
|
title('Final Ignition Sequence');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% Helper function
|
||||||
|
function u = valve_profile(t, events, initial_value)
|
||||||
|
% events: [t_start, target_value, ramp_duration]
|
||||||
|
% assumes events are sorted by t_start
|
||||||
|
|
||||||
|
u = initial_value * ones(size(t));
|
||||||
|
prev_value = initial_value;
|
||||||
|
|
||||||
|
for k = 1:size(events,1)
|
||||||
|
t0 = events(k,1);
|
||||||
|
target = events(k,2);
|
||||||
|
dur = events(k,3);
|
||||||
|
|
||||||
|
if dur <= 0
|
||||||
|
% Instant step
|
||||||
|
u(t >= t0) = target;
|
||||||
|
else
|
||||||
|
% Linear ramp from prev_value to target
|
||||||
|
t1 = t0 + dur;
|
||||||
|
idx_ramp = (t >= t0) & (t <= t1);
|
||||||
|
u(idx_ramp) = prev_value + ...
|
||||||
|
(target - prev_value) .* (t(idx_ramp) - t0) / dur;
|
||||||
|
|
||||||
|
% Hold new value after ramp
|
||||||
|
u(t > t1) = target;
|
||||||
|
end
|
||||||
|
|
||||||
|
prev_value = target;
|
||||||
|
end
|
||||||
|
end
|
||||||
99
Matlab_post_analysis/first_sequence.m
Normal file
99
Matlab_post_analysis/first_sequence.m
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
close all; clear all;
|
||||||
|
%% Final Static Fire Sequence
|
||||||
|
|
||||||
|
% Time vector
|
||||||
|
dt = 0.0001; % or 0.01, etc.
|
||||||
|
t = -8:dt:14; % colon, not linspace
|
||||||
|
|
||||||
|
|
||||||
|
%% Define sequence for each valve
|
||||||
|
% [ start_time, target_value, ramp_duration ]
|
||||||
|
|
||||||
|
N2_events = [
|
||||||
|
-3 100 0
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
Fuel_events = [
|
||||||
|
1 33.33 0.5
|
||||||
|
2 100 0.5
|
||||||
|
3.5 0 0.5
|
||||||
|
];
|
||||||
|
|
||||||
|
LOX_events = [
|
||||||
|
1.3 33.33 0.2
|
||||||
|
2 100 0.5
|
||||||
|
3.5 0 0.3
|
||||||
|
];
|
||||||
|
|
||||||
|
Pyro_events = [
|
||||||
|
0 100 0.01
|
||||||
|
1.8 0 0.01
|
||||||
|
];
|
||||||
|
|
||||||
|
%% Generate profiles (initial value = 0)
|
||||||
|
N2 = valve_profile(t, N2_events, 0);
|
||||||
|
Fuel = valve_profile(t, Fuel_events, 0);
|
||||||
|
LOX = valve_profile(t, LOX_events, 0);
|
||||||
|
Pyro = valve_profile(t, Pyro_events, 0);
|
||||||
|
|
||||||
|
%% Plot
|
||||||
|
% Existing
|
||||||
|
figure; hold on; grid on; box on;
|
||||||
|
plot(t, N2, '--', 'LineWidth', 1, 'Color', [0 0 1 0.5]);
|
||||||
|
plot(t, Fuel, '-', 'LineWidth', 1.5);
|
||||||
|
plot(t, LOX, '-', 'LineWidth', 1.5);
|
||||||
|
%plot(t, Pyro, '--', 'LineWidth', 1);
|
||||||
|
plot([0,0],[0,100],'-', 'LineWidth', 2,'Color', [1 0 0 0.5]); %Ignition
|
||||||
|
|
||||||
|
|
||||||
|
ax = gca;
|
||||||
|
ax.XTick = -4:1:10;
|
||||||
|
ax.XMinorTick = 'on';
|
||||||
|
ax.XAxis.MinorTickValues = -4:0.1:10;
|
||||||
|
ax.XMinorGrid = 'on';
|
||||||
|
ax.MinorGridAlpha = 0.15; % transparency
|
||||||
|
ax.MinorGridLineStyle = '-'; % solid but faint
|
||||||
|
ax.MinorGridColor = [0.8 0.8 0.8];
|
||||||
|
|
||||||
|
xlabel('Time [s]');
|
||||||
|
ylabel('Opening[%]');
|
||||||
|
legend('Nitrogen','Ethanol','LOX','Ignition','Location','best');
|
||||||
|
ylim([-5 105]);
|
||||||
|
xlim([-4 10]);
|
||||||
|
title('First Ignition Sequence (1 second)');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% Helper function
|
||||||
|
function u = valve_profile(t, events, initial_value)
|
||||||
|
% events: [t_start, target_value, ramp_duration]
|
||||||
|
% assumes events are sorted by t_start
|
||||||
|
|
||||||
|
u = initial_value * ones(size(t));
|
||||||
|
prev_value = initial_value;
|
||||||
|
|
||||||
|
for k = 1:size(events,1)
|
||||||
|
t0 = events(k,1);
|
||||||
|
target = events(k,2);
|
||||||
|
dur = events(k,3);
|
||||||
|
|
||||||
|
if dur <= 0
|
||||||
|
% Instant step
|
||||||
|
u(t >= t0) = target;
|
||||||
|
else
|
||||||
|
% Linear ramp from prev_value to target
|
||||||
|
t1 = t0 + dur;
|
||||||
|
idx_ramp = (t >= t0) & (t <= t1);
|
||||||
|
u(idx_ramp) = prev_value + ...
|
||||||
|
(target - prev_value) .* (t(idx_ramp) - t0) / dur;
|
||||||
|
|
||||||
|
% Hold new value after ramp
|
||||||
|
u(t > t1) = target;
|
||||||
|
end
|
||||||
|
|
||||||
|
prev_value = target;
|
||||||
|
end
|
||||||
|
end
|
||||||
425
Matlab_post_analysis/gpt_code.m
Normal file
425
Matlab_post_analysis/gpt_code.m
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
close all; clear; clc;
|
||||||
|
|
||||||
|
%% ================= CONFIG =================
|
||||||
|
|
||||||
|
eventsFile = "001rec_SI.csv"; % Events: time (ms), event name
|
||||||
|
dataFile = "001rec_ADC_data.csv"; % ADC data: time (ms), channels...
|
||||||
|
|
||||||
|
PARTIAL_OPEN_FRACTION = 0.25; % Valve position for *_OPEN (between 0 and 1)
|
||||||
|
IGNITION_DELAY = 3.0; % [s] after nitrogen open to assume ignition
|
||||||
|
|
||||||
|
% Valve ramp times [s] for each valve (you can tweak these)
|
||||||
|
rampConfig.LOX.open = 0.2; % CLOSED -> PARTIAL on LOX_OPEN
|
||||||
|
rampConfig.LOX.openFull = 1.5; % -> FULL on LOX_OPEN_FULL
|
||||||
|
rampConfig.LOX.close = 0.3; % -> CLOSED on LOX_FULL_CLOSED
|
||||||
|
|
||||||
|
rampConfig.ETHANOL.open = 0.5;
|
||||||
|
rampConfig.ETHANOL.openFull = 1.5;
|
||||||
|
rampConfig.ETHANOL.close = 0.5;
|
||||||
|
|
||||||
|
rampConfig.NITROGEN.open = 0.0;
|
||||||
|
rampConfig.NITROGEN.openFull = 0.0;
|
||||||
|
rampConfig.NITROGEN.close = 0.5;
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= LOAD DATA =================
|
||||||
|
|
||||||
|
events = readtable(eventsFile);
|
||||||
|
eventTime_ms = events{:,1}; % first column: time in ms
|
||||||
|
eventLabel = string(events{:,2}); % second column: event name as string array
|
||||||
|
|
||||||
|
result = readmatrix(dataFile); % ADC data
|
||||||
|
% result(:,1) assumed to be time in ms
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= TIME ALIGNMENT =================
|
||||||
|
% Reference = NITROGEN_OPEN_FULL if available, else NITROGEN_OPEN, else first event.
|
||||||
|
% We set that reference event to t = -3 s.
|
||||||
|
|
||||||
|
idxN2full = find(eventLabel == "NITROGEN_OPEN_FULL", 1, 'first');
|
||||||
|
idxN2open = find(eventLabel == "NITROGEN_OPEN", 1, 'first');
|
||||||
|
|
||||||
|
if ~isempty(idxN2full)
|
||||||
|
idxRef = idxN2full;
|
||||||
|
elseif ~isempty(idxN2open)
|
||||||
|
idxRef = idxN2open;
|
||||||
|
else
|
||||||
|
warning("No NITROGEN_OPEN[_FULL] event found. Using first event as reference.");
|
||||||
|
idxRef = 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
tRef_ms = eventTime_ms(idxRef);
|
||||||
|
|
||||||
|
% ADC time in seconds, such that nitrogen-open is at t = -3 s:
|
||||||
|
% t = (t_ms - tRef_ms)/1000 - 3 -> at t_ms = tRef_ms => t = -3
|
||||||
|
time = (result(:,1) - tRef_ms) ./ 1000 - 3; % [s]
|
||||||
|
|
||||||
|
% Event times in the same reference
|
||||||
|
eventTime_s = (eventTime_ms - tRef_ms) ./ 1000 - 3;
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= SYNTHETIC IGNITION EVENT =================
|
||||||
|
% Assume ignition occurs IGNITION_DELAY seconds after nitrogen open.
|
||||||
|
|
||||||
|
tN2open_s = eventTime_s(idxRef); % should be -3
|
||||||
|
tIgn_s = tN2open_s + IGNITION_DELAY; % typically 0 s
|
||||||
|
|
||||||
|
eventTime_s(end+1) = tIgn_s;
|
||||||
|
eventLabel(end+1) = "IGNITION";
|
||||||
|
|
||||||
|
% Sort events in time again
|
||||||
|
[eventTime_s, sortIdx] = sort(eventTime_s);
|
||||||
|
eventLabel = eventLabel(sortIdx);
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= CALIBRATIONS =================
|
||||||
|
|
||||||
|
% Your original scaling on column 14
|
||||||
|
result(:,14) = result(:,14) ./ 2;
|
||||||
|
|
||||||
|
% Temperature calibration (columns 4–9)
|
||||||
|
temp = 0.000111 .* result(:,4:9) + 2.31991;
|
||||||
|
|
||||||
|
% Pressure calibration (columns 10–17)
|
||||||
|
pressure = result(:,10:17) .* 0.000015 - 6.565;
|
||||||
|
|
||||||
|
% Load cell channels
|
||||||
|
load_cell = result(:,2:3);
|
||||||
|
|
||||||
|
% Thrust / weight calibration (in kg)
|
||||||
|
weight = -1.166759307845543e-04 .* 0.5 .* (load_cell(:,1) + load_cell(:,2)) ...
|
||||||
|
+ 4.971416323340051e+02;
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= IMPORTANT EVENTS FILTER =================
|
||||||
|
% Only show these in the event markers (others are hidden)
|
||||||
|
|
||||||
|
importantEvents = [ ... % "ENTER_STATIC_FIRE", ..."EXIT_STATIC_FIRE", ... %"ENTER_ARM", ...
|
||||||
|
"UPDATE_ARM", ... "EXIT_ARM", ...
|
||||||
|
"LOX_OPEN", ...
|
||||||
|
"LOX_OPEN_FULL", ...
|
||||||
|
"ETHANOL_OPEN", ...
|
||||||
|
"ETHANOL_OPEN_FULL", ...
|
||||||
|
"NITROGEN_OPEN", ...
|
||||||
|
"NITROGEN_OPEN_FULL", ...
|
||||||
|
"LOX_FULL_CLOSED", ...
|
||||||
|
"ETHANOL_FULL_CLOSED", ..."NITROGEN_FULL_CLOSED", ...
|
||||||
|
"ENTER_ABORT", ...
|
||||||
|
"EXIT_ABORT", ...
|
||||||
|
"IGNITION" ...
|
||||||
|
];
|
||||||
|
|
||||||
|
maskImportant = ismember(eventLabel, importantEvents);
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= RECONSTRUCT VALVE SEQUENCES =================
|
||||||
|
|
||||||
|
loxCmd = buildValveCmd(time, eventTime_s, eventLabel, "LOX", PARTIAL_OPEN_FRACTION, rampConfig.LOX);
|
||||||
|
ethCmd = buildValveCmd(time, eventTime_s, eventLabel, "ETHANOL", PARTIAL_OPEN_FRACTION, rampConfig.ETHANOL);
|
||||||
|
n2Cmd = buildValveCmd(time, eventTime_s, eventLabel, "NITROGEN", PARTIAL_OPEN_FRACTION, rampConfig.NITROGEN);
|
||||||
|
|
||||||
|
|
||||||
|
%% ================= PLOTTING: 3 STACKED SUBPLOTS =================
|
||||||
|
|
||||||
|
pressureNames = { ...
|
||||||
|
'LOX tank pressure', ...
|
||||||
|
'Ethanol tank pressure', ...
|
||||||
|
'Ethanol pressure in injection plate', ...
|
||||||
|
'Chamber pressure', ...
|
||||||
|
'Ethanol pressure at inlet', ...
|
||||||
|
'Pressure channel 6', ...
|
||||||
|
'Pressure channel 7'};
|
||||||
|
|
||||||
|
figure;
|
||||||
|
tiledlayout(3,1,'TileSpacing','compact');
|
||||||
|
|
||||||
|
% ----- Top: reconstructed valve sequence -----
|
||||||
|
ax1 = nexttile; hold on;
|
||||||
|
plot(time, loxCmd, 'LineWidth', 1.2, 'DisplayName', 'LOX');
|
||||||
|
plot(time, ethCmd, 'LineWidth', 1.2, 'DisplayName', 'ETHANOL');
|
||||||
|
plot(time, n2Cmd, 'LineWidth', 1.2, 'DisplayName', 'NITROGEN');
|
||||||
|
|
||||||
|
ylabel('Valve Opening');
|
||||||
|
ylim([-0.1 1.1]);
|
||||||
|
yticks([0 PARTIAL_OPEN_FRACTION 1]);
|
||||||
|
yticklabels({'0', sprintf(' %.2g',PARTIAL_OPEN_FRACTION*100), '100%'});
|
||||||
|
|
||||||
|
title('Final Hot Fire');
|
||||||
|
legend('Location','eastoutside');
|
||||||
|
grid on;
|
||||||
|
|
||||||
|
addEventLines(eventTime_s, eventLabel, maskImportant);
|
||||||
|
|
||||||
|
% ----- Middle: thrust -----
|
||||||
|
ax2 = nexttile; hold on;
|
||||||
|
plot(time, weight);
|
||||||
|
ylabel('Thrust [kg]');
|
||||||
|
grid on;
|
||||||
|
addEventLines(eventTime_s, eventLabel, maskImportant);
|
||||||
|
|
||||||
|
% ----- Bottom: all pressures overlayed -----
|
||||||
|
ax3 = nexttile; hold on;
|
||||||
|
for i = 1:5
|
||||||
|
plot(time, pressure(:,i), 'DisplayName', pressureNames{i});
|
||||||
|
end
|
||||||
|
ylabel('Pressure [bar]');
|
||||||
|
xlabel('Time [s]');
|
||||||
|
legend('Location','eastoutside');
|
||||||
|
grid on;
|
||||||
|
addEventLines(eventTime_s, eventLabel, maskImportant);
|
||||||
|
|
||||||
|
% Link x-axes for all subplots
|
||||||
|
linkaxes([ax1 ax2 ax3], 'x');
|
||||||
|
xlim([-4 10]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% ====== Make animation video (time-cursor moving in real time) ======
|
||||||
|
%{
|
||||||
|
videoName = 'nova2_run_animation.mp4';
|
||||||
|
|
||||||
|
v = VideoWriter(videoName, 'MPEG-4'); % use 'MPEG-4' for .mp4
|
||||||
|
v.FrameRate = 30; % frames per second
|
||||||
|
open(v);
|
||||||
|
|
||||||
|
% Time range of your data (already in seconds)
|
||||||
|
tMin = min(time);
|
||||||
|
tMax = max(time);
|
||||||
|
|
||||||
|
fps = v.FrameRate;
|
||||||
|
duration = tMax - tMin; % [s] actual test duration
|
||||||
|
nFrames = ceil(duration * fps); % so video length ≈ duration
|
||||||
|
tFrame = linspace(tMin, tMax, nFrames);
|
||||||
|
|
||||||
|
% Create a vertical cursor line on each subplot
|
||||||
|
axAll = [ax1 ax2 ax3];
|
||||||
|
hCursor = gobjects(numel(axAll), 1);
|
||||||
|
|
||||||
|
for i = 1:numel(axAll)
|
||||||
|
axes(axAll(i)); %#ok<LAXES> to make it current
|
||||||
|
hold(axAll(i), 'on');
|
||||||
|
hCursor(i) = xline(axAll(i), tMin, 'k-', 'LineWidth', 1.5);
|
||||||
|
end
|
||||||
|
|
||||||
|
% Sweep cursor across time and record each frame
|
||||||
|
for k = 1:nFrames
|
||||||
|
tCurr = tFrame(k);
|
||||||
|
|
||||||
|
% Move cursor to current time on all axes
|
||||||
|
for i = 1:numel(axAll)
|
||||||
|
hCursor(i).Value = tCurr;
|
||||||
|
end
|
||||||
|
|
||||||
|
drawnow limitrate; % update figure
|
||||||
|
frame = getframe(gcf); % capture current figure
|
||||||
|
writeVideo(v, frame); % write to video
|
||||||
|
end
|
||||||
|
|
||||||
|
close(v);
|
||||||
|
disp("Saved video to: " + videoName);
|
||||||
|
|
||||||
|
%}
|
||||||
|
%% ============= LOCAL FUNCTIONS (same file) =================
|
||||||
|
function addEventLines(eventTime_s, eventLabel, mask)
|
||||||
|
% addEventLines Draw vertical event markers (lines + rotated text) on current axes.
|
||||||
|
% addEventLines(eventTime_s, eventLabel)
|
||||||
|
% addEventLines(eventTime_s, eventLabel, mask)
|
||||||
|
%
|
||||||
|
% eventTime_s : vector of event times in seconds
|
||||||
|
% eventLabel : string/cellstr array of labels, same size as eventTime_s
|
||||||
|
% mask : optional logical mask to select which events to draw
|
||||||
|
|
||||||
|
if nargin < 3 || isempty(mask)
|
||||||
|
mask = true(size(eventTime_s));
|
||||||
|
end
|
||||||
|
|
||||||
|
ax = gca;
|
||||||
|
ylims = ylim(ax);
|
||||||
|
yrange = diff(ylims);
|
||||||
|
|
||||||
|
% Put label a bit inside the top of the axis
|
||||||
|
yLabel = ylims(2) - 0.02 * yrange;
|
||||||
|
|
||||||
|
% Keep only selected events
|
||||||
|
tAll = eventTime_s(mask);
|
||||||
|
labelsAll = eventLabel(mask);
|
||||||
|
|
||||||
|
if isempty(tAll)
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
% Group by unique time
|
||||||
|
[tUnique, ~, groupIdx] = unique(tAll);
|
||||||
|
|
||||||
|
for g = 1:numel(tUnique)
|
||||||
|
t = tUnique(g);
|
||||||
|
|
||||||
|
% All events at this time
|
||||||
|
inds = find(groupIdx == g);
|
||||||
|
groupLabels = labelsAll(inds);
|
||||||
|
n = numel(inds);
|
||||||
|
|
||||||
|
% ---- Decide label text ----
|
||||||
|
if n == 1
|
||||||
|
% Single event → show its actual name
|
||||||
|
lbl = char(groupLabels);
|
||||||
|
else
|
||||||
|
% Multiple events at same timestamp
|
||||||
|
if any(endsWith(groupLabels, "FULL_CLOSED"))
|
||||||
|
lbl = 'VALVES CLOSING ';
|
||||||
|
elseif any(endsWith(groupLabels, "OPEN_FULL"))
|
||||||
|
lbl = 'VALVES OPEN FULL';
|
||||||
|
else
|
||||||
|
lbl = 'MULTIPLE EVENTS';
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
% ---- Draw one vertical line ----
|
||||||
|
xline(ax, t, 'r--', 'HandleVisibility', 'off');
|
||||||
|
|
||||||
|
% ---- Draw one label on that line, inside the axis ----
|
||||||
|
text(ax, t, yLabel, lbl, ...
|
||||||
|
'Rotation', 90, ...
|
||||||
|
'Interpreter', 'none', ... % so "_" isn't treated as subscript
|
||||||
|
'VerticalAlignment', 'top', ...
|
||||||
|
'HorizontalAlignment', 'right', ...
|
||||||
|
'FontSize', 8, ...
|
||||||
|
'Clipping', 'on'); % keep it inside the axes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function cmd = buildValveCmd(time, eventTime_s, eventLabel, prefix, partialFrac, rampCfg)
|
||||||
|
% buildValveCmd Reconstruct valve command (0..1) for a given valve.
|
||||||
|
%
|
||||||
|
% cmd = buildValveCmd(time, eventTime_s, eventLabel, "LOX", partialFrac, rampCfg)
|
||||||
|
%
|
||||||
|
% prefix : "LOX", "ETHANOL", or "NITROGEN"
|
||||||
|
% partialFrac : value used for *_OPEN (e.g. 0.4)
|
||||||
|
% rampCfg : struct with fields .open, .openFull, .close (in seconds)
|
||||||
|
|
||||||
|
% Masks for events relevant to this valve
|
||||||
|
maskOpen = eventLabel == prefix + "_OPEN";
|
||||||
|
maskOpenFull = eventLabel == prefix + "_OPEN_FULL";
|
||||||
|
maskClosed = eventLabel == prefix + "_FULL_CLOSED";
|
||||||
|
|
||||||
|
timesAll = [ eventTime_s(maskOpen); ...
|
||||||
|
eventTime_s(maskOpenFull); ...
|
||||||
|
eventTime_s(maskClosed) ];
|
||||||
|
labelsAll = [ eventLabel(maskOpen); ...
|
||||||
|
eventLabel(maskOpenFull); ...
|
||||||
|
eventLabel(maskClosed) ];
|
||||||
|
|
||||||
|
if isempty(timesAll)
|
||||||
|
% No events for this valve: always closed
|
||||||
|
cmd = zeros(size(time));
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
% Sort valve events by time
|
||||||
|
[timesAll, idxSort] = sort(timesAll);
|
||||||
|
labelsAll = labelsAll(idxSort);
|
||||||
|
|
||||||
|
% Determine state right before the first time sample
|
||||||
|
currentState = 0.0; % start closed
|
||||||
|
for k = 1:numel(timesAll)
|
||||||
|
if timesAll(k) <= time(1)
|
||||||
|
lbl = labelsAll(k);
|
||||||
|
if lbl == prefix + "_OPEN"
|
||||||
|
currentState = partialFrac;
|
||||||
|
elseif lbl == prefix + "_OPEN_FULL"
|
||||||
|
currentState = 1.0;
|
||||||
|
elseif lbl == prefix + "_FULL_CLOSED"
|
||||||
|
currentState = 0.0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
% Build event list (for this valve) starting at time(1)
|
||||||
|
eTimes = time(1);
|
||||||
|
eTargets = currentState;
|
||||||
|
eRampDur = 0;
|
||||||
|
|
||||||
|
for k = 1:numel(timesAll)
|
||||||
|
t = timesAll(k);
|
||||||
|
if t <= time(1)
|
||||||
|
continue; % already accounted in initial state
|
||||||
|
end
|
||||||
|
lbl = labelsAll(k);
|
||||||
|
|
||||||
|
if lbl == prefix + "_OPEN"
|
||||||
|
target = partialFrac;
|
||||||
|
ramp = rampCfg.open;
|
||||||
|
elseif lbl == prefix + "_OPEN_FULL"
|
||||||
|
target = 1.0;
|
||||||
|
ramp = rampCfg.openFull;
|
||||||
|
elseif lbl == prefix + "_FULL_CLOSED"
|
||||||
|
target = 0.0;
|
||||||
|
ramp = rampCfg.close;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
end
|
||||||
|
|
||||||
|
eTimes(end+1) = t;
|
||||||
|
eTargets(end+1) = target;
|
||||||
|
eRampDur(end+1) = ramp;
|
||||||
|
end
|
||||||
|
|
||||||
|
% Convert these event + ramp definitions into a continuous command
|
||||||
|
cmd = applyValveRamps(time, eTimes, eTargets, eRampDur);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function cmd = applyValveRamps(time, eTimes, eTargets, eRampDur)
|
||||||
|
% applyValveRamps Build valve command with linear ramps between states.
|
||||||
|
%
|
||||||
|
% time : time vector
|
||||||
|
% eTimes : times of state changes
|
||||||
|
% eTargets : target state at each eTimes entry
|
||||||
|
% eRampDur : ramp duration after each event
|
||||||
|
|
||||||
|
cmd = zeros(size(time));
|
||||||
|
|
||||||
|
nEvents = numel(eTimes);
|
||||||
|
currentState = eTargets(1);
|
||||||
|
lastEndTime = time(1);
|
||||||
|
|
||||||
|
% Ensure row vectors
|
||||||
|
time = time(:).';
|
||||||
|
cmd = cmd(:).';
|
||||||
|
|
||||||
|
for i = 2:nEvents
|
||||||
|
tEvent = eTimes(i);
|
||||||
|
target = eTargets(i);
|
||||||
|
ramp = eRampDur(i);
|
||||||
|
|
||||||
|
% Constant segment from lastEndTime up to the event time
|
||||||
|
idxConst = time >= lastEndTime & time < tEvent;
|
||||||
|
cmd(idxConst) = currentState;
|
||||||
|
|
||||||
|
% Ramp segment from event time to event time + ramp
|
||||||
|
rampStart = tEvent;
|
||||||
|
rampEnd = tEvent + ramp;
|
||||||
|
|
||||||
|
if ramp > 0
|
||||||
|
idxRamp = time >= rampStart & time < rampEnd;
|
||||||
|
cmd(idxRamp) = currentState + (target - currentState) .* ...
|
||||||
|
(time(idxRamp) - rampStart) / ramp;
|
||||||
|
currentState = target;
|
||||||
|
lastEndTime = rampEnd;
|
||||||
|
else
|
||||||
|
% Instantaneous change
|
||||||
|
currentState = target;
|
||||||
|
lastEndTime = tEvent;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
% Final constant segment after the last ramp
|
||||||
|
idxTail = time >= lastEndTime;
|
||||||
|
cmd(idxTail) = currentState;
|
||||||
|
|
||||||
|
% Return as column vector to match input style
|
||||||
|
cmd = cmd(:);
|
||||||
|
end
|
||||||
1137
Matlab_post_analysis/initial_sequence.eps
Normal file
1137
Matlab_post_analysis/initial_sequence.eps
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Matlab_post_analysis/nova2_run_animation.mp4
Normal file
BIN
Matlab_post_analysis/nova2_run_animation.mp4
Normal file
Binary file not shown.
66
Matlab_post_analysis/post_analysis.m
Normal file
66
Matlab_post_analysis/post_analysis.m
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
close all;
|
||||||
|
events= readtable("001rec_SI.csv");
|
||||||
|
result = readmatrix("001rec_ADC_data.csv");
|
||||||
|
%result = result(14430:end, : );
|
||||||
|
result(:,14) = result(:,14)./2;
|
||||||
|
time = result(:,1)./1000;
|
||||||
|
|
||||||
|
temp = 0.000111 .* result(:,4:9) + 2.31991;
|
||||||
|
|
||||||
|
pressure = result(:,10:17).*(0.000015)-6.565;
|
||||||
|
|
||||||
|
load_cell = result(:,2:3);
|
||||||
|
weight = -1.166759307845543e-04 .* 0.5.*(load_cell(:,1) + load_cell(:,2)) + 4.971416323340051e+02;
|
||||||
|
|
||||||
|
figure()
|
||||||
|
scatter(time,weight,10)
|
||||||
|
title("load cell in Kg")
|
||||||
|
xlabel("Time in s")
|
||||||
|
ylabel("KG")
|
||||||
|
xlim([878.207 920.111+5.000])
|
||||||
|
|
||||||
|
|
||||||
|
pressureNames = { ...
|
||||||
|
'LOX tank pressure', ...
|
||||||
|
'Ethanol tank pressure', ...
|
||||||
|
'Ethanol pressure in injection plate', ...
|
||||||
|
'Chamber pressure', ...
|
||||||
|
'Ethanol pressure at inlet', ...
|
||||||
|
'Pressure channel 6', ...
|
||||||
|
'Pressure channel 7'};
|
||||||
|
|
||||||
|
for x = 1:7
|
||||||
|
figure()
|
||||||
|
hold on;
|
||||||
|
|
||||||
|
% Left axis → weight/thrust
|
||||||
|
yyaxis left
|
||||||
|
scatter(time, weight, 'DisplayName', 'Thrust');
|
||||||
|
ylabel("Thrust / Weight")
|
||||||
|
|
||||||
|
% Right axis → pressure
|
||||||
|
yyaxis right
|
||||||
|
scatter(time, pressure(:,x), 10, 'DisplayName', 'Pressure');
|
||||||
|
ylabel("Pressure")
|
||||||
|
|
||||||
|
% Title using your descriptive spreadsheet names
|
||||||
|
title(pressureNames{x})
|
||||||
|
|
||||||
|
xlabel("Time in s")
|
||||||
|
legend()
|
||||||
|
xlim([878.207 920.111 + 5.000])
|
||||||
|
end
|
||||||
|
|
||||||
|
%{
|
||||||
|
for x = 1:6
|
||||||
|
figure()
|
||||||
|
scatter(time,temp(:,x),10)
|
||||||
|
title(sprintf("temperature channel %d",x))
|
||||||
|
xlabel("Time in s")
|
||||||
|
ylabel("Temprature in C")
|
||||||
|
xlim([878.207 920.111+5.000])
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
1419
Matlab_post_analysis/second_hot_data.eps
Normal file
1419
Matlab_post_analysis/second_hot_data.eps
Normal file
File diff suppressed because it is too large
Load Diff
28
SI_COMMAND_MESSAGES.txt
Normal file
28
SI_COMMAND_MESSAGES.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
STOP_STREAM
|
||||||
|
START_STREAM,
|
||||||
|
RESET,
|
||||||
|
WIPE_STM,
|
||||||
|
UPTIME,
|
||||||
|
PING,
|
||||||
|
GET_STATE,
|
||||||
|
ARM,
|
||||||
|
DISARM,
|
||||||
|
STATIC_FIRE,
|
||||||
|
STATIC_FIRE3,
|
||||||
|
STATIC_FIRE5,
|
||||||
|
STATIC_FIRE7,
|
||||||
|
STATIC_FIRE9,
|
||||||
|
ABORT,
|
||||||
|
SERVO_SWEEP,
|
||||||
|
SERVO_SET,
|
||||||
|
SERVO_WIGGLE,
|
||||||
|
SERVO_STOP,
|
||||||
|
GET_READINGS,
|
||||||
|
SET_ST_SPEED,
|
||||||
|
START_FLASH,
|
||||||
|
START_USB,
|
||||||
|
TEST_RECORDER,
|
||||||
|
START_OXYVENT,
|
||||||
|
STOP_OXYVENT,
|
||||||
|
VERSION,
|
||||||
|
GET_RSSI
|
||||||
33
SI_LOG_MESSAGES.txt
Normal file
33
SI_LOG_MESSAGES.txt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
EMPTY_LOG = 0,
|
||||||
|
LOGGING_DATA,
|
||||||
|
FLASH_ERROR,
|
||||||
|
FLASH_START_ERROR,
|
||||||
|
FLASH_FULL,
|
||||||
|
FLASH_LOG_FULL,
|
||||||
|
ENTER_STATIC_FIRE,
|
||||||
|
EXIT_STATIC_FIRE,
|
||||||
|
ENTER_ARM,
|
||||||
|
UPDATE_ARM,
|
||||||
|
EXIT_ARM,
|
||||||
|
LOX_OPEN,
|
||||||
|
LOX_OPEN_FULL,
|
||||||
|
ETHANOL_OPEN,
|
||||||
|
ETHANOL_OPEN_FULL,
|
||||||
|
NITROGEN_OPEN,
|
||||||
|
NITROGEN_OPEN_FULL,
|
||||||
|
LOX_CLOSING,
|
||||||
|
LOX_FULL_CLOSED,
|
||||||
|
ETHANOL_CLOSING,
|
||||||
|
ETHANOL_FULL_CLOSED,
|
||||||
|
NITROGEN_CLOSING,
|
||||||
|
NITROGEN_FULL_CLOSED,
|
||||||
|
ETHANOL_DRAIN_OPEN,
|
||||||
|
LOX_DRAIN_OPEN,
|
||||||
|
ETHANOL_DRAIN_CLOSED,
|
||||||
|
LOX_DRAIN_CLOSED,
|
||||||
|
LOX_VENT_OPEN,
|
||||||
|
LOX_VENT_CLOSED,
|
||||||
|
LOX_VENT_ERROR_OPEN,
|
||||||
|
ENTER_ABORT,
|
||||||
|
EXIT_ABORT,
|
||||||
|
ERROR_RESET_IWDG
|
||||||
185
Serial_decoder.py
Normal file
185
Serial_decoder.py
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import serial
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import threading
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List
|
||||||
|
from datetime import datetime
|
||||||
|
import queue
|
||||||
|
import re
|
||||||
|
|
||||||
|
PORT = "COM9"
|
||||||
|
|
||||||
|
SerialPort = serial.Serial(port=PORT,baudrate=250000)
|
||||||
|
input_queue = queue.SimpleQueue()
|
||||||
|
output_queue = queue.SimpleQueue()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReturnDecoder:
|
||||||
|
name: str
|
||||||
|
text_respons: str = ""
|
||||||
|
ADC_data: List[int] = field(default_factory=lambda: [])
|
||||||
|
timestamp: int = 0
|
||||||
|
log_message: str = ""
|
||||||
|
bytes_read: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#length is 3*16+4+1
|
||||||
|
def ADC_FULLRANK(buffer: bytes) -> ReturnDecoder:
|
||||||
|
if len(buffer)<53 : return ReturnDecoder("EMPTY")
|
||||||
|
return_var = ReturnDecoder("ADC_FULLRANK")
|
||||||
|
for i in range(16):
|
||||||
|
return_var.ADC_data.append(int.from_bytes(bytes=buffer[3*i:3+3*i],byteorder="big",signed=True))
|
||||||
|
return_var.timestamp = int.from_bytes(bytes=buffer[48:52],byteorder="big",signed=False)
|
||||||
|
if buffer[52] != 0xFA:
|
||||||
|
return_var.name = f"FAULT_DECODER_END"
|
||||||
|
return_var.bytes_read = 1
|
||||||
|
print(buffer.hex(sep=" "))
|
||||||
|
return_var.bytes_read = 55
|
||||||
|
return return_var
|
||||||
|
|
||||||
|
LOG_MESSAGE_ENUM_LIST = []
|
||||||
|
with open("SI_LOG_MESSAGES.txt") as file:
|
||||||
|
for message in file:
|
||||||
|
LOG_MESSAGE_ENUM_LIST.append(re.sub("[\n,=0 ]","",message))
|
||||||
|
|
||||||
|
def SI_DECODER(buffer: bytes) -> ReturnDecoder:
|
||||||
|
if(len(buffer)<7): return ReturnDecoder("EMPTY")
|
||||||
|
return_var = ReturnDecoder("SI_DECODER")
|
||||||
|
LogNumber = int.from_bytes(bytes=buffer[0:2],byteorder="big",signed=False)
|
||||||
|
try:
|
||||||
|
return_var.log_message = LOG_MESSAGE_ENUM_LIST[LogNumber]
|
||||||
|
except:
|
||||||
|
return_var.name = "WRONG_SI_NUMBER"
|
||||||
|
return_var.log_message = LogNumber
|
||||||
|
return_var.bytes_read = 1
|
||||||
|
return return_var
|
||||||
|
return_var.timestamp = int.from_bytes(bytes=buffer[2:6],byteorder="big",signed=False)
|
||||||
|
if buffer[6] != 0xFA:
|
||||||
|
return_var.bytes_read = 1
|
||||||
|
return_var.name = f"FAULT_SI_DECODER_END"
|
||||||
|
print(buffer.hex(sep=" "))
|
||||||
|
return_var.bytes_read = 7
|
||||||
|
return return_var
|
||||||
|
|
||||||
|
|
||||||
|
AF_DECODERS = {
|
||||||
|
0x01: ADC_FULLRANK,
|
||||||
|
0x02: SI_DECODER
|
||||||
|
}
|
||||||
|
|
||||||
|
def AFDecoder(buffer: bytes) -> ReturnDecoder:
|
||||||
|
if len(buffer) < 2: return ReturnDecoder("EMPTY")
|
||||||
|
ret = ReturnDecoder("FAULT_DECODER")
|
||||||
|
ret.bytes_read = 1
|
||||||
|
if buffer[1] in AF_DECODERS:
|
||||||
|
try:
|
||||||
|
return AF_DECODERS[buffer[1]](buffer[2:])
|
||||||
|
except:
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
print("unkown message")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def PRDecoder(buffer: bytes) -> ReturnDecoder:
|
||||||
|
if buffer.find(b"\n") < 0:
|
||||||
|
return ReturnDecoder("EMPTY")
|
||||||
|
temp_str = buffer.decode("ascii",errors="replace")
|
||||||
|
ret = ReturnDecoder("PR_DECODER")
|
||||||
|
ret.text_respons = temp_str.split(sep='\n',maxsplit=2)[0]
|
||||||
|
ret.bytes_read = len(ret.text_respons)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def input_loop():
|
||||||
|
while True:
|
||||||
|
input_str = input()+'\n\r'
|
||||||
|
if input_str.find("exit") != -1:
|
||||||
|
return
|
||||||
|
input_queue.put(input_str)
|
||||||
|
|
||||||
|
def output_loop():
|
||||||
|
ret_DEC : ReturnDecoder = ReturnDecoder("EMPTY")
|
||||||
|
buffer : bytes = bytes(b"")
|
||||||
|
SerialPort.timeout = 0.1
|
||||||
|
input_str: str = ""
|
||||||
|
ret_DEC.name = "EMPTY"
|
||||||
|
ret_DEC.bytes_read = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
input_str = input_queue.get_nowait()
|
||||||
|
print(f"SD: sending {input_str}")
|
||||||
|
SerialPort.write(input_str.encode("ascii"))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
buffer = buffer + SerialPort.read_all()
|
||||||
|
AF_index = buffer.find(0xAF)
|
||||||
|
PR_index = buffer.find(b"PR")
|
||||||
|
if AF_index >= 0 and PR_index >= 0:
|
||||||
|
if AF_index < PR_index:
|
||||||
|
#if(AF_index): print(buffer[:AF_index].decode("ascii","replace"))
|
||||||
|
buffer = buffer[AF_index:]
|
||||||
|
ret_DEC = AFDecoder(buffer)
|
||||||
|
else:
|
||||||
|
#if(PR_index): print(buffer[:PR_index].decode("ascii","replace"))
|
||||||
|
buffer = buffer[PR_index:]
|
||||||
|
ret_DEC = PRDecoder(buffer)
|
||||||
|
elif AF_index >= 0:
|
||||||
|
#if(AF_index): print(buffer[:AF_index].decode("ascii","replace"))
|
||||||
|
buffer = buffer[AF_index:]
|
||||||
|
ret_DEC = AFDecoder(buffer)
|
||||||
|
elif PR_index >= 0:
|
||||||
|
#if(PR_index): print(buffer[:PR_index].decode("ascii","replace"))
|
||||||
|
buffer = buffer[PR_index:]
|
||||||
|
ret_DEC = PRDecoder(buffer)
|
||||||
|
|
||||||
|
if ret_DEC.bytes_read != 0:
|
||||||
|
buffer = buffer[ret_DEC.bytes_read:]
|
||||||
|
if ret_DEC.name != "EMPTY":
|
||||||
|
output_queue.put(ret_DEC)
|
||||||
|
ret_DEC = ReturnDecoder("EMPTY")
|
||||||
|
|
||||||
|
|
||||||
|
def GetReturn(timeout: float) -> ReturnDecoder | None:
|
||||||
|
try:
|
||||||
|
return output_queue.get(timeout=timeout)
|
||||||
|
except queue.Empty:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def SendCommand(input_str: str):
|
||||||
|
input_queue.put(input_str)
|
||||||
|
|
||||||
|
def StartInputOutput() -> tuple[threading.Thread, threading.Thread]:
|
||||||
|
t1 = threading.Thread(target=output_loop,daemon=True)
|
||||||
|
t2 = threading.Thread(target=input_loop, daemon=True)
|
||||||
|
t1.start()
|
||||||
|
t2.start()
|
||||||
|
return (t1,t2)
|
||||||
|
|
||||||
|
def StartOutput() -> threading.Thread:
|
||||||
|
t1 = threading.Thread(target=output_loop,daemon=True)
|
||||||
|
t1.start()
|
||||||
|
return t1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
t1 = threading.Thread(target=output_loop,daemon=True)
|
||||||
|
t2 = threading.Thread(target=input_loop)
|
||||||
|
t1.start()
|
||||||
|
t2.start()
|
||||||
|
while True:
|
||||||
|
value = GetReturn(0.5)
|
||||||
|
if t2.is_alive() == False:
|
||||||
|
break
|
||||||
|
if value == None:
|
||||||
|
continue
|
||||||
|
match value.name:
|
||||||
|
case "ADC_FULLRANK":
|
||||||
|
print(f"time:{value.timestamp},{value.ADC_data}")
|
||||||
|
case "SI_DECODER":
|
||||||
|
print(f"\033[Ftime:{value.timestamp}, SI:{value.log_message}",end="\r")
|
||||||
|
case "PR_DECODER":
|
||||||
|
print(f"PRD:{value.text_respons}")
|
||||||
|
case _:
|
||||||
|
print(f"Error {value.name}")
|
||||||
|
print("stopping")
|
||||||
|
|
||||||
246
mainplot.py
Normal file
246
mainplot.py
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import Serial_decoder as SD
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
# Matplotlib embedding in Tkinter
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
|
||||||
|
# === Serial threads (unchanged) ===
|
||||||
|
tasks = SD.StartInputOutput() # t1: output_loop (daemon), t2: input_loop (daemon)
|
||||||
|
FILENAME = f"SaveData/{datetime.today().strftime('%Y_%m_%d_%H_%M')}"
|
||||||
|
|
||||||
|
# === Plot history ===
|
||||||
|
MAX_POINTS = 100 # rolling window length
|
||||||
|
|
||||||
|
# Derived channels (what we actually plot):
|
||||||
|
# 0: Thrust (avg of load cells 1 & 2)
|
||||||
|
# 1-6: Temp 1..6
|
||||||
|
# 7-14: Pressure 1..8
|
||||||
|
DERIVED_CHANNEL_LABELS = [
|
||||||
|
"Thrust", # 0
|
||||||
|
"Temp 1", # 1
|
||||||
|
"Temp 2", # 2
|
||||||
|
"Temp 3", # 3
|
||||||
|
"Temp 4", # 4
|
||||||
|
"Temp 5", # 5
|
||||||
|
"Temp 6", # 6
|
||||||
|
"Pressure 1", # 7
|
||||||
|
"Pressure 2", # 8
|
||||||
|
"Pressure 3", # 9
|
||||||
|
"Pressure 4", # 10
|
||||||
|
"Pressure 5", # 11
|
||||||
|
"Pressure 6", # 12
|
||||||
|
"Pressure 7", # 13
|
||||||
|
"Pressure 8", # 14
|
||||||
|
]
|
||||||
|
NUM_DERIVED_CHANNELS = len(DERIVED_CHANNEL_LABELS) # 15
|
||||||
|
|
||||||
|
history_x = deque(maxlen=MAX_POINTS) # timestamps
|
||||||
|
history_y = [deque(maxlen=MAX_POINTS) for _ in range(NUM_DERIVED_CHANNELS)] # per-channel values
|
||||||
|
|
||||||
|
# === GUI setup ===
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Live 4x4 Grid + Selectable Real-Time Plot")
|
||||||
|
|
||||||
|
# Left: 4x4 grid (raw ADC values from 16 channels)
|
||||||
|
grid_frame = ttk.Frame(root)
|
||||||
|
grid_frame.grid(row=0, column=0, padx=10, pady=10, sticky="n")
|
||||||
|
|
||||||
|
labels = []
|
||||||
|
for row in range(4):
|
||||||
|
for col in range(4):
|
||||||
|
idx = row * 4 + col
|
||||||
|
lbl = tk.Label(
|
||||||
|
grid_frame, name=f"channel{idx}",
|
||||||
|
text="0", width=10, height=3,
|
||||||
|
borderwidth=2, relief="groove", font=("Arial", 14)
|
||||||
|
)
|
||||||
|
lbl.grid(row=row, column=col, padx=5, pady=5)
|
||||||
|
labels.append(lbl)
|
||||||
|
|
||||||
|
# Last / delta tick labels
|
||||||
|
lbl = tk.Label(root, text="Last Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14))
|
||||||
|
lbl.grid(row=4, column=0, columnspan=2)
|
||||||
|
labels.append(lbl) # index 16
|
||||||
|
|
||||||
|
lbl = tk.Label(root, text="Delta Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14))
|
||||||
|
lbl.grid(row=4, column=2, columnspan=2)
|
||||||
|
labels.append(lbl) # index 17
|
||||||
|
|
||||||
|
# Checkboxes to select plotted (derived) channels
|
||||||
|
check_frame = ttk.LabelFrame(root, text="Plot Selection")
|
||||||
|
check_frame.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nw")
|
||||||
|
|
||||||
|
check_vars = []
|
||||||
|
for i, ch_label in enumerate(DERIVED_CHANNEL_LABELS):
|
||||||
|
var = tk.IntVar(value=0)
|
||||||
|
chk = ttk.Checkbutton(
|
||||||
|
check_frame,
|
||||||
|
text=ch_label,
|
||||||
|
variable=var,
|
||||||
|
command=update_plot if 'update_plot' in globals() else None # placeholder; will be updated below
|
||||||
|
)
|
||||||
|
chk.grid(row=i // 4, column=i % 4, sticky="w", padx=4, pady=2)
|
||||||
|
check_vars.append(var)
|
||||||
|
|
||||||
|
# Right: Matplotlib plot
|
||||||
|
plot_frame = ttk.Frame(root)
|
||||||
|
plot_frame.grid(row=0, column=1, rowspan=2, padx=10, pady=10, sticky="n")
|
||||||
|
|
||||||
|
fig = Figure(figsize=(6, 4), dpi=100)
|
||||||
|
ax = fig.add_subplot(111)
|
||||||
|
ax.set_title("Selected Channels")
|
||||||
|
ax.set_xlabel("Timestamp")
|
||||||
|
ax.set_ylabel("Value")
|
||||||
|
canvas = FigureCanvasTkAgg(fig, master=plot_frame)
|
||||||
|
canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Optional controls
|
||||||
|
controls_frame = ttk.Frame(plot_frame)
|
||||||
|
controls_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
def clear_selection():
|
||||||
|
for v in check_vars:
|
||||||
|
v.set(0)
|
||||||
|
update_plot()
|
||||||
|
|
||||||
|
def clear_history():
|
||||||
|
"""Clear stored history for all plotted channels."""
|
||||||
|
history_x.clear()
|
||||||
|
for dq in history_y:
|
||||||
|
dq.clear()
|
||||||
|
update_plot() # refresh plot so it shows 'No data' message
|
||||||
|
|
||||||
|
ttk.Button(controls_frame, text="Clear Selection", command=clear_selection).pack(side="left")
|
||||||
|
ttk.Button(controls_frame, text="Clear History", command=clear_history).pack(side="left")
|
||||||
|
|
||||||
|
# === Update functions ===
|
||||||
|
Last_tick: int = 0
|
||||||
|
|
||||||
|
def update_grid(values: list, tick: int):
|
||||||
|
"""Update the 4x4 grid with raw ADC values and write ADC csv."""
|
||||||
|
global Last_tick
|
||||||
|
# NOTE: mode should be "a" or "a+", not "+a"
|
||||||
|
with open(f"{FILENAME}_ADC.csv", "a") as file:
|
||||||
|
file.write(f"{tick}")
|
||||||
|
for i in range(16): # 16 raw ADC channels
|
||||||
|
file.write(f",{values[i]}")
|
||||||
|
labels[i].config(text=f"{values[i]:n}")
|
||||||
|
ms_total = tick
|
||||||
|
hours, rem = divmod(ms_total, 3_600_000) # 1000*60*60
|
||||||
|
minutes, rem = divmod(rem, 60_000) # 1000*60
|
||||||
|
seconds, milliseconds = divmod(rem, 1000)
|
||||||
|
labels[16].config( text=f"UP Time:{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}")
|
||||||
|
labels[17].config(text=f"Delta Tick:{(tick - Last_tick):n}")
|
||||||
|
Last_tick = tick
|
||||||
|
file.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def update_SI(value: SD.ReturnDecoder):
|
||||||
|
# NOTE: mode should be "a" or "a+", not "+a"
|
||||||
|
with open(f"{FILENAME}_SI.csv", "a") as file:
|
||||||
|
file.write(f"{value.timestamp},{value.log_message}\n")
|
||||||
|
print(f"\033[F\033[2Ktime:{value.timestamp}, SI:{value.log_message}", end="\n")
|
||||||
|
|
||||||
|
Last_history_call: int = 0
|
||||||
|
|
||||||
|
def append_history(adc_values: list, timestamp: int):
|
||||||
|
"""Store the new sample in the rolling history (derived units)."""
|
||||||
|
global Last_history_call
|
||||||
|
if len(history_x) > 0 and timestamp < history_x[-1]:
|
||||||
|
return # discard out-of-order samples
|
||||||
|
if len(history_x) > 0 and (history_x[-1] + (int(time.time_ns() / 1000000) - Last_history_call)*2) < timestamp:
|
||||||
|
print(f"Discarding future sample: {timestamp} > {history_x[-1]} + {(int(time.time_ns() / 1000000) - Last_history_call)*2}")
|
||||||
|
return # discard samples too far in future
|
||||||
|
history_x.append(timestamp)
|
||||||
|
Last_history_call = int(time.time_ns() / 1000000) # in ms
|
||||||
|
|
||||||
|
# 0: Thrust (average of load cells 0 and 1)
|
||||||
|
lc_avg = (adc_values[0] + adc_values[1]) / 2
|
||||||
|
weight = -1.166759308000000e-04 * lc_avg + 4.971416323340051e+02
|
||||||
|
history_y[0].append(weight)
|
||||||
|
|
||||||
|
# 1-6: Temperatures from channels 2..7
|
||||||
|
for i in range(2, 8): # six thermocouples
|
||||||
|
temp = 0.000111 * adc_values[i] + 2.31991
|
||||||
|
history_y[i - 1].append(temp) # 2→1, 7→6
|
||||||
|
|
||||||
|
# 7-14: Pressures from channels 8..15
|
||||||
|
for i in range(8, 16): # eight pressure sensors
|
||||||
|
pres = 0.0000153522 * adc_values[i] - 6.5652036917
|
||||||
|
history_y[i - 1].append(pres) # 8→7, 15→14
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_plot():
|
||||||
|
"""Redraw plot based on selected derived channels and available history."""
|
||||||
|
ax.clear()
|
||||||
|
ax.set_title("Selected Channels")
|
||||||
|
ax.set_xlabel("Timestamp")
|
||||||
|
ax.set_ylabel("Value")
|
||||||
|
|
||||||
|
selected = [i for i, var in enumerate(check_vars) if var.get() == 1]
|
||||||
|
if len(history_x) == 0 or len(selected) == 0:
|
||||||
|
ax.text(
|
||||||
|
0.5, 0.5,
|
||||||
|
"No data / No channels selected",
|
||||||
|
ha="center", va="center",
|
||||||
|
transform=ax.transAxes
|
||||||
|
)
|
||||||
|
canvas.draw()
|
||||||
|
return
|
||||||
|
|
||||||
|
x = list(history_x)
|
||||||
|
for idx in selected:
|
||||||
|
# safety: guard against any mismatch, just in case
|
||||||
|
if idx < len(history_y):
|
||||||
|
y = list(history_y[idx])
|
||||||
|
ax.plot(x, y, label=DERIVED_CHANNEL_LABELS[idx])
|
||||||
|
|
||||||
|
ax.legend(loc="upper left", fontsize=8)
|
||||||
|
ax.grid(True, linestyle="--", alpha=0.3)
|
||||||
|
canvas.draw()
|
||||||
|
|
||||||
|
# Now that update_plot exists, fix the checkbox command to point to it
|
||||||
|
for chk_var, child in zip(check_vars, check_frame.winfo_children()):
|
||||||
|
if isinstance(child, ttk.Checkbutton):
|
||||||
|
child.config(command=update_plot)
|
||||||
|
|
||||||
|
def update_panel():
|
||||||
|
"""Main polling loop that consumes Serial_decoder outputs."""
|
||||||
|
values: SD.ReturnDecoder = SD.GetReturn(0.1) # non-blocking read with timeout
|
||||||
|
if values is None:
|
||||||
|
root.after(10, update_panel)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Stop if input thread died
|
||||||
|
if tasks[1].is_alive() is False:
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
match values.name:
|
||||||
|
case "ADC_FULLRANK":
|
||||||
|
# Update grid + history + plot
|
||||||
|
update_grid(values=values.ADC_data, tick=values.timestamp)
|
||||||
|
append_history(values.ADC_data, values.timestamp)
|
||||||
|
update_plot() # draw right away; if too heavy, throttle with a timer
|
||||||
|
case "SI_DECODER":
|
||||||
|
update_SI(values)
|
||||||
|
case "PR_DECODER":
|
||||||
|
print(f"PRD:{values.text_respons}")
|
||||||
|
case _:
|
||||||
|
print(f"Error {values.name}")
|
||||||
|
|
||||||
|
# Continue polling
|
||||||
|
root.after(10, update_panel) # 10 ms
|
||||||
|
|
||||||
|
# Start polling and GUI loop
|
||||||
|
update_panel()
|
||||||
|
|
||||||
|
# FC commands
|
||||||
|
SD.SerialPort.write(b"SET_ST_SPEED 100\n\r")
|
||||||
|
|
||||||
|
root.mainloop()
|
||||||
346
mainv2.py
Normal file
346
mainv2.py
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
import Serial_decoder as SD
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
# Matplotlib embedding in Tkinter
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
|
||||||
|
# === Serial threads (unchanged) ===
|
||||||
|
tasks = SD.StartInputOutput() # t1: output_loop (daemon), t2: input_loop (daemon)
|
||||||
|
FILENAME = f"SaveData/{datetime.today().strftime('%Y_%m_%d_%H_%M')}"
|
||||||
|
|
||||||
|
# === Commands window config ===
|
||||||
|
COMMANDS_FILE = "COMMANDS.txt" # one command per line
|
||||||
|
|
||||||
|
|
||||||
|
# === Plot history ===
|
||||||
|
MAX_POINTS = 100 # rolling window length
|
||||||
|
|
||||||
|
# Derived channels (what we actually plot):
|
||||||
|
# 0: Thrust (avg of load cells 1 & 2)
|
||||||
|
# 1-6: Temp 1..6
|
||||||
|
# 7-14: Pressure 1..8
|
||||||
|
DERIVED_CHANNEL_LABELS = [
|
||||||
|
"Thrust", # 0
|
||||||
|
"Temp 1", # 1
|
||||||
|
"Temp 2", # 2
|
||||||
|
"Temp 3", # 3
|
||||||
|
"Temp 4", # 4
|
||||||
|
"Temp 5", # 5
|
||||||
|
"Temp 6", # 6
|
||||||
|
"Pressure 1", # 7
|
||||||
|
"Pressure 2", # 8
|
||||||
|
"Pressure 3", # 9
|
||||||
|
"Pressure 4", # 10
|
||||||
|
"Pressure 5", # 11
|
||||||
|
"Pressure 6", # 12
|
||||||
|
"Pressure 7", # 13
|
||||||
|
"Pressure 8", # 14
|
||||||
|
]
|
||||||
|
NUM_DERIVED_CHANNELS = len(DERIVED_CHANNEL_LABELS) # 15
|
||||||
|
|
||||||
|
history_x = deque(maxlen=MAX_POINTS) # timestamps
|
||||||
|
history_y = [deque(maxlen=MAX_POINTS) for _ in range(NUM_DERIVED_CHANNELS)] # per-channel values
|
||||||
|
|
||||||
|
# === GUI setup ===
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Live 4x4 Grid + Selectable Real-Time Plot")
|
||||||
|
|
||||||
|
# Left: 4x4 grid (raw ADC values from 16 channels)
|
||||||
|
grid_frame = ttk.Frame(root)
|
||||||
|
grid_frame.grid(row=0, column=0, padx=10, pady=10, sticky="n")
|
||||||
|
|
||||||
|
labels = []
|
||||||
|
for row in range(4):
|
||||||
|
for col in range(4):
|
||||||
|
idx = row * 4 + col
|
||||||
|
lbl = tk.Label(
|
||||||
|
grid_frame, name=f"channel{idx}",
|
||||||
|
text="0", width=10, height=3,
|
||||||
|
borderwidth=2, relief="groove", font=("Arial", 14)
|
||||||
|
)
|
||||||
|
lbl.grid(row=row, column=col, padx=5, pady=5)
|
||||||
|
labels.append(lbl)
|
||||||
|
|
||||||
|
# Last / delta tick labels
|
||||||
|
lbl = tk.Label(root, text="Last Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14))
|
||||||
|
lbl.grid(row=4, column=0, columnspan=2)
|
||||||
|
labels.append(lbl) # index 16
|
||||||
|
|
||||||
|
lbl = tk.Label(root, text="Delta Tick:0", width=20, height=3, borderwidth=2, relief="groove", font=("Arial", 14))
|
||||||
|
lbl.grid(row=4, column=1, columnspan=2)
|
||||||
|
labels.append(lbl) # index 17
|
||||||
|
|
||||||
|
# Checkboxes to select plotted (derived) channels
|
||||||
|
check_frame = ttk.LabelFrame(root, text="Plot Selection")
|
||||||
|
check_frame.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nw")
|
||||||
|
|
||||||
|
check_vars = []
|
||||||
|
for i, ch_label in enumerate(DERIVED_CHANNEL_LABELS):
|
||||||
|
var = tk.IntVar(value=0)
|
||||||
|
chk = ttk.Checkbutton(
|
||||||
|
check_frame,
|
||||||
|
text=ch_label,
|
||||||
|
variable=var,
|
||||||
|
command=None # will assign update_plot after it is defined
|
||||||
|
)
|
||||||
|
chk.grid(row=i // 4, column=i % 4, sticky="w", padx=4, pady=2)
|
||||||
|
check_vars.append(var)
|
||||||
|
|
||||||
|
# Right: Matplotlib plot
|
||||||
|
plot_frame = ttk.Frame(root)
|
||||||
|
plot_frame.grid(row=0, column=1, rowspan=2, padx=10, pady=10, sticky="n")
|
||||||
|
|
||||||
|
fig = Figure(figsize=(6, 4), dpi=100)
|
||||||
|
ax = fig.add_subplot(111)
|
||||||
|
ax.set_title("Selected Channels")
|
||||||
|
ax.set_xlabel("Timestamp")
|
||||||
|
ax.set_ylabel("Value")
|
||||||
|
canvas = FigureCanvasTkAgg(fig, master=plot_frame)
|
||||||
|
canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Optional controls
|
||||||
|
controls_frame = ttk.Frame(plot_frame)
|
||||||
|
controls_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_selection():
|
||||||
|
for v in check_vars:
|
||||||
|
v.set(0)
|
||||||
|
update_plot()
|
||||||
|
|
||||||
|
|
||||||
|
def clear_history():
|
||||||
|
"""Clear stored history for all plotted channels."""
|
||||||
|
history_x.clear()
|
||||||
|
for dq in history_y:
|
||||||
|
dq.clear()
|
||||||
|
update_plot() # refresh plot so it shows 'No data' message
|
||||||
|
|
||||||
|
|
||||||
|
ttk.Button(controls_frame, text="Clear Selection", command=clear_selection).pack(side="left")
|
||||||
|
ttk.Button(controls_frame, text="Clear History", command=clear_history).pack(side="left")
|
||||||
|
|
||||||
|
|
||||||
|
# === Commands window ===
|
||||||
|
|
||||||
|
def open_commands_window():
|
||||||
|
"""
|
||||||
|
Create an extra window that shows buttons for each command found in COMMANDS.txt.
|
||||||
|
Each button sends the corresponding command string via SD.SendCommand.
|
||||||
|
"""
|
||||||
|
cmd_win = tk.Toplevel(root)
|
||||||
|
cmd_win.title("Command Buttons")
|
||||||
|
|
||||||
|
outer = ttk.Frame(cmd_win, padding=10)
|
||||||
|
outer.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Scrollable area in case there are many commands
|
||||||
|
canvas_widget = tk.Canvas(outer, borderwidth=0)
|
||||||
|
scrollbar = ttk.Scrollbar(outer, orient="vertical", command=canvas_widget.yview)
|
||||||
|
cmds_frame = ttk.Frame(canvas_widget)
|
||||||
|
|
||||||
|
cmds_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: canvas_widget.configure(scrollregion=canvas_widget.bbox("all"))
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas_widget.create_window((0, 0), window=cmds_frame, anchor="nw")
|
||||||
|
canvas_widget.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
canvas_widget.pack(side="left", fill="both", expand=True)
|
||||||
|
scrollbar.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
# Load commands from file
|
||||||
|
try:
|
||||||
|
with open(COMMANDS_FILE, "r") as f:
|
||||||
|
commands = [
|
||||||
|
line.strip()
|
||||||
|
for line in f
|
||||||
|
if line.strip() and not line.lstrip().startswith("#")
|
||||||
|
]
|
||||||
|
except OSError as e:
|
||||||
|
ttk.Label(
|
||||||
|
cmds_frame,
|
||||||
|
text=f"Could not read {COMMANDS_FILE}:\n{e}",
|
||||||
|
foreground="red",
|
||||||
|
justify="left"
|
||||||
|
).pack(anchor="w")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not commands:
|
||||||
|
ttk.Label(
|
||||||
|
cmds_frame,
|
||||||
|
text=f"No commands found in {COMMANDS_FILE}.",
|
||||||
|
foreground="red"
|
||||||
|
).pack(anchor="w")
|
||||||
|
return
|
||||||
|
s = ttk.Style()
|
||||||
|
s.configure('TButton', font=('Arial', 14))
|
||||||
|
# Create a button per command
|
||||||
|
for i , cmd in enumerate(commands):
|
||||||
|
def make_callback(c=cmd):
|
||||||
|
def _cb():
|
||||||
|
# Ensure it ends with newline/carriage return similar to console input
|
||||||
|
to_send = c
|
||||||
|
if not to_send.endswith("\n") and not to_send.endswith("\r"):
|
||||||
|
to_send = to_send + "\n\r"
|
||||||
|
SD.SendCommand(to_send)
|
||||||
|
return _cb
|
||||||
|
|
||||||
|
row = i // 2
|
||||||
|
col = i % 2
|
||||||
|
|
||||||
|
btn = ttk.Button(cmds_frame, text=cmd, command=make_callback(),style='TButton')
|
||||||
|
btn.grid(row=row, column=col, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
|
cmds_frame.columnconfigure(0, weight=1)
|
||||||
|
cmds_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# Add a button in the main UI to reopen the commands window if needed
|
||||||
|
ttk.Button(controls_frame, text="Open Commands", command=open_commands_window).pack(side="left", padx=(10, 0))
|
||||||
|
|
||||||
|
|
||||||
|
# === Update functions ===
|
||||||
|
Last_tick: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
def update_grid(values: list, tick: int):
|
||||||
|
"""Update the 4x4 grid with raw ADC values and write ADC csv."""
|
||||||
|
global Last_tick
|
||||||
|
# NOTE: mode should be "a" or "a+", not "+a"
|
||||||
|
with open(f"{FILENAME}_ADC.csv", "a") as file:
|
||||||
|
file.write(f"{tick}")
|
||||||
|
for i in range(16): # 16 raw ADC channels
|
||||||
|
file.write(f",{values[i]}")
|
||||||
|
labels[i].config(text=f"{values[i]:n}")
|
||||||
|
ms_total = tick
|
||||||
|
hours, rem = divmod(ms_total, 3_600_000) # 1000*60*60
|
||||||
|
minutes, rem = divmod(rem, 60_000) # 1000*60
|
||||||
|
seconds, milliseconds = divmod(rem, 1000)
|
||||||
|
labels[16].config(text=f"UP Time:{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}")
|
||||||
|
labels[17].config(text=f"Delta Tick:{(tick - Last_tick):n}")
|
||||||
|
Last_tick = tick
|
||||||
|
file.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def update_SI(value: SD.ReturnDecoder):
|
||||||
|
# NOTE: mode should be "a" or "a+", not "+a"
|
||||||
|
with open(f"{FILENAME}_SI.csv", "a") as file:
|
||||||
|
file.write(f"{value.timestamp},{value.log_message}\n")
|
||||||
|
print(f"Time:{value.timestamp}, SI:{value.log_message}", end="\n")
|
||||||
|
|
||||||
|
|
||||||
|
Last_history_call: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
def append_history(adc_values: list, timestamp: int):
|
||||||
|
"""Store the new sample in the rolling history (derived units)."""
|
||||||
|
global Last_history_call
|
||||||
|
if len(history_x) > 0 and timestamp < history_x[-1]:
|
||||||
|
return # discard out-of-order samples
|
||||||
|
if len(history_x) > 0 and (history_x[-1] + (int(time.time_ns() / 1000000) - Last_history_call) * 2) < timestamp:
|
||||||
|
print(f"Discarding future sample: {timestamp} > {history_x[-1]} + "
|
||||||
|
f"{(int(time.time_ns() / 1000000) - Last_history_call) * 2}")
|
||||||
|
return # discard samples too far in future
|
||||||
|
history_x.append(timestamp)
|
||||||
|
Last_history_call = int(time.time_ns() / 1000000) # in ms
|
||||||
|
|
||||||
|
# 0: Thrust (average of load cells 0 and 1)
|
||||||
|
lc_avg = (adc_values[0] + adc_values[1]) / 2
|
||||||
|
weight = -1.166759308000000e-04 * lc_avg + 4.971416323340051e+02
|
||||||
|
history_y[0].append(weight)
|
||||||
|
|
||||||
|
# 1-6: Temperatures from channels 2..7
|
||||||
|
for i in range(2, 8): # six thermocouples
|
||||||
|
temp = 0.000111 * adc_values[i] + 2.31991
|
||||||
|
history_y[i - 1].append(temp) # 2→1, 7→6
|
||||||
|
|
||||||
|
# 7-14: Pressures from channels 8..15
|
||||||
|
for i in range(8, 16): # eight pressure sensors
|
||||||
|
if i == 12:
|
||||||
|
pres = 0.0000153522 * (adc_values[i] / 2) - 6.5652036917
|
||||||
|
history_y[i - 1].append(pres) # 8→7, 15→14
|
||||||
|
else:
|
||||||
|
pres = 0.0000153522 * adc_values[i] - 6.5652036917
|
||||||
|
history_y[i - 1].append(pres) # 8→7, 15→14
|
||||||
|
|
||||||
|
|
||||||
|
def update_plot():
|
||||||
|
"""Redraw plot based on selected derived channels and available history."""
|
||||||
|
ax.clear()
|
||||||
|
ax.set_title("Selected Channels")
|
||||||
|
ax.set_xlabel("Timestamp")
|
||||||
|
ax.set_ylabel("Value")
|
||||||
|
|
||||||
|
selected = [i for i, var in enumerate(check_vars) if var.get() == 1]
|
||||||
|
if len(history_x) == 0 or len(selected) == 0:
|
||||||
|
ax.text(
|
||||||
|
0.5, 0.5,
|
||||||
|
"No data / No channels selected",
|
||||||
|
ha="center", va="center",
|
||||||
|
transform=ax.transAxes
|
||||||
|
)
|
||||||
|
canvas.draw()
|
||||||
|
return
|
||||||
|
|
||||||
|
x = list(history_x)
|
||||||
|
for idx in selected:
|
||||||
|
# safety: guard against any mismatch, just in case
|
||||||
|
if idx < len(history_y):
|
||||||
|
y = list(history_y[idx])
|
||||||
|
ax.plot(x, y, label=DERIVED_CHANNEL_LABELS[idx])
|
||||||
|
|
||||||
|
ax.legend(loc="upper left", fontsize=8)
|
||||||
|
ax.grid(True, linestyle="--", alpha=0.3)
|
||||||
|
canvas.draw()
|
||||||
|
|
||||||
|
|
||||||
|
# Now that update_plot exists, fix the checkbox command to point to it
|
||||||
|
for child in check_frame.winfo_children():
|
||||||
|
if isinstance(child, ttk.Checkbutton):
|
||||||
|
child.config(command=update_plot)
|
||||||
|
|
||||||
|
|
||||||
|
def update_panel():
|
||||||
|
"""Main polling loop that consumes Serial_decoder outputs."""
|
||||||
|
values: SD.ReturnDecoder = SD.GetReturn(0.1) # non-blocking read with timeout
|
||||||
|
if values is None:
|
||||||
|
root.after(10, update_panel)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Stop if input thread died
|
||||||
|
if tasks[1].is_alive() is False:
|
||||||
|
root.quit()
|
||||||
|
|
||||||
|
match values.name:
|
||||||
|
case "ADC_FULLRANK":
|
||||||
|
# Update grid + history + plot
|
||||||
|
update_grid(values=values.ADC_data, tick=values.timestamp)
|
||||||
|
append_history(values.ADC_data, values.timestamp)
|
||||||
|
update_plot() # draw right away; if too heavy, throttle with a timer
|
||||||
|
case "SI_DECODER":
|
||||||
|
update_SI(values)
|
||||||
|
case "PR_DECODER":
|
||||||
|
print(f"PRD:{values.text_respons}")
|
||||||
|
case _:
|
||||||
|
print(f"Error {values.name}")
|
||||||
|
|
||||||
|
# Continue polling
|
||||||
|
root.after(10, update_panel) # 10 ms
|
||||||
|
|
||||||
|
|
||||||
|
# Start polling and GUI loop
|
||||||
|
update_panel()
|
||||||
|
|
||||||
|
# FC commands
|
||||||
|
SD.SerialPort.write(b"SET_ST_SPEED 100\n\r")
|
||||||
|
|
||||||
|
# Open the commands window at startup
|
||||||
|
open_commands_window()
|
||||||
|
|
||||||
|
root.mainloop()
|
||||||
203
py_void_deocde.py
Normal file
203
py_void_deocde.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import os
|
||||||
|
from typing import BinaryIO, Tuple
|
||||||
|
import re
|
||||||
|
|
||||||
|
START_MARKER = 0xAF
|
||||||
|
END_MARKER = 0xAF # is the same and thus no end marker but start marker can act as end of previos message
|
||||||
|
|
||||||
|
END_ADC_FILE =""
|
||||||
|
|
||||||
|
def list_files_with_suffix(suffix=".bin") -> list[str]:
|
||||||
|
# Get the directory where the script is located
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# List all files in that directory that end with the given suffix
|
||||||
|
matching_files = [f for f in os.listdir(script_dir)
|
||||||
|
if os.path.isfile(os.path.join(script_dir, f)) and f.endswith(suffix)]
|
||||||
|
return matching_files
|
||||||
|
|
||||||
|
def list_files_with_phrase(phrase :str) -> list[str]:
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
matching_files = [f for f in os.listdir(script_dir)
|
||||||
|
if os.path.isfile(os.path.join(script_dir, f)) and f.find(phrase) != -1]
|
||||||
|
|
||||||
|
def scan_to_end_marker(f: BinaryIO, start_index: int, end_marker: int = END_MARKER) -> tuple[int]:
|
||||||
|
"""
|
||||||
|
Utility: starting from `start_index`, read forward until `end_marker` is found.
|
||||||
|
Returns the number of bytes consumed (including the end marker) or -1 on EOF/error.
|
||||||
|
Leaves the file position at the byte *after* the end marker if successful.
|
||||||
|
"""
|
||||||
|
f.seek(start_index)
|
||||||
|
bytes_read = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
b = f.read(1)
|
||||||
|
if not b:
|
||||||
|
# EOF before finding end marker
|
||||||
|
return tuple(-1,)
|
||||||
|
bytes_read += 1
|
||||||
|
if b[0] == end_marker:
|
||||||
|
return (end_marker,)
|
||||||
|
|
||||||
|
|
||||||
|
def decoder_1_ADC(start_index: int, f: BinaryIO, filename: str) -> tuple[int, tuple[int]]:
|
||||||
|
f.seek(start_index)
|
||||||
|
channels = int.from_bytes(f.read(1),byteorder="big",signed=False)
|
||||||
|
|
||||||
|
lst = []
|
||||||
|
|
||||||
|
for i in range(channels):
|
||||||
|
lst.append(int.from_bytes(f.read(3),byteorder="big",signed=False))
|
||||||
|
|
||||||
|
timestamp = int.from_bytes(f.read(4),byteorder="big",signed=False)
|
||||||
|
end_marker = f.read(1)
|
||||||
|
if not end_marker:
|
||||||
|
end_marker = END_MARKER
|
||||||
|
else:
|
||||||
|
end_marker = end_marker[0]
|
||||||
|
if(end_marker != END_MARKER):
|
||||||
|
print(f"start_index{start_index} end_marker:{end_marker.hex()}")
|
||||||
|
return (-1)
|
||||||
|
|
||||||
|
with open(f"{filename}_ADC_data.csv", "a") as file:
|
||||||
|
file.write(f"{timestamp}")
|
||||||
|
for number in lst:
|
||||||
|
file.write(f",{number}")
|
||||||
|
file.write("\n")
|
||||||
|
|
||||||
|
return (3*channels+5,(timestamp,lst))
|
||||||
|
|
||||||
|
LOG_MESSAGE_ENUM_LIST = []
|
||||||
|
with open("SI_LOG_MESSAGES.txt") as file:
|
||||||
|
for message in file:
|
||||||
|
LOG_MESSAGE_ENUM_LIST.append(re.sub("[\n,=0 ]","",message))
|
||||||
|
|
||||||
|
def decoder_SI(start_index: int, f: BinaryIO, filename: str) -> tuple[int, tuple[int]]:
|
||||||
|
f.seek(start_index)
|
||||||
|
LogNumber = int.from_bytes(bytes=f.read(2),byteorder="big",signed=False)
|
||||||
|
TimeStamp = int.from_bytes(bytes=f.read(4),byteorder="big",signed=False)
|
||||||
|
try:
|
||||||
|
LogName = LOG_MESSAGE_ENUM_LIST[LogNumber]
|
||||||
|
except:
|
||||||
|
LogName = f"ERROR UNKOWN: {LogNumber}"
|
||||||
|
with open(f"{filename}_SI.csv", "a") as file:
|
||||||
|
file.write(f"{TimeStamp},{LogName}\n")
|
||||||
|
|
||||||
|
return (6,(TimeStamp,LogNumber))
|
||||||
|
|
||||||
|
COMMAND_MESSAGE_ENUM_LIST = []
|
||||||
|
with open("SI_COMMAND_MESSAGES.txt") as file:
|
||||||
|
for message in file:
|
||||||
|
COMMAND_MESSAGE_ENUM_LIST.append(re.sub("[\n,=0 ]","",message))
|
||||||
|
|
||||||
|
def decoder_SI_command(start_index: int, f: BinaryIO, filename: str) -> tuple[int, tuple[int]]:
|
||||||
|
f.seek(start_index)
|
||||||
|
LogNumber = int.from_bytes(bytes=f.read(2),byteorder="big",signed=False)
|
||||||
|
TimeStamp = int.from_bytes(bytes=f.read(4),byteorder="big",signed=False)
|
||||||
|
try:
|
||||||
|
LogName = COMMAND_MESSAGE_ENUM_LIST[LogNumber]
|
||||||
|
except:
|
||||||
|
LogName = f"ERROR UNKOWN: {LogNumber}"
|
||||||
|
with open(f"{filename}_SI.csv", "a") as file:
|
||||||
|
file.write(f"{TimeStamp},{LogName}\n")
|
||||||
|
return (6,(TimeStamp,LogNumber))
|
||||||
|
|
||||||
|
def decode_unknown(start_index: int, f: BinaryIO, filename: str) -> int:
|
||||||
|
"""
|
||||||
|
Fallback decoder for unknown indicator values.
|
||||||
|
Strategy: consume until END_MARKER. You can also choose to return -1 instead.
|
||||||
|
"""
|
||||||
|
return scan_to_end_marker(f, start_index, END_MARKER)
|
||||||
|
|
||||||
|
|
||||||
|
# -------- Dispatcher that scans the file and calls the decoders -------- #
|
||||||
|
|
||||||
|
def process_frames_in_file(filename: str) -> Tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Scans `filename` for frames with START_MARKER (0xAF), reads the indicator byte,
|
||||||
|
dispatches to the appropriate decoder using structural pattern matching, and advances.
|
||||||
|
|
||||||
|
Decoder contract:
|
||||||
|
- Signature: decoder(start_index: int, f: BinaryIO, filename: str) -> int
|
||||||
|
- `start_index` is the index of the first byte AFTER the indicator byte.
|
||||||
|
- Return the number of bytes consumed starting at `start_index` (typically includes the END_MARKER),
|
||||||
|
or -1 on failure.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(ok_count, fail_count): the number of frames successfully decoded and the number that failed.
|
||||||
|
"""
|
||||||
|
ok = 0
|
||||||
|
fail = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(f"{filename}.bin", "rb") as f:
|
||||||
|
# Stream through the file byte-by-byte to find START_MARKER
|
||||||
|
while True:
|
||||||
|
b = f.read(1)
|
||||||
|
if not b:
|
||||||
|
break # EOF
|
||||||
|
|
||||||
|
if b[0] != START_MARKER:
|
||||||
|
continue # keep scanning
|
||||||
|
|
||||||
|
# Read the indicator byte immediately after the start marker
|
||||||
|
indicator_raw = f.read(1)
|
||||||
|
if not indicator_raw:
|
||||||
|
# EOF right after a start marker; incomplete frame
|
||||||
|
fail += 1
|
||||||
|
break
|
||||||
|
|
||||||
|
indicator = indicator_raw[0]
|
||||||
|
start_index = f.tell() # first byte after the indicator
|
||||||
|
|
||||||
|
# ---- Structural Pattern Matching to choose decoder ---- #
|
||||||
|
match indicator:
|
||||||
|
case 0x01:
|
||||||
|
decoder = decoder_1_ADC
|
||||||
|
case 0x02:
|
||||||
|
decoder = decoder_SI
|
||||||
|
case 0x03:
|
||||||
|
decoder = decoder_SI_command
|
||||||
|
case _:
|
||||||
|
print(f"Could not recognize indicator:{indicator} at:{start_index-1}")
|
||||||
|
decoder = decode_unknown
|
||||||
|
# ------------------------------------------------------ #
|
||||||
|
|
||||||
|
# Call the decoder with the agreed arguments
|
||||||
|
bytes_used = decoder(start_index, f, filename)[0]
|
||||||
|
|
||||||
|
if bytes_used == -1:
|
||||||
|
# Failed to decode; resynchronize by advancing one byte past start_index
|
||||||
|
fail += 1
|
||||||
|
f.seek(start_index) # go to the first payload byte
|
||||||
|
_ = f.read(1) # advance 1 byte and continue scanning
|
||||||
|
else:
|
||||||
|
ok += 1
|
||||||
|
# Advance to the next position right after the bytes we consumed
|
||||||
|
next_pos = start_index + bytes_used
|
||||||
|
f.seek(next_pos)
|
||||||
|
|
||||||
|
return ok, fail
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
# File can't be opened/read
|
||||||
|
raise RuntimeError(f"Could not process file '{filename}': {e}") from e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
files = list_files_with_suffix()
|
||||||
|
files_csv = list_files_with_suffix(".csv")
|
||||||
|
|
||||||
|
for i in range(len(files_csv)):
|
||||||
|
for x in range(len(files)):
|
||||||
|
if files[x].split(".")[0] in files_csv[i]:
|
||||||
|
os.remove(files_csv[i])
|
||||||
|
print(f"Removing file: {files_csv[i]}")
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
print(process_frames_in_file(file.split(".")[0]))
|
||||||
|
|
||||||
|
print(files)
|
||||||
35
run1.m
Normal file
35
run1.m
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
close all
|
||||||
|
|
||||||
|
result = readmatrix("2025_12_03_16_23_RUN1_ADC.csv");
|
||||||
|
%result = result(14430:end, : );
|
||||||
|
result(:,14) = result(:,14)./2;
|
||||||
|
time = result(:,1);
|
||||||
|
|
||||||
|
temp = 0.000111 .* result(:,4:9) + 2.31991;
|
||||||
|
|
||||||
|
pressure = result(:,10:17).*(0.000015)-6.565;
|
||||||
|
|
||||||
|
load_cell = result(:,2:3);
|
||||||
|
weight = -1.166759307845543e-04 .* 0.5.*(load_cell(:,1) + load_cell(:,2)) + 4.971416323340051e+02;
|
||||||
|
|
||||||
|
figure()
|
||||||
|
scatter(time,weight,10)
|
||||||
|
title("load cell in Kg")
|
||||||
|
xlabel("Time in ms")
|
||||||
|
ylabel("KG")
|
||||||
|
|
||||||
|
for x = 1:7
|
||||||
|
figure()
|
||||||
|
scatter(time,pressure(:,x),10)
|
||||||
|
title(sprintf("Pressure channel %d",x))
|
||||||
|
xlabel("Time in ms")
|
||||||
|
ylabel("Pressure")
|
||||||
|
end
|
||||||
|
|
||||||
|
for x = 1:6
|
||||||
|
figure()
|
||||||
|
scatter(time,temp(:,x),10)
|
||||||
|
title(sprintf("temperature channel %d",x))
|
||||||
|
xlabel("Time in ms")
|
||||||
|
ylabel("Temprature in C")
|
||||||
|
end
|
||||||
38
run2.m
Normal file
38
run2.m
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
close all
|
||||||
|
|
||||||
|
result = readmatrix("RUN2_ADC_data.csv");
|
||||||
|
%result = result(14430:end, : );
|
||||||
|
result(:,14) = result(:,14)./2;
|
||||||
|
time = result(:,1)./1000;
|
||||||
|
|
||||||
|
temp = 0.000111 .* result(:,4:9) + 2.31991;
|
||||||
|
|
||||||
|
pressure = result(:,10:17).*(0.000015)-6.565;
|
||||||
|
|
||||||
|
load_cell = result(:,2:3);
|
||||||
|
weight = -1.166759307845543e-04 .* 0.5.*(load_cell(:,1) + load_cell(:,2)) + 4.971416323340051e+02;
|
||||||
|
|
||||||
|
figure()
|
||||||
|
scatter(time,weight,10)
|
||||||
|
title("load cell in Kg")
|
||||||
|
xlabel("Time in s")
|
||||||
|
ylabel("KG")
|
||||||
|
xlim([900.817 911.840])
|
||||||
|
|
||||||
|
for x = 1:7
|
||||||
|
figure()
|
||||||
|
scatter(time,pressure(:,x),10)
|
||||||
|
title(sprintf("Pressure channel %d",x))
|
||||||
|
xlabel("Time in s")
|
||||||
|
ylabel("Pressure")
|
||||||
|
xlim([900.817 911.840])
|
||||||
|
end
|
||||||
|
|
||||||
|
for x = 1:6
|
||||||
|
figure()
|
||||||
|
scatter(time,temp(:,x),10)
|
||||||
|
title(sprintf("temperature channel %d",x))
|
||||||
|
xlabel("Time in s")
|
||||||
|
ylabel("Temprature in C")
|
||||||
|
xlim([900.817 911.840])
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user