|
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
#include <tt_app.h>
|
|
|
|
|
|
|
|
#include <tt_lvgl_toolbar.h>
|
|
|
|
|
|
|
|
#include <tactility/lvgl_fonts.h>
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <lwip/sockets.h>
|
|
|
|
|
|
|
|
#include <lwip/inet.h>
|
|
|
|
|
|
|
|
#include "esp_log.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define TAG "RobotArm"
|
|
|
|
|
|
|
|
#define ARM_HOST "192.168.68.103"
|
|
|
|
|
|
|
|
#define ARM_PORT 80
|
|
|
|
|
|
|
|
#define ARM_PATH "/api/mcp"
|
|
|
|
|
|
|
|
#define NUM_JOINTS 6
|
|
|
|
|
|
|
|
#define SEQ_MAX 16
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
|
|
|
const char* name;
|
|
|
|
|
|
|
|
const char* cute;
|
|
|
|
|
|
|
|
uint32_t bg;
|
|
|
|
|
|
|
|
uint32_t accent;
|
|
|
|
|
|
|
|
int home, rmin, rmax;
|
|
|
|
|
|
|
|
int value, last_sent;
|
|
|
|
|
|
|
|
} Joint;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Joint joints[NUM_JOINTS] = {
|
|
|
|
|
|
|
|
{"base","Base",0xFFD6E0,0xFF8FA8,70,0,180,70,-1},
|
|
|
|
|
|
|
|
{"shoulder","Shldr",0xD6E8FF,0x8FB6FF,40,0,70,40,-1},
|
|
|
|
|
|
|
|
{"elbow","Elbow",0xFFE8C5,0xFFB86A,20,0,120,20,-1},
|
|
|
|
|
|
|
|
{"pitch","Pitch",0xD5F0D5,0x88D488,90,30,150,90,-1},
|
|
|
|
|
|
|
|
{"roll","Roll",0xE8D5FF,0xB088FF,90,30,150,90,-1},
|
|
|
|
|
|
|
|
{"gripper","Grip",0xFFF0B3,0xFFD060,90,0,180,90,-1},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct { int v[NUM_JOINTS]; } Frame;
|
|
|
|
|
|
|
|
static Frame seq[SEQ_MAX];
|
|
|
|
|
|
|
|
static int seq_len = 0, seq_idx = -1;
|
|
|
|
|
|
|
|
static bool seq_playing = false;
|
|
|
|
|
|
|
|
static int seq_play_pos = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
|
|
|
AppHandle app;
|
|
|
|
|
|
|
|
lv_obj_t* status;
|
|
|
|
|
|
|
|
lv_obj_t* sliders[6];
|
|
|
|
|
|
|
|
lv_obj_t* vals[6];
|
|
|
|
|
|
|
|
lv_obj_t* seq_label;
|
|
|
|
|
|
|
|
lv_obj_t* play_label;
|
|
|
|
|
|
|
|
lv_obj_t* blocks[SEQ_MAX];
|
|
|
|
|
|
|
|
int pend[6];
|
|
|
|
|
|
|
|
bool has[6];
|
|
|
|
|
|
|
|
int ticks[6];
|
|
|
|
|
|
|
|
lv_timer_t* poll;
|
|
|
|
|
|
|
|
lv_timer_t* seq_timer;
|
|
|
|
|
|
|
|
} Ctx;
|
|
|
|
|
|
|
|
static Ctx* g = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static uint16_t my_htons(uint16_t v){ return (v<<8)|(v>>8); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int http_post(const char* host,int port,const char* path,const char* body,char* out,size_t olen){
|
|
|
|
|
|
|
|
int fd=lwip_socket(AF_INET,SOCK_STREAM,0);
|
|
|
|
|
|
|
|
if(fd<0) return -1;
|
|
|
|
|
|
|
|
struct sockaddr_in s; memset(&s,0,sizeof(s));
|
|
|
|
|
|
|
|
s.sin_family=AF_INET; s.sin_port=my_htons(port); s.sin_addr.s_addr=ipaddr_addr(host);
|
|
|
|
|
|
|
|
struct timeval tv={5,0};
|
|
|
|
|
|
|
|
lwip_setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
|
|
|
|
|
|
|
|
lwip_setsockopt(fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv));
|
|
|
|
|
|
|
|
if(lwip_connect(fd,(struct sockaddr*)&s,sizeof(s))<0){close(fd);return -2;}
|
|
|
|
|
|
|
|
char hdr[256];
|
|
|
|
|
|
|
|
int bl=strlen(body);
|
|
|
|
|
|
|
|
int hl=snprintf(hdr,sizeof(hdr),"POST %s HTTP/1.1\r\nHost: %s:%d\r\nContent-Type: application/json\r\nContent-Length: %d\r\nConnection: close\r\n\r\n",path,host,port,bl);
|
|
|
|
|
|
|
|
if(lwip_send(fd,hdr,hl,0)<0){close(fd);return -3;}
|
|
|
|
|
|
|
|
if(lwip_send(fd,body,bl,0)<0){close(fd);return -3;}
|
|
|
|
|
|
|
|
int tot=0;
|
|
|
|
|
|
|
|
while(tot<(int)olen-1){int r=lwip_recv(fd,out+tot,olen-1-tot,0); if(r<=0) break; tot+=r;}
|
|
|
|
|
|
|
|
out[tot]='\0'; close(fd);
|
|
|
|
|
|
|
|
char* bp=strstr(out,"\r\n\r\n"); if(bp){bp+=4; memmove(out,bp,strlen(bp)+1);}
|
|
|
|
|
|
|
|
return tot>0?0:-4;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool jget(const char* js,const char* key,int* out){
|
|
|
|
|
|
|
|
const char* p=strstr(js,key);
|
|
|
|
|
|
|
|
if(!p) return false;
|
|
|
|
|
|
|
|
p+=strlen(key);
|
|
|
|
|
|
|
|
while(*p && *p!=':'){
|
|
|
|
|
|
|
|
p++;
|
|
|
|
|
|
|
|
if(!*p) return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
p++;
|
|
|
|
|
|
|
|
while(*p && (*p==' '||*p=='\t'||*p=='"'||*p=='\\')) p++;
|
|
|
|
|
|
|
|
int sign=1;
|
|
|
|
|
|
|
|
if(*p=='-'){sign=-1;p++;}
|
|
|
|
|
|
|
|
float v=0,frac=0.1f;
|
|
|
|
|
|
|
|
bool dot=false,got=false;
|
|
|
|
|
|
|
|
while(*p){
|
|
|
|
|
|
|
|
if(*p>='0'&&*p<='9'){got=true; if(!dot) v=v*10+(*p-'0'); else {v+=(*p-'0')*frac; frac*=0.1f;}}
|
|
|
|
|
|
|
|
else if(*p=='.'&&!dot) dot=true;
|
|
|
|
|
|
|
|
else break;
|
|
|
|
|
|
|
|
p++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!got) return false;
|
|
|
|
|
|
|
|
*out=(int)(v*sign+0.5f);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool parse_state(const char* r,int* b,int* s,int* e,int* p,int* ro,int* gr){
|
|
|
|
|
|
|
|
int v; bool ok=true;
|
|
|
|
|
|
|
|
if(jget(r,"base",&v)) *b=v; else ok=false;
|
|
|
|
|
|
|
|
if(jget(r,"shoulder",&v)) *s=v; else ok=false;
|
|
|
|
|
|
|
|
if(jget(r,"elbow",&v)) *e=v; else ok=false;
|
|
|
|
|
|
|
|
if(jget(r,"pitch",&v)) *p=v; else ok=false;
|
|
|
|
|
|
|
|
if(jget(r,"roll",&v)) *ro=v; else ok=false;
|
|
|
|
|
|
|
|
if(jget(r,"gripper",&v)) *gr=v; else ok=false;
|
|
|
|
|
|
|
|
return ok;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool rpc_get(int* b,int* s,int* e,int* p,int* ro,int* gr){
|
|
|
|
|
|
|
|
const char* body="{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"get_arm_state\",\"arguments\":{}}}";
|
|
|
|
|
|
|
|
char resp[2048]; memset(resp,0,sizeof(resp));
|
|
|
|
|
|
|
|
if(http_post(ARM_HOST,ARM_PORT,ARM_PATH,body,resp,sizeof(resp))!=0) return false;
|
|
|
|
|
|
|
|
return parse_state(resp,b,s,e,p,ro,gr);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool rpc_move(const char* j,int a){
|
|
|
|
|
|
|
|
char body[300]; snprintf(body,sizeof(body),
|
|
|
|
|
|
|
|
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"move_joint\",\"arguments\":{\"joint\":\"%s\",\"angle\":%d,\"duration\":0.5}}}",
|
|
|
|
|
|
|
|
j,a);
|
|
|
|
|
|
|
|
char r[1024]; memset(r,0,sizeof(r));
|
|
|
|
|
|
|
|
return http_post(ARM_HOST,ARM_PORT,ARM_PATH,body,r,sizeof(r))==0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool rpc_move_all(int b,int s,int e,int p,int ro,int gr,float dur){
|
|
|
|
|
|
|
|
char body[420]; snprintf(body,sizeof(body),
|
|
|
|
|
|
|
|
"{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"move_all_joints\",\"arguments\":{\"base\":%d,\"shoulder\":%d,\"elbow\":%d,\"pitch\":%d,\"roll\":%d,\"gripper\":%d,\"duration\":%.1f}}}",
|
|
|
|
|
|
|
|
b,s,e,p,ro,gr,dur);
|
|
|
|
|
|
|
|
char r[1024]; memset(r,0,sizeof(r));
|
|
|
|
|
|
|
|
int rc=http_post(ARM_HOST,ARM_PORT,ARM_PATH,body,r,sizeof(r));
|
|
|
|
|
|
|
|
ESP_LOGI(TAG,"move_all %d %d %d rc=%d",b,s,e,rc);
|
|
|
|
|
|
|
|
return rc==0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool rpc_home(void){
|
|
|
|
|
|
|
|
const char* b="{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"home_arm\",\"arguments\":{\"duration\":1.0}}}";
|
|
|
|
|
|
|
|
char r[512]; memset(r,0,sizeof(r)); return http_post(ARM_HOST,ARM_PORT,ARM_PATH,b,r,sizeof(r))==0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void set_status(const char* t){ if(g&&g->status) lv_label_set_text(g->status,t); }
|
|
|
|
|
|
|
|
static void apply_ui(void){
|
|
|
|
|
|
|
|
if(!g) return;
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){
|
|
|
|
|
|
|
|
if(g->sliders[i]) lv_slider_set_value(g->sliders[i],joints[i].value,LV_ANIM_OFF);
|
|
|
|
|
|
|
|
if(g->vals[i]){char buf[8]; snprintf(buf,sizeof(buf),"%d",joints[i].value); lv_label_set_text(g->vals[i],buf);}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void refresh_arm(void){
|
|
|
|
|
|
|
|
set_status("Reading .103...");
|
|
|
|
|
|
|
|
int b,s,e,p,ro,gr;
|
|
|
|
|
|
|
|
if(rpc_get(&b,&s,&e,&p,&ro,&gr)){
|
|
|
|
|
|
|
|
joints[0].value=b; joints[1].value=s; joints[2].value=e;
|
|
|
|
|
|
|
|
joints[3].value=p; joints[4].value=ro; joints[5].value=gr;
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){joints[i].last_sent=joints[i].value; if(g){g->pend[i]=joints[i].value; g->has[i]=false; g->ticks[i]=0;}}
|
|
|
|
|
|
|
|
apply_ui();
|
|
|
|
|
|
|
|
char buf[32]; snprintf(buf,sizeof(buf),"B%d S%d E%d",b,s,e); set_status(buf);
|
|
|
|
|
|
|
|
} else set_status("No .103");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void del_ref_cb(lv_timer_t* t){ lv_timer_delete(t); refresh_arm(); }
|
|
|
|
|
|
|
|
static void init_cb(lv_timer_t* t){ lv_timer_delete(t); refresh_arm(); }
|
|
|
|
|
|
|
|
static void poll_cb(lv_timer_t* t){
|
|
|
|
|
|
|
|
(void)t; if(!g) return;
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){
|
|
|
|
|
|
|
|
if(!g->has[i]) continue;
|
|
|
|
|
|
|
|
g->ticks[i]++; if(g->ticks[i]<3) continue;
|
|
|
|
|
|
|
|
g->has[i]=false; g->ticks[i]=0;
|
|
|
|
|
|
|
|
int tgt=g->pend[i]; if(joints[i].last_sent==tgt) continue;
|
|
|
|
|
|
|
|
joints[i].last_sent=tgt; joints[i].value=tgt;
|
|
|
|
|
|
|
|
char buf[20]; snprintf(buf,sizeof(buf),"%s %d",joints[i].cute,tgt); set_status(buf);
|
|
|
|
|
|
|
|
bool ok=rpc_move(joints[i].name,tgt);
|
|
|
|
|
|
|
|
snprintf(buf,sizeof(buf),"%s %d %s",joints[i].cute,tgt,ok?"ok":"fail"); set_status(buf);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void slider_cb(lv_event_t* e){
|
|
|
|
|
|
|
|
int idx=(int)(intptr_t)lv_event_get_user_data(e);
|
|
|
|
|
|
|
|
int v=(int)lv_slider_get_value(lv_event_get_target(e));
|
|
|
|
|
|
|
|
joints[idx].value=v;
|
|
|
|
|
|
|
|
if(g){
|
|
|
|
|
|
|
|
if(g->vals[idx]){char b[8]; snprintf(b,sizeof(b),"%d",v); lv_label_set_text(g->vals[idx],b);}
|
|
|
|
|
|
|
|
g->pend[idx]=v; g->has[idx]=true; g->ticks[idx]=0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ── sequencer ── */
|
|
|
|
|
|
|
|
static void update_seq_ui(void){
|
|
|
|
|
|
|
|
if(!g) return;
|
|
|
|
|
|
|
|
if(g->seq_label){
|
|
|
|
|
|
|
|
if(seq_len==0) lv_label_set_text(g->seq_label,"empty");
|
|
|
|
|
|
|
|
else {char b[16]; snprintf(b,sizeof(b),"%d/%d", (seq_idx>=0?seq_idx+1:seq_len), seq_len); lv_label_set_text(g->seq_label,b);}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i=0;i<SEQ_MAX;i++){
|
|
|
|
|
|
|
|
if(!g->blocks[i]) continue;
|
|
|
|
|
|
|
|
if(i<seq_len){
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(g->blocks[i],LV_OPA_COVER,0);
|
|
|
|
|
|
|
|
if(i==seq_idx){
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(g->blocks[i],3,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_color(g->blocks[i],lv_color_hex(0xFFFFFF),0);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(g->blocks[i],2,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_color(g->blocks[i],lv_color_hex(0x3A3A3A),0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(g->blocks[i],LV_OPA_30,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(g->blocks[i],0,0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void load_frame(int fidx){
|
|
|
|
|
|
|
|
if(fidx<0||fidx>=seq_len) return;
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){joints[i].value=seq[fidx].v[i]; joints[i].last_sent=joints[i].value; if(g){g->pend[i]=joints[i].value; g->has[i]=false;}}
|
|
|
|
|
|
|
|
apply_ui(); seq_idx=fidx; update_seq_ui();
|
|
|
|
|
|
|
|
char b[20]; snprintf(b,sizeof(b),"Frame %d",fidx+1); set_status(b);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void seq_add_cb(void){ if(seq_len>=SEQ_MAX){set_status("Seq full"); return;} for(int i=0;i<NUM_JOINTS;i++) seq[seq_len].v[i]=joints[i].value; seq_len++; seq_idx=seq_len-1; update_seq_ui(); char b[16]; snprintf(b,sizeof(b),"+ %d",seq_len); set_status(b); }
|
|
|
|
|
|
|
|
static void seq_rem_cb(void){
|
|
|
|
|
|
|
|
if(seq_len==0||seq_idx<0){set_status("Nothing"); return;}
|
|
|
|
|
|
|
|
int rem=seq_idx;
|
|
|
|
|
|
|
|
for(int f=rem;f<seq_len-1;f++) seq[f]=seq[f+1];
|
|
|
|
|
|
|
|
seq_len--; if(seq_len==0){seq_idx=-1; update_seq_ui(); set_status("- empty"); return;}
|
|
|
|
|
|
|
|
if(seq_idx>=seq_len) seq_idx=seq_len-1;
|
|
|
|
|
|
|
|
load_frame(seq_idx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void seq_prev_cb(void){ if(seq_len==0) return; int n=(seq_idx<=0)?seq_len-1:seq_idx-1; load_frame(n); }
|
|
|
|
|
|
|
|
static void seq_next_cb(void){ if(seq_len==0) return; int n=(seq_idx>=seq_len-1)?0:seq_idx+1; load_frame(n); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void seq_timer_cb(lv_timer_t* t){
|
|
|
|
|
|
|
|
(void)t; if(!seq_playing||seq_len==0) return;
|
|
|
|
|
|
|
|
int f=seq_play_pos%seq_len;
|
|
|
|
|
|
|
|
int* v=seq[f].v;
|
|
|
|
|
|
|
|
if(!rpc_move_all(v[0],v[1],v[2],v[3],v[4],v[5],0.8f)){
|
|
|
|
|
|
|
|
set_status("Seq fail"); seq_playing=false;
|
|
|
|
|
|
|
|
if(g&&g->play_label) lv_label_set_text(g->play_label,"Play");
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){joints[i].value=v[i]; joints[i].last_sent=v[i]; if(g){g->pend[i]=v[i]; g->has[i]=false;}}
|
|
|
|
|
|
|
|
apply_ui(); seq_idx=f; update_seq_ui();
|
|
|
|
|
|
|
|
char b[20]; snprintf(b,sizeof(b),"Play %d/%d",f+1,seq_len); set_status(b);
|
|
|
|
|
|
|
|
seq_play_pos++; if(seq_play_pos>=seq_len) seq_play_pos=0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void seq_play_cb(lv_event_t* e){
|
|
|
|
|
|
|
|
(void)e; if(seq_len==0){set_status("No frames"); return;}
|
|
|
|
|
|
|
|
seq_playing=!seq_playing;
|
|
|
|
|
|
|
|
if(g&&g->play_label) lv_label_set_text(g->play_label, seq_playing?"Pause":"Play");
|
|
|
|
|
|
|
|
if(seq_playing){seq_play_pos=(seq_idx>=0?seq_idx:0); set_status("Playing");} else set_status("Paused");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void seq_add_ev(lv_event_t* e){(void)e; seq_add_cb();}
|
|
|
|
|
|
|
|
static void seq_rem_ev(lv_event_t* e){(void)e; seq_rem_cb();}
|
|
|
|
|
|
|
|
static void seq_prev_ev(lv_event_t* e){(void)e; seq_prev_cb();}
|
|
|
|
|
|
|
|
static void seq_next_ev(lv_event_t* e){(void)e; seq_next_cb();}
|
|
|
|
|
|
|
|
static void block_click_cb(lv_event_t* e){int idx=(int)(intptr_t)lv_event_get_user_data(e); if(idx<0||idx>=seq_len) return; load_frame(idx);}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void home_cb(lv_event_t* e){(void)e; set_status("Homing..."); if(rpc_home()){lv_timer_create(del_ref_cb,1200,NULL); set_status("Homed :3");} else set_status("Fail");}
|
|
|
|
|
|
|
|
static void read_cb(lv_event_t* e){(void)e; refresh_arm();}
|
|
|
|
|
|
|
|
static void open_cb(lv_event_t* e){(void)e; joints[5].value=0; apply_ui(); rpc_move("gripper",0); set_status("Open");}
|
|
|
|
|
|
|
|
static void close_cb(lv_event_t* e){(void)e; joints[5].value=180; apply_ui(); rpc_move("gripper",180); set_status("Close");}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void onShow(AppHandle app,void* data,lv_obj_t* parent){
|
|
|
|
|
|
|
|
(void)data;
|
|
|
|
|
|
|
|
Ctx* c=(Ctx*)calloc(1,sizeof(Ctx)); if(!c) return;
|
|
|
|
|
|
|
|
c->app=app; g=c;
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){joints[i].last_sent=-1; c->pend[i]=joints[i].value; c->has[i]=false;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* tb=tt_lvgl_toolbar_create_for_app(parent,app); lv_obj_align(tb,LV_ALIGN_TOP_MID,0,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* root=lv_obj_create(parent);
|
|
|
|
|
|
|
|
lv_obj_set_size(root,LV_PCT(100),LV_PCT(100));
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(root,2,0); lv_obj_set_style_pad_top(root,34,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(root,0,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(root,lv_color_hex(0xFFF8F0),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(root,LV_OPA_COVER,0);
|
|
|
|
|
|
|
|
lv_obj_set_scroll_dir(root, LV_DIR_VER);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* hdr=lv_obj_create(root); lv_obj_set_size(hdr,LV_PCT(100),16);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(hdr,0,0); lv_obj_set_style_bg_opa(hdr,LV_OPA_TRANSP,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(hdr,0,0); lv_obj_set_pos(hdr,0,0);
|
|
|
|
|
|
|
|
lv_obj_clear_flag(hdr, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
|
|
|
|
lv_obj_t* title=lv_label_create(hdr); lv_label_set_text(title,"Robot Arm :3");
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(title,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(title,lv_color_hex(0x3D2B5A),0); lv_obj_set_pos(title,2,0);
|
|
|
|
|
|
|
|
c->status=lv_label_create(hdr); lv_label_set_text(c->status,"Conn .103...");
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(c->status,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(c->status,lv_color_hex(0xA08090),0); lv_obj_set_pos(c->status,90,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* brow=lv_obj_create(root); lv_obj_set_size(brow,LV_PCT(100),20);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(brow,0,0); lv_obj_set_style_bg_opa(brow,LV_OPA_TRANSP,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(brow,0,0); lv_obj_set_pos(brow,0,16);
|
|
|
|
|
|
|
|
lv_obj_clear_flag(brow, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
|
|
|
|
struct { const char* t; uint32_t col; lv_event_cb_t cb; } btns[]={
|
|
|
|
|
|
|
|
{"Home",0xFFD6E0,home_cb},{"Read",0xD6E8FF,read_cb},{"Open",0xD5F0D5,open_cb},{"Close",0xFFE8C5,close_cb},};
|
|
|
|
|
|
|
|
for(int i=0;i<4;i++){
|
|
|
|
|
|
|
|
lv_obj_t* b=lv_btn_create(brow); lv_obj_set_size(b,56,18);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(b,lv_color_hex(btns[i].col),0); lv_obj_set_style_radius(b,8,0);
|
|
|
|
|
|
|
|
lv_obj_set_pos(b,i*62+2,0);
|
|
|
|
|
|
|
|
lv_obj_t* l=lv_label_create(b); lv_label_set_text(l,btns[i].t);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(l,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(l,lv_color_hex(0x4A356A),0); lv_obj_center(l);
|
|
|
|
|
|
|
|
lv_obj_add_event_cb(b,btns[i].cb,LV_EVENT_CLICKED,NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ── large nice vertical sliders: 3 cols, 2x height 22x72 per request ── */
|
|
|
|
|
|
|
|
lv_obj_t* grid=lv_obj_create(root);
|
|
|
|
|
|
|
|
lv_obj_set_size(grid,LV_PCT(100),196);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(grid,0,0); lv_obj_set_style_bg_opa(grid,LV_OPA_TRANSP,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(grid,2,0); lv_obj_set_pos(grid,0,36);
|
|
|
|
|
|
|
|
lv_obj_clear_flag(grid, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for(int i=0;i<NUM_JOINTS;i++){
|
|
|
|
|
|
|
|
int col=i%3, row=i/3;
|
|
|
|
|
|
|
|
lv_obj_t* card=lv_obj_create(grid);
|
|
|
|
|
|
|
|
lv_obj_set_size(card,102,96);
|
|
|
|
|
|
|
|
lv_obj_set_pos(card,col*104+2,row*98);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(card,lv_color_hex(joints[i].bg),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(card,LV_OPA_90,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(card,12,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(card,0,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(card,3,0);
|
|
|
|
|
|
|
|
lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* name=lv_label_create(card);
|
|
|
|
|
|
|
|
lv_label_set_text(name,joints[i].cute);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(name,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(name,lv_color_hex(0x4A356A),0); lv_obj_set_pos(name,2,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
c->vals[i]=lv_label_create(card);
|
|
|
|
|
|
|
|
char vb[8]; snprintf(vb,sizeof(vb),"%d",joints[i].value);
|
|
|
|
|
|
|
|
lv_label_set_text(c->vals[i],vb);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(c->vals[i],lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(c->vals[i],lv_color_hex(0xC04060),0); lv_obj_set_pos(c->vals[i],40,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* rlbl=lv_label_create(card);
|
|
|
|
|
|
|
|
char rb[12]; snprintf(rb,sizeof(rb),"%d-%d",joints[i].rmin,joints[i].rmax);
|
|
|
|
|
|
|
|
lv_label_set_text(rlbl,rb);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(rlbl,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(rlbl,lv_color_hex(0xA090A0),0); lv_obj_set_pos(rlbl,58,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* sl=lv_slider_create(card);
|
|
|
|
|
|
|
|
c->sliders[i]=sl;
|
|
|
|
|
|
|
|
lv_obj_set_size(sl,22,72);
|
|
|
|
|
|
|
|
lv_obj_set_pos(sl,40,20);
|
|
|
|
|
|
|
|
lv_slider_set_range(sl,joints[i].rmin,joints[i].rmax);
|
|
|
|
|
|
|
|
lv_slider_set_value(sl,joints[i].value,LV_ANIM_OFF);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(sl,lv_color_hex(0xFFFFFF),LV_PART_MAIN);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(sl,LV_OPA_80,LV_PART_MAIN);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(sl,10,LV_PART_MAIN);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(sl,lv_color_hex(joints[i].accent),LV_PART_INDICATOR);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(sl,10,LV_PART_INDICATOR);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(sl,lv_color_hex(0xFFFFFF),LV_PART_KNOB);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_color(sl,lv_color_hex(joints[i].accent),LV_PART_KNOB);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(sl,2,LV_PART_KNOB);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(sl,12,LV_PART_KNOB);
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(sl,3,LV_PART_KNOB);
|
|
|
|
|
|
|
|
lv_obj_add_event_cb(sl,slider_cb,LV_EVENT_VALUE_CHANGED,(void*)(intptr_t)i);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ── sequencer at bottom, NOT fixed — scrollable with content ──
|
|
|
|
|
|
|
|
visual: colored blocks, no info, current highlighted, larger buttons */
|
|
|
|
|
|
|
|
lv_obj_t* seqbar=lv_obj_create(root);
|
|
|
|
|
|
|
|
lv_obj_set_size(seqbar,316,96);
|
|
|
|
|
|
|
|
lv_obj_set_pos(seqbar,2,232);
|
|
|
|
|
|
|
|
lv_obj_clear_flag(seqbar, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(seqbar,lv_color_hex(0xF0E8FF),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(seqbar,LV_OPA_90,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(seqbar,12,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(seqbar,1,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_color(seqbar,lv_color_hex(0xD0C0E0),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_pad_all(seqbar,4,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_obj_t* seq_t=lv_label_create(seqbar); lv_label_set_text(seq_t,"Seq");
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(seq_t,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(seq_t,lv_color_hex(0x3D2B5A),0); lv_obj_set_pos(seq_t,2,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
c->seq_label=lv_label_create(seqbar); lv_label_set_text(c->seq_label,"empty");
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(c->seq_label,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(c->seq_label,lv_color_hex(0x6A5A7A),0); lv_obj_set_pos(c->seq_label,30,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct { const char* t; uint32_t col; lv_event_cb_t cb; int play; } sbtns[]={
|
|
|
|
|
|
|
|
{"-",0xFFB7B7,seq_rem_ev,0},{"<",0xD6E8FF,seq_prev_ev,0},
|
|
|
|
|
|
|
|
{"Play",0xC5F5C5,seq_play_cb,1},{">",0xD6E8FF,seq_next_ev,0},{"+",0xFFE8A0,seq_add_ev,0},};
|
|
|
|
|
|
|
|
for(int i=0;i<5;i++){
|
|
|
|
|
|
|
|
lv_obj_t* b=lv_btn_create(seqbar);
|
|
|
|
|
|
|
|
lv_obj_set_size(b,(i==2)?56:38,32);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(b,lv_color_hex(sbtns[i].col),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(b,8,0);
|
|
|
|
|
|
|
|
int xs[5]={64,110,158,220,260};
|
|
|
|
|
|
|
|
lv_obj_set_pos(b,xs[i],0);
|
|
|
|
|
|
|
|
lv_obj_t* l=lv_label_create(b); if(sbtns[i].play) c->play_label=l;
|
|
|
|
|
|
|
|
lv_label_set_text(l,sbtns[i].t);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_font(l,lvgl_get_text_font(FONT_SIZE_SMALL),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_text_color(l,lv_color_hex(0x2A2A5A),0); lv_obj_center(l);
|
|
|
|
|
|
|
|
lv_obj_add_event_cb(b,sbtns[i].cb,LV_EVENT_CLICKED,NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t blk_cols[16]={
|
|
|
|
|
|
|
|
0xFF8FA8,0x8FB6FF,0xFFB86A,0x88D488,0xB088FF,0xFFD060,0xFF7AA2,0x7AC8FF,
|
|
|
|
|
|
|
|
0xFDBA74,0x86EFAC,0xA78BFA,0xFDE68A,0xFCA5A5,0x93C5FD,0xBEF264,0xFDA4AF
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
for(int i=0;i<SEQ_MAX;i++){
|
|
|
|
|
|
|
|
lv_obj_t* blk=lv_btn_create(seqbar);
|
|
|
|
|
|
|
|
lv_obj_set_size(blk,22,22);
|
|
|
|
|
|
|
|
lv_obj_set_pos(blk,(i%8)*24+2,(i/8)*24+38);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_color(blk,lv_color_hex(blk_cols[i]),0);
|
|
|
|
|
|
|
|
lv_obj_set_style_radius(blk,6,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_border_width(blk,0,0);
|
|
|
|
|
|
|
|
lv_obj_set_style_bg_opa(blk,LV_OPA_30,0);
|
|
|
|
|
|
|
|
c->blocks[i]=blk;
|
|
|
|
|
|
|
|
lv_obj_add_event_cb(blk,block_click_cb,LV_EVENT_CLICKED,(void*)(intptr_t)i);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
update_seq_ui();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
c->poll=lv_timer_create(poll_cb,100,NULL);
|
|
|
|
|
|
|
|
c->seq_timer=lv_timer_create(seq_timer_cb,1300,NULL);
|
|
|
|
|
|
|
|
lv_timer_create(init_cb,600,NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void onHide(AppHandle app,void* data){
|
|
|
|
|
|
|
|
(void)app;(void)data;
|
|
|
|
|
|
|
|
if(g){
|
|
|
|
|
|
|
|
if(g->poll) lv_timer_delete(g->poll);
|
|
|
|
|
|
|
|
if(g->seq_timer) lv_timer_delete(g->seq_timer);
|
|
|
|
|
|
|
|
free(g); g=NULL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
seq_playing=false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc,char* argv[]){(void)argc;(void)argv; tt_app_register((AppRegistration){.onShow=onShow,.onHide=onHide}); return 0;}
|