- 0.1.2:power_cycle 按完电源键后看电流,没开就重按,最多 3 次;新事件 19 BOOT_CHECK - 0.1.3:UP_VBUS_DET(IO6)启动时配成输入,status 加 "host":true|false|null - rack:单板页显示「上游:已连电脑 / 未连电脑」 - HANDOFF:损耗两档的来历(Hub 工作 ≈1.1 W)、板 B 第 2 槽 PRSNT2# 卡低 Co-Authored-By: Claude Opus 5.5 <[email protected]>
174 行
6.8 KiB
C
174 行
6.8 KiB
C
/* `status` 与 `events` 两条只读命令的正文(A12,需求 §10.8)。
|
||
|
||
两条都是**只读、幂等**的,所以不进判重窗口(cmd.h)。
|
||
输出一律是整数(mV / µA / m°C / 千分比 / ms),不引浮点,和固件内部口径一致;
|
||
读不到的量写 `null`,**不写 0** —— 0 mA 与「这一圈没读到」是两回事,
|
||
上位机的卡死判据(电流曲线)拿 0 当真值会直接误判。 */
|
||
#include <stdio.h>
|
||
|
||
#include "board_pins.h"
|
||
#include "cmd_priv.h"
|
||
#include "evlog.h"
|
||
#include "hal_gpio.h"
|
||
#include "hal_time.h"
|
||
#include "ota.h"
|
||
#include "pca9555.h"
|
||
#include "phonerail.h"
|
||
#include "rail.h"
|
||
#include "rail_cfg.h"
|
||
#include "slot.h"
|
||
#include "t_actuator.h"
|
||
|
||
static const char *b(bool v) { return v ? "true" : "false"; }
|
||
|
||
/* C2:`"ota":{"st":"dl","got":N,"size":N,"err":null,"pend":false,"bad":null}`。
|
||
`err` 是上一次失败的原因(成功 / 没做过是 null);`pend` = 正在跑的固件还没自证健康;
|
||
`bad` = 被判无效、回滚掉的那份固件的版本号。版本号来自镜像头,按 ASCII 过滤一遍再写。 */
|
||
static void ota_obj(cj_writer_t *w)
|
||
{
|
||
ota_status_t st;
|
||
ota_status_get(&st);
|
||
cj_w(w, ",\"ota\":{\"st\":\"%s\",\"got\":%lu,\"size\":%lu,\"err\":", ota_phase_str(st.phase),
|
||
(unsigned long)st.got, (unsigned long)st.size);
|
||
if (st.err == OTA_E_NONE) {
|
||
cj_w(w, "null");
|
||
} else {
|
||
cj_w(w, "\"%s\"", ota_err_str(st.err));
|
||
}
|
||
cj_w(w, ",\"pend\":%s,\"bad\":", b(st.pending));
|
||
if (st.bad[0] == '\0') {
|
||
cj_w(w, "null}");
|
||
return;
|
||
}
|
||
char v[sizeof(st.bad)];
|
||
size_t n = 0;
|
||
for (const char *s = st.bad; *s != '\0' && n < sizeof(v) - 1; s++) {
|
||
const unsigned char ch = (unsigned char)*s;
|
||
v[n++] = (ch < 0x20 || ch >= 0x7F || ch == '"' || ch == '\\') ? '?' : (char)ch;
|
||
}
|
||
v[n] = '\0';
|
||
cj_w(w, "\"%s\"}", v);
|
||
}
|
||
|
||
static void rail_obj(cj_writer_t *w, int rail, const safety_snap_t *s)
|
||
{
|
||
const int i = rail - 1;
|
||
cj_w(w, "{\"r\":%d,\"pr\":%s,\"en\":%s,\"vb\":%s,\"set\":%d", rail, b(slot_present(rail)),
|
||
b(rail_efuse_is_on(rail)), b(slot_vbus_is_on(rail)), rail_setpoint_mv(rail));
|
||
if (s != NULL) {
|
||
if (s->rail_ok[i]) {
|
||
cj_w(w, ",\"mv\":%ld,\"ua\":%ld", (long)s->rail[i].bus_mv,
|
||
(long)s->rail[i].current_ua);
|
||
} else {
|
||
cj_w(w, ",\"mv\":null,\"ua\":null");
|
||
}
|
||
if (s->io_ok[i]) {
|
||
cj_w(w, ",\"pg\":%s,\"flt\":%s", b(pca9555_efuse_pg(&s->io[i])),
|
||
b(pca9555_efuse_fault(&s->io[i])));
|
||
} else {
|
||
cj_w(w, ",\"pg\":null,\"flt\":null");
|
||
}
|
||
}
|
||
cj_w(w, ",\"st\":\"%s\",\"res\":\"%s\"}", t_actuator_state_str(t_actuator_state(rail)),
|
||
t_actuator_result_str(t_actuator_last_result(rail)));
|
||
}
|
||
|
||
static void thermal_obj(cj_writer_t *w, const thermal_snap_t *t)
|
||
{
|
||
cj_w(w, ",\"temp\":[");
|
||
for (int i = 0; i < T_THERMAL_NTC_COUNT; i++) {
|
||
if (t->temp_ok[i]) {
|
||
cj_w(w, "%s%ld", i ? "," : "", (long)t->temp_mc[i]);
|
||
} else {
|
||
cj_w(w, "%snull", i ? "," : "");
|
||
}
|
||
}
|
||
cj_w(w, "],\"fan\":{\"duty\":%u,\"floor\":%u,\"en\":%s,\"rpm\":[", (unsigned)t->duty,
|
||
(unsigned)t_thermal_floor(), b(t->fan_en));
|
||
for (int i = 0; i < T_THERMAL_FAN_COUNT; i++) {
|
||
if (t->fan[i].rpm_valid) {
|
||
cj_w(w, "%s%lu", i ? "," : "", (unsigned long)t->fan[i].rpm);
|
||
} else {
|
||
cj_w(w, "%snull", i ? "," : "");
|
||
}
|
||
}
|
||
cj_w(w, "],\"stall\":[");
|
||
for (int i = 0; i < T_THERMAL_FAN_COUNT; i++) {
|
||
cj_w(w, "%s%s", i ? "," : "", b(t->fan[i].stalled));
|
||
}
|
||
cj_w(w, "]},\"derate\":%s", b(t->derate));
|
||
}
|
||
|
||
void cmd_report_status(cj_writer_t *w, const cmd_env_t *env)
|
||
{
|
||
const safety_snap_t *s = env->safety;
|
||
cj_w(w, ",\"fw\":\"%s\",\"up\":%lu", phonerail_version(), (unsigned long)hal_time_now_ms());
|
||
/* 上游 USB-C 插没插电脑(`UP_VBUS_DET`,bringup 里配的输入)。读不到写 null。
|
||
⚠️ 这是 VBUS 在不在,不是 Hub 有没有枚举 / 挂起:电脑休眠时 VBUS 多半还在。 */
|
||
int host = 0;
|
||
if (hal_gpio_read(BOARD_PIN_UP_VBUS_DET, &host) == HAL_OK) {
|
||
cj_w(w, ",\"host\":%s", b(host != 0));
|
||
} else {
|
||
cj_w(w, ",\"host\":null");
|
||
}
|
||
if (s != NULL) {
|
||
if (s->input_ok) {
|
||
cj_w(w, ",\"in\":{\"mv\":%ld,\"ua\":%ld}", (long)s->input.bus_mv,
|
||
(long)s->input.current_ua);
|
||
} else {
|
||
cj_w(w, ",\"in\":null");
|
||
}
|
||
}
|
||
cj_w(w, ",\"rails\":[");
|
||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||
if (rail > 1) {
|
||
cj_w(w, ",");
|
||
}
|
||
rail_obj(w, rail, s);
|
||
}
|
||
cj_w(w, "]");
|
||
if (env->thermal != NULL) {
|
||
thermal_obj(w, env->thermal);
|
||
}
|
||
if (s != NULL) {
|
||
cj_w(w, ",\"safety\":{\"cycles\":%lu,\"skipped\":%lu,\"i2c_err\":%lu,\"events\":%lu}",
|
||
(unsigned long)s->cycles, (unsigned long)s->skipped,
|
||
(unsigned long)s->i2c_errors, (unsigned long)s->events);
|
||
}
|
||
ota_obj(w);
|
||
evlog_stats_t es;
|
||
evlog_stats(&es);
|
||
cmd_stats_t cs;
|
||
cmd_stats(&cs);
|
||
/* `ev_next` 是**已落盘**的下一个序号;还在 RAM 里的 `ev_pending` 条要等 events 触发落盘。
|
||
上位机拿 `ev_next` 与自己的游标比,就知道要不要拉日志,不用每次都拉。 */
|
||
cj_w(w, ",\"ev_next\":%lu,\"ev_pending\":%lu,\"cmd\":{\"lines\":%lu,\"dups\":%lu,\"rej\":%lu}",
|
||
(unsigned long)es.next_seq, (unsigned long)es.pending, (unsigned long)cs.lines,
|
||
(unsigned long)cs.dups, (unsigned long)cs.rejects);
|
||
}
|
||
|
||
void cmd_report_events(cj_writer_t *w, uint32_t from_seq)
|
||
{
|
||
(void)evlog_flush(0);
|
||
evlog_stats_t es;
|
||
evlog_stats(&es);
|
||
|
||
evlog_rec_t rec[CMD_EVENTS_MAX];
|
||
const uint32_t n = evlog_read(from_seq, rec, CMD_EVENTS_MAX);
|
||
/* `evlog_read()` 从 max(from, oldest) 开始读、序号连续(洞也占位),
|
||
所以每条的序号可以这样算回来 —— 回应里逐条带上,上位机不用自己推。 */
|
||
const uint32_t start = (from_seq < es.oldest_seq) ? es.oldest_seq : from_seq;
|
||
|
||
/* `oldest` 大于上位机的游标 ⇒ 中间那一段已经被环形缓冲盖掉了(evlog.h)。 */
|
||
cj_w(w, ",\"oldest\":%lu,\"next\":%lu,\"ev\":[", (unsigned long)es.oldest_seq,
|
||
(unsigned long)es.next_seq);
|
||
for (uint32_t i = 0; i < n; i++) {
|
||
/* [序号, 上电毫秒, 路, 类型, arg, mV, µA]。类型是 `log_ev_t` 的冻结数字,
|
||
名字表在上位机(中文名进不了 ASCII 行,也没必要每条都带)。 */
|
||
cj_w(w, "%s[%lu,%lu,%u,%u,%u,%ld,%ld]", i ? "," : "", (unsigned long)(start + i),
|
||
(unsigned long)rec[i].t_ms, (unsigned)rec[i].rail, (unsigned)rec[i].type,
|
||
(unsigned)rec[i].arg, (long)rec[i].bus_mv, (long)rec[i].current_ua);
|
||
}
|
||
cj_w(w, "]");
|
||
}
|