//
// Grimoire
// Copyright 2025 Wenting Zhang
//
// Original copyright information:
// Copyright (c) 2022 - Analog Devices Inc. All Rights Reserved.
//
// This file is licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.  You may
// obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations
// under the License.
//

#include "platform.h"
#include "board.h"
#include "shell.h"
#include "shell_printf.h"
#include "term.h"
#include "app_main.h"
#include "app.h"
#include "xmodem.h"

#ifdef printf
#undef printf
#endif

#ifdef vprintf
#undef vprintf
#endif

#define printf(...) shell_printf(ctx, __VA_ARGS__)
#define vprintf(x,y) shell_vprintf(ctx, x, y)

/***********************************************************************
 * Misc helper functions
 **********************************************************************/
static void dump_bytes(shell_context_t *ctx, unsigned char *rdata, unsigned addr, unsigned rlen) {
    unsigned i;
    for (i = 0; i < rlen; i++) {
        if ((i % 16) == 0) {
            if (i) {
                printf("\n");
            }
            printf("%08x: ", addr + i);
        }
        printf("%02x ", rdata[i]);
    }
    printf("\n");
}

int confirm_danger(shell_context_t *ctx, char *warnStr) {
    char c;

    printf( "%s\n", warnStr );
    printf( "Are you sure you want to continue? [y/n]" );

    c = term_getch( &ctx->t, TERM_INPUT_WAIT );
    printf( "%c\n", isprint( c ) ? c : ' ' );

    if( tolower(c) == 'y' ) {
        return(1);
    }

    return(0);
}

/***********************************************************************
 * CMD: syslog
 **********************************************************************/
const char shell_help_syslog[] = "\n";
const char shell_help_summary_syslog[] = "Show the live system log";

#define MAX_TS_LINE  32
#define MAX_LOG_LINE 256

void shell_syslog(shell_context_t *ctx, int argc, char **argv) {
    char *line;
    char *lbuf;
    char *ts;
    int c;

    ts = SHELL_MALLOC(MAX_TS_LINE);
    lbuf = SHELL_MALLOC(MAX_LOG_LINE);

    c = 0;
    do {
        line = syslog_next(ts, MAX_TS_LINE, lbuf, MAX_LOG_LINE);
        if (line) {
            printf("%s %s\n", ts, line);
        }
        c = term_getch(&ctx->t, TERM_INPUT_DONT_WAIT);
#ifdef FREE_RTOS
        if ((line == NULL) && (c < 0)) {
            vTaskDelay(pdMS_TO_TICKS(50));
        }
#endif
    } while (c < 0);

    if (ts) {
        SHELL_FREE(ts);
    }
    if (lbuf) {
        SHELL_FREE(lbuf);
    }
}

/***********************************************************************
 * CMD: stacks
 **********************************************************************/
const char shell_help_stacks[] = "\n";
const char shell_help_summary_stacks[] = "Report task stack usage";

static void shell_print_task_stack(shell_context_t *ctx, TaskHandle_t task) {
    if (task) {
        printf(" %s: %u\n",
            pcTaskGetName(task), (unsigned)uxTaskGetStackHighWaterMark(task)
        );
    }
}

void shell_stacks(shell_context_t *ctx, int argc, char **argv ) {
    printf("High Water Marks are in 32-bit words (zero is bad).\n");
    printf("Task Stask High Water Marks:\n");

    shell_print_task_stack(ctx, startup_task_handle);
    shell_print_task_stack(ctx, housekeeping_task_handle);
    shell_print_task_stack(ctx, usb_device_task_handle);
    shell_print_task_stack(ctx, usb_pd_task_handle);
    shell_print_task_stack(ctx, ui_task_handle);
    shell_print_task_stack(ctx, key_scan_task_handle);
    shell_print_task_stack(ctx, power_mon_task_handle);
}

/***********************************************************************
 * CMD: test
 **********************************************************************/
const char shell_help_test[] = "\n";
const char shell_help_summary_test[] = "Test command";

void shell_test(shell_context_t *ctx, int argc, char **argv ) {

//    uint8_t val = atoi(argv[1]);
//    uint8_t buf[2];
//    buf[0] = 0x19;
//    buf[1] = val | 0x80;
//     pal_i2c_write_payload(ADV7611_I2C, ADV7611_I2C_ADDR, buf, 2);

//    uint8_t val;
//
//    val = adv7611_read_reg(ADV7611_I2C_ADDR, 0x6a);
//    if (val & 0x10)
//        printf("TMDS clock detected\n");
//    else
//        printf("No TMDS clock detected\n");
//
//    uint16_t val16;
//    val = adv7611_read_reg(HDMI_I2C_ADDR, 0x51); // D8-D1
//    val16 = (uint16_t)val << 1;
//    val = adv7611_read_reg(HDMI_I2C_ADDR, 0x52);
//    val16 |= val >> 7;
//
//    printf("TMDS frequency %d MHz\n", val16);
//
//    val = adv7611_read_reg(HDMI_I2C_ADDR, 0x04);
//    if (val & 0x2)
//        printf("TMDS PLL locked\n");
//    else
//        printf("TMDS PLL not locked\n");
//
//    val = adv7611_read_reg(HDMI_I2C_ADDR, 0x05);
//    if (val & 0x80)
//        printf("HDMI mode detected\n");
//    else
//        printf("DVI mode detected\n");
//
//    val = adv7611_read_reg(HDMI_I2C_ADDR, 0x07);
//    if (val & 0x20)
//        printf("DE regeneration locked\n");
//    else
//        printf("DE regeneration not locked\n");
//
//    if (val & 0x80)
//        printf("Vertical filter locked\n");
//    else
//        printf("Vertical filter not locked\n");
//
//    val16 = (uint16_t)adv7611_read_reg(HDMI_I2C_ADDR, 0x1e) << 8;
//    val16 |= adv7611_read_reg(HDMI_I2C_ADDR, 0x1f);
//    printf("Total line width: %d\n", val16);
//
//    val16 = (uint16_t)(adv7611_read_reg(HDMI_I2C_ADDR, 0x07) & 0x1f) << 8;
//    val16 |= adv7611_read_reg(HDMI_I2C_ADDR, 0x08);
//    printf("Active line width: %d\n", val16);
//
//    val16 = (uint16_t)(adv7611_read_reg(HDMI_I2C_ADDR, 0x09) & 0x1f) << 8;
//    val16 |= adv7611_read_reg(HDMI_I2C_ADDR, 0x0a);
//    printf("Active field height: %d\n", val16);
}

/***********************************************************************
 * CMD: i2c_probe
 **********************************************************************/
const char shell_help_i2c_probe[] = "<i2c_port>\n"
  "  i2c_port - I2C port to probe\n";
const char shell_help_summary_i2c_probe[] = "Probe an I2C port for active devices";

void shell_i2c_probe(shell_context_t *ctx, int argc, char **argv)
{
    pal_i2c_t *i2c;
    int i;

    if (argc != 2) {
        printf("Invalid arguments. Type help [<command>] for usage.\n");
        return;
    }

    int i2c_port = strtol(argv[1], NULL, 0);
    i2c = NULL;

    if (i2c_port == 1) {
        i2c = &pi2c1;
    }
    else {
        printf("Invalid I2C port!\n");
        return;
    }

    printf("Probing I2C port %d:\n", i2c_port);
    for (i = 0x08; i < 0x78; i++) {
        bool result = pal_i2c_ping(i2c, i);
        if (result == true) {
            printf(" Found device 0x%02x\n", i);
        }
    }
}


//const char shell_help_fl[] = "<operation> [brightness]\n"
//  "  operation - on | off | set\n"
//  "  brightness - 0-255\n";
//const char shell_help_summary_fl[] = "Control front light";
//
//void shell_fl(shell_context_t *ctx, int argc, char **argv)
//{
//    if ((argc < 2) || (argc > 3)) {
//        printf("Invalid arguments. Type help [<command>] for usage.\n");
//        return;
//    }
//
//    if (strcmp(argv[1], "on") == 0) {
//    	power_on_fl();
//    }
//    else if (strcmp(argv[1], "off") == 0) {
//    	power_off_fl();
//    }
//
//    if (argc == 3) {
//    	int brightness = strtol(argv[2], NULL, 0);
//    	power_set_fl_brightness(brightness);
//    }
//}


/***********************************************************************
 * XMODEM helper functions
 **********************************************************************/
typedef struct {
    shell_context_t *ctx;
} xmodem_state_t;

static void shell_xmodem_putchar(int c, void *usr) {
    xmodem_state_t *x = (xmodem_state_t *)usr;
    shell_context_t *ctx = x->ctx;
    term_state_t *t = &ctx->t;

    term_putch(t, c);
}

static int shell_xmodem_getchar(int timeout, void *usr) {
    xmodem_state_t *x = (xmodem_state_t *)usr;
    shell_context_t *ctx = x->ctx;
    term_state_t *t = &ctx->t;
    int c;

    c = term_getch(t, timeout);

    return(c);
}

typedef struct {
    xmodem_state_t xmodem;
    spiffs_file f;
    void *data;
    int size;
} file_xfer_state_t;

static void file_data_write(void *usr, void *data, int size) {
    file_xfer_state_t *state = (file_xfer_state_t *)usr;

    /*
     * Need to double-buffer the data in order to strip off the
     * trailing packet bytes at the end of an xmodem transfer.
     */
    if (state->data == NULL) {
        state->data = SHELL_MALLOC(1024);
        memcpy(state->data, data, size);
        state->size = size;
    } else {
        if (data) {
            SPIFFS_write(&spiffs_fs, state->f, state->data, state->size);
            memcpy(state->data, data, size);
            state->size = size;
        } else {
            uint8_t *buf = (uint8_t *)state->data;
            while (state->size && buf[state->size-1] == '\x1A') {
               state->size--;
            }
            SPIFFS_write(&spiffs_fs, state->f, state->data, state->size);
            if (state->data) {
                SHELL_FREE(state->data);
                state->data = NULL;
            }
        }
    }
}

static void file_data_read(void *usr, void *data, int size) {
    file_xfer_state_t *state = (file_xfer_state_t *)usr;

    if (size > 0) {
        SPIFFS_read(&spiffs_fs, state->f, data, size);
    }
}

/***********************************************************************
 * CMD: format
 **********************************************************************/
const char shell_help_format[] = "";
const char shell_help_summary_format[] = "Formats an internal flash filesystem";

void shell_format(shell_context_t *ctx, int argc, char **argv) {
    printf("Be patient, this may take a while.\n");
    printf("Formatting...\n");

    TickType_t start = xTaskGetTickCount();
    SPIFFS_unmount(&spiffs_fs);
    if (SPIFFS_format(&spiffs_fs) != SPIFFS_OK) {
        printf("SPIFFS format failed: %d\n", SPIFFS_errno(&spiffs_fs));
    }
    TickType_t end = xTaskGetTickCount();
    printf("Time spent: %d seconds\n", (end - start) / configTICK_RATE_HZ);

    printf("Done. Please reset the board\n");
}

/***********************************************************************
 * CMD: rm/del
 **********************************************************************/
const char shell_help_rm[] = "<file1> [<file2> ...]\n";
const char shell_help_summary_rm[] = "Removes a file";

#include <stdio.h>

void shell_rm(shell_context_t *ctx, int argc, char **argv) {
    int i;

    if (argc < 2) {
        printf( "Usage: rm <file1> [<file2> ...]\n" );
        return;
    }

    for (i = 1; i < argc; i++) {
        if (SPIFFS_remove(&spiffs_fs, argv[i]) != 0) {
          printf("Unable to remove '%s'\n", argv[i]);
        }
    }
}

//const char shell_help_dump[] = "[addr] <len>\n"
//    "  addr - Starting address to dump\n"
//    "  len - Number of bytes to dump (default 1)\n";
//const char shell_help_summary_dump[] = "Hex dump of flash contents";
//
//void shell_dump(shell_context_t *ctx, int argc, char **argv)
//{
//    uintptr_t addr = 0;
//    unsigned len = 1;
//    uint8_t *buf;
//    int ok;
//
//    if (argc < 2) {
//        printf("Invalid arguments\n");
//        return;
//    }
//
//    addr = strtoul(argv[1], NULL, 0);
//    if (argc > 2) {
//        len = strtoul(argv[2], NULL, 0);
//    }
//
//    buf = SHELL_MALLOC(len);
//    if (buf) {
//        ok = spif_read(addr, len, buf);
//        if (ok == 0) {
//            dump_bytes(ctx, buf, addr, len);
//        }
//        SHELL_FREE(buf);
//    }
//}
//
const char shell_help_fdump[] =
 "[file] <start> <size>\n"
 "  file - File to dump\n"
 "  start - Start offset in bytes (default: 0)\n"
 "  size - Size in bytes (default: full file)\n";
const char shell_help_summary_fdump[] = "Dumps the contents of a file in hex";

#define DUMP_SIZE 512

void shell_fdump(shell_context_t *ctx, int argc, char **argv) {
    spiffs_file f;
    size_t start = 0;
    size_t size = SIZE_MAX;
    uint8_t *buf = NULL;
    size_t rlen;
    int c;

    if( argc < 2 ) {
        printf("No file given\n");
        return;
    }

    if (argc > 2) {
        start = (size_t)strtoul(argv[2], NULL, 0);
    }

    if (argc > 3) {
        size = (size_t)strtoul(argv[3], NULL, 0);
    }

    f = SPIFFS_open(&spiffs_fs, argv[1], SPIFFS_O_RDONLY, 0);
    if (f) {
        buf = SHELL_MALLOC(DUMP_SIZE);
        SPIFFS_lseek(&spiffs_fs, f, start, SPIFFS_SEEK_SET);
        do {
            rlen = (size < DUMP_SIZE) ? size : DUMP_SIZE;
            rlen = SPIFFS_read(&spiffs_fs, f, buf, rlen);
            if (rlen) {
                dump_bytes(ctx, buf, start, rlen);
                size -= rlen;
                start += rlen;
                c = term_getch(&ctx->t, TERM_INPUT_DONT_WAIT);
            }
        } while (size && rlen && (c < 0));
        if (buf) {
            SHELL_FREE(buf);
        }
        SPIFFS_close(&spiffs_fs, f);
    }
    else {
        printf("Unable to open '%s'\n", argv[1]);
    }
}

/***********************************************************************
 * CMD: recv
 **********************************************************************/
const char shell_help_recv[] = "<file>\n"
    "  Transfer and save to file\n";
const char shell_help_summary_recv[] = "Receive a file via XMODEM";

void shell_recv( shell_context_t *ctx, int argc, char **argv )
{
    file_xfer_state_t file_state = { 0 };
    long size;

    if( argc != 2 ) {
        printf( "Usage: recv <file>\n" );
        return;
    }

    file_state.xmodem.ctx = ctx;

    file_state.f = SPIFFS_open(&spiffs_fs, argv[1], SPIFFS_O_CREAT | SPIFFS_O_TRUNC | SPIFFS_O_WRONLY, 0);
    if( file_state.f == 0) {
        printf( "unable to open file %s\n", argv[ 1 ] );
        return;
    }
    printf( "Prepare your terminal for XMODEM send ... " );
    term_set_mode(&ctx->t, TERM_MODE_COOKED, 0);
    size = XmodemReceiveCrc(file_data_write, &file_state, 4*1024*1024,
        shell_xmodem_getchar, shell_xmodem_putchar);
    term_set_mode(&ctx->t, TERM_MODE_COOKED, 1);
    if (size < 0) {
        printf( "XMODEM Error: %ld\n", size);
    } else {
        printf( "received and saved as %s\n", argv[ 1 ] );
    }
    SPIFFS_close(&spiffs_fs, file_state.f);
}

/***********************************************************************
 * CMD: send
 **********************************************************************/
const char shell_help_send[] = "<file1> [<file2> ...]\n";
const char shell_help_summary_send[] = "Send files via YMODEM.";

typedef struct {
    file_xfer_state_t state;
    const char *fname;
    size_t fsize;
} ymodem_state_t;

static void ymodem_hdr(void *usr, void *xmodemBuffer, int xmodemSize) {
    ymodem_state_t *y = (ymodem_state_t *)usr;
    snprintf(xmodemBuffer, xmodemSize, "%s%c%u", y->fname, 0, (unsigned)y->fsize);
}

static void ymodem_end(void *xs, void *xmodemBuffer, int xmodemSize) {
}

void shell_send(shell_context_t *ctx, int argc, char **argv)
{
    size_t size;
    spiffs_file fp = 0;
    int ret = -1;
    int i;

    ymodem_state_t y = {
        .state.xmodem.ctx = ctx,
    };

    if (argc < 2) {
        printf("Usage: %s <file1> [<file2> ...]\n", argv[0]);
        return;
    }

    printf ("Prepare your terminal for YMODEM receive...\n");
    term_set_mode(&ctx->t, TERM_MODE_COOKED, 0);
    for (i = 1; i < argc; i++) {
        fp = SPIFFS_open(&spiffs_fs, argv[i], SPIFFS_O_RDONLY, 0);
        if (fp) {
            spiffs_stat s;
            SPIFFS_fstat(&spiffs_fs, fp, &s);
            size = s.size;
            y.fname = argv[i]; y.fsize = size; y.state.f = fp;
            ret = XmodemTransmit(ymodem_hdr, &y, 128, 0, 1,
                shell_xmodem_getchar, shell_xmodem_putchar);
            if (ret >= 0) {
                ret = XmodemTransmit(file_data_read, &y, y.fsize, 1, 0,
                    shell_xmodem_getchar, shell_xmodem_putchar);
            }
            SPIFFS_close(&spiffs_fs, fp);
            if (ret < 0) {
                break;
            }
        }
        else {
            printf("File not found\n");
            ret = -1;
        }
    }
    if (ret >= 0) {
        ret = XmodemTransmit(ymodem_end, &y, 128, 0, 1,
            shell_xmodem_getchar, shell_xmodem_putchar);
    }
    term_set_mode(&ctx->t, TERM_MODE_COOKED, 1);
    if (ret < 0) {
        printf( "YMODEM Error: %ld\n", ret);
    }
}


/***********************************************************************
 * CMD: df
 **********************************************************************/
const char shell_help_df[] = "\n";
const char shell_help_summary_df[] = "Shows internal filesystem disk full status";

void shell_df(shell_context_t *ctx, int argc, char **argv) {
    printf("%10s %10s %10s %5s\n", "Size", "Used", "Available", "Use %");

    int32_t serr; uint32_t ssize; uint32_t sused;
    serr = SPIFFS_info(&spiffs_fs, &ssize, &sused);
    if (serr == SPIFFS_OK) {
        printf("%10u %10u %10u %5u\n",
            (unsigned)ssize, (unsigned)sused, (unsigned)(ssize - sused),
            (unsigned)((100 * sused) / ssize));
    }
}


const char shell_help_setvolt[] = "<rail> <volt>\n";
const char shell_help_summary_setvolt[] = "Set voltage";

void shell_setvolt(shell_context_t *ctx, int argc, char **argv) {
    if (argc < 3) {
        printf("Usage: <rail> <volt>\n", argv[0]);
        return;
    }

    float volt = strtof(argv[2], NULL);
    if (strcmp(argv[1], "vcom") == 0) {
        power_set_vcom(volt);
    }
    else if (strcmp(argv[1], "vgh") == 0) {
        power_set_vgh(volt);
    }
}


const char shell_help_setcfg[] = "<set|get|save> [key] [value]\n";
const char shell_help_summary_setcfg[] = "Sets configuration. Remember to use save to save it to the flash.";

typedef struct {
    const char *name;
    void *pointer;
    enum {UINT8, UINT16, UINT32, FLOAT32} type;
} cfg_var_t;

cfg_var_t vars[] = {
    {"pclk_hz", &(config.pclk_hz), UINT32},
    {"hfp", &(config.hfp), UINT8},
    {"vfp", &(config.vfp), UINT8},
    {"hsync", &(config.hsync), UINT8},
    {"vsync", &(config.vsync), UINT8},
    {"hact", &(config.hact), UINT16},
    {"hblk", &(config.hblk), UINT16},
    {"vact", &(config.vact), UINT16},
    {"vblk", &(config.vblk), UINT16},
    {"size_x_mm", &(config.size_x_mm), UINT16},
    {"size_y_mm", &(config.size_y_mm), UINT16},
    {"mfg_week", &(config.mfg_week), UINT8},
    {"mfg_year", &(config.mfg_year), UINT8},
    {"vcom", &(config.vcom), FLOAT32},
    {"vgh", &(config.vgh), FLOAT32},
    {"tcon_vfp", &(config.tcon_vfp), UINT8},
    {"tcon_vsync", &(config.tcon_vsync), UINT8},
    {"tcon_vbp", &(config.tcon_vbp), UINT8},
    {"tcon_vact", &(config.tcon_vact), UINT16},
    {"tcon_hfp", &(config.tcon_hfp), UINT8},
    {"tcon_hsync", &(config.tcon_hsync), UINT8},
    {"tcon_hbp", &(config.tcon_hbp), UINT8},
    {"tcon_hact", &(config.tcon_hact), UINT16},
    {"mirror", &(config.mirror), UINT8},
    {"input_sel", &(config.input_sel), UINT8}
};
int num_vars = sizeof(vars) / sizeof(cfg_var_t);

static void setcfg_set_helper(cfg_var_t *var, char *val) {
    if (var->type == UINT8) {
        *(uint8_t *)(var->pointer) = strtol(val, NULL, 10);
    }
    else if (var->type == UINT16) {
        *(uint16_t *)(var->pointer) = strtol(val, NULL, 10);
    }
    else if (var->type == UINT32) {
        *(uint32_t *)(var->pointer) = strtol(val, NULL, 10);
    }
    else if (var->type == FLOAT32) {
        *(float *)(var->pointer) = strtof(val, NULL);
    }
}

static void setcfg_get_helper(shell_context_t *ctx, cfg_var_t *var) {
    if (var->type == UINT8) {
        printf("%d\n", *(uint8_t *)(var->pointer));
    }
    else if (var->type == UINT16) {
        printf("%d\n", *(uint16_t *)(var->pointer));
    }
    else if (var->type == UINT32) {
        printf("%d\n", *(uint32_t *)(var->pointer));
    }
    else if (var->type == FLOAT32) {
        printf("%f\n", *(float *)(var->pointer));
    }
}

void shell_setcfg(shell_context_t *ctx, int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s\n", shell_help_setcfg);
        return;
    }

    cfg_var_t *var = NULL;
    if (argc >= 3) {
        for (int i = 0; i < num_vars; i++) {
            if (strcmp(argv[2], vars[i].name) == 0) {
                var = &vars[i];
            }
        }
        if (var == NULL) {
            printf("Unknown key %s", argv[2]);
            return;
        }
    }

    if (strcmp(argv[1], "set") == 0) {
        if (argc < 4) {
            printf("Key and value required for set\n");
            return;
        }
        setcfg_set_helper(var, argv[3]);
    }
    else if (strcmp(argv[1], "get") == 0) {
        if (argc < 4) {
            // Get every var
            for (int i = 0; i < num_vars; i++) {
                printf("%s: ", vars[i].name);
                setcfg_get_helper(ctx, &vars[i]);
            }
        }
        else {
            setcfg_get_helper(ctx, var);
        }
    }
    else if (strcmp(argv[1], "save") == 0) {
        config_save();
    }
}

const char shell_help_sensor[] = "\n";
const char shell_help_summary_sensor[] = "Get sensor readings";

void shell_sensor(shell_context_t *ctx, int argc, char **argv) {
    printf("EPD Supplies:\n");
    printf("VP:     %5.2f V\n", power_get_rail_voltage(RAIL_VP));
    printf("VGH:    %5.2f V\n", power_get_rail_voltage(RAIL_VGH));
    printf("VN:     %5.2f V\n", power_get_rail_voltage(RAIL_VN));
    printf("VGL:    %5.2f V\n", power_get_rail_voltage(RAIL_VGL));
    printf("VCOM:   %5.2f V\n", power_get_rail_voltage(RAIL_VCOM));
    printf("5VES:   %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_5VES), power_get_rail_current(RAIL_5VES));
    printf("5VEG:   %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_5VEG), power_get_rail_current(RAIL_5VEG));

    printf("System Supplies:\n");
    printf("VBUS:   %5.2f V\n", power_get_rail_voltage(RAIL_VBUS));
    printf("3V3:    %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_3V3), power_get_rail_current(RAIL_3V3));
    printf("1V8VID: %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_1V8VID), power_get_rail_current(RAIL_1V8VID));
    printf("3V3VID: %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_3V3VID), power_get_rail_current(RAIL_3V3VID));
    printf("5V2FL:  %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_5V2FL), power_get_rail_current(RAIL_5V2FL));
    printf("1V35:   %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_1V35), power_get_rail_current(RAIL_1V35));
    printf("1V2:    %5.2f V %3.0f mA\n", power_get_rail_voltage(RAIL_1V2), power_get_rail_current(RAIL_1V2));

    float p_cur, p_avg, p_max;
    float p_cur_sum, p_avg_sum, p_max_sum;

    printf("Power Consumption (CUR, AVG, MAX):\n");
    power_get_rail_power(RAIL_3V3, &p_cur, &p_avg, &p_max);
    printf("MCU + IO:  %5.1f mW  %5.1f mW  %5.1f mW\n", p_cur, p_avg, p_max);
    power_get_rail_power(RAIL_1V35, &p_cur, &p_avg, &p_max);
    printf("FPGA DDR:  %5.1f mW  %5.1f mW  %5.1f mW\n", p_cur, p_avg, p_max);
    power_get_rail_power(RAIL_1V2, &p_cur, &p_avg, &p_max);
    printf("FPGA CORE: %5.1f mW  %5.1f mW  %5.1f mW\n", p_cur, p_avg, p_max);
    power_get_rail_power(RAIL_1V8VID, &p_cur_sum, &p_avg_sum, &p_max_sum);
    power_get_rail_power(RAIL_3V3VID, &p_cur, &p_avg, &p_max);
    p_cur_sum += p_cur; p_avg_sum += p_avg; p_max_sum += p_max;
    printf("VIDEO IN:  %5.1f mW  %5.1f mW  %5.1f mW\n", p_cur_sum, p_avg_sum, p_max_sum);
    power_get_rail_power(RAIL_5VES, &p_cur_sum, &p_avg_sum, &p_max_sum);
    power_get_rail_power(RAIL_5VEG, &p_cur, &p_avg, &p_max);
    p_cur_sum += p_cur; p_avg_sum += p_avg; p_max_sum += p_max;
    printf("EPD HV:    %5.1f mW  %5.1f mW  %5.1f mW\n", p_cur_sum, p_avg_sum, p_max_sum);
}
