#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncWebSocket.h>
// ---- Wi-Fi AP ----
const char* AP_SSID = "AR4_Controller";
const char* AP_PASS = "ar4pass123"; // en az 8 karakter
// ---- ESP32 <-> Teensy4.1 UART ----
// Kablona göre RX/TX pinlerini ayarla (sıklıkla RX2=16, TX2=17)
#define RXD2 16
#define TXD2 17
HardwareSerial& TEENSY = Serial2; // ESP32 tarafında Serial2
// ---- Web Sunucu + WebSocket ----
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
// ---- Satır tamponu ----
String lineBuf = "";
String lastResponse = "";
// ---- Teensy'e komut yolla ----
void sendToTeensy(const String& cmd) {
String out = cmd;
if (!out.endsWith("\n")) out += "\n";
TEENSY.print(out);
Serial.print("[TX] "); Serial.print(out);
}
// ---- WebSocket eventleri ----
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client,
AwsEventType type, void * arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.println("// ---- WebSocket Client Connected ----");
client->text("WS Connected");
} else if (type == WS_EVT_DISCONNECT) {
Serial.println("// ---- WebSocket Client Disconnected ----");
} else if (type == WS_EVT_DATA) {
String msg; msg.reserve(len);
for (size_t i=0;i<len;i++) msg += (char)data[i];
sendToTeensy(msg); // tarayıcıdan geleni Teensy'e ilet
}
}
const char INDEX_HTML[] PROGMEM = R"rawliteral(
// ---- Gömülü HTML/JS ----
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>AR4 Web HMI</title>
<!-- =================== GLOBAL THEME & LAYOUT (TEK :root) =================== -->
<style>
:root{
/* ====================== AYAR DEĞİŞKENLERİ =======================
Bu bölüm tema ve ölçü ayarlarının merkezi. Gri açıklamaları SİLME!
Yeni ek açıklamalar:
- pv-* Program View ölçüleri
- logic-* Logic&IO satır aralıkları
- grid-* küçük yardımcı kolon yapıları
- gcode-narrow: G-Code sağ panelini daraltmaya yönelik
*/
/* Program View – UZUN butonlar */
--pv-long-w: 150px;
--pv-long-h: 30px;
/* Program View – KISA butonlar */
--pv-short-w: 100px;
--pv-short-h: 30px;
--pv-short-gap: 6px; /* kısa buton ile yanındaki kutular arası */
--pv-short-row-gap: 0px; /* kısa buton satırları dikey aralık (sıkı) */
/* Üst “Start/FWD/REW/STOP” barı */
--play-stop-h: 56px;
--play-stop-w: 120px;
--fwd-rew-h: 35px;
--fwd-rew-w: 70px;
--top-gap: 6px; /* üst bar içi boşluk */
/* Program listbox */
--prog-list-w: 400px;
--prog-gap: 6px;
/* Hız parametreleri */
--motion-label-w: 110px;
--motion-gap: 6px;
--speed-inp-h: 20px;
--speed-inp-w: 60px;
/* Mini kutular (kısa sayısal girişler) */
--mini-inp-h: 20px;
--mini-inp-w: 60px;
/* Joint/Aux */
--joint-inp-w: 52px;
--joint-inp-h: 24px;
--joint-range-w: 110px;
--joint-btn-w: 44px;
/* Cartesian/Tool */
--axis-inp-w: 52px;
--axis-inp-h: 24px;
--axis-btn-w: 44px;
/* (Speed çerçevesindeki ince ayarlar) */
--speed-row-gap: 2px; /* satır yüksekliği */
/* (Program view deki ince ayarlar) */
--pv-label-gap: 0px; /* "Command Type" altındaki yükseklik */
--pv-button-row-gap: 0px; /* Buton satır yüksekliği */
/* Logic & IO */
--logic-gap-x: 6px; /* IF WAIT SET yanal boşlu */
--logic-gap-y: 3px; /* IF WAIT SET satır yüksekliği */
--logic-tight-gap: 0px; /* "Pos register" ve "Read COM Device satır */
--logic-label-gap: 0px; /* Logic&IO label↔input box aralığı */
--logic-inp-h: 24px;
--logic-inp-w4: 60px; /* 4ch civarı kısa alan */
--logic-inp-w8: 90px; /* 8ch civarı orta alan */
--logic-btn-h: 28px;
--logic-dd-w: 120px;
/* Tema (koyu) */
--bg:#0f1115; --card:#2b2f39; --line:#3a3f4a; --text:#e6e7ea; --muted:#a6adbb;
--hdr:#9bd3f9; --hdr2:#b6f3d0; --primary:#3b82f6; --ok:#16a34a; --warn:#ef4444; --gray:#6b7280;
/* Z-index üst bar için */
--z:9999;
}
*{box-sizing:border-box}
html,body{overflow-x:hidden}
body{margin:0;font-family:system-ui, Arial;background:var(--bg);color:var(--text)}
/* =================== Sekmeler =================== */
.tabs{display:flex;gap:6px;padding:10px;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:var(--z)}
.tabBtn{padding:8px 12px;border:1px solid var(--line);border-bottom:2px solid transparent;border-radius:8px 8px 0 0;background:#1a1d25;color:#dfe3ea;cursor:pointer;font-weight:600;font-size:13px}
.tabBtn.active{background:#253044;color:#fff;border-bottom-color:#5fa8ff;box-shadow:inset 0 -2px 0 #5fa8ff}
.tabPane{display:none;padding:10px}
.tabPane.active{display:block}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:8px;box-shadow:0 8px 24px rgba(0,0,0,.25)}
.row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.col{display:flex;flex-direction:column;gap:8px}
.col-tight{display:flex;flex-direction:column;gap:var(--speed-row-gap)} /* Speed çerçevesi satır aralığı */
label.small{font-size:12px;color:var(--muted)}
.miniLbl{font-size:11px;color:var(--muted);margin-top:var(--mini-label-gap)}
.badge-ok{color:#062a13;background:#5ee38e;padding:2px 8px;border-radius:999px;font-size:12px}
input[type="text"],input[type="number"]{height:30px;padding:0 8px;border:1px solid var(--line);border-radius:8px;font-size:12px;background:#1b1e26;color:var(--text)}
input.ro{background:#171a21;color:#dfe3ea;cursor:default}
input.ro[readonly]{pointer-events:none}
input.ro[type=number]::-webkit-outer-spin-button,input.ro[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
input.ro[type=number]{-moz-appearance:textfield}
.wch5{width:var(--speed-inp-w)}
.miniInp{height:var(--mini-inp-h);width:var(--mini-inp-w)}
button.cmd{padding:6px 10px;border:1px solid #0000;border-radius:8px;background:var(--primary);color:#fff;font-weight:600;cursor:pointer;font-size:12px;line-height:1;text-align:center;white-space:nowrap}
.cmd:active{transform:translateY(1px)}
.stack{white-space:pre-line;line-height:1.05}
.centerTxt{display:flex;align-items:center;justify-content:center;text-align:center}
.btnGray{background:var(--gray)!important}
.btnGreen{background:var(--ok)!important}
.btnRed{background:var(--warn)!important}
.layout{display:grid;grid-template-columns:.9fr 1.1fr;gap:8px}
.topTwo{display:grid;grid-template-columns:1fr .9fr;gap:8px;align-items:start}
.playBar{display:flex;flex-wrap:nowrap;gap:var(--top-gap);align-items:stretch}
.btnStart,.btnStop{height:var(--play-stop-h);min-width:var(--play-stop-w);font-size:14px;flex:0 0 auto}
.btnFwd,.btnRew{height:var(--fwd-rew-h);min-width:var(--fwd-rew-w);font-size:14px;flex:0 0 auto;align-self:center}
.keyHint{font-size:10px;color:#9aa4b2;text-align:center;margin-top:2px}
.progRow{display:flex;gap:6px;align-items:center}
.progRow input{flex:1}
.motionRow{display:flex;align-items:center;gap:var(--motion-gap)}
.motionRow>.mLabel{display:inline-block;width:var(--motion-label-w);text-align:right;padding-right:4px;color:var(--muted)}
.motionRow input{height:var(--speed-inp-h);width:var(--speed-inp-w)}
.unitBtn{background:#1b1e26;border:1px solid var(--line);color:#dfe3ea;cursor:pointer;height:var(--speed-inp-h);padding:0 8px;border-radius:8px;font-size:12px;display:flex;align-items:center}
.unitWrap{position:relative;display:inline-block}
.unitMenu{position:absolute;top:calc(var(--speed-inp-h) + 4px);right:0;background:#232733;border:1px solid var(--line);border-radius:8px;box-shadow:0 12px 28px rgba(0,0,0,.35);display:none;z-index:var(--z);min-width:140px}
.unitMenu .opt{padding:8px 10px;cursor:pointer;font-size:13px;color:#e9edf3}
.unitMenu .opt:hover{background:#2f3442}
/* Program List + Sağ buton kolon (SABİT YAN YANA – kaydırma yerine yatay scroll) */
.listWrap{display:flex;gap:var(--prog-gap);align-items:stretch;flex-wrap:nowrap;overflow:auto}
.list{width:var(--prog-list-w);background:#1b1e26;border:1px solid var(--line);border-radius:10px;overflow:auto}
.item{padding:6px 10px;border-bottom:1px solid var(--line);cursor:pointer;font-size:13px;color:#e5e7eb}
.item.sel{background:#273248}
.sideBtns{display:flex;flex-direction:column;gap:calc(6px + var(--pv-button-row-gap));min-width:calc(var(--pv-long-w) + 2px)}
.pvShortRow{margin-top:var(--pv-short-row-gap)} /* (YENİ) kısa buton satır aralığı */
/* Uzun/Kısa grup boyutları */
.pvLong{width:var(--pv-long-w);min-width:var(--pv-long-w);max-width:var(--pv-long-w);height:var(--pv-long-h)}
.pvShort{width:var(--pv-short-w);min-width:var(--pv-short-w);max-width:var(--pv-short-w);height:var(--pv-short-h)}
/* Dropdown */
.dropWrap{position:relative}
.dropWrap>label.small{display:block;margin-bottom:var(--pv-label-gap)} /* PV label-input aralığı */
.dropBtn{text-align:left;position:relative}
.dropMenu{position:absolute;top:36px;left:0;width:100%;max-height:240px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:#232733;box-shadow:0 12px 28px rgba(0,0,0,.35);display:none;z-index:var(--z)}
.dropItem{padding:8px 10px;cursor:pointer;font-size:13px;color:#e5e7eb}
.dropItem:hover{background:#2f3442}
.insideInput{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:#fff;color:#000;border-radius:4px;padding:2px 4px;height:22px;width:46px}
/* Joint/Aux */
.twoCols{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.jRow{display:flex;align-items:center;gap:6px;flex-wrap:nowrap}
.jLbl{font-weight:700;width:28px;text-align:center}
.limLbl{font-size:11px;color:#a5adbf;min-width:32px;text-align:center}
.rangeWrap{position:relative;display:inline-block}
.rangeWrap input[type=range]{width:var(--joint-range-w);margin:0}
.thumbVal{position:absolute;top:-22px;transform:translateX(-50%);padding:1px 4px;font-size:11px;background:#0b0d12;color:#fff;border-radius:6px;pointer-events:none;white-space:nowrap}
.jMinus,.jPlus{min-width:var(--joint-btn-w)}
/* Cartesian/Tool */
.rightGrid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.axisGrid2col{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.axisCol{display:flex;flex-direction:column;gap:8px}
.axisChip{display:flex;align-items:center;gap:6px;background:#1b1e26;border:1px solid var(--line);border-radius:10px;padding:6px 8px}
.axisChip .jLbl{width:auto;min-width:24px}
.axisBtns{display:flex;gap:4px}
.axisBtns .cmd{min-width:var(--axis-btn-w)}
/* Logic & IO */
.logicRow{display:flex;flex-wrap:wrap;gap:var(--logic-gap-x);align-items:center}
.logicRow .lbl{font-weight:700;color:var(--hdr2);min-width:36px}
.logicRow .dd{min-width:var(--logic-dd-w)}
.logicRow input{height:var(--logic-inp-h)}
.logicBtn{height:var(--logic-btn-h)}
.logicRow + .logicRow{margin-top:calc(var(--logic-gap-y) )}
.logicRow.tight{margin-top:var(--logic-tight-gap)!important} /* (YENİ) üstüne yapıştırma */
.logicRow .miniLbl,.logicRow label.small{margin-bottom:var(--logic-label-gap)} /* IO label-input aralığı */
/* Registers sayfası yardımcıları */
.regRow{display:grid;grid-template-columns: 40px auto;align-items:center;gap:6px}
.regRow input{width:40px;height:24px}
.prRow{display:grid;grid-template-columns: repeat(6,50px) 1fr;gap:6px;align-items:center}
.prRow input{width:50px;height:24px}
/* Genel grid yardımcıları */
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.grid-4{display:grid;grid-template-columns:repeat(4,minmax(120px,1fr));gap:8px}
.grid-6{display:grid;grid-template-columns:repeat(6,minmax(80px,1fr));gap:8px}
.overlabel{display:flex;flex-direction:column}
.overlabel label{font-size:12px;color:var(--muted);margin-bottom:2px}
.listbox{width:100%;height:560px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:#0d1120;padding:6px;font:13px ui-monospace,Consolas,Menlo,monospace;color:#eaeaea}
.note{color:#a9b8ff;font-size:12px}
/* G-Code sağ paneli ~50 karakter daraltma */
.gcodeRight{flex:1;min-width:520px;max-width:calc(100% - 50ch);}
/* Responsive */
@media (max-width:1280px){ .layout{grid-template-columns:1fr!important} }
@media (max-width:840px){ .rightGrid{grid-template-columns:1fr} }
</style>
</head>
<body>
<!-- ========================================================= -->
<!-- ▒▒ SEKME BAR ▒▒ -->
<div class="tabs" id="tabs">
<button class="tabBtn active" data-tab="main">Main Controls</button>
<button class="tabBtn" data-tab="config">Config Settings</button>
<button class="tabBtn" data-tab="kin">Kinematics</button>
<button class="tabBtn" data-tab="io">Input Outputs</button>
<button class="tabBtn" data-tab="regs">Registers</button>
<button class="tabBtn" data-tab="vision">Vision</button>
<button class="tabBtn" data-tab="gcode">G-Code</button>
<button class="tabBtn" data-tab="log">Log</button>
</div>
<!-- ========================================================= -->
<!-- ▒▒ TAB: MAIN CONTROLS ▒▒ -->
<div class="tabPane active" id="tab-main">
<div class="layout">
<!-- SOL BLOK -->
<div class="col">
<div class="topTwo">
<!-- Play/Stop -->
<div class="card">
<div class="row">
<span class="badge-ok" id="almStatus">SYSTEM READY - NO ACTIVE ALARMS</span>
<span id="runStatus" class="miniLbl">PROGRAM STOPPED</span>
<span id="xbcStatus" class="miniLbl" style="margin-left:auto;">Xbox OFF</span>
</div>
<div class="playBar" style="margin-top:6px;">
<div><button class="cmd btnGreen centerTxt btnStart" onclick="runProg()">▶ Start</button><div class="keyHint">F5</div></div>
<div><button class="cmd btnGray centerTxt btnFwd" onclick="stepFwd()">FWD</button><div class="keyHint">→</div></div>
<div><button class="cmd btnGray centerTxt btnRew" onclick="stepRev()">REW</button><div class="keyHint">←</div></div>
<div><button class="cmd btnRed centerTxt btnStop" onclick="stopProg()">STOP</button><div class="keyHint">Esc</div></div>
</div>
<div class="progRow" style="margin-top:6px;">
<label class="small">Program:</label>
<input id="progName" type="text" placeholder="program.adı" />
<button class="cmd btnGray" onclick="send('LOAD '+byId('progName').value)">Load</button>
<button class="cmd btnGray" onclick="send('NEWPROG')">New Program</button>
</div>
</div>
<!-- Speed -->
<div class="card">
<div class="col-tight">
<div class="motionRow"><span class="mLabel">Speed</span><input id="speedVal" type="number" value="50" />
<div class="unitWrap"><button id="unitBtn" class="unitBtn" type="button"><span id="unitLbl">Percent</span> ▼</button>
<div id="unitMenu" class="unitMenu" role="menu" aria-hidden="true">
<div class="opt" data-unit="Percent">Percent</div><div class="opt" data-unit="Seconds">Seconds</div><div class="opt" data-unit="mm per Sec">mm per Sec</div>
</div>
</div>
</div>
<div class="motionRow"><span class="mLabel">Acceleration</span><input id="accVal" type="number" value="50" /><span class="small">%</span></div>
<div class="motionRow"><span class="mLabel">Deceleration</span><input id="decVal" type="number" value="50" /><span class="small">%</span></div>
<div class="motionRow"><span class="mLabel">Ramp</span><input id="rampVal" type="number" value="10" /><span class="small">%</span></div>
<div class="motionRow"><span class="mLabel">Rounding</span><input id="roundVal" type="number" value="0.00" step="0.1" /><span class="small">mm</span></div>
</div>
</div>
</div>
<!-- Program + Sağ komutlar (SABİT YAN YANA) -->
<div class="card" id="progCard" style="margin:0 10px;">
<div class="row" style="font-weight:700;color:var(--hdr)">
Program (Listbox)
<!-- (YENİ) Current Row solda, kendi label’ının sağında -->
<span class="row" style="gap:6px;margin-left:16px">
<label class="small" for="curRow">Current Row</label>
<input id="curRow" class="miniInp" type="number" value="0"/>
</span>
</div>
<div class="listWrap" style="gap:var(--prog-gap)">
<div id="progList" class="list"></div>
<div class="sideBtns" id="sideBtns">
<!-- Command Type -->
<div class="dropWrap">
<label class="small">Command Type</label>
<button id="cmdTypeBtn" class="cmd pvLong dropBtn" type="button">Move J ▼</button>
<div id="cmdMenu" class="dropMenu" role="menu" aria-hidden="true"></div>
</div>
<!-- Uzun grup -->
<button class="cmd pvLong">Teach New Position</button>
<button class="cmd pvLong">Modify Position</button>
<button class="cmd pvLong">Delete</button>
<button class="cmd pvLong">Return</button>
<button class="cmd pvLong">Auto Calibrate CMD</button>
<button class="cmd pvLong">Camera On</button>
<button class="cmd pvLong">Camera Off</button>
<!-- Kısa grup (YENİ: .pvShortRow ile dikey sıklaştırma ayarı) -->
<div class="row pvShortRow" style="gap:var(--pv-short-gap);align-items:flex-end">
<button class="cmd pvShort">Wait Time</button>
<div class="col-tight"><label class="miniLbl" for="waitTimeVal">seconds</label>
<input id="waitTimeVal" type="number" value="1.0" step="0.1" style="height:var(--mini-inp-h);width:60px"/></div>
</div>
<div class="row pvShortRow" style="gap:var(--pv-short-gap);align-items:flex-end">
<button class="cmd pvShort">Create Tab</button>
<div class="col-tight"><label class="miniLbl" for="createTabVal">Tab</label>
<input id="createTabVal" type="number" value="1" style="height:var(--mini-inp-h);width:60px"/></div>
</div>
<div class="row pvShortRow" style="gap:var(--pv-short-gap);align-items:flex-end">
<button class="cmd pvShort">Jump to Tab</button>
<div class="col-tight"><label class="miniLbl" for="jumpTabVal">Tab</label>
<input id="jumpTabVal" type="number" value="2" style="height:var(--mini-inp-h);width:60px"/></div>
</div>
<div class="row pvShortRow" style="gap:var(--pv-short-gap);align-items:flex-end;flex-wrap:nowrap">
<button class="cmd pvShort">Servo</button>
<div class="col-tight"><label class="miniLbl" for="servoNum">Number</label>
<input id="servoNum" type="number" value="1" style="height:var(--mini-inp-h);width:60px"/></div>
<div class="col-tight"><label class="miniLbl" for="servoPos">Position</label>
<input id="servoPos" type="number" value="180" style="height:var(--mini-inp-h);width:60px"/></div>
</div>
<div class="row pvShortRow" style="gap:var(--pv-short-gap);align-items:flex-end;flex-wrap:nowrap">
<button class="cmd pvShort">Register</button>
<div class="col-tight"><label class="miniLbl" for="regNum">Register</label>
<input id="regNum" type="number" value="1" style="height:var(--mini-inp-h);width:60px"/></div>
<div class="col-tight"><label class="miniLbl" for="regEq">(++/--)</label>
<input id="regEq" type="number" value="1" style="height:var(--mini-inp-h);width:60px"/></div>
</div>
<div class="row pvShortRow" style="gap:var(--pv-short-gap);align-items:flex-end;flex-wrap:nowrap">
<button class="cmd pvShort">Vision Find</button>
<div class="col-tight"><label class="miniLbl" for="visPass">Pass Tab</label>
<input id="visPass" type="number" value="1" style="height:var(--mini-inp-h);width:60px"/></div>
<div class="col-tight"><label class="miniLbl" for="visFail">Fail Tab</label>
<input id="visFail" type="number" value="2" style="height:var(--mini-inp-h);width:60px"/></div>
</div>
</div>
</div>
<!-- Manual Program Entry + alt butonlar -->
<div class="col" style="margin-top:8px;">
<label class="small">Manual Program Entry</label>
<input id="manEntry" type="text" value="Move J J1 10" />
</div>
<div class="row" style="gap:6px;flex-wrap:wrap;margin-top:6px;">
<button class="cmd" onclick="getSelected()">Get Selected</button>
<button class="cmd" onclick="insertItem()">Insert</button>
<button class="cmd" onclick="replaceItem()">Replace</button>
<button class="cmd" onclick="openText()">Open Text</button>
<button class="cmd" onclick="reloadProg()">Reload</button>
</div>
</div>
</div>
<!-- SAĞ BLOK -->
<div class="col">
<!-- Joint Jog -->
<div class="card" style="margin-right:6px;">
<div class="row" style="font-weight:700;color:var(--hdr);width:100%;justify-content:flex-start;gap:12px">
<span>Joint Jog (J1..J6)</span>
<!-- (YENİ) Increment solda, kendi label’ının sağında -->
<label class="small" for="incVal">Increment</label>
<input id="incVal" class="miniInp" type="number" value="5" step="0.1"/>
<label class="small" style="display:flex;align-items:center;gap:6px;">
<input id="chkInc" type="checkbox" /> Incremental Jog
</label>
</div>
<div class="twoCols" id="jjWrap"></div>
</div>
<!-- Cartesian & Tool -->
<div class="rightGrid">
<div class="card"><div class="row" style="font-weight:700;color:var(--hdr)">Cartesian</div><div class="axisGrid2col" id="cartWrap"></div></div>
<div class="card"><div class="row" style="font-weight:700;color:var(--hdr)">Tool Frame</div><div class="axisGrid2col" id="toolWrap"></div></div>
</div>
<!-- AUX -->
<div class="card" style="margin-right:6px;">
<div class="row" style="font-weight:700;color:var(--hdr)">Aux Axes (J7, J8, J9)</div>
<div class="col" id="auxWrap"></div>
</div>
<!-- Logic & IO -->
<div class="card" id="logicIO" style="margin:0 10px;">
<div class="row" style="font-weight:700;color:var(--hdr)">Logic & IO</div>
<div class="logicRow">
<span class="lbl">IF</span>
<select class="cmd btnGray dd"><option>5v Input</option><option>Register</option><option>COM Device</option><option>MB Coil</option><option>MB Input</option><option>MB Holding Reg</option><option>MB Input Reg</option></select>
<input type="number" style="width:var(--logic-inp-w4)" value="1"/><span>=</span>
<input type="number" style="width:var(--logic-inp-w4)" value="1"/>
<select class="cmd btnGray dd"><option>Call Prog</option><option>Jump Tab</option><option>Stop</option></select>
<input type="text" style="width:var(--logic-inp-w8)" value="MAIN1"/><span>•</span>
<button class="cmd logicBtn">Insert IF CMD</button>
</div>
<div class="logicRow">
<span class="lbl">WAIT</span>
<select class="cmd btnGray dd"><option>5v Input</option><option>MB Coil</option><option>MB Input</option></select>
<input type="number" style="width:var(--logic-inp-w4)" value="1"/><span>=</span>
<input type="number" style="width:var(--logic-inp-w4)" value="1"/><span>Timeout=</span>
<input type="number" style="width:var(--logic-inp-w4)" value="3"/><span>•</span>
<button class="cmd logicBtn">Insert WAIT CMD</button>
</div>
<div class="logicRow">
<span class="lbl">SET</span>
<select class="cmd btnGray dd"><option>5v Output</option><option>MB Coil</option><option>MB Register</option></select>
<input type="number" style="width:var(--logic-inp-w4)" value="1"/><span>=</span>
<input type="number" style="width:var(--logic-inp-w4)" value="1"/><span>•</span>
<button class="cmd logicBtn">Insert SET CMD</button>
</div>
<!-- (YENİ) position register ve read com device satırlarını üste yapıştır -->
<div class="logicRow tight">
<button class="cmd logicBtn">Position Register</button>
<div class="col-tight" style="gap:2px;"><label class="miniLbl">Pos Reg</label><input type="number" style="width:var(--logic-inp-w8);height:var(--logic-inp-h)" value="1"/></div>
<div class="col-tight" style="gap:2px;"><label class="miniLbl">Element</label><input type="number" style="width:var(--logic-inp-w8);height:var(--logic-inp-h)" value="1"/></div>
<div class="col-tight" style="gap:2px;"><label class="miniLbl">(++/--)</label><input type="number" style="width:var(--logic-inp-w8);height:var(--logic-inp-h)" value="1"/></div>
</div>
<div class="logicRow tight">
<button class="cmd logicBtn">Read COM Device</button>
<div class="col-tight" style="gap:2px;"><label class="miniLbl">Port</label><input type="number" style="width:var(--logic-inp-w8);height:var(--logic-inp-h)" value="1"/></div>
<div class="col-tight" style="gap:2px;"><label class="miniLbl">Char</label><input type="text" style="width:var(--logic-inp-w8);height:var(--logic-inp-h)" value="A"/></div>
</div>
</div>
<!-- Hızlı Log -->
<div class="card" style="margin-right:6px;">
<div class="row" style="font-weight:700;color:var(--hdr2)">Log</div>
<div id="logBox" style="white-space:pre-wrap;color:#d1d5db">// ---- Teensy mesajları burada ----</div>
</div>
</div>
</div>
</div>
<!-- ========================================================= -->
<!-- ▒▒ TAB: CONFIG SETTINGS ▒▒ -->
<div class="tabPane" id="tab-config">
<div class="card" style="border-color:#5b1f1f;background:#221517">
<div style="color:#ff6b6b;font-weight:800">
UNABLE TO ESTABLISH COMMUNICATIONS WITH TEENSY 4.1 CONTROLLER
</div>
</div>
<div class="row">
<!-- Sol kolon -->
<div class="col" style="min-width:300px;flex:1">
<div class="card" style="margin-bottom:6px">
<div style="font-weight:700;color:var(--hdr)">Communication</div>
<div class="col" style="margin-top:6px">
<label class="small">TEENSY COM PORT:</label>
<div class="row"><input type="text" id="comPortEntryField" style="width:4ch" value="4"/><button class="cmd btnGray" onclick="setCom()">Set Com Teensy</button></div>
<label class="small">5v IO BOARD COM PORT:</label>
<div class="row"><input type="text" id="com2PortEntryField" style="width:4ch" value="2"/><button class="cmd btnGray" onclick="setCom2()">Set Com IO Board</button></div>
</div>
</div>
<div class="card" style="margin-bottom:6px">
<div style="font-weight:700;color:var(--hdr)">Theme</div>
<div class="row" style="margin-top:6px"><button class="cmd btnGray" onclick="lightTheme()">Light</button><button class="cmd btnGray" onclick="darkTheme()">Dark</button></div>
</div>
<div class="card">
<div style="font-weight:700;color:var(--hdr)">Encoder Control</div>
<div class="col" style="margin-top:6px">
<label><input type="checkbox"/> J1 Open Loop (disable encoder)</label>
<label><input type="checkbox"/> J2 Open Loop (disable encoder)</label>
<label><input type="checkbox"/> J3 Open Loop (disable encoder)</label>
<label><input type="checkbox"/> J4 Open Loop (disable encoder)</label>
<label><input type="checkbox"/> J5 Open Loop (disable encoder)</label>
<label><input type="checkbox"/> J6 Open Loop (disable encoder)</label>
</div>
</div>
</div>
<!-- Orta kolon -->
<div class="col" style="flex:2;min-width:560px">
<div class="card">
<div style="font-weight:700;color:var(--hdr);margin-bottom:6px">Calibration Panel</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1.1fr;gap:10px">
<div class="col">
<div style="font-weight:700;color:#bcd9ff;margin-bottom:6px">Robot Calibration</div>
<button class="cmd btnGray">Auto Calibrate</button>
<div class="row" style="margin-top:6px;gap:20px">
<div style="display:grid;grid-template-columns:repeat(3,auto);row-gap:4px;column-gap:8px">
<label><input type="checkbox"/> J1</label><label><input type="checkbox"/> J2</label><label><input type="checkbox"/> J3</label>
<label><input type="checkbox"/> J4</label><label><input type="checkbox"/> J5</label><label><input type="checkbox"/> J6</label>
<label><input type="checkbox"/> J7</label><label><input type="checkbox"/> J8</label><label><input type="checkbox"/> J9</label>
</div>
<div style="display:grid;grid-template-columns:repeat(3,auto);row-gap:4px;column-gap:8px">
<label><input type="checkbox"/> J1</label><label><input type="checkbox"/> J2</label><label><input type="checkbox"/> J3</label>
<label><input type="checkbox"/> J4</label><label><input type="checkbox"/> J5</label><label><input type="checkbox"/> J6</label>
<label><input type="checkbox"/> J7</label><label><input type="checkbox"/> J8</label><label><input type="checkbox"/> J9</label>
</div>
</div>
<div class="col" style="margin-top:8px">
<button class="cmd btnGray" onclick="calRobotJ1()">Calibrate J1 Only</button>
<button class="cmd btnGray" onclick="calRobotJ2()">Calibrate J2 Only</button>
<button class="cmd btnGray" onclick="calRobotJ3()">Calibrate J3 Only</button>
<button class="cmd btnGray" onclick="calRobotJ4()">Calibrate J4 Only</button>
<button class="cmd btnGray" onclick="calRobotJ5()">Calibrate J5 Only</button>
<button class="cmd btnGray" onclick="calRobotJ6()">Calibrate J6 Only</button>
<button class="cmd btnGray" onclick="CalZeroPos()">Force CaL to Home</button>
<button class="cmd btnGray" onclick="CalRestPos()">Force Cal to Rest</button>
</div>
</div>
<div class="col">
<div style="font-weight:700;color:#bcd9ff;margin-bottom:6px">Calibration Offsets</div>
<div class="col">
<div class="row" style="justify-content:space-between"><label class="small">J1 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J2 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J3 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J4 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J5 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J6 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J7 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J8 Offset</label><input type="number" style="width:8ch"></div>
<div class="row" style="justify-content:space-between"><label class="small">J9 Offset</label><input type="number" style="width:8ch"></div>
</div>
</div>
<div class="col">
<div class="card" style="padding:8px">
<div style="font-weight:700;color:#bcd9ff;margin-bottom:6px">7th Axis Calibration</div>
<div class="row" style="justify-content:space-between"><label class="small">7th Axis Length</label><input type="number" style="width:6ch" value="500.0"></div>
<div class="row" style="justify-content:space-between"><label class="small">MM per Rotation</label><input type="number" style="width:6ch" value="4.0"></div>
<div class="row" style="justify-content:space-between"><label class="small">Drive Steps</label><input type="number" style="width:6ch" value="400.0"></div>
<div class="row" style="margin-top:6px"><button class="cmd btnGray" onclick="zeroAxis7()">Set Axis 7 Calibration to Zero</button><button class="cmd btnGray" onclick="calRobotJ7()">Autocalibrate Axis 7</button></div>
<div class="miniLbl">StepPin=12 / DirPin=13 / CalPin=36</div>
</div>
<div class="card" style="padding:8px">
<div style="font-weight:700;color:#bcd9ff;margin-bottom:6px">8th Axis Calibration</div>
<div class="row" style="justify-content:space-between"><label class="small">8th Axis Length</label><input type="number" style="width:6ch" value="500.0"></div>
<div class="row" style="justify-content:space-between"><label class="small">MM per Rotation</label><input type="number" style="width:6ch" value="4.0"></div>
<div class="row" style="justify-content:space-between"><label class="small">Drive Steps</label><input type="number" style="width:6ch" value="400.0"></div>
<div class="row" style="margin-top:6px"><button class="cmd btnGray" onclick="zeroAxis8()">Set Axis 8 Calibration to Zero</button><button class="cmd btnGray" onclick="calRobotJ8()">Autocalibrate Axis 8</button></div>
<div class="miniLbl">StepPin=32 / DirPin=33 / CalPin=37</div>
</div>
<div class="card" style="padding:8px">
<div style="font-weight:700;color:#bcd9ff;margin-bottom:6px">9th Axis Calibration</div>
<div class="row" style="justify-content:space-between"><label class="small">9th Axis Length</label><input type="number" style="width:6ch" value="500.0"></div>
<div class="row" style="justify-content:space-between"><label class="small">MM per Rotation</label><input type="number" style="width:6ch" value="4.0"></div>
<div class="row" style="justify-content:space-between"><label class="small">Drive Steps</label><input type="number" style="width:6ch" value="400.0"></div>
<div class="row" style="margin-top:6px"><button class="cmd btnGray" onclick="zeroAxis9()">Set Axis 9 Calibration to Zero</button><button class="cmd btnGray" onclick="calRobotJ9()">Autocalibrate Axis 9</button></div>
<div class="miniLbl">StepPin=34 / DirPin=35 / CalPin=38</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sağ kolon -->
<div class="col" style="min-width:300px;flex:1.1">
<div class="card" style="margin-bottom:6px">
<div style="font-weight:700;color:var(--hdr)">Robot Color</div>
<label class="small" style="margin-top:6px">Color</label>
<select style="width:100%">
<option>DarkOrange</option><option>Red</option><option>Gold</option><option>Yellow</option>
<option>Green</option><option>Blue</option><option>Navy</option><option>Black</option>
<option>DimGray</option><option>Gray</option><option>Silver</option><option>White</option>
</select>
</div>
<div class="card">
<div style="font-weight:700;color:var(--hdr)">Import Models</div>
<button class="cmd btnGray" style="margin-top:6px" onclick="import_stl_file()">Import STL</button>
<label class="small" style="margin-top:8px">File Name</label><input type="text"/>
<label class="small" style="margin-top:6px">X Position</label><input type="number"/>
<label class="small">Y Position</label><input type="number"/>
<label class="small">Z Position</label><input type="number"/>
<label class="small">Z Rotation</label><input type="number"/>
<button class="cmd btnGray" style="margin-top:6px" onclick="update_stl_transform()">Update Position</button>
</div>
</div>
</div>
<div class="row" style="align-items:stretch;margin-top:8px">
<div class="card" style="flex:1 1 600px">
<div style="font-weight:700;color:var(--hdr)">Controller I/O</div>
<label class="small" style="margin-top:6px">Last Command Sent to Controller</label>
<input type="text" id="cmdSentEntryField" style="width:100%;max-width:100%;min-width:100%;font-family:monospace" size="100"/>
<label class="small" style="margin-top:6px">Last Response From Controller</label>
<input type="text" id="cmdRecEntryField" style="width:100%;max-width:100%;min-width:100%;font-family:monospace" size="100"/>
</div>
<div class="card" style="flex:0 0 260px;display:flex;flex-direction:column;justify-content:center;align-items:stretch">
<button class="cmd" style="background:#2d7ff9" onclick="SaveAndApplyCalibration()">SAVE Eeprom</button>
<label class="miniLbl" style="margin-top:8px;text-align:center">
Sayfada yapılan değişikliklerin kalıcı olması için "SAVE Eeprom" a basın
</label>
</div>
</div>
</div>
<!-- =================================================================== -->
<!-- █████ TAB: KINEMATICS (Responsive 5 sütun, ayarlanabilir) █████ -->
<style>
/* ========== KINEMATICS: Sekme içi scoped değişkenler ========== */
#tab-kin{
/* **** SADECE KINEMATICS İÇİN AYARLAR **** */
/* Genel (varsayılan) */
--row-gap: 6px; /* satır aralığı */
--label-w: 140px; /* label genişliği */
--inp-w: 70px; /* input genişliği */
--label-gap: 8px; /* label–box arası mesafe */
--card-pad: 10px; /* kart iç boşluğu */
--col-min: 280px; /* grid kolon min genişliği */
--card-max: 1000px; /* (genel) tüm kartların max genişliği */
/* DH Params (genel) */
--dh-min: 300px; /* DH kart min genişlik */
--dh-inp-w: 56px; /* DH input genişliği */
/* Tool Frame (genel) */
--tf-min: 300px; /* TF kart min genişlik */
--tf-col-w: 80px; /* TF hücre genişliği */
--tf-gap: 2px; /* TF hücreler arası boşluk */
/* Buton kartı (genel) */
--btn-max: 350px; /* Buton kartı max genişlik */
}
/* ========== KART BAZLI (BAĞIMSIZ) AYARLAR ========== */
/* Sütun 1 */
.card--motor { --card-max: 520px; --inp-w: 45px; --label-gap: 10px; }
.card--calib { --card-max: 520px; --inp-w: 45px; --label-gap: 10px; }
/* Sütun 2 */
.card--limits { --card-max: 560px; --inp-w: 72px; --label-gap: 8px; }
.card--steps { --card-max: 480px; --inp-w: 80px; --label-gap: 12px; }
/* Sütun 3 */
.card--microstep { --card-max: 420px; --inp-w: 72px; --label-gap: 8px; }
.card--encoder { --card-max: 420px; --inp-w: 72px; --label-gap: 8px; }
/* Sütun 4 */
.card--dh { --card-max: 640px; --label-gap: 6px; --dh-inp-w: 60px; }
.card--tf { --card-max: 520px; --label-gap: 8px; --tf-col-w: 76px; }
/* Sütun 5 */
.card--buttons { --card-max: 360px; }
/* Grid iskeleti */
#tab-kin .kinWrap{display:grid;grid-template-columns:repeat(5,minmax(var(--col-min),1fr));gap:12px}
@media (max-width:1400px){#tab-kin .kinWrap{grid-template-columns:repeat(4,minmax(var(--col-min),1fr))}}
@media (max-width:1100px){#tab-kin .kinWrap{grid-template-columns:repeat(3,minmax(var(--col-min),1fr))}}
@media (max-width:900px){#tab-kin .kinWrap{grid-template-columns:repeat(2,minmax(var(--col-min),1fr))}}
@media (max-width:640px){#tab-kin .kinWrap{grid-template-columns:1fr}}
#tab-kin .k-card{background:#1b1e26;border:1px solid #3a3f4a;border-radius:10px;padding:var(--card-pad);color:#e6e7ea;box-sizing:border-box;max-width:var(--card-max);overflow:hidden}
#tab-kin .k-title{font-weight:700;color:#9bd3f9;margin:0 0 8px 0}
#tab-kin .k-col{display:flex;flex-direction:column;gap:var(--row-gap)}
#tab-kin .k-row{display:flex;align-items:center;gap:var(--label-gap)}
#tab-kin .k-label{min-width:var(--label-w);font-size:13px;color:#a6adbb}
#tab-kin .k-inp{width:var(--inp-w);height:24px;background:#0f1115;border:1px solid #3a3f4a;border-radius:8px;color:#e6e7ea;padding:0 6px}
#tab-kin .k-inp:focus{outline:none;border-color:#5fa8ff}
#tab-kin .k-stack{display:flex;flex-direction:column;gap:12px}
/* DH Params */
#tab-kin .dh-card{min-width:var(--dh-min)}
#tab-kin .dh-head{display:grid;grid-template-columns:28px repeat(4,var(--dh-inp-w));gap:4px;color:#a6adbb;font-size:12px;margin:0}
#tab-kin .dh-grid{display:flex;flex-direction:column;gap:4px}
#tab-kin .dh-row{display:grid;grid-template-columns:28px repeat(4,var(--dh-inp-w));gap:4px;align-items:center}
#tab-kin .dh-row b{justify-self:start}
#tab-kin .k-inp-dh{width:var(--dh-inp-w)}
/* Tool Frame */
#tab-kin .tf-card{min-width:var(--tf-min)}
#tab-kin .tf-head,#tab-kin .tf-grid{display:grid;grid-template-columns:repeat(6,minmax(0,var(--tf-col-w)));gap:var(--tf-gap)}
#tab-kin .tf-grid .k-inp{width:100%;min-width:0}
/* Buton Kartı */
#tab-kin .btn-card{max-width:var(--btn-max)}
#tab-kin .k-btn{display:block;width:100%;padding:8px 10px;border:0;border-radius:8px;background:#3b82f6;color:#fff;font-weight:700;cursor:pointer}
#tab-kin .k-btn.gray{background:#6b7280}
</style>
<div class="tabPane" id="tab-kin">
<div class="kinWrap">
<!-- Sütun 1: Motor Directions (üst) + Calibration Directions (alt) -->
<div class="k-stack">
<div class="k-card card--motor">
<div class="k-title">Motor Directions</div>
<div class="k-col">
<div class="k-row"><span class="k-label">J1 Motor Direction</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J2 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J3 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J4 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J5 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J6 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J7 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J8 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J9 Motor Direction</span><input class="k-inp" type="number" value="1"></div>
</div>
</div>
<div class="k-card card--calib">
<div class="k-title">Calibration Directions</div>
<div class="k-col">
<div class="k-row"><span class="k-label">J1 Calibration Dir.</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J2 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J3 Calibration Dir.</span><input class="k-inp" type="number" value="1"></div>
<div class="k-row"><span class="k-label">J4 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J5 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J6 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J7 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J8 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
<div class="k-row"><span class="k-label">J9 Calibration Dir.</span><input class="k-inp" type="number" value="0"></div>
</div>
</div>
</div>
<!-- Sütun 2: Axis Limits + Steps per Degree -->
<div class="k-stack">
<div class="k-card card--limits">
<div class="k-title">Axis Limits</div>
<div class="k-col">
<div class="k-row"><span class="k-label">J1 Pos Limit</span><input class="k-inp" type="number" value="170"></div>
<div class="k-row"><span class="k-label">J1 Neg Limit</span><input class="k-inp" type="number" value="170"></div>
<div class="k-row"><span class="k-label">J2 Pos Limit</span><input class="k-inp" type="number" value="170"></div>
<div class="k-row"><span class="k-label">J2 Neg Limit</span><input class="k-inp" type="number" value="90"></div>
<div class="k-row"><span class="k-label">J3 Pos Limit</span><input class="k-inp" type="number" value="42"></div>
<div class="k-row"><span class="k-label">J3 Neg Limit</span><input class="k-inp" type="number" value="42"></div>
<div class="k-row"><span class="k-label">J4 Pos Limit</span><input class="k-inp" type="number" value="91"></div>
<div class="k-row"><span class="k-label">J4 Neg Limit</span><input class="k-inp" type="number" value="89"></div>
<div class="k-row"><span class="k-label">J5 Pos Limit</span><input class="k-inp" type="number" value="105"></div>
<div class="k-row"><span class="k-label">J5 Neg Limit</span><input class="k-inp" type="number" value="105"></div>
<div class="k-row"><span class="k-label">J6 Pos Limit</span><input class="k-inp" type="number" value="180"></div>
<div class="k-row"><span class="k-label">J6 Neg Limit</span><input class="k-inp" type="number" value="180"></div>
</div>
</div>
<div class="k-card card--steps">
<div class="k-title">Steps / Degree</div>
<div class="k-col">
<div class="k-row"><span class="k-label">J1 Step/Deg</span><input class="k-inp" type="number" value="44.4444"></div>
<div class="k-row"><span class="k-label">J2 Step/Deg</span><input class="k-inp" type="number" value="55.5555"></div>
<div class="k-row"><span class="k-label">J3 Step/Deg</span><input class="k-inp" type="number" value="55.5555"></div>
<div class="k-row"><span class="k-label">J4 Step/Deg</span><input class="k-inp" type="number" value="49.7777"></div>
<div class="k-row"><span class="k-label">J5 Step/Deg</span><input class="k-inp" type="number" value="21.8602"></div>
<div class="k-row"><span class="k-label">J6 Step/Deg</span><input class="k-inp" type="number" value="22.2222"></div>
</div>
</div>
</div>
<!-- Sütun 3: Drive Microstep + Encoder CPR -->
<div class="k-stack">
<div class="k-card card--microstep">
<div class="k-title">Drive Microstep</div>
<div class="k-col">
<div class="k-row"><span class="k-label">J1 Drive Microstep</span><input class="k-inp" type="number" value="400"></div>
<div class="k-row"><span class="k-label">J2 Drive Microstep</span><input class="k-inp" type="number" value="400"></div>
<div class="k-row"><span class="k-label">J3 Drive Microstep</span><input class="k-inp" type="number" value="400"></div>
<div class="k-row"><span class="k-label">J4 Drive Microstep</span><input class="k-inp" type="number" value="400"></div>
<div class="k-row"><span class="k-label">J5 Drive Microstep</span><input class="k-inp" type="number" value="400"></div>
<div class="k-row"><span class="k-label">J6 Drive Microstep</span><input class="k-inp" type="number" value="400"></div>
</div>
</div>
<div class="k-card card--encoder">
<div class="k-title">Encoder CPR</div>
<div class="k-col">
<div class="k-row"><span class="k-label">J1 Encoder CPR</span><input class="k-inp" type="number" value="4000"></div>
<div class="k-row"><span class="k-label">J2 Encoder CPR</span><input class="k-inp" type="number" value="4000"></div>
<div class="k-row"><span class="k-label">J3 Encoder CPR</span><input class="k-inp" type="number" value="4000"></div>
<div class="k-row"><span class="k-label">J4 Encoder CPR</span><input class="k-inp" type="number" value="4000"></div>
<div class="k-row"><span class="k-label">J5 Encoder CPR</span><input class="k-inp" type="number" value="4000"></div>
<div class="k-row"><span class="k-label">J6 Encoder CPR</span><input class="k-inp" type="number" value="4000"></div>
</div>
</div>
</div>
<!-- Sütun 4: DH Params + Tool Frame Offset -->
<div class="k-stack">
<div class="k-card dh-card card--dh">
<div class="k-title">DH Params</div>
<div class="dh-head"><span></span><span>DH-θ</span><span>DH-α</span><span>DH-d</span><span>DH-a</span></div>
<div class="dh-grid" style="margin-top:4px">
<div class="dh-row"><b>J1</b><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="169.77"><input class="k-inp k-inp-dh" type="number" value="0"></div>
<div class="dh-row"><b>J2</b><input class="k-inp k-inp-dh" type="number" value="-90"><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="64.2"></div>
<div class="dh-row"><b>J3</b><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="305"></div>
<div class="dh-row"><b>J4</b><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="-90"><input class="k-inp k-inp-dh" type="number" value="222.63"><input class="k-inp k-inp-dh" type="number" value="0"></div>
<div class="dh-row"><b>J5</b><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="90"><input class="k-inp k-inp-dh" type="number" value="0"><input class="k-inp k-inp-dh" type="number" value="0"></div>
<div class="dh-row"><b>J6</b><input class="k-inp k-inp-dh" type="number" value="180"><input class="k-inp k-inp-dh" type="number" value="-90"><input class="k-inp k-inp-dh" type="number" value="41"><input class="k-inp k-inp-dh" type="number" value="0"></div>
</div>
</div>
<div class="k-card tf-card card--tf">
<div class="k-title">Tool Frame Offset</div>
<div class="tf-head"><span>X</span><span>Y</span><span>Z</span><span>Rz</span><span>Ry</span><span>Rx</span></div>
<div class="tf-grid" style="margin-top:4px">
<input class="k-inp" type="number" value="0">
<input class="k-inp" type="number" value="0">
<input class="k-inp" type="number" value="0">
<input class="k-inp" type="number" value="0">
<input class="k-inp" type="number" value="0">
<input class="k-inp" type="number" value="0">
</div>
<label style="display:flex;align-items:center;gap:8px;margin-top:6px;color:#a6adbb">
<input type="checkbox"> Disable Wrist Rotation - Linear Moves
</label>
</div>
</div>
<!-- Sütun 5: Butonlar (tek kart) -->
<div class="k-card btn-card card--buttons">
<div class="k-title">Defaults / Save</div>
<button class="k-btn gray" style="margin-bottom:8px" onclick="logCall?.('Load AR4-MK3 Defaults')">Load AR4-MK3 Defaults</button>
<button class="k-btn gray" style="margin-bottom:8px" onclick="logCall?.('Load AR4-MK2 Defaults')">Load AR4-MK2 Defaults</button>
<button class="k-btn gray" style="margin-bottom:8px" onclick="logCall?.('Load AR4 Defaults')">Load AR4 Defaults</button>
<button class="k-btn gray" style="margin-bottom:12px" onclick="logCall?.('Load AR3 Defaults')">Load AR3 Defaults</button>
<button class="k-btn" onclick="logCall?.('SaveAndApplyCalibration')">SAVE</button>
</div>
</div>
</div>
<!-- █████ TAB: KINEMATICS BİTTİ █████ -->
<!-- =================================================================== -->
<!-- █████ TAB: INPUTS & OUTPUTS — PY yerleşimiyle hizalanmış █████ -->
<div class="tabPane" id="tab-io">
<div class="row" style="align-items:flex-start">
<!-- ▼ 5v IO BOARD (sol blok: Servo & DO satırları) -->
<div class="card col" style="flex:1;min-width:520px">
<div style="font-weight:700">5v IO BOARD</div>
<!-- Satır ızgarası: [ServoBut] [ServoVal] [=] [DO But] [=] [In Val] -->
<div class="col" style="margin-top:6px;gap:8px">
<!-- Başlık satırı (boş/başlıklar Python ekranına benzer) -->
<div class="row" style="gap:10px;color:#9aa4b2">
<span style="width:80px;"></span>
<span style="width:46px;"></span>
<span style="width:10px;"></span>
<span style="width:80px;"></span>
<span style="width:10px;"></span>
<span style="width:46px;"></span>
</div>
<!-- y=40 -> servo0 ON + DO1 ON -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo0on')">Servo 0</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo0onEntryField">
<button class="cmd" style="width:80px" onclick="logCall('DO1on')">DO on</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO1onEntryField">
</div>
<!-- y=80 -> servo0 OFF + DO1 OFF -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo0off')">Servo 0</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo0offEntryField">
<button class="cmd btnGray" style="width:80px" onclick="logCall('DO1off')">DO off</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO1offEntryField">
</div>
<!-- y=120 -> servo1 ON + DO2 ON -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo1on')">Servo 1</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo1onEntryField">
<button class="cmd" style="width:80px" onclick="logCall('DO2on')">DO on</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO2onEntryField">
</div>
<!-- y=160 -> servo1 OFF + DO2 OFF -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo1off')">Servo 1</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo1offEntryField">
<button class="cmd btnGray" style="width:80px" onclick="logCall('DO2off')">DO off</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO2offEntryField">
</div>
<!-- y=200 -> servo2 ON + DO3 ON -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo2on')">Servo 2</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo2onEntryField">
<button class="cmd" style="width:80px" onclick="logCall('DO3on')">DO on</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO3onEntryField">
</div>
<!-- y=240 -> servo2 OFF + DO3 OFF -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo2off')">Servo 2</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo2offEntryField">
<button class="cmd btnGray" style="width:80px" onclick="logCall('DO3off')">DO off</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO3offEntryField">
</div>
<!-- y=280 -> servo3 ON + DO4 ON -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo3on')">Servo 3</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo3onEntryField">
<button class="cmd" style="width:80px" onclick="logCall('DO4on')">DO on</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO4onEntryField">
</div>
<!-- y=320 -> servo3 OFF + DO4 OFF -->
<div class="row" style="gap:10px">
<button class="cmd btnGray" style="width:80px" onclick="logCall('Servo3off')">Servo 3</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="servo3offEntryField">
<button class="cmd btnGray" style="width:80px" onclick="logCall('DO4off')">DO off</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO4offEntryField">
</div>
<!-- y=360 -> (boş servo) + DO5 ON -->
<div class="row" style="gap:10px">
<span style="width:80px"></span>
<span style="width:46px"></span>
<span style="width:10px"></span>
<button class="cmd" style="width:80px" onclick="logCall('DO5on')">DO on</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO5onEntryField">
</div>
<!-- y=400 -> (boş servo) + DO5 OFF -->
<div class="row" style="gap:10px">
<span style="width:80px"></span>
<span style="width:46px"></span>
<span style="width:10px"></span>
<button class="cmd btnGray" style="width:80px" onclick="logCall('DO5off')">DO off</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO5offEntryField">
</div>
<!-- y=440 -> (boş servo) + DO6 ON -->
<div class="row" style="gap:10px">
<span style="width:80px"></span>
<span style="width:46px"></span>
<span style="width:10px"></span>
<button class="cmd" style="width:80px" onclick="logCall('DO6on')">DO on</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO6onEntryField">
</div>
<!-- y=480 -> (boş servo) + DO6 OFF -->
<div class="row" style="gap:10px">
<span style="width:80px"></span>
<span style="width:46px"></span>
<span style="width:10px"></span>
<button class="cmd btnGray" style="width:80px" onclick="logCall('DO6off')">DO off</button>
<span>=</span>
<input type="number" style="width:46px;height:24px" id="DO6offEntryField">
</div>
</div>
<!-- Bilgi notları -->
<div style="margin-top:12px;font-size:12px;color:#a6adbb;line-height:1.35">
The following IO are available when using the default 5v <b>Nano</b> board for IO: Inputs = 2–7 / Outputs = 8–13 / Servos = A0–A7<br>
The following IO are available when using the default 5v <b>Mega</b> board for IO: Inputs = 0–27 / Outputs = 28–53 / Servos = A0–A7<br>
Please review this tutorial video on using 5v IO boards:
<a href="https://youtu.be/76F6dS4ar8Y?si=Z6NstZy1zNeHgtCF" target="_blank" rel="noopener">https://youtu.be/76F6dS4ar8Y?si=Z6NstZy1zNeHgtCF</a><br>
5v board inputs are high impedance and susceptible to floating voltage — inputs use a pull-up resistor and will read high when nothing is connected — it's best to connect your input signal to GND and wait for the input signal to = 0
</div>
</div>
<!-- ▼ AUX COM DEVICE (orta blok) -->
<div class="card col" style="flex:0 0 320px;min-width:300px">
<div style="font-weight:700">AUX COM DEVICE</div>
<div class="overlabel" style="margin-top:6px"><label>Aux Com Port</label><input type="text" id="com3PortEntryField" value="3" style="max-width:80px"></div>
<div class="overlabel"><label>Char to Read</label><input type="text" id="com3charPortEntryField" value="#" style="max-width:80px"></div>
<button class="cmd btnGray" style="margin-top:6px" onclick="logCall('TestAuxCom')">Test Aux COM Device</button>
<div class="overlabel" style="margin-top:6px"><label>Output</label><input type="text" id="com3outPortEntryField" style="max-width:240px"></div>
</div>
<!-- ▼ MODBUS DEVICE (sağ blok) -->
<div class="card col" style="flex:0 0 380px;min-width:340px">
<div style="font-weight:700">MODBUS DEVICE</div>
<div class="overlabel" style="margin-top:6px"><label>Slave ID</label><input type="number" id="MBslaveEntryField" style="max-width:90px"></div>
<div class="overlabel"><label>Modbus Address</label><input type="number" id="MBaddressEntryField" style="max-width:120px"></div>
<div class="overlabel"><label>Operation Value</label><input type="number" id="MBoperValEntryField" style="max-width:120px"></div>
<div class="col" style="margin:10px 0;gap:8px">
<button class="cmd btnGray" onclick="logCall('MBreadCoil')">Read Coil</button>
<button class="cmd btnGray" onclick="logCall('MBreadInput')">Read Discrete Input</button>
<button class="cmd btnGray" onclick="logCall('MBreadHoldReg')">Read Holding Register</button>
<button class="cmd btnGray" onclick="logCall('MBreadInputReg')">Read Input Register</button>
<button class="cmd btnGray" onclick="logCall('MBwriteCoil')">Write Coil</button>
<button class="cmd btnGray" onclick="logCall('MBwriteReg')">Write Register</button>
</div>
<div class="overlabel"><label>Output Response:</label><input type="text" id="MBoutputEntryField" style="max-width:320px"></div>
</div>
</div>
</div>
<!-- █████ TAB: INPUTS & OUTPUTS BİTTİ █████ -->
<!-- =================================================================== -->
<!-- █████ TAB: REGISTERS — sekme içi scoped değişkenlerle █████ -->
<style>
/* ================== REGISTERS: Sekme içi scoped değişkenler ==================*/
#tab-regs{
--reg-left-min: 220px; /* sol kart min genişlik */
--pr-card-min: 620px; /* PR kart min genişlik */
--pr-card-max: 500px; /* PR kart MAX genişlik (kenara kadar uzamasın) */
--pr-col-width: 72px; /* PR kutu genişliği */
--pr-name-col: 64px; /* sağdaki PR etiket sütunu genişliği */
--cell-gap: 8px; /* sütunlar arası yatay boşluk */
--row-gap: 6px; /* R satır boşluğu */
--pr-row-gap: 6px; /* PR satır boşluğu */
}
/* Registers – sol kart */
#tab-regs .regList{display:flex;flex-direction:column;gap:var(--row-gap);margin-top:6px}
#tab-regs .regRow{display:flex;align-items:center;gap:var(--cell-gap)}
#tab-regs .regRow input{
width:70px;height:24px;
background:#1b1e26;border:1px solid var(--line);border-radius:8px;
color:var(--text);padding:0 6px;font-size:12px;
}
#tab-regs .regRow span{min-width:34px;color:#dbe2ee}
/* PR grid: 6 kutu + sabit genişlikte PR etiketi sütunu */
#tab-regs .prHead,
#tab-regs .prRow{
display:grid;
grid-template-columns:repeat(6,var(--pr-col-width)) var(--pr-name-col);
gap:var(--cell-gap);
align-items:center;
}
#tab-regs .prHead{color:var(--muted);font-size:12px;margin:6px 0 4px;}
#tab-regs .prRow{margin:0;}
#tab-regs .prList{display:flex;flex-direction:column;gap:var(--pr-row-gap)}
#tab-regs .prRow input{
width:var(--pr-col-width);height:24px;
background:#1b1e26;border:1px solid var(--line);border-radius:8px;
color:#e6e7ea;padding:0 6px;font-size:12px;
}
#tab-regs .prRow span{justify-self:start;color:#dbe2ee}
/* Kart genişlikleri */
#tab-regs .card.regLeft {min-width:var(--reg-left-min);flex:0 0 auto}
#tab-regs .card.regRight{
min-width:var(--pr-card-min);
max-width:var(--pr-card-max);
flex:1 1 auto;
}
</style>
<div class="tabPane" id="tab-regs">
<div class="row">
<!-- Sol: R1..R16 -->
<div class="card col regLeft">
<div style="font-weight:700;color:var(--hdr)">R1..R16</div>
<div class="regList">
<div class="regRow"><input type="number" value="0"><span>R1</span></div>
<div class="regRow"><input type="number" value="0"><span>R2</span></div>
<div class="regRow"><input type="number" value="0"><span>R3</span></div>
<div class="regRow"><input type="number" value="0"><span>R4</span></div>
<div class="regRow"><input type="number" value="0"><span>R5</span></div>
<div class="regRow"><input type="number" value="0"><span>R6</span></div>
<div class="regRow"><input type="number" value="0"><span>R7</span></div>
<div class="regRow"><input type="number" value="0"><span>R8</span></div>
<div class="regRow"><input type="number" value="0"><span>R9</span></div>
<div class="regRow"><input type="number" value="0"><span>R10</span></div>
<div class="regRow"><input type="number" value="0"><span>R11</span></div>
<div class="regRow"><input type="number" value="0"><span>R12</span></div>
<div class="regRow"><input type="number" value="0"><span>R13</span></div>
<div class="regRow"><input type="number" value="0"><span>R14</span></div>
<div class="regRow"><input type="number" value="0"><span>R15</span></div>
<div class="regRow"><input type="number" value="0"><span>R16</span></div>
</div>
</div>
<!-- Sağ: PR1..PR16 -->
<div class="card col regRight">
<div style="font-weight:700;color:var(--hdr)">PR1..PR16</div>
<!-- Sütun başlıkları -->
<div class="prHead" style="margin-top:6px">
<span>X</span><span>Y</span><span>Z</span><span>Rz</span><span>Ry</span><span>Rx</span><span></span>
</div>
<!-- Satırların boşluğu .prList gap’inden kontrol edilir -->
<div class="prList">
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR1</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR2</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR3</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR4</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR5</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR6</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR7</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR8</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR9</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR10</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR11</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR12</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR13</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR14</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR15</span></div>
<div class="prRow"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><input type="number" value="0"><span>PR16</span></div>
</div>
</div>
</div>
</div>
<!-- █████ TAB: REGISTERS BİTTİ █████ -->
<!-- =================================================================== -->
<!-- █████ TAB: VISION █████ -->
<div class="tabPane" id="tab-vision">
<div class="card col" style="max-width:600px">
<div style="font-weight:700;color:var(--hdr)">Vision</div>
<div class="row" style="margin-top:6px">
<label class="small">Camera Index</label><input type="number" value="0">
<button class="cmd btnGray" onclick="logCall('Vision Connect')">Connect</button>
</div>
<div class="row" style="margin-top:6px">
<label class="small">Template File</label><input type="text" placeholder="file.tpl">
<button class="cmd btnGray" onclick="logCall('Vision Load')">Load</button>
</div>
</div>
</div>
<!-- =================================================================== -->
<!-- █████ TAB: G-CODE █████ -->
<div class="tabPane" id="tab-gcode">
<div class="row" style="align-items:flex-start">
<!-- Sol panel -->
<div class="card col" style="flex:0 0 560px;min-width:320px">
<div style="font-weight:700;color:var(--hdr)">Program</div>
<button class="cmd btnGray" style="margin-top:6px" onclick="logCall('loadGcodeProg')">Load Program</button>
<input type="text" id="GcodeProgEntryField" placeholder="" style="margin-top:6px">
<div style="font-weight:700;color:var(--hdr);margin-top:10px">Set Start Position</div>
<div class="grid-6" style="margin-top:6px">
<div class="overlabel"><label>X</label><input type="number" id="GC_ST_E1_EntryField" value="324.414"></div>
<div class="overlabel"><label>Y</label><input type="number" id="GC_ST_E2_EntryField" value="81.585"></div>
<div class="overlabel"><label>Z</label><input type="number" id="GC_ST_E3_EntryField" value="159.248"></div>
<div class="overlabel"><label>Rz</label><input type="number" id="GC_ST_E4_EntryField" value="-164.528"></div>
<div class="overlabel"><label>Ry</label><input type="number" id="GC_ST_E5_EntryField" value="88.324"></div>
<div class="overlabel"><label>Rx</label><input type="number" id="GC_ST_E6_EntryField" value="-161.517"></div>
</div>
<button class="cmd btnGray" style="margin-top:6px" onclick="logCall('SetGcodeStartPos')">Set Start Position</button>
<div class="overlabel" style="margin-top:10px"><label>Start Position Offset</label></div>
<div class="grid-6">
<input type="number" id="GC_SToff_E1_EntryField" value="0">
<input type="number" id="GC_SToff_E2_EntryField" value="0">
<input type="number" id="GC_SToff_E3_EntryField" value="20">
<input type="number" id="GC_SToff_E4_EntryField" value="0">
<input type="number" id="GC_SToff_E5_EntryField" value="0">
<input type="number" id="GC_SToff_E6_EntryField" value="0">
</div>
<button class="cmd btnGray" style="margin-top:6px" onclick="logCall('MoveGcodeStartPos')">Move to Start Offset</button>
<div class="overlabel" style="margin-top:10px"><label>Filename</label><input type="text" id="GcodeFilenameField"></div>
<div class="col" style="gap:6px;margin-top:8px">
<button class="cmd" onclick="logCall('GCconvertProg')">Convert & Upload to SD</button>
<button class="cmd btnGray" onclick="logCall('GCstopProg')">Stop Conversion & Upload</button>
<button class="cmd btnGray" onclick="logCall('GCdelete')">Delete File from SD</button>
<button class="cmd btnGray" onclick="logCall('GCread yes')">Read Files from SD</button>
<button class="cmd" onclick="logCall('GCplay')">Play Gcode File</button>
<button class="cmd" onclick="logCall('Save GCODE DATA')">SAVE DATA</button>
</div>
</div>
<!-- Sağ büyük panel (daraltıldı) -->
<div class="col gcodeRight">
<div class="row" style="justify-content:space-between;align-items:center;margin:0 6px 6px">
<div style="font-weight:700;color:#84ff84">GCODE IDLE</div>
<div class="row"><label class="small">Current Row:</label><input type="number" id="GcodCurRowEntryField" style="width:80px"></div>
</div>
<div id="gcodeView" class="listbox" style="height:560px"></div>
</div>
</div>
</div>
<!-- =================================================================== -->
<!-- █████ TAB: LOG █████ -->
<div class="tabPane" id="tab-log">
<div class="card col">
<div style="font-weight:700;color:var(--hdr)">Error Log</div>
<div id="bigLog" class="listbox" style="min-height:420px"></div>
<div style="margin-top:10px;"><button class="cmd btnGray" onclick="clearLog()">Clear Log</button></div>
</div>
</div>
<script>
/* ===== Yardımcı ===== */
const byId=(id)=>document.getElementById(id);
function appendLog(t){const lb=byId("logBox");if(!lb)return;lb.textContent+=("\\n"+t);lb.scrollTop=lb.scrollHeight;}
function send(s){if(window.ws&&ws.readyState===1)ws.send(s);}
/* ===== Sekmeler + URL hash ===== */
(function(){
const tabs=document.querySelectorAll('.tabBtn');const panes=document.querySelectorAll('.tabPane');
function activate(key){tabs.forEach(b=>b.classList.toggle('active',b.dataset.tab===key));panes.forEach(p=>p.classList.toggle('active',p.id===('tab-'+key)));setTimeout(syncListHeight,0);}
tabs.forEach(btn=>btn.addEventListener('click',()=>{const key=btn.dataset.tab;activate(key);location.hash=key;}));
const initial=(location.hash||'').replace('#','')||'main';activate(Array.from(tabs).some(b=>b.dataset.tab===initial)?initial:'main');
})();
/* ===== WebSocket (opsiyonel) ===== */
let ws=null;
function wsConnect(){
try{
if(location.protocol.startsWith('http')){
ws=new WebSocket(`ws://${location.host}/ws`);
window.ws=ws;
ws.onopen = ()=>appendLog("// WS Connected");
ws.onclose= ()=>{appendLog("// WS Closed. Reconnecting...");setTimeout(wsConnect,1000);};
ws.onmessage=(ev)=>handleFeedback(ev.data);
}else{
appendLog("// WS disabled (file://)");
}
}catch(e){ appendLog("// WS error: "+e.message); }
}
wsConnect();
/* ===== Speed Unit dropdown ===== */
let speedUnit="Percent";
const unitBtn=byId("unitBtn"),unitMenu=byId("unitMenu"),unitLbl=byId("unitLbl");
unitBtn?.addEventListener("click",(e)=>{e.preventDefault();e.stopPropagation();unitMenu.style.display=unitMenu.style.display==="block"?"none":"block";});
unitMenu?.querySelectorAll(".opt").forEach(opt=>opt.addEventListener("click",(ev)=>{ev.preventDefault();ev.stopPropagation();speedUnit=opt.getAttribute('data-unit');unitLbl.textContent=speedUnit;unitMenu.style.display="none";},false));
document.addEventListener("click",()=>unitMenu&&(unitMenu.style.display="none"));
/* ===== Program listesi (mock) ===== */
let progItems=["N10 HOME","N20 MOVE J1 10","N30 MOVE J2 20","N40 WAIT 1.0"];let selIndex=-1;
function renderList(){const ul=byId("progList");if(!ul)return;ul.innerHTML="";progItems.forEach((txt,i)=>{const div=document.createElement("div");div.className="item"+(i===selIndex?" sel":"");div.textContent=txt;div.addEventListener('click',()=>{selIndex=i;renderList();});ul.appendChild(div);});syncListHeight();}
function syncListHeight(){const list=byId("progList");const side=byId("sideBtns");if(list&&side)list.style.height=side.offsetHeight+"px";}
window.addEventListener("resize",syncListHeight);renderList();
/* ===== Command Type dropdown + PR içi input ===== */
const CMD_TYPES=["Move J","OFF J","Move L","Move R","Move A mid","Move A end","Move C Center","Move C Start","Move C Plane","Start Spline","End Spline","Move PR","OFF PR","Teach PR","Move Vis"];
let currentCmdType="Move J";
const cmdBtn=byId("cmdTypeBtn"),cmdMenu=byId("cmdMenu");
function ensurePrInside(show){const old=cmdBtn?.querySelector("input.insideInput");if(old)old.remove();if(show){const inp=document.createElement("input");inp.type="number";inp.value="1";inp.className="insideInput";inp.title="PR#";inp.addEventListener("click",(e)=>e.stopPropagation());cmdBtn.appendChild(inp);}}
cmdBtn?.addEventListener("click",(e)=>{e.preventDefault();e.stopPropagation();cmdMenu.style.display=cmdMenu.style.display==="block"?"none":"block";});
function buildCmdTypeMenu(){if(!cmdMenu)return;cmdMenu.innerHTML="";CMD_TYPES.forEach(name=>{const d=document.createElement("div");d.className="dropItem";d.textContent=name;d.addEventListener("click",(ev)=>{ev.preventDefault();ev.stopPropagation();currentCmdType=name;cmdBtn.textContent=name+" ▼";cmdBtn.classList.add("pvLong");ensurePrInside(name==="Move PR"||name==="OFF PR"||name==="Teach PR");cmdMenu.style.display="none";},false);cmdMenu.appendChild(d);});}
buildCmdTypeMenu();document.addEventListener("click",()=>cmdMenu&&(cmdMenu.style.display="none"));
/* ===== Manual panel stub ===== */
window.getSelected=()=>{if(selIndex>=0){send("GET_SELECTED "+selIndex);appendLog("// Get Selected -> "+progItems[selIndex]);}};
window.insertItem=()=>{const txt=byId("manEntry").value.trim();if(!txt)return;const insAt=(selIndex>=0?selIndex+1:progItems.length);progItems.splice(insAt,0,txt);renderList();send("INSERT "+insAt+" "+txt);};
window.replaceItem=()=>{const txt=byId("manEntry").value.trim();if(selIndex<0||!txt)return;progItems[selIndex]=txt;renderList();send("REPLACE "+selIndex+" "+txt);};
window.openText=()=>send("OPEN_TEXT");window.reloadProg=()=>send("RELOAD");window.runProg=()=>send("RUN");window.stopProg=()=>send("STOP");window.stepFwd=()=>send("STEP FWD");window.stepRev=()=>send("STEP REV");
/* ===== Increment rozetleri ===== */
function refreshIncBadges(){
const incActive=byId("chkInc").checked;
const step=parseFloat(byId("incVal").value)||0;
[[".axisMinus","−"],[".axisPlus","+"],[".toolMinus","−"],[".toolPlus","+"],[".jMinus","−"],[".jPlus","+"],[".auxMinus","−"],[".auxPlus","+"]]
.forEach(([sel,sym])=>{
document.querySelectorAll(sel).forEach(btn=>{
btn.classList.add("stack","centerTxt");
btn.textContent = incActive ? `${sym}\n(${step})` : sym;
});
});
}
byId("chkInc").addEventListener("change",refreshIncBadges);
byId("incVal").addEventListener("input",refreshIncBadges);
refreshIncBadges();
/* ===== Slider balon ===== */
function attachThumbBubble(rangeEl,bubbleEl){
const min=parseFloat(rangeEl.min||"0");
const max=parseFloat(rangeEl.max||"100");
const val=parseFloat(rangeEl.value||"0");
const pct=(val-min)/(max-min);
const w=rangeEl.offsetWidth;
const x=pct*w;
bubbleEl.textContent=val;
bubbleEl.style.left=x+"px";
}
function initBubble(rangeEl,bubbleEl){const sync=()=>attachThumbBubble(rangeEl,bubbleEl);requestAnimationFrame(()=>{sync();setTimeout(sync,0);});}
/* ===== Ortak ===== */
function getStep(){const incActive=byId("chkInc").checked;if(incActive){const v=parseFloat(byId("incVal").value)||0;return(v>0?v:1);}return 1;}
function clamp(v,min,max){return Math.max(min,Math.min(max,v));}
/* ===== JOINT JOG ===== */
const jointDefs=[["J1",-180,180],["J2",-180,180],["J3",-180,180],["J4",-180,180],["J5",-180,180],["J6",-180,180]];
function makeJointBlock(name,nL,pL){
const wrap=document.createElement("div");
const row=document.createElement("div");row.className="jRow";
const lbl=document.createElement("div");lbl.className="jLbl";lbl.textContent=name;
const box=document.createElement("input");box.type="number";box.className="ro";box.value="0.0";box.step="0.1";box.readOnly=true;box.id=`${name}_cur`;box.style.height="var(--joint-inp-h)";box.style.width="var(--joint-inp-w)";
const btnNeg=document.createElement("button");btnNeg.className="cmd btnGray stack jMinus";btnNeg.textContent="−";btnNeg.style.minWidth="var(--joint-btn-w)";
const minL=document.createElement("span");minL.className="limLbl";minL.textContent=nL;
const rWrap=document.createElement("div");rWrap.className="rangeWrap";
const slider=document.createElement("input");slider.type="range";slider.min=nL;slider.max=pL;slider.value=(nL+pL)/2;slider.id=`${name}_slider`;
const bubble=document.createElement("span");bubble.className="thumbVal";bubble.id=`${name}_bbl`;bubble.textContent=slider.value;
rWrap.append(slider,bubble);
const maxL=document.createElement("span");maxL.className="limLbl";maxL.textContent=pL;
const btnPos=document.createElement("button");btnPos.className="cmd btnGray stack jPlus";btnPos.textContent="+";btnPos.style.minWidth="var(--joint-btn-w)";
row.append(lbl,box,btnNeg,minL,rWrap,maxL,btnPos);wrap.append(row);
function updateBubble(){attachThumbBubble(slider,bubble);}initBubble(slider,bubble);
window.addEventListener("resize",updateBubble);
slider.addEventListener("input",updateBubble);
slider.addEventListener("change",()=>{updateBubble();applyJointSlider(name);});
btnNeg.addEventListener("click",()=>{const step=getStep();slider.value=clamp(parseFloat(slider.value)-step,nL,pL);updateBubble();applyJointSlider(name);});
btnPos.addEventListener("click",()=>{const step=getStep();slider.value=clamp(parseFloat(slider.value)+step,nL,pL);updateBubble();applyJointSlider(name);});
return wrap;
}
function renderJJ(){const jj=byId("jjWrap");jj.innerHTML="";const leftCol=document.createElement("div");const rightCol=document.createElement("div");leftCol.className="col";rightCol.className="col";for(let i=0;i<3;i++)leftCol.appendChild(makeJointBlock(...jointDefs[i]));for(let i=3;i<6;i++)rightCol.appendChild(makeJointBlock(...jointDefs[i]));jj.append(leftCol,rightCol);}renderJJ();
/* ===== CARTESIAN & TOOL ===== */
function axisChipWithInput(labelTxt){
const chip=document.createElement("div");chip.className="axisChip";
const lab=document.createElement("div");lab.className="jLbl";lab.textContent=labelTxt;
const inp=document.createElement("input");inp.className="ro";inp.type="number";inp.value="0.0";inp.step="0.1";inp.readOnly=true;inp.id=`AX_${labelTxt}`;inp.style.width="var(--axis-inp-w)";inp.style.height="var(--axis-inp-h)";
const btns=document.createElement("div");btns.className="axisBtns";
const bMinus=document.createElement("button");bMinus.className="cmd btnGray stack axisMinus";bMinus.textContent="−";
const bPlus=document.createElement("button");bPlus.className="cmd btnGray stack axisPlus";bPlus.textContent="+";
btns.append(bMinus,bPlus);chip.append(lab,inp,btns);return chip;
}
function axisChipNoInput(labelTxt){
const chip=document.createElement("div");chip.className="axisChip";
const lab=document.createElement("div");lab.className="jLbl";lab.textContent=labelTxt;
const btns=document.createElement("div");btns.className="axisBtns";
const bMinus=document.createElement("button");bMinus.className="cmd btnGray stack toolMinus";bMinus.textContent="−";
const bPlus=document.createElement("button");bPlus.className="cmd btnGray stack toolPlus";bPlus.textContent="+";
btns.append(bMinus,bPlus);chip.append(lab,btns);return chip;
}
function renderCartesian(){const grid=byId("cartWrap");grid.innerHTML="";const left=document.createElement("div");left.className="axisCol";const right=document.createElement("div");right.className="axisCol";["X","Y","Z"].forEach(ax=>left.appendChild(axisChipWithInput(ax)));["Rx","Ry","Rz"].forEach(ax=>right.appendChild(axisChipWithInput(ax)));grid.append(left,right);}
function renderTool(){const grid=byId("toolWrap");grid.innerHTML="";const left=document.createElement("div");left.className="axisCol";const right=document.createElement("div");right.className="axisCol";["Tx","Ty","Tz"].forEach(ax=>left.appendChild(axisChipNoInput(ax)));["Trx","Try","Trz"].forEach(ax=>right.appendChild(axisChipNoInput(ax)));grid.append(left,right);}renderCartesian();renderTool();
/* ===== AUX ===== */
const auxDefs=[["J7",-999,999],["J8",-999,999],["J9",-999,999]];
function makeAuxRow(name,nL,pL){
const wrap=document.createElement("div");
const row=document.createElement("div");row.className="jRow";
const lbl=document.createElement("div");lbl.className="jLbl";lbl.textContent=name;
const box=document.createElement("input");box.type="number";box.className="ro";box.value="0.0";box.step="0.1";box.readOnly=true;box.id=`${name}_cur`;box.style.height="var(--joint-inp-h)";box.style.width="var(--joint-inp-w)";
const btnNeg=document.createElement("button");btnNeg.className="cmd btnGray stack auxMinus";btnNeg.textContent="−";btnNeg.style.minWidth="var(--joint-btn-w)";
const minL=document.createElement("span");minL.className="limLbl";minL.textContent=nL;
const rWrap=document.createElement("div");rWrap.className="rangeWrap";
const slider=document.createElement("input");slider.type="range";slider.min=nL;slider.max=pL;slider.value=(nL+pL)/2;slider.id=`${name}_slider`;
const bubble=document.createElement("span");bubble.className="thumbVal";bubble.id=`${name}_bbl`;bubble.textContent=slider.value;
rWrap.append(slider,bubble);
const maxL=document.createElement("span");maxL.className="limLbl";maxL.textContent=pL;
const btnPos=document.createElement("button");btnPos.className="cmd btnGray stack auxPlus";btnPos.textContent="+";btnPos.style.minWidth="var(--joint-btn-w)";
row.append(lbl,box,btnNeg,minL,rWrap,maxL,btnPos);wrap.append(row);
function updateBubble(){attachThumbBubble(slider,bubble);}initBubble(slider,bubble);
window.addEventListener("resize",updateBubble);
slider.addEventListener("input",updateBubble);
slider.addEventListener("change",()=>{updateBubble();applyJointSlider(name);});
btnNeg.addEventListener("click",()=>{const step=getStep();slider.value=clamp(parseFloat(slider.value)-step,nL,pL);updateBubble();applyJointSlider(name);});
btnPos.addEventListener("click",()=>{const step=getStep();slider.value=clamp(parseFloat(slider.value)+step,nL,pL);updateBubble();applyJointSlider(name);});
return wrap;
}
function renderAux(){const aw=byId("auxWrap");aw.innerHTML="";auxDefs.forEach(d=>aw.appendChild(makeAuxRow(...d)));}renderAux();
/* ===== Slider komutu ===== */
function applyJointSlider(name){const s=byId(`${name}_slider`);const target=parseFloat(s.value)||0;send(`JOG_ABS ${name} ${target.toFixed(3)}`);}
/* ===== Feedback parse ===== */
function handleFeedback(line){
appendLog(line);
if(typeof line!=="string"||!line.startsWith("FB:"))return;
const tokens=line.slice(3).trim().split(/\s+/);
tokens.forEach(tok=>{
const m=tok.match(/^([A-Za-z0-9]+)=(\-?\d+(\.\d+)?)/);
if(!m)return;
const key=m[1];const val=parseFloat(m[2]);
if(/^J[1-9]$/.test(key)){
const cur=byId(`${key}_cur`);
const sld=byId(`${key}_slider`);
const bbl=byId(`${key}_bbl`);
if(cur)cur.value=val.toFixed(3);
if(sld){sld.value=val;if(bbl)attachThumbBubble(sld,bbl);}
}else if(["X","Y","Z","Rx","Ry","Rz"].includes(key)){
const inp=byId(`AX_${key}`);if(inp)inp.value=val.toFixed(3);
}
});
}
/* ===== Responsive taşıma: Logic&IO'yu PV altına (telefon) ===== */
(function(){const BREAK=1280;const layout=document.querySelector('.layout');const rightCol=layout?.children[1];const progCard=document.getElementById('progCard');const logicIO=document.getElementById('logicIO');if(!layout||!rightCol||!progCard||!logicIO)return;let placedLeft=false;function moveLogic(){const w=window.innerWidth;if(w<=BREAK&&!placedLeft){progCard.insertAdjacentElement('afterend',logicIO);placedLeft=true;}else if(w>BREAK&&placedLeft){rightCol.appendChild(logicIO);placedLeft=false;}setTimeout(syncListHeight,0);}window.addEventListener('resize',moveLogic);moveLogic();})();
/* ================== G-code viewer dummy ================== */
(function initGcodeView(){
const el = byId('gcodeView'); if(!el) return;
const lines = [];
for(let i=1;i<=400;i++){ lines.push(`N${String(i).padStart(4,'0')} G01 X${i} Y${i/2} Z${i/3}`); }
el.innerHTML = lines.map(s=>`<div>${s}</div>`).join('');
})();
/* ================== Error Log seed & clear ================== */
(function initElog(){
const el = document.getElementById('bigLog'); if(!el) return;
const seed = ['##BEGINNING OF LOG##','UI Ready'];
el.innerHTML = seed.map(s=>`<div>${s}</div>`).join('');
})();
function clearLog(){
const el = document.getElementById('bigLog');
const first = el.firstChild ? el.firstChild.textContent : '##BEGINNING OF LOG##';
el.innerHTML = `<div>${first}</div>`;
appendLog && appendLog('// Log cleared');
}
/* Küçük yardımcı */
function logCall(msg){appendLog(`// ${msg}`);}
function setCom(){logCall('SetCom Teensy');}
function setCom2(){logCall('SetCom IO');}
function lightTheme(){document.body.style.filter='invert(0)';logCall('Theme Light');}
function darkTheme(){document.body.style.filter='invert(0)';logCall('Theme Dark');}
function SaveAndApplyCalibration(){logCall('SaveAndApplyCalibration');}
function import_stl_file(){logCall('Import STL');}
function update_stl_transform(){logCall('Update STL');}
function calRobotJ1(){} function calRobotJ2(){} function calRobotJ3(){} function calRobotJ4(){} function calRobotJ5(){} function calRobotJ6(){} function CalZeroPos(){} function CalRestPos(){} function zeroAxis7(){} function calRobotJ7(){} function zeroAxis8(){} function calRobotJ8(){} function zeroAxis9(){} function calRobotJ9(){}
</script>
</body>
</html>
// ================ HTML buraya kadar ===================
)rawliteral";
// ===================== setup / loop =====================
void setup() {
Serial.begin(115200);
// ---- Teensy seri hattını başlat ----
TEENSY.begin(115200, SERIAL_8N1, RXD2, TXD2);
// ---- Wi-Fi AP ----
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASS);
Serial.print("// ---- AP IP: "); Serial.println(WiFi.softAPIP());
// ---- WebSocket ----
ws.onEvent(onWsEvent);
server.addHandler(&ws);
// ---- Ana sayfa ----
server.on("/", HTTP_GET, [](AsyncWebServerRequest* req){
req->send_P(200, "text/html; charset=UTF-8", INDEX_HTML);
});
// ---- CORS gerekiyorsa ----
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
// ---- Sunucu başlat ----
server.begin();
Serial.println("// ---- HTTP server started ----");
}
void loop() {
ws.cleanupClients();
// ---- Teensy'den gelen satırları oku ve UI'ye yayınla ----
while (TEENSY.available()) {
char c = (char)TEENSY.read();
if (c == '\r') continue;
if (c == '\n') {
if (lineBuf.length()) {
lastResponse = lineBuf;
ws.textAll(lastResponse); // -> Tarayıcı Log + bigWidget
lineBuf = "";
}
} else {
if (lineBuf.length() < 4096) lineBuf += c;
}
}
}