From 10e19dab46407cb9cf23923d2431cef0f2a50733 Mon Sep 17 00:00:00 2001 From: Xiao Pan Date: Mon, 28 Apr 2025 20:25:40 -0700 Subject: feat: read past data from remote pi Using linked list so I can keep adding data without worrying about arrary max length. I also moved old code that can only read live data to old/remote_plot_live.c as an archive. --- old/remote_plot_live.c | 666 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 old/remote_plot_live.c (limited to 'old') diff --git a/old/remote_plot_live.c b/old/remote_plot_live.c new file mode 100644 index 0000000..40e11d1 --- /dev/null +++ b/old/remote_plot_live.c @@ -0,0 +1,666 @@ +// references: +// https://api.libssh.org/stable/libssh_tutorial.html +// https://api.libssh.org/stable/libssh_tutor_guided_tour.html +// http://plplot.org/documentation.php +// http://plplot.org/examples.php +// plplot example 0 and ext-cairo-test.c +// https://docs.gtk.org/gtk4/class.DrawingArea.html +// https://www.gtk.org/docs/getting-started/hello-world/ +// https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/print-editor/print-editor.c +// https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/main.c + +#include +#include +#include +#include + +// verify_knownhost() +#include +#include + +#include // open() + +// plplot +#include +#include +#include +//#include +#include +#include + +#include + +// max wire unsigned number is 65535 +// max wire signed number maybe is -32768 to 32767 +// voltage max format 6 bytes ,5.999 +// temp max format 6 bytes ,-49.9 +// timestamp max 10 bytes 1745742182 +// 1 timestamp, 96 voltage, 32 temp, 1 \n, 1 \0 +// 10+96*7+32*7+1+1=908 bytes +#define MAX_XFER_BUF_SIZE 778 + +#define LEN 10 +#define VOLTLEN 96 +#define TEMPLEN 32 + +const char *checkbutton_names[]={ + "0x630_BMS_Cell_4_Voltage", + "0x630_BMS_Cell_3_Voltage", + "0x630_BMS_Cell_2_Voltage", + "0x630_BMS_Cell_1_Voltage", + "0x631_BMS_Cell_8_Voltage", + "0x631_BMS_Cell_7_Voltage", + "0x631_BMS_Cell_6_Voltage", + "0x631_BMS_Cell_5_Voltage", + "0x632_BMS_Cell_12_Voltage", + "0x632_BMS_Cell_11_Voltage", + "0x632_BMS_Cell_10_Voltage", + "0x632_BMS_Cell_9_Voltage", + "0x633_BMS_Cell_4_Voltage", + "0x633_BMS_Cell_3_Voltage", + "0x633_BMS_Cell_2_Voltage", + "0x633_BMS_Cell_1_Voltage", + "0x634_BMS_Cell_8_Voltage", + "0x634_BMS_Cell_7_Voltage", + "0x634_BMS_Cell_6_Voltage", + "0x634_BMS_Cell_5_Voltage", + "0x635_BMS_Cell_12_Voltage", + "0x635_BMS_Cell_11_Voltage", + "0x635_BMS_Cell_10_Voltage", + "0x635_BMS_Cell_9_Voltage", + "0x636_BMS_Cell_4_Voltage", + "0x636_BMS_Cell_3_Voltage", + "0x636_BMS_Cell_2_Voltage", + "0x636_BMS_Cell_1_Voltage", + "0x637_BMS_Cell_8_Voltage", + "0x637_BMS_Cell_7_Voltage", + "0x637_BMS_Cell_6_Voltage", + "0x637_BMS_Cell_5_Voltage", + "0x638_BMS_Cell_12_Voltage", + "0x638_BMS_Cell_11_Voltage", + "0x638_BMS_Cell_10_Voltage", + "0x638_BMS_Cell_9_Voltage", + "0x639_BMS_Cell_4_Voltage", + "0x639_BMS_Cell_3_Voltage", + "0x639_BMS_Cell_2_Voltage", + "0x639_BMS_Cell_1_Voltage", + "0x63a_BMS_Cell_8_Voltage", + "0x63a_BMS_Cell_7_Voltage", + "0x63a_BMS_Cell_6_Voltage", + "0x63a_BMS_Cell_5_Voltage", + "0x63b_BMS_Cell_12_Voltage", + "0x63b_BMS_Cell_11_Voltage", + "0x63b_BMS_Cell_10_Voltage", + "0x63b_BMS_Cell_9_Voltage", + "0x63c_BMS_Cell_4_Voltage", + "0x63c_BMS_Cell_3_Voltage", + "0x63c_BMS_Cell_2_Voltage", + "0x63c_BMS_Cell_1_Voltage", + "0x63d_BMS_Cell_8_Voltage", + "0x63d_BMS_Cell_7_Voltage", + "0x63d_BMS_Cell_6_Voltage", + "0x63d_BMS_Cell_5_Voltage", + "0x63e_BMS_Cell_12_Voltage", + "0x63e_BMS_Cell_11_Voltage", + "0x63e_BMS_Cell_10_Voltage", + "0x63e_BMS_Cell_9_Voltage", + "0x63f_BMS_Cell_4_Voltage", + "0x63f_BMS_Cell_3_Voltage", + "0x63f_BMS_Cell_2_Voltage", + "0x63f_BMS_Cell_1_Voltage", + "0x640_BMS_Cell_8_Voltage", + "0x640_BMS_Cell_7_Voltage", + "0x640_BMS_Cell_6_Voltage", + "0x640_BMS_Cell_5_Voltage", + "0x641_BMS_Cell_12_Voltage", + "0x641_BMS_Cell_11_Voltage", + "0x641_BMS_Cell_10_Voltage", + "0x641_BMS_Cell_9_Voltage", + "0x642_BMS_Cell_4_Voltage", + "0x642_BMS_Cell_3_Voltage", + "0x642_BMS_Cell_2_Voltage", + "0x642_BMS_Cell_1_Voltage", + "0x643_BMS_Cell_8_Voltage", + "0x643_BMS_Cell_7_Voltage", + "0x643_BMS_Cell_6_Voltage", + "0x643_BMS_Cell_5_Voltage", + "0x644_BMS_Cell_12_Voltage", + "0x644_BMS_Cell_11_Voltage", + "0x644_BMS_Cell_10_Voltage", + "0x644_BMS_Cell_9_Voltage", + "0x645_BMS_Cell_4_Voltage", + "0x645_BMS_Cell_3_Voltage", + "0x645_BMS_Cell_2_Voltage", + "0x645_BMS_Cell_1_Voltage", + "0x646_BMS_Cell_8_Voltage", + "0x646_BMS_Cell_7_Voltage", + "0x646_BMS_Cell_6_Voltage", + "0x646_BMS_Cell_5_Voltage", + "0x647_BMS_Cell_12_Voltage", + "0x647_BMS_Cell_11_Voltage", + "0x647_BMS_Cell_10_Voltage", + "0x647_BMS_Cell_9_Voltage", + "0x680_BMS_Section_4_Temp", + "0x680_BMS_Section_3_Temp", + "0x680_BMS_Section_2_Temp", + "0x680_BMS_Section_1_Temp", + "0x683_BMS_Section_4_Temp", + "0x683_BMS_Section_3_Temp", + "0x683_BMS_Section_2_Temp", + "0x683_BMS_Section_1_Temp", + "0x686_BMS_Section_4_Temp", + "0x686_BMS_Section_3_Temp", + "0x686_BMS_Section_2_Temp", + "0x686_BMS_Section_1_Temp", + "0x689_BMS_Section_4_Temp", + "0x689_BMS_Section_3_Temp", + "0x689_BMS_Section_2_Temp", + "0x689_BMS_Section_1_Temp", + "0x68c_BMS_Section_4_Temp", + "0x68c_BMS_Section_3_Temp", + "0x68c_BMS_Section_2_Temp", + "0x68c_BMS_Section_1_Temp", + "0x68f_BMS_Section_4_Temp", + "0x68f_BMS_Section_3_Temp", + "0x68f_BMS_Section_2_Temp", + "0x68f_BMS_Section_1_Temp", + "0x692_BMS_Section_4_Temp", + "0x692_BMS_Section_3_Temp", + "0x692_BMS_Section_2_Temp", + "0x692_BMS_Section_1_Temp", + "0x695_BMS_Section_4_Temp", + "0x695_BMS_Section_3_Temp", + "0x695_BMS_Section_2_Temp", + "0x695_BMS_Section_1_Temp" +}; + +typedef struct { + ssh_session session; + sftp_session sftp; + PLFLT t[LEN]; + PLFLT volt[LEN][VOLTLEN]; + PLFLT temp[LEN][TEMPLEN]; + GtkWidget *area; + GtkWidget *checkbutton[VOLTLEN+TEMPLEN]; + GtkWidget *volt_label; + GtkWidget *temp_label; +}DATA; + +static gboolean sftp_read_sync(gpointer user_data) + //int sftp_read_sync(ssh_session session, sftp_session sftp) +{ + //printf("sftp_read_sync begin\n"); + DATA *data=user_data; + + int access_type; + sftp_file file; + char buffer[MAX_XFER_BUF_SIZE]; + int nbytes, rc; + access_type = O_RDONLY; + + // This is to represent a loop over time + // Let's try a random walk process + + file = sftp_open(data->sftp, "/tmp/mycan", + access_type, 0); + if (file == NULL) { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(data->session)); + //return SSH_ERROR; + return G_SOURCE_REMOVE; + } + + for(int i=0;i<(LEN-1);i++) + { + //data->t[i]=data->t[i+1]; + for(int j=0;jvolt[i][j]=data->volt[i+1][j]; + for(int j=0;jtemp[i][j]=data->temp[i+1][j]; + } + + for(;;) + { + nbytes = sftp_read(file, buffer, sizeof(buffer)); + if (nbytes == 0) { + break; // EOF + } else if (nbytes < 0) { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(data->session)); + sftp_close(file); + //return SSH_ERROR; + return G_SOURCE_REMOVE; + } + + //printf("receive: %s",buffer); + //data->buffer[LEN-1]=atof(buffer); + // would be better if check strtok return NULL or not + strtok(buffer,","); + //printf("start print receive\n"); + for(int i=0;ivolt[LEN-1][i]=atof(strtok(NULL,",")); + //printf("volt[%d][%d]: %g\n",LEN-1,i,data->volt[LEN-1][i]); + } + for(int i=0;itemp[LEN-1][i]=atof(strtok(NULL,",")); + //printf("temp[%d][%d]: %g\n",LEN-1,i,data->temp[LEN-1][i]); + } + //printf("end print receive\n"); + } + + rc = sftp_close(file); + if (rc != SSH_OK) { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(data->session)); + //return rc; + return G_SOURCE_REMOVE; + } + + for(int i=0;it[i]++; + //printf("sftp_read_sync end\n"); + + { + double sum,avg; + // max 29 char + 1 \0: Average temperature: 6553.5 C + char str[30]; + sum=avg=0; + + for(int i=0;ivolt[LEN-1][i]; + avg=sum/VOLTLEN; + sprintf(str,"Average voltage: %.3f V",avg); + gtk_label_set_text(GTK_LABEL(data->volt_label),str); + sum=0; + for(int i=0;itemp[LEN-1][i]; + avg=sum/TEMPLEN; + sprintf(str,"Average temperature: %.1f C",avg); + gtk_label_set_text(GTK_LABEL(data->temp_label),str); + } + + gtk_widget_queue_draw(data->area); + + //return SSH_OK; + // must return G_SOURCE_CONTINUE to keep polling + // return G_SOURCE_REMOVE to stop + return G_SOURCE_CONTINUE; +} + +// https://api.libssh.org/stable/libssh_tutor_guided_tour.html +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + unsigned char *hash = NULL; + ssh_key srv_pubkey = NULL; + size_t hlen; + char buf[10]; + char *hexa; + char *p; + int cmp; + int rc; + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA1, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + switch (state) { + case SSH_KNOWN_HOSTS_OK: + /* OK */ + + break; + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr, "Host key for server changed: it is now:\n"); + ssh_print_hexa("Public key hash", hash, hlen); + fprintf(stderr, "For security reasons, connection will be stopped\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr, "The host key for this server was not found but an other" + "type of key exists.\n"); + fprintf(stderr, "An attacker might change the default server key to" + "confuse your client into thinking the key does not exist\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr, "Could not find known host file.\n"); + fprintf(stderr, "If you accept the host key here, the file will be" + "automatically created.\n"); + + /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ + + case SSH_KNOWN_HOSTS_UNKNOWN: + hexa = ssh_get_hexa(hash, hlen); + fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); + fprintf(stderr, "Public key hash: %s\n", hexa); + ssh_string_free_char(hexa); + ssh_clean_pubkey_hash(&hash); + p = fgets(buf, sizeof(buf), stdin); + if (p == NULL) { + return -1; + } + + cmp = strncasecmp(buf, "yes", 3); + if (cmp != 0) { + return -1; + } + + rc = ssh_session_update_known_hosts(session); + if (rc < 0) { + fprintf(stderr, "Error %s\n", strerror(errno)); + return -1; + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + fprintf(stderr, "Error %s", ssh_get_error(session)); + ssh_clean_pubkey_hash(&hash); + return -1; + } + + ssh_clean_pubkey_hash(&hash); + return 0; +} + +// http://plplot.org/documentation.php +// http://plplot.org/examples.php +// plplot example 0 and ext-cairo-test.c +static void draw_function (GtkDrawingArea *area, + cairo_t *cr, + int width, + int height, + gpointer user_data) +{ + //printf("draw begin\n"); + DATA *data=user_data; + PLFLT xmin = data->t[0], xmax = data->t[LEN-1]; + PLFLT ymin, ymax; + int plot_counts=0; + gboolean active_checkbutton[VOLTLEN+TEMPLEN]; + + for(int i=0;i<(VOLTLEN+TEMPLEN);i++) + if((active_checkbutton[i]=gtk_check_button_get_active(GTK_CHECK_BUTTON(data->checkbutton[i])))) + plot_counts++; + + plsdev( "extcairo" ); + + { + char str[12]; + sprintf(str,"%dx%d", + gtk_widget_get_width(data->area), + gtk_widget_get_height(data->area)); + plsetopt("geometry", str); + } + + // change to white background and black foreground, and gray color palette + // http://plplot.org/docbook-manual/plplot-html-5.15.0/color.html + // seems should be put before plinit; or need pladv(), plvpor(), plwind() for a new picture? + // more see example 16 + // http://plplot.org/examples.php?demo=16 + // /usr/share/plplot5.15.0/examples/c/x16c.c + plspal0("cmap0_black_on_white.pal"); + plspal1("cmap1_gray.pal", 1); + + // Initialize plplot + if(plot_counts>3) + plstar(3,(plot_counts+2)/3); + else + plstar(1,plot_counts); + pl_cmd( PLESC_DEVINIT, cr ); + + for(int i=0;i<(VOLTLEN+TEMPLEN);i++) + { + if(active_checkbutton[i]) + { + PLFLT y[LEN]; + + for(int j=0;jvolt[j][i]; + } + else + { + // according to .dbc file + ymin=-50; + ymax=100; + y[j]=data->temp[j][i-VOLTLEN]; + } + } + + // Create a labelled box to hold the plot. + plenv(xmin, xmax, ymin, ymax, 0, 0); + if(it, y, "#(727)"); + // pline for line + plline(LEN, data->t, y); + } + } + + // Close PLplot library + plend(); +} + +static void print_data (GtkWidget *widget, gpointer user_data) +{ + DATA *data=user_data; + for(int i=0;it[i]); + for(int j=0;jvolt[i][j]); + for(int j=0;jtemp[i][j]); + putchar('\n'); + } +} + +static void activate (GtkApplication *app, gpointer user_data) +{ + DATA *data=user_data; + GtkWidget *window; + data->area = gtk_drawing_area_new (); + GtkWidget *button; + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL,1); + GtkWidget *scrolled_window; + GtkWidget *flowbox_volt = gtk_flow_box_new(); + GtkWidget *flowbox_temp = gtk_flow_box_new(); + GtkWidget *expander_volt = gtk_expander_new("Choose cell voltages to plot"); + GtkWidget *expander_temp = gtk_expander_new("Choose cell temperatures to plot"); + + data->volt_label=gtk_label_new("Average voltage:"); + data->temp_label=gtk_label_new("Average temperature:"); + + for(int i=0;i<(VOLTLEN+TEMPLEN);i++) + data->checkbutton[i]=gtk_check_button_new_with_label(checkbutton_names[i]); + gtk_check_button_set_active(GTK_CHECK_BUTTON(data->checkbutton[0]),TRUE); + gtk_check_button_set_active(GTK_CHECK_BUTTON(data->checkbutton[VOLTLEN]),TRUE); + + window = gtk_application_window_new (app); + gtk_window_set_title (GTK_WINDOW (window), "remote_plot"); + gtk_window_set_default_size (GTK_WINDOW (window), 1000, 1000); + + //gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (data->area), 600); + //gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (data->area), 600); + gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (data->area), + draw_function, + user_data, NULL); + gtk_widget_set_vexpand(data->area,TRUE); + gtk_widget_set_hexpand(data->area,TRUE); + + button = gtk_button_new_with_label ("Print data"); + g_signal_connect (button, "clicked", G_CALLBACK (print_data), user_data); + + // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/print-editor/print-editor.c + scrolled_window=gtk_scrolled_window_new(); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (scrolled_window), TRUE); + gtk_widget_set_vexpand (scrolled_window, TRUE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), box); + + for(int i=0;i<(VOLTLEN+TEMPLEN);i++) + { + if(icheckbutton[i]); + else + gtk_flow_box_append(GTK_FLOW_BOX(flowbox_temp), data->checkbutton[i]); + } + gtk_expander_set_child (GTK_EXPANDER (expander_volt), flowbox_volt); + gtk_expander_set_child (GTK_EXPANDER (expander_temp), flowbox_temp); + + gtk_box_append(GTK_BOX(box), button); + gtk_box_append(GTK_BOX(box), data->volt_label); + gtk_box_append(GTK_BOX(box), data->temp_label); + gtk_box_append(GTK_BOX(box), expander_volt); + gtk_box_append(GTK_BOX(box), expander_temp); + gtk_box_append(GTK_BOX(box), data->area); + gtk_window_set_child (GTK_WINDOW (window), scrolled_window); + + gtk_window_present (GTK_WINDOW (window)); + + g_timeout_add(1000,sftp_read_sync,user_data); + //printf("after g_timeout_add\n"); +} + +// https://gitlab.gnome.org/GNOME/gtk/-/blob/main/demos/gtk-demo/main.c +static int command_line (GApplication *app, GApplicationCommandLine *cmdline, gpointer user_data) +{ + DATA *data=user_data; + int rc; + GVariantDict *options = g_application_command_line_get_options_dict (cmdline); + // not sure if using char * here is ok + char *dest="Spartan_Racing_Charger@10.0.0.9"; + // https://docs.gtk.org/glib/gvariant-format-strings.html#pointers + // &s copy the pointer + g_variant_dict_lookup (options, "destination", "&s", &dest); + + // Open session and set options + data->session = ssh_new(); + if (data->session == NULL) + exit(-1); + ssh_options_set(data->session, SSH_OPTIONS_HOST, dest); + + // Connect to server + rc = ssh_connect(data->session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to %s: %s\n", + dest, ssh_get_error(data->session)); + ssh_free(data->session); + exit(-1); + } + + // Verify the server's identity + // For the source code of verify_knownhost(), check previous example + if (verify_knownhost(data->session) < 0) + { + ssh_disconnect(data->session); + ssh_free(data->session); + exit(-1); + } + + // Authenticate ourselves + // https://api.libssh.org/stable/libssh_tutor_authentication.html + rc = ssh_userauth_publickey_auto(data->session, NULL, NULL); + if (rc != SSH_AUTH_SUCCESS) + { + fprintf(stderr, "Error authenticating with key: %s\n", + ssh_get_error(data->session)); + ssh_disconnect(data->session); + ssh_free(data->session); + exit(-1); + } + + data->sftp = sftp_new(data->session); + if (data->sftp == NULL) + { + fprintf(stderr, "Error allocating SFTP session: %s\n", + ssh_get_error(data->session)); + return SSH_ERROR; + } + + rc = sftp_init(data->sftp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing SFTP session: code %d.\n", + sftp_get_error(data->sftp)); + sftp_free(data->sftp); + return rc; + } + + // https://discourse.gnome.org/t/gio-application-commandline-does-trigger-activate-signal/2988/22 + // > If you pass G_APPLICATION_HANDLES_COMMAND_LINE in the flags, the + // > command-line args are passed to the primary instance and ::activate is + // > not emitted. An example again is an something like --hide-window or + // > --do-without-disturbing-user which wouldn’t work if the default were to + // > trigger ::activate and open the window. So just pass + // > G_APPLICATION_HANDLES_COMMAND_LINE and then call + // > g_application_activate() in your ::command-line handler + g_application_activate(app); + + return 0; +} + +int main (int argc, char **argv) +{ + DATA data; + GtkApplication *app; + int status; + + for(int i=0;i