// 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