rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
log_writer.c File Reference

Writes a run as CSV rows to a sink, see log_writer.h. More...

#include "log_writer.h"
#include "supply.h"
#include <math.h>
#include <stdio.h>
#include <string.h>

Macros

#define LOG_SERVO_NO_ANGLE   0x8000u
 The packed angle that is no angle (log_servo_pack()).

Enumerations

enum  {
  RAIL_NONE = 0 , RAIL_V , RAIL_I , RAIL_P ,
  RAIL_Q , RAIL_E
}
 The supply figures of a SERVO run's row, by column (log_rail_t). More...

Functions

static const char * source_word (bool modelled)
 The last column of both files: what produced the row's values.
static bool put (log_writer_t *w, const char *s, size_t n)
 A short write latches the writer, and the bytes the sink took stay in the file: nothing takes them back out of a card.
void log_writer_init (log_writer_t *w, const log_sink_t *sink)
 Sets up a writer on a sink.
static bool commit (log_writer_t *w)
 Tell the sink to keep what it has been given.
bool log_writer_commit (log_writer_t *w)
 Commit the rows written since the last commit.
void log_writer_set_cols (log_writer_t *w, const log_cols_t *cols)
 The INA3221 columns of this file, before its header: with SENSE_MAP in force a group per watched target (log_cols.h), read from the window bench_state_t.chw carries; NULL for SERVO_SENSE's CH1 to CH3, as an IOmcu older than 4.25 has them.
static bool header (log_writer_t *w, bool servo)
 The header of a bench file: a motor run's, or with servo a SERVO run's, which adds k_servo_cols before source.
bool log_writer_header (log_writer_t *w)
 Write the header row.
static int fmt (char *out, size_t cap, float v, int decimals)
 A reading that is not finite is written as an empty cell, which the reader counts as absent.
static bool finish_row (log_writer_t *w, float t_s, char *line, int n)
 The end of every row, whichever kind: the line goes out, the counts move, and the commit comes on rows or on the run's own clock, whichever is first.
static bool cell (char *line, size_t cap, int *n, bool present, float v, int decimals)
 One cell of a row: the separator, then the value if present.
static bool text_cell (char *line, size_t cap, int *n, const char *text)
 Appends one text cell to a row.
bool log_writer_supply_row (log_writer_t *w, float t_s, const supply_state_t *s)
 Append one supply sample, for a run that is the supply's output being on.
static bool rail_figure (const log_rail_t *r, unsigned which, float *v)
 The cell of a supply figure on a SERVO run's row (RAIL_*), from r: false when r has no reading or total of it.
static bool bench_row (log_writer_t *w, float t_s, const bench_state_t *b, bool servo, const log_rail_t *rail, const log_servo_t *sv)
 One bench row, a motor run's with servo false and a SERVO run's with it true: then the five columns k_cols marks are rail's.
bool log_writer_row (log_writer_t *w, float t_s, const bench_state_t *b)
 Append one sample at t_s seconds, and commit when the row completes an interval.
bool log_writer_servo_row (log_writer_t *w, float t_s, const bench_state_t *b, const log_rail_t *rail, const log_servo_t *sv)
 Append one sample of a run ARMED from SERVO: log_writer_row()'s row with voltage, current, power, charge and energy from rail.
void log_rail_take_ina (log_rail_t *out, const sense_link_win_t *win, bool row_modelled)
 The servo rail's INA3221 window for a SERVO run's row, into out, with nothing counted: while an INA3221 channel is the rail's meter (servo_source.h).
uint32_t log_servo_pack (bool pulse_ok, uint16_t pulse_us, bool angle_ok, float angle_deg)
 The pulse and the angle in one 32-bit word, for the render side to hand the control task in one atomic store.
void log_servo_unpack (uint32_t packed, bool row_modelled, log_servo_t *out)
 log_servo_pack()'s word back into out, for a row: no angle on a modelled row, whose figures the encoder did not produce.
void log_rail_take (log_rail_t *out, const supply_state_t *s, bool row_modelled)
 The supply's reading for a SERVO run's row, into out, with nothing counted.
void log_rail_count (bench_totals_t *t, log_rail_t *r, float dt_s, bool driving)
 Count r's readings into the run's totals t over dt_s, and put the totals on r.

Variables

static const char *const k_head
static const char *const k_ch123
static const char *const k_tail = "source\n"
static const char *const k_supply_header
static const char *const k_servo_cols = "pulse (us);angle (deg);"
 A SERVO run's file: the commanded pulse and the measured angle, before source.

Detailed Description

Writes a run as CSV rows to a sink, see log_writer.h.

SPDX-License-Identifier: MIT

Enumeration Type Documentation

◆ anonymous enum

anonymous enum

The supply figures of a SERVO run's row, by column (log_rail_t).

Enumerator
RAIL_NONE 

a column the bench sample fills

RAIL_V 

voltage

RAIL_I 

current

RAIL_P 

power

RAIL_Q 

charge

RAIL_E 

energy

Function Documentation

◆ bench_row()

bool bench_row ( log_writer_t * w,
float t_s,
const bench_state_t * b,
bool servo,
const log_rail_t * rail,
const log_servo_t * sv )
static

One bench row, a motor run's with servo false and a SERVO run's with it true: then the five columns k_cols marks are rail's.

Parameters
wThe writer.
t_sThe time of the row, in s.
bThe bench sample.
servotrue for a SERVO run's row.
railThe supply's figures on a SERVO run's row, or NULL.
svThe pulse and angle on a SERVO run's row, or NULL.
Returns
true when the row is written.

◆ cell()

bool cell ( char * line,
size_t cap,
int * n,
bool present,
float v,
int decimals )
static

One cell of a row: the separator, then the value if present.

False once the row no longer fits, checked before anything is written past what the last cell left: snprintf answers the length it wanted, not the one it had, so an offset taken from it unchecked points past the line.

Parameters
lineThe row text.
capThe size of line, in bytes.
nThe length of the row so far; updated.
presenttrue when v is written.
vThe value.
decimalsThe digits after the point.
Returns
true when the cell fits.

◆ commit()

bool commit ( log_writer_t * w)
static

Tell the sink to keep what it has been given.

A writer that has already failed is refused before anything is attempted. The file has a hole in it from the row that failed, and a flush that succeeds after that would clear the pending count and report a commit – which is a caller being told an incomplete file is safely on the card. Once failed, always failed; the file is closed and said to be short.

Nothing to commit is success, not a transaction: on a card an empty commit costs a directory write and buys nothing.

Parameters
wThe writer.
Returns
true on success or when nothing is pending.

◆ finish_row()

bool finish_row ( log_writer_t * w,
float t_s,
char * line,
int n )
static

The end of every row, whichever kind: the line goes out, the counts move, and the commit comes on rows or on the run's own clock, whichever is first.

The clock bound is what holds when rows arrive slower than 20Hz – the far end answering every other poll, say – where waiting for LOG_WRITER_FLUSH_ROWS would leave more than a second of run at risk.

Parameters
wThe writer.
t_sThe time of the row, in s.
lineThe row text.
nThe length of line, in bytes.
Returns
true when the row is written.

◆ fmt()

int fmt ( char * out,
size_t cap,
float v,
int decimals )
static

A reading that is not finite is written as an empty cell, which the reader counts as absent.

"nan" is not a number to the reader, and 0 is a wrong value.

Parameters
outReceives the text.
capThe size of out, in bytes.
vThe reading.
decimalsThe digits after the point.
Returns
The number of characters written; 0 for a value that is not finite.

◆ header()

bool header ( log_writer_t * w,
bool servo )
static

The header of a bench file: a motor run's, or with servo a SERVO run's, which adds k_servo_cols before source.

Parameters
wThe writer.
servotrue for a SERVO run's file.
Returns
true on success; false when a write failed.

◆ log_rail_count()

void log_rail_count ( bench_totals_t * t,
log_rail_t * r,
float dt_s,
bool driving )

Count r's readings into the run's totals t over dt_s, and put the totals on r.

Charge from a current reading, energy from a power reading, as bench_totals_count() counts them: a step over BENCH_TOTALS_MAX_STEP_S (1.0s) counts 1.0s, a step that is not positive counts nothing, and nothing is counted unless driving. bench_totals_reset() starts a run. A withheld reading counts nothing and takes no totals: its row carries none, the model's totals of an earlier stretch included. Either pointer NULL does nothing.

Parameters
tThe run's totals.
rThe reading; its charge_mah, energy_wh and counted are set.
dt_sThe time since the last count, in s.
drivingtrue while the bank is ARMED.

◆ log_rail_take()

void log_rail_take ( log_rail_t * out,
const supply_state_t * s,
bool row_modelled )

The supply's reading for a SERVO run's row, into out, with nothing counted.

The readings that arrived, by s's ok bits, when the supply's source is the row's: a supply that is the panel's model (supply_state_t.modelled) on a modelled row, a measured one on a measured row. Otherwise none, and out->withheld says so: a row's source names all its figures. s NULL takes none.

Parameters
outReceives the reading.
sThe supply's state, or NULL.
row_modelledtrue when the row is the panel's model (bench_state_simulated()).

◆ log_rail_take_ina()

void log_rail_take_ina ( log_rail_t * out,
const sense_link_win_t * win,
bool row_modelled )

The servo rail's INA3221 window for a SERVO run's row, into out, with nothing counted: while an INA3221 channel is the rail's meter (servo_source.h).

The window's mean bus voltage, its mean current and their product, as SERVO's line shows them, each by the window's current and voltage flags. None on a modelled row, and out->withheld says so: the part reads through the IOmcu. win NULL takes none.

Parameters
outReceives the reading.
winThe newest window of the rail's channel, or NULL.
row_modelledtrue when the row is the panel's model.

◆ log_servo_pack()

uint32_t log_servo_pack ( bool pulse_ok,
uint16_t pulse_us,
bool angle_ok,
float angle_deg )

The pulse and the angle in one 32-bit word, for the render side to hand the control task in one atomic store.

The pulse in bits 16 to 31, 0 for none; the angle in centidegrees, signed, in bits 0 to 15, 0x8000 for none. An angle that is not finite or lies past +-327.67deg packs as none, and so does a pulse of 0.

Parameters
pulse_oktrue when a pulse is commanded.
pulse_usThe commanded pulse, us.
angle_oktrue when an angle was measured.
angle_degThe angle, deg.
Returns
The packed word.

◆ log_servo_unpack()

void log_servo_unpack ( uint32_t packed,
bool row_modelled,
log_servo_t * out )

log_servo_pack()'s word back into out, for a row: no angle on a modelled row, whose figures the encoder did not produce.

Parameters
packedThe word.
row_modelledtrue when the row is the panel's model.
outReceives the pulse and angle; NULL does nothing.

◆ log_writer_commit()

bool log_writer_commit ( log_writer_t * w)

Commit the rows written since the last commit.

log_writer_row() calls it on the schedule above. A caller calls it by hand when a run has gone quiet with rows still uncommitted, and at the end of a run when closing the sink does not commit by itself.

A commit with nothing pending does nothing and reports success: on a card it would be a transaction that buys nothing. A commit that fails latches the writer the way a failed write does, because a row the sink will not keep is a row the file has lost.

Parameters
wThe writer.
Returns
true on success; false when the commit failed.

◆ log_writer_header()

bool log_writer_header ( log_writer_t * w)

Write the header row.

log_writer_row calls it if it has not been called, so a file cannot lack a header.

Parameters
wThe writer.
Returns
true on success; false when a write failed.

◆ log_writer_init()

void log_writer_init ( log_writer_t * w,
const log_sink_t * sink )

Sets up a writer on a sink.

Parameters
wThe writer to set up.
sinkThe sink the rows go to.

◆ log_writer_row()

bool log_writer_row ( log_writer_t * w,
float t_s,
const bench_state_t * b )

Append one sample at t_s seconds, and commit when the row completes an interval.

False means one of two things, and log_writer_failed() tells them apart:

A write or the commit after it failed. log_writer_failed() is true and stays true, and every later row is refused: the file has a hole in it and no amount of card answering again makes it whole.

The arguments were refused – w or b NULL, or t_s not finite. Nothing was attempted and nothing is latched. That is a caller with a bug rather than a card that has gone, and ending a run over it would throw away a recording for a reason the card had no part in.

So a caller that watches only the latch has to check the return as well if it can pass either of those. The panel cannot: its row carries a struct by value and a finite time: a supply run's sum of steps, or a bench run's wall time since the arm (log_cadence.h).

Parameters
wThe writer.
t_sThe time of the row, in s.
bThe sample to write.
Returns
true on success; false when refused or failed (see log_writer_failed()).

◆ log_writer_servo_row()

bool log_writer_servo_row ( log_writer_t * w,
float t_s,
const bench_state_t * b,
const log_rail_t * rail,
const log_servo_t * sv )

Append one sample of a run ARMED from SERVO: log_writer_row()'s row with voltage, current, power, charge and energy from rail.

The header is the motor run's with pulse (us);angle (deg) before source, so both files read by one rule. The supply's cells are written as a supply file writes them: voltage and power to 0.01, current to 0.001, charge to 1mAh, energy to 0.01Wh; a cell rail has no reading or total of is empty, and so are all five with rail NULL. rpm, the temperatures, the INA228's and the ESC's columns, the INA3221 window and source are b's, as log_writer_row() writes them. The pulse is written in whole us and the angle to 0.1deg, each empty when sv has none, and both with sv NULL. Same returns, commits and latch as log_writer_row().

Parameters
wThe writer.
t_sThe time of the row, in s.
bThe bench sample.
railThe supply's reading and totals, or NULL.
svThe pulse and angle, or NULL.
Returns
true on success; false when refused or failed (see log_writer_failed()).

◆ log_writer_set_cols()

void log_writer_set_cols ( log_writer_t * w,
const log_cols_t * cols )

The INA3221 columns of this file, before its header: with SENSE_MAP in force a group per watched target (log_cols.h), read from the window bench_state_t.chw carries; NULL for SERVO_SENSE's CH1 to CH3, as an IOmcu older than 4.25 has them.

Taken until the header is written, ignored after.

Parameters
wThe writer.
colsThe INA3221 columns, or NULL.

◆ log_writer_supply_row()

bool log_writer_supply_row ( log_writer_t * w,
float t_s,
const supply_state_t * s )

Append one supply sample, for a run that is the supply's output being on.

The file's first row writes the supply header – time; set (V); voltage (V); limit (A); current (A); power (W); mode; charge (mAh); energy (Wh); source – so a file is one kind or the other and the caller keeps it so. Readings that did not arrive, and totals nothing counted, are empty cells; the mode is CV, CC or OFF, and empty while the supply does not answer. Same returns, commits and latch as log_writer_row().

Parameters
wThe writer.
t_sThe time of the row, in s.
sThe supply sample.
Returns
true on success; false when refused or failed (see log_writer_failed()).

◆ put()

bool put ( log_writer_t * w,
const char * s,
size_t n )
static

A short write latches the writer, and the bytes the sink took stay in the file: nothing takes them back out of a card.

Nothing is written after them either, a line feed least of all. The row they start has no line feed, and that is what the reader drops it by (log_reader_row()): a row cut after "95" of "9554" has its fields and a number that parses, and only the missing terminator tells it from a whole row.

Parameters
wThe writer.
sThe bytes.
nThe number of bytes.
Returns
true when the sink took all n bytes.

◆ rail_figure()

bool rail_figure ( const log_rail_t * r,
unsigned which,
float * v )
static

The cell of a supply figure on a SERVO run's row (RAIL_*), from r: false when r has no reading or total of it.

Parameters
rThe supply's reading and totals, or NULL.
whichRAIL_V to RAIL_E.
vReceives the figure when true.
Returns
true when the figure is a reading or a counted total.

◆ source_word()

const char * source_word ( bool modelled)
static

The last column of both files: what produced the row's values.

Parameters
modelledtrue when the row's values are modelled.
Returns
LOG_WRITER_SRC_MODEL or LOG_WRITER_SRC_MEASURED.

◆ text_cell()

bool text_cell ( char * line,
size_t cap,
int * n,
const char * text )
static

Appends one text cell to a row.

Parameters
lineThe row text.
capThe size of line, in bytes.
nThe length of the row so far; updated.
textThe text.
Returns
true when the cell fits.

Variable Documentation

◆ k_ch123

const char* const k_ch123
static
Initial value:
=
"ch1 current (A);ch1 max (A);ch1 voltage (V);"
"ch2 current (A);ch2 max (A);ch2 voltage (V);"
"ch3 current (A);ch3 max (A);ch3 voltage (V);"

◆ k_head

const char* const k_head
static
Initial value:
=
"time (s);voltage (V);current (A);power (W);rpm (rpm);"
"esc (C);motor (C);charge (mAh);energy (Wh);"
"ina voltage (V);ina current (A);esc current (A);window;"

◆ k_supply_header

const char* const k_supply_header
static
Initial value:
=
"time (s);set (V);voltage (V);limit (A);current (A);power (W);mode;"
"charge (mAh);energy (Wh);source\n"