完成抓取放置流程
This commit is contained in:
1 parent
a226b15413
commit
48dfbeedea
2 files changed
+38
-431
No files matched your search
@@ -48,6 +48,9 @@ void actuator_init(void)
|
|||||||
/* 初始化舵机 PWM */
|
/* 初始化舵机 PWM */
|
||||||
servo_init(ACTUATOR_SERVO_GPIO);
|
servo_init(ACTUATOR_SERVO_GPIO);
|
||||||
|
|
||||||
|
/* 配置舵机限位: 抬起=80°, 放下=160° */
|
||||||
|
servo_set_limits(80.0f, 160.0f);
|
||||||
|
|
||||||
/* 配置 4 路电机: 闭环位置模式 + 使能 */
|
/* 配置 4 路电机: 闭环位置模式 + 使能 */
|
||||||
const uint8_t addrs[] = {
|
const uint8_t addrs[] = {
|
||||||
ACTUATOR_ADDR_ANGLE, ACTUATOR_ADDR_X,
|
ACTUATOR_ADDR_ANGLE, ACTUATOR_ADDR_X,
|
||||||
|
|||||||
+35
-431
@@ -8,7 +8,6 @@
|
|||||||
#include "magnet.h"
|
#include "magnet.h"
|
||||||
#include "servo.h"
|
#include "servo.h"
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "actuator.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -118,65 +117,38 @@ button:active{opacity:.7}
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2><i class="tag green"></i>轴控制 (XYZA)</h2>
|
|
||||||
<div class="row"><label>X mm</label><input id="ax_x" type="number" value="0" min="0" max="210" step="0.1"><span class="unit">→电机2 (CW) 0~210</span></div>
|
|
||||||
<div class="row"><label>Y mm</label><input id="ax_y" type="number" value="0" min="0" max="335" step="0.1"><span class="unit">→电机3+4 (CCW) 0~335</span></div>
|
|
||||||
<div class="row"><label>Z °</label><input id="ax_zrot" type="number" value="0" step="0.1"><span class="unit">→电机1 (±: CW/CCW)</span></div>
|
|
||||||
<div class="row"><label>舵机</label><input id="ax_servo" type="number" value="90" min="0" max="180" step="0.5"><span class="unit">°</span></div>
|
|
||||||
<div class="row"><label>速度</label><input id="ax_vel" type="number" value="500" min="0" max="5000"><span class="unit">RPM</span></div>
|
|
||||||
<div class="row"><label>加速度</label><input id="ax_acc" type="number" value="50" min="0" max="255"></div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="btn-go" onclick="axisMove()">移动到目标</button>
|
|
||||||
<button class="btn-stop" onclick="api('stop?addr=1');api('stop?addr=2');api('stop?addr=3');api('stop?addr=4')">急停全部</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2><i class="tag"></i>电机使能</h2>
|
<h2><i class="tag"></i>电机使能</h2>
|
||||||
<div class="row"><label>地址</label><input id="en_addr" type="text" value="1" placeholder="1 或 1|2"></div>
|
<div class="row"><label>地址</label><input id="en_addr" type="number" value="1" min="1" max="247"></div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-on" onclick="apiEach('enable','en_addr','&state=1')">使能</button>
|
<button class="btn-on" onclick="api('enable?addr='+v('en_addr')+'&state=1')">使能</button>
|
||||||
<button class="btn-off" onclick="apiEach('enable','en_addr','&state=0')">关闭</button>
|
<button class="btn-off" onclick="api('enable?addr='+v('en_addr')+'&state=0')">关闭</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2><i class="tag"></i>速度模式</h2>
|
<h2><i class="tag"></i>速度模式</h2>
|
||||||
<div class="row"><label>地址</label><input id="v_addr" type="text" value="1" placeholder="1 或 1|2"></div>
|
<div class="row"><label>地址</label><input id="v_addr" type="number" value="1" min="1" max="247"></div>
|
||||||
<div class="row"><label>方向</label><select id="v_dir"><option value="0">CW 正转</option><option value="1">CCW 反转</option></select></div>
|
<div class="row"><label>方向</label><select id="v_dir"><option value="0">CW 正转</option><option value="1">CCW 反转</option></select></div>
|
||||||
<div class="row"><label>速度</label><input id="v_vel" type="number" value="100" min="0" max="5000"><span class="unit">RPM</span></div>
|
<div class="row"><label>速度</label><input id="v_vel" type="number" value="100" min="0" max="5000"><span class="unit">RPM</span></div>
|
||||||
<div class="row"><label>加速度</label><input id="v_acc" type="number" value="10" min="0" max="255"></div>
|
<div class="row"><label>加速度</label><input id="v_acc" type="number" value="10" min="0" max="255"></div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-go" onclick="apiEach('velocity','v_addr','&dir='+v('v_dir')+'&vel='+v('v_vel')+'&acc='+v('v_acc'))">启动</button>
|
<button class="btn-go" onclick="api('velocity?addr='+v('v_addr')+'&dir='+v('v_dir')+'&vel='+v('v_vel')+'&acc='+v('v_acc'))">启动</button>
|
||||||
<button class="btn-stop" onclick="apiEach('stop','v_addr','')">停止</button>
|
<button class="btn-stop" onclick="api('stop?addr='+v('v_addr'))">停止</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2><i class="tag"></i>位置模式</h2>
|
<h2><i class="tag"></i>位置模式</h2>
|
||||||
<div class="row"><label>地址</label><input id="p_addr" type="text" value="1" placeholder="1 或 1|2"></div>
|
<div class="row"><label>地址</label><input id="p_addr" type="number" value="1" min="1" max="247"></div>
|
||||||
<div class="row"><label>方向</label><select id="p_dir"><option value="0">CW 正转</option><option value="1">CCW 反转</option></select></div>
|
<div class="row"><label>方向</label><select id="p_dir"><option value="0">CW 正转</option><option value="1">CCW 反转</option></select></div>
|
||||||
<div class="row"><label>速度</label><input id="p_vel" type="number" value="100" min="0" max="5000"><span class="unit">RPM</span></div>
|
<div class="row"><label>速度</label><input id="p_vel" type="number" value="100" min="0" max="5000"><span class="unit">RPM</span></div>
|
||||||
<div class="row"><label>加速度</label><input id="p_acc" type="number" value="10" min="0" max="255"></div>
|
<div class="row"><label>加速度</label><input id="p_acc" type="number" value="10" min="0" max="255"></div>
|
||||||
<div class="row"><label>脉冲数</label><input id="p_clk" type="number" value="2000" min="0"></div>
|
<div class="row"><label>脉冲数</label><input id="p_clk" type="number" value="2000" min="0"></div>
|
||||||
<div class="row"><label>模式</label><select id="p_raf"><option value="0">相对运动</option><option value="1">绝对运动</option></select></div>
|
<div class="row"><label>模式</label><select id="p_raf"><option value="0">相对运动</option><option value="1">绝对运动</option></select></div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-go" onclick="apiEach('position','p_addr','&dir='+v('p_dir')+'&vel='+v('p_vel')+'&acc='+v('p_acc')+'&clk='+v('p_clk')+'&raf='+v('p_raf'))">启动</button>
|
<button class="btn-go" onclick="api('position?addr='+v('p_addr')+'&dir='+v('p_dir')+'&vel='+v('p_vel')+'&acc='+v('p_acc')+'&clk='+v('p_clk')+'&raf='+v('p_raf'))">启动</button>
|
||||||
<button class="btn-stop" onclick="apiEach('stop','p_addr','')">停止</button>
|
<button class="btn-stop" onclick="api('stop?addr='+v('p_addr'))">停止</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2><i class="tag green"></i>角度直控 (°)</h2>
|
|
||||||
<div class="row"><label>地址</label><input id="ag_addr" type="text" value="1" placeholder="1 或 1|2"></div>
|
|
||||||
<div class="row"><label>角度</label><input id="ag_angle" type="number" value="90" min="-3600" max="3600" step="0.1"><span class="unit">° (3200=360°)</span></div>
|
|
||||||
<div class="row"><label>速度</label><input id="ag_vel" type="number" value="500" min="0" max="5000"><span class="unit">RPM</span></div>
|
|
||||||
<div class="row"><label>加速度</label><input id="ag_acc" type="number" value="50" min="0" max="255"></div>
|
|
||||||
<div class="row"><label>模式</label><select id="ag_raf"><option value="1">绝对运动</option><option value="0">相对运动</option></select></div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="btn-go" onclick="apiEach('angle','ag_addr','&angle='+v('ag_angle')+'&vel='+v('ag_vel')+'&acc='+v('ag_acc')+'&raf='+v('ag_raf'))">转到角度</button>
|
|
||||||
<button class="btn-stop" onclick="apiEach('stop','ag_addr','')">停止</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -202,56 +174,6 @@ button:active{opacity:.7}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2><i class="tag amber"></i>位置清零 & 堵转解除</h2>
|
|
||||||
<div class="row"><label>地址</label><input id="rz_addr" type="number" value="1" min="1" max="247"></div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="btn-go" onclick="api('reset_pos?addr='+v('rz_addr'))">位置清零</button>
|
|
||||||
<button class="btn-stop" onclick="api('reset_clog?addr='+v('rz_addr'))">解除堵转</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2><i class="tag"></i>读取参数</h2>
|
|
||||||
<div class="row"><label>地址</label><input id="rp_addr" type="number" value="1" min="1" max="247"></div>
|
|
||||||
<div class="row"><label>参数</label><select id="rp_param">
|
|
||||||
<option value="0">0-固件版本</option><option value="1">1-相电阻/电感</option>
|
|
||||||
<option value="2">2-PID参数</option><option value="3">3-总线电压</option>
|
|
||||||
<option value="5">5-相电流</option><option value="7">7-编码器值</option>
|
|
||||||
<option value="8">8-目标位置</option><option value="9">9-实时转速</option>
|
|
||||||
<option value="10" selected>10-实时位置</option><option value="11">11-位置误差</option>
|
|
||||||
<option value="13">13-状态标志位</option><option value="14">14-驱动参数</option>
|
|
||||||
<option value="15">15-系统状态</option><option value="16">16-回零状态</option>
|
|
||||||
</select></div>
|
|
||||||
<button class="btn-go btn-block" onclick="readParam()">读取</button>
|
|
||||||
<div id="rp_out" style="font-size:.75rem;color:var(--sub);margin-top:8px;word-break:break-all"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2><i class="tag amber"></i>回零控制</h2>
|
|
||||||
<div class="row"><label>地址</label><input id="og_addr" type="number" value="1" min="1" max="247"></div>
|
|
||||||
<div class="row"><label>模式</label><select id="og_omode">
|
|
||||||
<option value="0">0-单圈就近</option><option value="1">1-单圈方向</option>
|
|
||||||
<option value="2">2-多圈无限位</option><option value="3">3-多圈有限位</option>
|
|
||||||
</select></div>
|
|
||||||
<div class="row"><label>方向</label><select id="og_odir"><option value="0">CW</option><option value="1">CCW</option></select></div>
|
|
||||||
<div class="row"><label>速度</label><input id="og_ovel" type="number" value="200" min="0" max="5000"><span class="unit">RPM</span></div>
|
|
||||||
<div class="row"><label>超时</label><input id="og_otm" type="number" value="10000" min="0"><span class="unit">ms</span></div>
|
|
||||||
<div class="row"><label>碰检速</label><input id="og_slvel" type="number" value="100" min="0" max="5000"><span class="unit">RPM</span></div>
|
|
||||||
<div class="row"><label>碰检流</label><input id="og_slma" type="number" value="500" min="0"><span class="unit">mA</span></div>
|
|
||||||
<div class="row"><label>碰检时</label><input id="og_slms" type="number" value="100" min="0"><span class="unit">ms</span></div>
|
|
||||||
<div class="row"><label>存储</label><select id="og_svf"><option value="0">不存储</option><option value="1">存储</option></select></div>
|
|
||||||
<div class="row"><label>上电回零</label><select id="og_potf"><option value="0">不使能</option><option value="1">使能</option></select></div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="btn-go" onclick="api('origin_set?addr='+v('og_addr')+'&svf='+v('og_svf'))">设零点</button>
|
|
||||||
<button class="btn-on" onclick="api('origin_config?addr='+v('og_addr')+'&svf='+v('og_svf')+'&omode='+v('og_omode')+'&odir='+v('og_odir')+'&ovel='+v('og_ovel')+'&otm='+v('og_otm')+'&slvel='+v('og_slvel')+'&slma='+v('og_slma')+'&slms='+v('og_slms')+'&potf='+v('og_potf'))">配置参数</button>
|
|
||||||
</div>
|
|
||||||
<div class="btn-row">
|
|
||||||
<button class="btn-go" onclick="api('origin_go?addr='+v('og_addr')+'&omode='+v('og_omode'))">触发回零</button>
|
|
||||||
<button class="btn-stop" onclick="api('origin_stop?addr='+v('og_addr'))">中断回零</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" id="proto-card">
|
<div class="card" id="proto-card">
|
||||||
@@ -286,41 +208,6 @@ function api(cmd){
|
|||||||
l.innerHTML='<div class="err">请求失败</div>'+l.innerHTML
|
l.innerHTML='<div class="err">请求失败</div>'+l.innerHTML
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function apiEach(ep,id,ext){
|
|
||||||
var addrs=v(id).split('|');
|
|
||||||
for(var i=0;i<addrs.length;i++){
|
|
||||||
api(ep+'?addr='+addrs[i].trim()+ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function axisMove(){
|
|
||||||
var x=v('ax_x'),y=v('ax_y'),z=v('ax_zrot'),s=v('ax_servo');
|
|
||||||
var vel=v('ax_vel'),acc=v('ax_acc');
|
|
||||||
/* X: CW(dir=0) | Y: CCW(dir=1) 由 actuator_motor_mm 自动处理 | Z: CW(dir=0) */
|
|
||||||
/* 允许 0 值: 绝对模式下 0=回到原点 */
|
|
||||||
api('mm?addr=2&mm='+(x||'0')+'&vel='+vel+'&acc='+acc+'&raf=1');
|
|
||||||
api('mm?addr=3&mm='+(y||'0')+'&vel='+vel+'&acc='+acc+'&raf=1');
|
|
||||||
api('angle?addr=1&angle='+(z||'0')+'&vel='+vel+'&acc='+acc+'&raf=1');
|
|
||||||
api('servo?angle='+(s||'90'));
|
|
||||||
}
|
|
||||||
function readParam(){
|
|
||||||
var addr=v('rp_addr'),param=v('rp_param');
|
|
||||||
fetch('/api/read_param?addr='+addr+'¶m='+param)
|
|
||||||
.then(r=>r.json()).then(d=>{
|
|
||||||
var h='addr='+d.addr+' param='+d.param+' sign='+d.sign;
|
|
||||||
if(d.angle!==undefined){
|
|
||||||
var sg=d.sign?'-':'';
|
|
||||||
h+='<br><b style="color:var(--accent);font-size:1.2rem">'+sg+d.angle.toFixed(2)+'°</b>';
|
|
||||||
h+=' <span style="font-size:.7rem">(raw: '+d.val32+' / 0x'+d.val32.toString(16).toUpperCase()+')</span>';
|
|
||||||
}
|
|
||||||
if(d.val16!==undefined && d.angle===undefined){
|
|
||||||
h+='<br><b style="color:var(--accent)">'+d.val16+'</b> <span style="font-size:.7rem">(16bit)</span>';
|
|
||||||
}
|
|
||||||
h+='<br><span style="font-size:.6rem;opacity:.5">raw: '+d.hex+'</span>';
|
|
||||||
document.getElementById('rp_out').innerHTML = h;
|
|
||||||
}).catch(function(e){
|
|
||||||
document.getElementById('rp_out').innerHTML='读取失败'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fetch('/api/status').then(function(r){return r.json()}).then(function(d){
|
fetch('/api/status').then(function(r){return r.json()}).then(function(d){
|
||||||
document.getElementById('wifi').innerHTML='<i class="dot"></i>WiFi: '+d.ssid;
|
document.getElementById('wifi').innerHTML='<i class="dot"></i>WiFi: '+d.ssid;
|
||||||
document.getElementById('ip').textContent='IP: '+d.ip
|
document.getElementById('ip').textContent='IP: '+d.ip
|
||||||
@@ -449,44 +336,38 @@ static int get_query_int(httpd_req_t *req, const char *key, int def)
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float get_query_float(httpd_req_t *req, const char *key, float def)
|
|
||||||
{
|
|
||||||
char buf[128];
|
|
||||||
size_t len = httpd_req_get_url_query_len(req) + 1;
|
|
||||||
if (len > sizeof(buf)) len = sizeof(buf);
|
|
||||||
if (httpd_req_get_url_query_str(req, buf, len) == ESP_OK) {
|
|
||||||
char val[16];
|
|
||||||
if (httpd_query_key_value(buf, key, val, sizeof(val)) == ESP_OK) {
|
|
||||||
return (float)atof(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* HTTP 处理器
|
* HTTP 处理器
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
static esp_err_t root_handler(httpd_req_t *req)
|
static esp_err_t root_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
|
const char *html = INDEX_HTML;
|
||||||
|
size_t total = strlen(html);
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
||||||
httpd_resp_set_hdr(req, "Connection", "close");
|
httpd_resp_set_hdr(req, "Connection", "close");
|
||||||
|
|
||||||
/* 一次性发送, 由 ESP-IDF 内部处理 TCP 缓冲管理,
|
while (offset < total) {
|
||||||
* 避免手动分块导致的 EAGAIN / ECONNRESET 偶发失败 */
|
size_t chunk = total - offset;
|
||||||
esp_err_t err = httpd_resp_send(req, INDEX_HTML, HTTPD_RESP_USE_STRLEN);
|
if (chunk > 1024) chunk = 1024;
|
||||||
if (err != ESP_OK) {
|
esp_err_t err = httpd_resp_send_chunk(req, html + offset, (int)chunk);
|
||||||
/* 客户端断开是正常现象 (刷新页面/关闭标签), 仅警告即可 */
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "发送 HTML 失败: %d (客户端可能已断开)", err);
|
ESP_LOGE(TAG, "发送 HTML 失败: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
offset += chunk;
|
||||||
}
|
}
|
||||||
return err;
|
httpd_resp_send_chunk(req, NULL, 0); /* 结束分块 */
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t api_enable_handler(httpd_req_t *req)
|
static esp_err_t api_enable_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
int addr = get_query_int(req, "addr", 1);
|
int addr = get_query_int(req, "addr", 1);
|
||||||
int state = get_query_int(req, "state", 0);
|
int state = get_query_int(req, "state", 0);
|
||||||
actuator_motor_enable((uint8_t)addr, (bool)state);
|
Emm_V5_En_Control((uint8_t)addr, (bool)state, false);
|
||||||
char resp[64];
|
char resp[64];
|
||||||
snprintf(resp, sizeof(resp), "电机 %d %s", addr, state ? "使能" : "关闭");
|
snprintf(resp, sizeof(resp), "电机 %d %s", addr, state ? "使能" : "关闭");
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
||||||
@@ -500,7 +381,7 @@ static esp_err_t api_velocity_handler(httpd_req_t *req)
|
|||||||
int dir = get_query_int(req, "dir", 0);
|
int dir = get_query_int(req, "dir", 0);
|
||||||
int vel = get_query_int(req, "vel", 100);
|
int vel = get_query_int(req, "vel", 100);
|
||||||
int acc = get_query_int(req, "acc", 10);
|
int acc = get_query_int(req, "acc", 10);
|
||||||
actuator_motor_vel((uint8_t)addr, (uint8_t)dir, (uint16_t)vel, (uint8_t)acc);
|
Emm_V5_Vel_Control((uint8_t)addr, (uint8_t)dir, (uint16_t)vel, (uint8_t)acc, false);
|
||||||
char resp[64];
|
char resp[64];
|
||||||
snprintf(resp, sizeof(resp), "速度模式: 地址%d %s %dRPM acc=%d",
|
snprintf(resp, sizeof(resp), "速度模式: 地址%d %s %dRPM acc=%d",
|
||||||
addr, dir ? "CCW" : "CW", vel, acc);
|
addr, dir ? "CCW" : "CW", vel, acc);
|
||||||
@@ -517,8 +398,8 @@ static esp_err_t api_position_handler(httpd_req_t *req)
|
|||||||
int acc = get_query_int(req, "acc", 10);
|
int acc = get_query_int(req, "acc", 10);
|
||||||
int clk = get_query_int(req, "clk", 2000);
|
int clk = get_query_int(req, "clk", 2000);
|
||||||
int raf = get_query_int(req, "raf", 0);
|
int raf = get_query_int(req, "raf", 0);
|
||||||
actuator_motor_pos((uint8_t)addr, (uint8_t)dir, (uint16_t)vel, (uint8_t)acc,
|
Emm_V5_Pos_Control((uint8_t)addr, (uint8_t)dir, (uint16_t)vel, (uint8_t)acc,
|
||||||
(uint32_t)clk, (bool)raf);
|
(uint32_t)clk, (bool)raf, false);
|
||||||
char resp[80];
|
char resp[80];
|
||||||
snprintf(resp, sizeof(resp), "位置模式: 地址%d %s %dRPM %d脉冲 %s",
|
snprintf(resp, sizeof(resp), "位置模式: 地址%d %s %dRPM %d脉冲 %s",
|
||||||
addr, dir ? "CCW" : "CW", vel, clk, raf ? "绝对" : "相对");
|
addr, dir ? "CCW" : "CW", vel, clk, raf ? "绝对" : "相对");
|
||||||
@@ -530,7 +411,7 @@ static esp_err_t api_position_handler(httpd_req_t *req)
|
|||||||
static esp_err_t api_stop_handler(httpd_req_t *req)
|
static esp_err_t api_stop_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
int addr = get_query_int(req, "addr", 1);
|
int addr = get_query_int(req, "addr", 1);
|
||||||
actuator_motor_stop((uint8_t)addr);
|
Emm_V5_Stop_Now((uint8_t)addr, false);
|
||||||
char resp[32];
|
char resp[32];
|
||||||
snprintf(resp, sizeof(resp), "电机 %d 已停止", addr);
|
snprintf(resp, sizeof(resp), "电机 %d 已停止", addr);
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
||||||
@@ -574,9 +455,9 @@ static esp_err_t api_servo_handler(httpd_req_t *req)
|
|||||||
char val[16];
|
char val[16];
|
||||||
if (httpd_query_key_value(buf, "pos", val, sizeof(val)) == ESP_OK) {
|
if (httpd_query_key_value(buf, "pos", val, sizeof(val)) == ESP_OK) {
|
||||||
if (strcmp(val, "up") == 0) {
|
if (strcmp(val, "up") == 0) {
|
||||||
actuator_servo_up();
|
servo_set_up();
|
||||||
} else {
|
} else {
|
||||||
actuator_servo_down();
|
servo_set_down();
|
||||||
}
|
}
|
||||||
char resp[32];
|
char resp[32];
|
||||||
snprintf(resp, sizeof(resp), "舵机: %s", val);
|
snprintf(resp, sizeof(resp), "舵机: %s", val);
|
||||||
@@ -586,7 +467,7 @@ static esp_err_t api_servo_handler(httpd_req_t *req)
|
|||||||
}
|
}
|
||||||
if (httpd_query_key_value(buf, "angle", val, sizeof(val)) == ESP_OK) {
|
if (httpd_query_key_value(buf, "angle", val, sizeof(val)) == ESP_OK) {
|
||||||
float angle = atof(val);
|
float angle = atof(val);
|
||||||
actuator_servo_set_angle(angle);
|
servo_set_angle(angle);
|
||||||
char resp[32];
|
char resp[32];
|
||||||
snprintf(resp, sizeof(resp), "舵机: %.1f°", (double)angle);
|
snprintf(resp, sizeof(resp), "舵机: %.1f°", (double)angle);
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
||||||
@@ -598,214 +479,6 @@ static esp_err_t api_servo_handler(httpd_req_t *req)
|
|||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 轴联动: XYZA 同时驱动 (mm + °) ---- */
|
|
||||||
static esp_err_t api_axis_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
float x_mm = get_query_float(req, "x", 0.0f);
|
|
||||||
float y_mm = get_query_float(req, "y", 0.0f);
|
|
||||||
float z_deg = get_query_float(req, "zrot", 0.0f);
|
|
||||||
float s_ang = get_query_float(req, "servo", -1.0f);
|
|
||||||
int vel = get_query_int(req, "vel", ACTUATOR_DEFAULT_VEL);
|
|
||||||
int acc = get_query_int(req, "acc", ACTUATOR_DEFAULT_ACC);
|
|
||||||
|
|
||||||
/* 允许 0 值: 绝对模式下 0=回到原点 */
|
|
||||||
actuator_motor_mm(ACTUATOR_ADDR_X, x_mm, (uint16_t)vel, (uint8_t)acc, true);
|
|
||||||
actuator_motor_mm(ACTUATOR_ADDR_Y1, y_mm, (uint16_t)vel, (uint8_t)acc, true);
|
|
||||||
actuator_motor_angle(ACTUATOR_ADDR_ANGLE, z_deg, (uint16_t)vel, (uint8_t)acc, true);
|
|
||||||
if (s_ang >= 0.0f)
|
|
||||||
actuator_servo_set_angle(s_ang);
|
|
||||||
|
|
||||||
char resp[128];
|
|
||||||
snprintf(resp, sizeof(resp),
|
|
||||||
"轴联动: X=%.1fmm Y=%.1fmm Z=%.1f° Servo=%.1f° vel=%d acc=%d",
|
|
||||||
(double)x_mm, (double)y_mm, (double)z_deg, (double)s_ang, vel, acc);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 直线距离控制: mm → 脉冲 (丝杆导程 8mm) ---- */
|
|
||||||
static esp_err_t api_mm_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 2); /* 默认 X 轴 */
|
|
||||||
float mm = get_query_float(req, "mm", 0.0f);
|
|
||||||
int vel = get_query_int(req, "vel", ACTUATOR_DEFAULT_VEL);
|
|
||||||
int acc = get_query_int(req, "acc", ACTUATOR_DEFAULT_ACC);
|
|
||||||
int raf = get_query_int(req, "raf", 1); /* 默认绝对运动 */
|
|
||||||
|
|
||||||
actuator_motor_mm((uint8_t)addr, mm, (uint16_t)vel, (uint8_t)acc, (bool)raf);
|
|
||||||
|
|
||||||
uint32_t clk = ACTUATOR_MM_TO_PULSES(mm);
|
|
||||||
char resp[96];
|
|
||||||
snprintf(resp, sizeof(resp),
|
|
||||||
"直线模式: 地址%d %.2fmm → %lu脉冲 %s vel=%dRPM acc=%d",
|
|
||||||
addr, (double)mm, (unsigned long)clk, raf ? "绝对" : "相对", vel, acc);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
* 底层调试 API (直接对接 Emm_V5, 不走 actuator)
|
|
||||||
* ============================================================ */
|
|
||||||
|
|
||||||
/* ---- 当前位置清零 ---- */
|
|
||||||
static esp_err_t api_reset_pos_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
Emm_V5_Reset_CurPos_To_Zero((uint8_t)addr);
|
|
||||||
char resp[32];
|
|
||||||
snprintf(resp, sizeof(resp), "电机 %d 位置已清零", addr);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 解除堵转保护 ---- */
|
|
||||||
static esp_err_t api_reset_clog_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
Emm_V5_Reset_Clog_Pro((uint8_t)addr);
|
|
||||||
char resp[32];
|
|
||||||
snprintf(resp, sizeof(resp), "电机 %d 堵转保护已解除", addr);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 读取系统参数 (按 Emm 固件手册 V1.0.5 帧格式解析) ---- */
|
|
||||||
/* 帧格式: {addr, cmd, sign(00/01), data[4]BE, 0x6B} */
|
|
||||||
/* 位置类(Emm): val32=0~65535→角度=val32*360/65536 */
|
|
||||||
/* 速度类: val16=RPM */
|
|
||||||
static esp_err_t api_read_param_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
int param = get_query_int(req, "param", 0); /* EmmSysParams_t */
|
|
||||||
|
|
||||||
Emm_V5_Read_Sys_Params((uint8_t)addr, (EmmSysParams_t)param);
|
|
||||||
|
|
||||||
uint8_t rx[128];
|
|
||||||
uint8_t len = Emm_V5_Receive_Data(rx, sizeof(rx));
|
|
||||||
|
|
||||||
/* 解析: 跳过 addr(1)+cmd(1)+sign(1), 取 data[4] 大端 */
|
|
||||||
uint32_t val32 = 0;
|
|
||||||
uint16_t val16 = 0;
|
|
||||||
int sign = 0;
|
|
||||||
float angle = 0.0f;
|
|
||||||
|
|
||||||
if (len >= 8) {
|
|
||||||
sign = rx[2];
|
|
||||||
val32 = ((uint32_t)rx[3] << 24) | ((uint32_t)rx[4] << 16) |
|
|
||||||
((uint32_t)rx[5] << 8) | ((uint32_t)rx[6]);
|
|
||||||
/* Emm 固件: 0~65535 表示 0~360°, 仅低 16 位有效 */
|
|
||||||
uint16_t pos16 = (uint16_t)(val32 & 0xFFFF);
|
|
||||||
angle = (sign ? -1.0f : 1.0f) * pos16 * 360.0f / 65536.0f;
|
|
||||||
}
|
|
||||||
if (len >= 6) {
|
|
||||||
val16 = ((uint16_t)rx[3] << 8) | ((uint16_t)rx[4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
char json[512];
|
|
||||||
int off = snprintf(json, sizeof(json),
|
|
||||||
"{\"addr\":%d,\"param\":%d,\"len\":%d,\"sign\":%d"
|
|
||||||
",\"val32\":%lu,\"val16\":%u,\"angle\":%.2f",
|
|
||||||
addr, param, len, sign,
|
|
||||||
(unsigned long)val32, (unsigned)val16, (double)angle);
|
|
||||||
off += snprintf(json + off, sizeof(json) - off, ",\"hex\":\"");
|
|
||||||
for (int i = 0; i < len && off < (int)sizeof(json) - 4; i++) {
|
|
||||||
off += snprintf(json + off, sizeof(json) - off, "%02X", rx[i]);
|
|
||||||
}
|
|
||||||
off += snprintf(json + off, sizeof(json) - off, "\"}");
|
|
||||||
httpd_resp_set_type(req, "application/json");
|
|
||||||
httpd_resp_send(req, json, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 角度直控: 输入 ° 自动换算为 Emm 脉冲 (3200脉冲=360°) ---- */
|
|
||||||
static esp_err_t api_angle_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
float angle = get_query_float(req, "angle", 0.0f);
|
|
||||||
int vel = get_query_int(req, "vel", ACTUATOR_DEFAULT_VEL);
|
|
||||||
int acc = get_query_int(req, "acc", ACTUATOR_DEFAULT_ACC);
|
|
||||||
int raf = get_query_int(req, "raf", 1); /* 默认绝对运动 */
|
|
||||||
|
|
||||||
actuator_motor_angle((uint8_t)addr, angle, (uint16_t)vel, (uint8_t)acc, (bool)raf);
|
|
||||||
|
|
||||||
uint32_t clk = EMM_V5_DEG_TO_CLK(angle);
|
|
||||||
char resp[96];
|
|
||||||
snprintf(resp, sizeof(resp),
|
|
||||||
"角度模式: 地址%d %.1f° → %lu脉冲 %s vel=%dRPM acc=%d",
|
|
||||||
addr, (double)angle, (unsigned long)clk, raf ? "绝对" : "相对", vel, acc);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 回零: 设置零点 ---- */
|
|
||||||
static esp_err_t api_origin_set_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
int svf = get_query_int(req, "svf", 0); /* 0=不存储 1=存储 */
|
|
||||||
Emm_V5_Origin_Set_O((uint8_t)addr, (bool)svf);
|
|
||||||
char resp[48];
|
|
||||||
snprintf(resp, sizeof(resp), "电机 %d 零点已设置 (svF=%d)", addr, svf);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 回零: 配置参数 ---- */
|
|
||||||
static esp_err_t api_origin_config_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
int svf = get_query_int(req, "svf", 0);
|
|
||||||
int omode = get_query_int(req, "omode", 0);
|
|
||||||
int odir = get_query_int(req, "odir", 0);
|
|
||||||
int ovel = get_query_int(req, "ovel", 200);
|
|
||||||
int otm = get_query_int(req, "otm", 10000);
|
|
||||||
int slvel = get_query_int(req, "slvel", 100);
|
|
||||||
int slma = get_query_int(req, "slma", 500);
|
|
||||||
int slms = get_query_int(req, "slms", 100);
|
|
||||||
int potf = get_query_int(req, "potf", 0);
|
|
||||||
|
|
||||||
Emm_V5_Origin_Modify_Params((uint8_t)addr, (bool)svf,
|
|
||||||
(uint8_t)omode, (uint8_t)odir, (uint16_t)ovel, (uint32_t)otm,
|
|
||||||
(uint16_t)slvel, (uint16_t)slma, (uint16_t)slms, (bool)potf);
|
|
||||||
char resp[128];
|
|
||||||
snprintf(resp, sizeof(resp),
|
|
||||||
"电机 %d 回零参数已配置 (mode=%d dir=%d vel=%d tm=%lu svF=%d potF=%d)",
|
|
||||||
addr, omode, odir, ovel, (unsigned long)otm, svf, potf);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 回零: 触发回零 ---- */
|
|
||||||
static esp_err_t api_origin_go_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
int omode = get_query_int(req, "omode", 0);
|
|
||||||
Emm_V5_Origin_Trigger_Return((uint8_t)addr, (uint8_t)omode, false);
|
|
||||||
char resp[48];
|
|
||||||
snprintf(resp, sizeof(resp), "电机 %d 回零已触发 (mode=%d)", addr, omode);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 回零: 强制中断 ---- */
|
|
||||||
static esp_err_t api_origin_stop_handler(httpd_req_t *req)
|
|
||||||
{
|
|
||||||
int addr = get_query_int(req, "addr", 1);
|
|
||||||
Emm_V5_Origin_Interrupt((uint8_t)addr);
|
|
||||||
char resp[32];
|
|
||||||
snprintf(resp, sizeof(resp), "电机 %d 回零已中断", addr);
|
|
||||||
httpd_resp_set_type(req, "text/plain; charset=utf-8");
|
|
||||||
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* favicon — 204 No Content, 避免浏览器反复请求 */
|
/* favicon — 204 No Content, 避免浏览器反复请求 */
|
||||||
static esp_err_t favicon_handler(httpd_req_t *req)
|
static esp_err_t favicon_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
@@ -836,21 +509,13 @@ void edc_proto_on_cycle(const edc_cycle_t *cycle, edc_channel_t ch)
|
|||||||
cycle->cycle_id, ch == EDC_CH_I2C ? "I2C" : "UART",
|
cycle->cycle_id, ch == EDC_CH_I2C ? "I2C" : "UART",
|
||||||
cycle->frag_count);
|
cycle->frag_count);
|
||||||
|
|
||||||
/* I2C / UART 冗余下发, 同一 cycle_id 只执行一次 */
|
/* I2C / UART 冗余下发, 同一 cycle_id 只回一次 DONE */
|
||||||
if (cycle->cycle_id == s_last_done_id) {
|
if (cycle->cycle_id == s_last_done_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s_last_done_id = cycle->cycle_id;
|
s_last_done_id = cycle->cycle_id;
|
||||||
|
|
||||||
/* 通信控制链路暂时断开, 仅 Web 手控生效 */
|
/* 回复 MPU: Cycle 已收到 (后续实际执行电机后应移到执行完毕处) */
|
||||||
#if 0 /* TODO: 恢复通信控制时取消注释 */
|
|
||||||
/* 执行 Cycle 运动 */
|
|
||||||
if (actuator_execute_cycle(cycle) == 0) {
|
|
||||||
/* 回复 MPU: Cycle 执行完成 */
|
|
||||||
edc_proto_send_done(cycle->cycle_id);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/* 通信断开期间仍回复 DONE 以维持协议握手 (但不执行运动) */
|
|
||||||
edc_proto_send_done(cycle->cycle_id);
|
edc_proto_send_done(cycle->cycle_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -937,7 +602,7 @@ static void http_server_init(void)
|
|||||||
httpd_handle_t server = NULL;
|
httpd_handle_t server = NULL;
|
||||||
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
|
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
|
||||||
cfg.stack_size = 8192; /* 默认 4096 不够, 增大 */
|
cfg.stack_size = 8192; /* 默认 4096 不够, 增大 */
|
||||||
cfg.max_uri_handlers = 26;
|
cfg.max_uri_handlers = 16;
|
||||||
cfg.max_open_sockets = 4;
|
cfg.max_open_sockets = 4;
|
||||||
cfg.lru_purge_enable = true;
|
cfg.lru_purge_enable = true;
|
||||||
|
|
||||||
@@ -1007,67 +672,6 @@ static void http_server_init(void)
|
|||||||
};
|
};
|
||||||
httpd_register_uri_handler(server, &uri_servo);
|
httpd_register_uri_handler(server, &uri_servo);
|
||||||
|
|
||||||
httpd_uri_t uri_axis = {
|
|
||||||
.uri = "/api/axis", .method = HTTP_GET,
|
|
||||||
.handler = api_axis_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_axis);
|
|
||||||
|
|
||||||
httpd_uri_t uri_mm = {
|
|
||||||
.uri = "/api/mm", .method = HTTP_GET,
|
|
||||||
.handler = api_mm_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_mm);
|
|
||||||
|
|
||||||
/* ---- 底层调试 API (直接 Emm_V5) ---- */
|
|
||||||
httpd_uri_t uri_reset_pos = {
|
|
||||||
.uri = "/api/reset_pos", .method = HTTP_GET,
|
|
||||||
.handler = api_reset_pos_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_reset_pos);
|
|
||||||
|
|
||||||
httpd_uri_t uri_reset_clog = {
|
|
||||||
.uri = "/api/reset_clog", .method = HTTP_GET,
|
|
||||||
.handler = api_reset_clog_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_reset_clog);
|
|
||||||
|
|
||||||
httpd_uri_t uri_read_param = {
|
|
||||||
.uri = "/api/read_param", .method = HTTP_GET,
|
|
||||||
.handler = api_read_param_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_read_param);
|
|
||||||
|
|
||||||
httpd_uri_t uri_angle = {
|
|
||||||
.uri = "/api/angle", .method = HTTP_GET,
|
|
||||||
.handler = api_angle_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_angle);
|
|
||||||
|
|
||||||
httpd_uri_t uri_origin_set = {
|
|
||||||
.uri = "/api/origin_set", .method = HTTP_GET,
|
|
||||||
.handler = api_origin_set_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_origin_set);
|
|
||||||
|
|
||||||
httpd_uri_t uri_origin_config = {
|
|
||||||
.uri = "/api/origin_config", .method = HTTP_GET,
|
|
||||||
.handler = api_origin_config_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_origin_config);
|
|
||||||
|
|
||||||
httpd_uri_t uri_origin_go = {
|
|
||||||
.uri = "/api/origin_go", .method = HTTP_GET,
|
|
||||||
.handler = api_origin_go_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_origin_go);
|
|
||||||
|
|
||||||
httpd_uri_t uri_origin_stop = {
|
|
||||||
.uri = "/api/origin_stop", .method = HTTP_GET,
|
|
||||||
.handler = api_origin_stop_handler, .user_ctx = NULL
|
|
||||||
};
|
|
||||||
httpd_register_uri_handler(server, &uri_origin_stop);
|
|
||||||
|
|
||||||
/* favicon.ico — 返回空, 避免 404 警告 */
|
/* favicon.ico — 返回空, 避免 404 警告 */
|
||||||
httpd_uri_t uri_favicon = {
|
httpd_uri_t uri_favicon = {
|
||||||
.uri = "/favicon.ico",
|
.uri = "/favicon.ico",
|
||||||
|
|||||||
Reference in new issue
Block a user