diff options
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | list.c | 111 | ||||
-rw-r--r-- | list.h | 76 | ||||
-rw-r--r-- | old/remote_plot_live.c | 666 | ||||
-rw-r--r-- | remote_plot.c | 248 |
5 files changed, 995 insertions, 118 deletions
@@ -2,11 +2,17 @@ PREFIX = /usr/local all: remote_plot -remote_plot: remote_plot.c - cc -Wall $$(pkg-config --cflags --libs libssh plplot gtk4) -lm -o $@ $< +list.o: list.c list.h + cc -Wall -c $< + +remote_plot.o: remote_plot.c + cc -Wall $$(pkg-config --cflags --libs libssh plplot gtk4) -lm -c $< + +remote_plot: list.o remote_plot.o + cc -Wall $$(pkg-config --cflags --libs libssh plplot gtk4) -lm -o $@ $^ clean: - rm -f remote_plot + rm -f remote_plot remote_plot.o list.o install: all mkdir -p ${DESTDIR}${PREFIX}/bin @@ -0,0 +1,111 @@ +// "C Primer Plus" Chapter17 practice 17 practice problem 2 + +//list.c--支持列表操作的函数 +#include <stdio.h> +#include <stdlib.h> +#include "list.h" + +//局部函数原型 +static void CopyToNode(Item item, Node * pnode); + +//接口函数 +//把列表设置为空列表 +void InitializeList(List * plist) +{ + plist->head=plist->end=NULL; +} + +//如果列表为空则返回真 +bool ListIsEmpty(const List * plist) +{ + if(plist->head==NULL && plist->end==NULL) + return true; + else + return false; +} + +//如果列表已满则返回真 +bool ListIsFull(const List * plist) +{ + Node * pt; + bool full; + + pt=(Node *)malloc(sizeof(Node)); + if(pt==NULL) + full=true; + else + full=false; + free(pt); + return full; +} + +//返回节点数 +unsigned int ListItemCount(const List * plist) +{ + unsigned int count=0; + Node * pnode=plist->head; //设置到列表的开始处 + + while(pnode!=NULL) + { + ++count; + pnode=pnode->next; //把1设置为下一个节点 + } + return count; +} + +//创建存放项目的节点,并把它添加到 +//由plist指向的列表(较慢的实现方法?)尾部 +bool AddItem(Item item, List * plist) +{ + Node * pnew; + + pnew=(Node *)malloc(sizeof(Node)); + if(pnew==NULL) + return false; //(分配空间)失败时退出函数 + + CopyToNode(item, pnew); + pnew->next=NULL; + if(ListIsEmpty(plist)) //空列表,因此把pnew + { + plist->head=pnew; //放在列表头部 + plist->end=pnew; //放在列表尾部 + } + else + { + plist->end->next=pnew; //把pnew添加到结尾处 + plist->end=pnew; + } + return true; +} + +//访问每个节点并对它们分别执行有pfun指向的函数 +void Traverse(const List * plist, void(* pfun)(Item item)) +{ + Node * pnode=plist->head; + while(pnode!=NULL) + { + (*pfun)(pnode->item); + pnode=pnode->next; + } +} + +//释放由malloc()分配的内存 +//把列表指针设置为NULL +void EmptyTheList(List * plist) +{ + Node * psave; + while(plist->head != NULL) + { + psave=(plist->head)->next; //保存下一个节点的地址 + free(plist->head); //释放当前节点 + plist->head=psave; //前进到下一个节点 + } +} + +//局部函数定义 +//把一个项目复制到一个节点中 +static void CopyToNode(Item item, Node * pnode) +{ + pnode->item=item; //结构赋值 +} + @@ -0,0 +1,76 @@ +// "C Primer Plus" Chapter17 practice 17 practice problem 2 + +/*list.h--简单列表类型的头文件*/ +#ifndef LIST_H_ +#define LIST_H_ +#include <stdbool.h> +#include <stdint.h> //uint32_t + +/*特定与程序的声明*/ +#define VOLTLEN 96 +#define TEMPLEN 32 + +typedef struct can +{ + uint32_t t; + double volt[VOLTLEN]; + double temp[TEMPLEN]; +}; + +/*一般类型定义*/ +typedef struct can Item; + +typedef struct node +{ + Item item; + struct node * next; +}Node; + +typedef struct list +{ + Node * head; + Node * end; +}List; +/*函数原型*/ +/*操作: 初始化一个列表 */ //operation +/*操作前: plist指向一个列表 */ //preconditions +/*操作后: 该列表被初始化为空列表 */ //postconditions +void InitializeList(List * plist); + +/*操作: 确定列表是否为空列表 */ +/*操作前: plist指向一个已经初始化的列表 */ +/*操作后: 如果该列表为空则返回true; 否则返回false*/ +bool ListIsEmpty(const List * plist); + +/*操作: 确定列表是否已满*/ +/*操作前: plist指向一个已初始化的列表*/ +/*操作后: 如果该列表为空则返回true; 否则返回false*/ +bool ListIsFull(const List * plist); + +/*操作: 确定列表中项目的个数*/ +/*操作前: plist指向一个已初始化的列表*/ +/*操作后: 返回该列表中项目的个数*/ +unsigned int ListItemCount(const List * plist); + +/*操作: 在列表尾部添加一个项目*/ +/*操作前: item是要被增加到列表的项目*/ +/* plist指向一个已初始化的列表*/ +/*操作后: 如果可能的话,在列表尾部添加一个新项目,*/ +/* 函数返回true;否则函数返回false*/ +bool AddItem(Item item, List * plist); + +/*操作: 把一个函数作用于列表中的每个项目*/ +/*操作前: plist指向一个已出书画的列表*/ +/* pfun指向一个函数,该函数接受*/ +/* 一个Item参数并且无返回值*/ +/*操作后: pfun指向的函数被作用到*/ +/* 列表中的每个项目一次*/ +void Traverse(const List * plist, void (* pfun)(Item item)); + +/*操作: 释放已分配的内存(如果有)*/ +/*操作前: plist指向一个已初始化的列表*/ +/*操作后: 为该列表分配的内存已被释放*/ +/* 并且该列表被置为空列表*/ +void EmptyTheList(List * plist); + +#endif // LIST_H_ 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 <libssh/libssh.h> +#include <stdlib.h> +#include <stdio.h> +#include <libssh/sftp.h> + +// verify_knownhost() +#include <errno.h> +#include <string.h> + +#include <fcntl.h> // open() + +// plplot +#include <plplot/plConfig.h> +#include <plplot/plplot.h> +#include <math.h> +//#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <gtk/gtk.h> + +// 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;j<VOLTLEN;j++) + data->volt[i][j]=data->volt[i+1][j]; + for(int j=0;j<TEMPLEN;j++) + data->temp[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;i<VOLTLEN;i++) + { + data->volt[LEN-1][i]=atof(strtok(NULL,",")); + //printf("volt[%d][%d]: %g\n",LEN-1,i,data->volt[LEN-1][i]); + } + for(int i=0;i<TEMPLEN;i++) + { + data->temp[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;i<LEN;i++) + data->t[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;i<VOLTLEN;i++) + sum+=data->volt[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;i<TEMPLEN;i++) + sum+=data->temp[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;j<LEN;j++) + { + if(i<VOLTLEN) + { + // according to .dbc file + ymin=0; + ymax=6.; + y[j]=data->volt[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(i<VOLTLEN) + pllab("Time (s)","Voltage (V)",checkbutton_names[i]); + else + pllab("Time (s)","Temperature (C)",checkbutton_names[i]); + // Plot the data that was prepared above. + // plstring for scatter + // #(NNN) is Hershey font code, more see example 5, 6, 21 + // #(727) is centred X symbol; other maybe useful codes: #(728) + // hershey font code see http://plplot.org/examples.php?demo=07 ? + plstring(LEN, data->t, 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;i<LEN;i++) + { + printf("%g",data->t[i]); + for(int j=0;j<VOLTLEN;j++) + printf(",%g",data->volt[i][j]); + for(int j=0;j<TEMPLEN;j++) + printf(",%g",data->temp[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(i<VOLTLEN) + gtk_flow_box_append(GTK_FLOW_BOX(flowbox_volt), data->checkbutton[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<LEN;i++) + { + data.t[i]=i; + for(int j=0;j<VOLTLEN;j++) + data.volt[i][j]=0; + for(int j=0;j<TEMPLEN;j++) + data.temp[i][j]=0; + } + + // if I don't handle command line + //app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS); + // if I handle command line + app = gtk_application_new ("org.gtk.example", G_APPLICATION_HANDLES_COMMAND_LINE); + + g_application_add_main_option(G_APPLICATION(app),"destination",'d',0,G_OPTION_ARG_STRING,"ssh destination","Spartan_Racing_Charger@10.0.0.9"); + + g_signal_connect (app, "activate", G_CALLBACK (activate), &data); + g_signal_connect (app, "command-line", G_CALLBACK (command_line), &data); + status = g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + + //printf("before ssh free\n"); + + ssh_disconnect(data.session); + ssh_free(data.session); + // Segmentation fault (core dumped) error, why? maybe double free? so no free is ok? + //sftp_free(sftp); + + return status; +} diff --git a/remote_plot.c b/remote_plot.c index 40e11d1..64014eb 100644 --- a/remote_plot.c +++ b/remote_plot.c @@ -27,21 +27,26 @@ //#include <stdlib.h> #include <time.h> #include <unistd.h> - #include <gtk/gtk.h> +#include "list.h" +#include<stdint.h> //uint32_t + +// use double for PLFLT +#define PL_DOUBLE // 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 +// 1 timestamp, 96 voltage, 32 temp, 1 \n +// 10+96*6+32*6+1=779 bytes +#define ENTRY_SIZE 779 +// 16 KiB == 16384 bytes +// 16384/ENTRY_SIZE == 21 +// 21*ENTRY_SIZE == 16359 +// https://api.libssh.org/stable/libssh_tutor_sftp.html +#define MAX_XFER_BUF_SIZE 16384/ENTRY_SIZE*ENTRY_SIZE const char *checkbutton_names[]={ "0x630_BMS_Cell_4_Voltage", @@ -177,13 +182,13 @@ const char *checkbutton_names[]={ typedef struct { ssh_session session; sftp_session sftp; - PLFLT t[LEN]; - PLFLT volt[LEN][VOLTLEN]; - PLFLT temp[LEN][TEMPLEN]; + List cans; GtkWidget *area; GtkWidget *checkbutton[VOLTLEN+TEMPLEN]; GtkWidget *volt_label; GtkWidget *temp_label; + // .csv file already read offset + uint32_t offset; }DATA; static gboolean sftp_read_sync(gpointer user_data) @@ -201,8 +206,7 @@ static gboolean sftp_read_sync(gpointer user_data) // 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); + file = sftp_open(data->sftp, sftp_expand_path(data->sftp,"~/.local/share/mycan.csv"), access_type, 0); if (file == NULL) { fprintf(stderr, "Can't open file for reading: %s\n", ssh_get_error(data->session)); @@ -210,18 +214,13 @@ static gboolean sftp_read_sync(gpointer user_data) return G_SOURCE_REMOVE; } - for(int i=0;i<(LEN-1);i++) - { - //data->t[i]=data->t[i+1]; - for(int j=0;j<VOLTLEN;j++) - data->volt[i][j]=data->volt[i+1][j]; - for(int j=0;j<TEMPLEN;j++) - data->temp[i][j]=data->temp[i+1][j]; - } - + if(data->offset != 0) + sftp_seek(file,data->offset); for(;;) { nbytes = sftp_read(file, buffer, sizeof(buffer)); + //printf("nbytes: %d\n",nbytes); + //printf("%d\n",MAX_XFER_BUF_SIZE); if (nbytes == 0) { break; // EOF } else if (nbytes < 0) { @@ -230,24 +229,38 @@ static gboolean sftp_read_sync(gpointer user_data) sftp_close(file); //return SSH_ERROR; return G_SOURCE_REMOVE; + } else if ((nbytes%ENTRY_SIZE) != 0) { + fprintf(stderr, "sftp read nbytes not a multiple of %d\n",ENTRY_SIZE); + 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;i<VOLTLEN;i++) + // TODO: check strtok return NULL or not + //printf("before strtok\n"); + for(int i=0;i<(nbytes/ENTRY_SIZE);i++) { - data->volt[LEN-1][i]=atof(strtok(NULL,",")); - //printf("volt[%d][%d]: %g\n",LEN-1,i,data->volt[LEN-1][i]); - } - for(int i=0;i<TEMPLEN;i++) - { - data->temp[LEN-1][i]=atof(strtok(NULL,",")); - //printf("temp[%d][%d]: %g\n",LEN-1,i,data->temp[LEN-1][i]); + Item temp; + // TODO: pass endptr and check it and other things to be safe, see https://stackoverflow.com/questions/34206446 + if(i) + temp.t=strtoul(strtok(NULL,","),NULL,10); + else + temp.t=strtoul(strtok(buffer,","),NULL,10); + for(int j=0;j<VOLTLEN;j++) + temp.volt[j]=atof(strtok(NULL,",")); + for(int j=0;j<(TEMPLEN-1);j++) + temp.temp[j]=atof(strtok(NULL,",")); + temp.temp[TEMPLEN-1]=atof(strtok(NULL,"\n")); + //printf("before AddItem\n"); + if(AddItem(temp,&(data->cans))==false) + { + fprintf(stderr,"Problem allocating memory\n"); + break; + } + //printf("after AddItem\n"); } - //printf("end print receive\n"); + //printf("after strtok\n"); + data->offset+=nbytes; } rc = sftp_close(file); @@ -258,24 +271,22 @@ static gboolean sftp_read_sync(gpointer user_data) return G_SOURCE_REMOVE; } - for(int i=0;i<LEN;i++) - data->t[i]++; //printf("sftp_read_sync end\n"); { double sum,avg; - // max 29 char + 1 \0: Average temperature: 6553.5 C - char str[30]; + // max 28 char + 1 \0: Average temperature: 6553.5 C + char str[29]; sum=avg=0; for(int i=0;i<VOLTLEN;i++) - sum+=data->volt[LEN-1][i]; + sum+=data->cans.end->item.volt[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;i<TEMPLEN;i++) - sum+=data->temp[LEN-1][i]; + sum+=data->cans.end->item.temp[i]; avg=sum/TEMPLEN; sprintf(str,"Average temperature: %.1f C",avg); gtk_label_set_text(GTK_LABEL(data->temp_label),str); @@ -386,100 +397,102 @@ static void draw_function (GtkDrawingArea *area, 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]; + if(!ListIsEmpty(&(data->cans))) + { + //printf("draw begin\n"); + PLFLT xmin=data->cans.head->item.t; + PLFLT xmax=data->cans.end->item.t; + 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++; + 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" ); + plsdev( "extcairo" ); - { - char str[12]; - sprintf(str,"%dx%d", - gtk_widget_get_width(data->area), - gtk_widget_get_height(data->area)); - plsetopt("geometry", str); - } + { + 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 ); + // 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]) + for(int i=0;i<(VOLTLEN+TEMPLEN);i++) { - PLFLT y[LEN]; - - for(int j=0;j<LEN;j++) + if(active_checkbutton[i]) { if(i<VOLTLEN) { // according to .dbc file ymin=0; ymax=6.; - y[j]=data->volt[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(i<VOLTLEN) - pllab("Time (s)","Voltage (V)",checkbutton_names[i]); - else - pllab("Time (s)","Temperature (C)",checkbutton_names[i]); - // Plot the data that was prepared above. - // plstring for scatter - // #(NNN) is Hershey font code, more see example 5, 6, 21 - // #(727) is centred X symbol; other maybe useful codes: #(728) - // hershey font code see http://plplot.org/examples.php?demo=07 ? - plstring(LEN, data->t, y, "#(727)"); - // pline for line - plline(LEN, data->t, y); + // Create a labelled box to hold the plot. + plenv(xmin, xmax, ymin, ymax, 0, 0); + if(i<VOLTLEN) + pllab("Time (s)","Voltage (V)",checkbutton_names[i]); + else + pllab("Time (s)","Temperature (C)",checkbutton_names[i]); + // Plot the data that was prepared above. + // plstring for scatter + // #(NNN) is Hershey font code, more see example 5, 6, 21 + // #(727) is centred X symbol; other maybe useful codes: #(728) + // hershey font code see http://plplot.org/examples.php?demo=07 ? + { + Node * pnode = data->cans.head; + while(pnode!=NULL) + { + if(i<VOLTLEN) + plptex(pnode->item.t,pnode->item.volt[i],1.,0.,0.5,"#(727)"); + else + plptex(pnode->item.t,pnode->item.temp[i-VOLTLEN],1.,0.,0.5,"#(727)"); + pnode=pnode->next; + } + } + } } - } - // Close PLplot library - plend(); + // Close PLplot library + plend(); + } } static void print_data (GtkWidget *widget, gpointer user_data) { DATA *data=user_data; - for(int i=0;i<LEN;i++) - { - printf("%g",data->t[i]); - for(int j=0;j<VOLTLEN;j++) - printf(",%g",data->volt[i][j]); - for(int j=0;j<TEMPLEN;j++) - printf(",%g",data->temp[i][j]); - putchar('\n'); - } + printf("%u",data->cans.end->item.t); + for(int i=0;i<VOLTLEN;i++) + printf(",%g",data->cans.end->item.volt[i]); + for(int i=0;i<TEMPLEN;i++) + printf(",%g",data->cans.end->item.temp[i]); + putchar('\n'); } static void activate (GtkApplication *app, gpointer user_data) @@ -515,7 +528,7 @@ static void activate (GtkApplication *app, gpointer user_data) gtk_widget_set_vexpand(data->area,TRUE); gtk_widget_set_hexpand(data->area,TRUE); - button = gtk_button_new_with_label ("Print data"); + button = gtk_button_new_with_label ("Print last second 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 @@ -545,6 +558,7 @@ static void activate (GtkApplication *app, gpointer user_data) gtk_window_present (GTK_WINDOW (window)); + //printf("before g_timeout_add\n"); g_timeout_add(1000,sftp_read_sync,user_data); //printf("after g_timeout_add\n"); } @@ -634,14 +648,15 @@ int main (int argc, char **argv) GtkApplication *app; int status; - for(int i=0;i<LEN;i++) + InitializeList(&(data.cans)); + if(ListIsFull(&(data.cans))) { - data.t[i]=i; - for(int j=0;j<VOLTLEN;j++) - data.volt[i][j]=0; - for(int j=0;j<TEMPLEN;j++) - data.temp[i][j]=0; + fprintf(stderr,"No memory available! Bye!\n"); + exit(1); } + data.offset=0; + + //printf("after init\n"); // if I don't handle command line //app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS); @@ -652,6 +667,7 @@ int main (int argc, char **argv) g_signal_connect (app, "activate", G_CALLBACK (activate), &data); g_signal_connect (app, "command-line", G_CALLBACK (command_line), &data); + //printf("before g_app run\n"); status = g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); @@ -662,5 +678,7 @@ int main (int argc, char **argv) // Segmentation fault (core dumped) error, why? maybe double free? so no free is ok? //sftp_free(sftp); + EmptyTheList(&(data.cans)); + return status; } |