aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile12
-rw-r--r--list.c111
-rw-r--r--list.h76
-rw-r--r--old/remote_plot_live.c666
-rw-r--r--remote_plot.c248
5 files changed, 995 insertions, 118 deletions
diff --git a/Makefile b/Makefile
index 7a9cd9e..adccbbf 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/list.c b/list.c
new file mode 100644
index 0000000..49aeaac
--- /dev/null
+++ b/list.c
@@ -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; //结构赋值
+}
+
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..88cd4f5
--- /dev/null
+++ b/list.h
@@ -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;
}