|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="ko">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8"/>
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
|
|
|
<title>Device Configuration UI</title>
|
|
|
|
|
<link rel="stylesheet" href="style.css"/>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<!-- ───── Sidebar ───── -->
|
|
|
|
|
<aside class="sidebar">
|
|
|
|
|
<h2>Configuration</h2>
|
|
|
|
|
<details class="nav" open>
|
|
|
|
|
<summary>Device configuration</summary>
|
|
|
|
|
<div class="nav-items">
|
|
|
|
|
<button data-page="ssid">SSID</button>
|
|
|
|
|
<button data-page="network">Network & Server</button>
|
|
|
|
|
<button data-page="io">I/O</button>
|
|
|
|
|
</div>
|
|
|
|
|
</details>
|
|
|
|
|
<details class="nav">
|
|
|
|
|
<summary>Protocol configuration</summary>
|
|
|
|
|
<div class="nav-items">
|
|
|
|
|
<button data-page="reg">Config</button>
|
|
|
|
|
<button data-page="modbus" id="modbusSidebarBtn" style="display: none">MODBUS</button>
|
|
|
|
|
<button data-page="opcua" id="opcUaSidebarBtn" style="display: none">OPC_UA</button>
|
|
|
|
|
<button data-page="can" id="canSidebarBtn" style="display: none">CAN</button>
|
|
|
|
|
</div>
|
|
|
|
|
</details>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<!-- ───── Content ───── -->
|
|
|
|
|
<div class="content">
|
|
|
|
|
<header class="topbar">
|
|
|
|
|
<span id="page-title">SSID</span>
|
|
|
|
|
<button class="save-file">Save</button>
|
|
|
|
|
</header>
|
|
|
|
|
<main class="board">
|
|
|
|
|
|
|
|
|
|
<!-- ▽ SSID PAGE ▽ -->
|
|
|
|
|
<section id="page-ssid" class="page">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h3>SSID List</h3>
|
|
|
|
|
<template id="ssid-template">
|
|
|
|
|
<div class="ssid-row">
|
|
|
|
|
<input class="ssid-name" placeholder="Name"/>
|
|
|
|
|
<input class="ssid-pw" type="password" placeholder="Password"/>
|
|
|
|
|
<button type="button" class="toggle-pw">show</button>
|
|
|
|
|
<select>
|
|
|
|
|
<option value="none">NONE</option>
|
|
|
|
|
<option value="wpa/wpa2">WPA/WPA2</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div id="ssid-list"></div>
|
|
|
|
|
<button id="add-ssid" class="icon-button" title="add SSID">➕</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="actions">
|
|
|
|
|
<div class="file-upload-wrapper-device"
|
|
|
|
|
data-file-type="device"
|
|
|
|
|
data-expected-filename="deviceConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/device"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-device">
|
|
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-device">Load Local Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-device-file">Download Local Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ▽ NETWORK PAGE ▽ -->
|
|
|
|
|
<section id="page-network" class="page" style="display:none">
|
|
|
|
|
<div class="card" style="margin-bottom:32px">
|
|
|
|
|
<h3>Network</h3>
|
|
|
|
|
<div class="grid-3-col" style="margin-bottom:16px">
|
|
|
|
|
<div class="radio-group">
|
|
|
|
|
<label class="radio-label"><input type="radio" name="ipMode" value="static"> Static</label>
|
|
|
|
|
<label class="radio-label"><input type="radio" name="ipMode" value="dhcp"> DHCP</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="grid-3-col"><span>IP</span><input id="wifi_ip" /></div>
|
|
|
|
|
<div class="grid-3-col"><span>SUBNET</span><input id="wifi_netmask" /></div>
|
|
|
|
|
<div class="grid-3-col"><span>GATEWAY</span><input id="wifi_gateway" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="grid-3-col"><span>DNS #1</span><input id="wifi_dns1" /></div>
|
|
|
|
|
<div class="grid-3-col"><span>DNS #2</span><input id="wifi_dns2" /></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h3>Server</h3>
|
|
|
|
|
<div class="grid-3-col"><strong></strong><span style="text-align:center">IP</span><span
|
|
|
|
|
style="text-align:center">Port</span></div>
|
|
|
|
|
<div class="grid-3-col"><span>TIoT Server</span> <input id="protocol_server_ip" placeholder="ex)123.123.123.123"/><input
|
|
|
|
|
id="protocol_server_port"/></div>
|
|
|
|
|
<div class="grid-3-col"><span>Update Server</span><input id="update_server_ip" placeholder="ex)123.123.123.123"/><input
|
|
|
|
|
id="update_server_port"/></div>
|
|
|
|
|
<div class="grid-3-col"><span>DGPS Server</span> <input id="rtcm_server_ip" placeholder="ex)123.123.123.123"/><input
|
|
|
|
|
id="rtcm_server_port"/></div>
|
|
|
|
|
<div class="grid-3-col"><span>OPC-UA</span> <input id="opc_ua_server_ip" placeholder="ex)123.123.123.123"/><input
|
|
|
|
|
id="opc_ua_server_port"/></div>
|
|
|
|
|
<div class="grid-3-col"><span>MODBUS</span> <input id="modbus_server_ip" placeholder="ex)123.123.123.123"/><input
|
|
|
|
|
id="modbus_server_port"/></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="actions">
|
|
|
|
|
<div class="file-upload-wrapper-device"
|
|
|
|
|
data-file-type="device"
|
|
|
|
|
data-expected-filename="deviceConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/device"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-device">
|
|
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-device">Load Local Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-device-file">Download Local Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ▽ I/O PAGE ▽ -->
|
|
|
|
|
<section id="page-io" class="page" style="display:none">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="io-top">
|
|
|
|
|
<!-- Ethernet -->
|
|
|
|
|
<div class="io-group">
|
|
|
|
|
<h4>Ethernet</h4>
|
|
|
|
|
<div class="io-fieldset">
|
|
|
|
|
<div class="io-row"><span class="label-inline">IP</span><span class="colon">:</span><input
|
|
|
|
|
id="eth_ip"></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">SUBNET</span><span
|
|
|
|
|
class="colon">:</span><input id="eth_netmask"></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">GATEWAY</span><span
|
|
|
|
|
class="colon">:</span><input id="eth_gateway"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- LTE -->
|
|
|
|
|
<div class="io-group">
|
|
|
|
|
<h4>LTE</h4>
|
|
|
|
|
<div class="io-fieldset">
|
|
|
|
|
<div class="io-row"><span class="label-inline">IP</span><span class="colon">:</span><input
|
|
|
|
|
id="lte_ip"></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">PORT</span><span class="colon">:</span><input
|
|
|
|
|
id="lte_port"></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">NETMASK</span><span
|
|
|
|
|
class="colon">:</span><input id="lte_netmask"></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">GATEWAY</span><span
|
|
|
|
|
class="colon">:</span><input id="lte_gateway"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="io-bot">
|
|
|
|
|
<!-- RS-485 -->
|
|
|
|
|
<div class="io-group">
|
|
|
|
|
<h4>MODBUS 422 / 485</h4>
|
|
|
|
|
<div class="io-fieldset">
|
|
|
|
|
<div class="io-row"><span class="label-inline">Mode</span><span class="colon">:</span>
|
|
|
|
|
<select id="rs485_mode">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="half">half</option>
|
|
|
|
|
<option value="full">full</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">Speed</span><span
|
|
|
|
|
class="colon">:</span><select id="rs485_baudrate">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="9600">9600</option>
|
|
|
|
|
<option value="115200">115200</option>
|
|
|
|
|
</select></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">Parity</span><span
|
|
|
|
|
class="colon">:</span><select id="rs485_parity">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="odd">ODD</option>
|
|
|
|
|
<option value="even">EVEN</option>
|
|
|
|
|
</select></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">Data bits</span><span
|
|
|
|
|
class="colon">:</span><input id="rs485_data_bits"></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">Stop bits</span><span
|
|
|
|
|
class="colon">:</span><input id="rs485_stop_bits"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- CAN -->
|
|
|
|
|
<div class="io-group">
|
|
|
|
|
<h4>CAN / IMU Re-map</h4>
|
|
|
|
|
<div class="io-fieldset">
|
|
|
|
|
<div class="io-row"><span class="label-inline">Can Type</span><span class="colon">:</span>
|
|
|
|
|
<select id="can_bus_type">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="extended">extended</option>
|
|
|
|
|
<option value="standard">standard</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="io-row"><span class="label-inline">Can Speed</span><span class="colon">:</span>
|
|
|
|
|
<select id="can_baudrate">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="1000">100kb/s</option>
|
|
|
|
|
<option value="2500">250kb/s</option>
|
|
|
|
|
<option value="5000">500kb/s</option>
|
|
|
|
|
<option value="10000">1M/s</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">imu re-map x</span><span class="colon">:</span>
|
|
|
|
|
<select id="imu_remap_x">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="x">x</option>
|
|
|
|
|
<option value="y">y</option>
|
|
|
|
|
<option value="z">z</option>
|
|
|
|
|
<option value="-x">-x</option>
|
|
|
|
|
<option value="-y">-y</option>
|
|
|
|
|
<option value="-z">-z</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">imu re-map y</span><span
|
|
|
|
|
class="colon">:</span><select id="imu_remap_y">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="x">x</option>
|
|
|
|
|
<option value="y">y</option>
|
|
|
|
|
<option value="z">z</option>
|
|
|
|
|
<option value="-x">-x</option>
|
|
|
|
|
<option value="-y">-y</option>
|
|
|
|
|
<option value="-z">-z</option>
|
|
|
|
|
</select></div>
|
|
|
|
|
<div class="io-row"><span class="label-inline">imu re-map z</span><span
|
|
|
|
|
class="colon">:</span><select id="imu_remap_z">
|
|
|
|
|
<option value="">NONE</option>
|
|
|
|
|
<option value="x">x</option>
|
|
|
|
|
<option value="y">y</option>
|
|
|
|
|
<option value="z">z</option>
|
|
|
|
|
<option value="-x">-x</option>
|
|
|
|
|
<option value="-y">-y</option>
|
|
|
|
|
<option value="-z">-z</option>
|
|
|
|
|
</select></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="actions">
|
|
|
|
|
<div class="file-upload-wrapper-device"
|
|
|
|
|
data-file-type="device"
|
|
|
|
|
data-expected-filename="deviceConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/device"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-device">
|
|
|
|
|
<input type="file" class="upload-input-device" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-device">Load Local Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-device-file">Download Local Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ▽ Protocol PAGE (placeholder) ▽ -->
|
|
|
|
|
<section id="page-reg" class="page" style="display:none">
|
|
|
|
|
<div class="card"><h3>Config</h3>
|
|
|
|
|
<div class="io-group">
|
|
|
|
|
<div class="reg-io-grid">
|
|
|
|
|
<div class="io-fieldset">
|
|
|
|
|
<div class="io-row"><span class="label">Device type</span>
|
|
|
|
|
<select id="reg_device_type">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="TIOT">TIOT</option>
|
|
|
|
|
<option value="RTLS">RTLS</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">Version</span><input id="reg_version"></div>
|
|
|
|
|
<div class="io-row"><span class="label">Equipment</span>
|
|
|
|
|
<select id="reg_equipment">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="ITV">ITV</option>
|
|
|
|
|
<option value="RS">RS</option>
|
|
|
|
|
<option value="ECH">ECH</option>
|
|
|
|
|
<option value="RTG">RTG</option>
|
|
|
|
|
<option value="RMG">RMG</option>
|
|
|
|
|
<option value="MHC">MHC</option>
|
|
|
|
|
<option value="STS">STS</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">Equipment ID</span><input id="reg_equipment_id">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">MEID</span><input id="reg_meid"></div>
|
|
|
|
|
<div class="io-row"><span class="label">Protocol</span>
|
|
|
|
|
<select id="reg_protocol">
|
|
|
|
|
<option value="NONE">NONE</option>
|
|
|
|
|
<option value="MODBUS">MODBUS</option>
|
|
|
|
|
<option value="OPC_UA">OPC_UA</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">heading_on</span>
|
|
|
|
|
<select id="reg_heading_on">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="on">on</option>
|
|
|
|
|
<option value="off">off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">heading_imu_on</span>
|
|
|
|
|
<select id="reg_heading_imu_on">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="on">on</option>
|
|
|
|
|
<option value="off">off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-fieldset">
|
|
|
|
|
<div class="io-row"><span class="label">DR On/Off</span>
|
|
|
|
|
<select id="reg_dr_on_off">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="on">on</option>
|
|
|
|
|
<option value="off">off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">Speed Data Source</span>
|
|
|
|
|
<select id="reg_speed_data_source">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="CAN">CAN</option>
|
|
|
|
|
<option value="GPS">GPS</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">2Byte Order</span>
|
|
|
|
|
<select id="reg_two_byte_order">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="little">little</option>
|
|
|
|
|
<option value="big">big</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">4Byte Order</span>
|
|
|
|
|
<select id="reg_four_byte_order">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="little">little</option>
|
|
|
|
|
<option value="littleSwap">little swap</option>
|
|
|
|
|
<option value="bigSwap">big swap</option>
|
|
|
|
|
<option value="big">big</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">CAN Input</span>
|
|
|
|
|
<select id="reg_can_input">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="on">on</option>
|
|
|
|
|
<option value="off">off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">fixmode_on</span>
|
|
|
|
|
<select id="reg_fixmode_on">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="on">on</option>
|
|
|
|
|
<option value="off">off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row"><span class="label">Analog Input Level</span>
|
|
|
|
|
<select id="reg_analog_input_level">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="2">2</option>
|
|
|
|
|
<option value="4">4</option>
|
|
|
|
|
<option value="6">6</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row checkbox-group">
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="ai0"/>AI0
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="ai1"/>AI1
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="ai2"/>AI2
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="ai3"/>AI3
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="io-row checkbox-group">
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="di0"/>DI0
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="di1"/>DI1
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="di2"/>DI2
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox-with-text">
|
|
|
|
|
<input type="checkbox" id="di3"/>DI3
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="config-actions">
|
|
|
|
|
<div class="file-upload-wrapper-protocol"
|
|
|
|
|
data-file-type="protocol"
|
|
|
|
|
data-expected-filename="protocolConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/protocol"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol">
|
|
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-protocol-file">Download Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<template id="modbus-row-template">
|
|
|
|
|
<tr>
|
|
|
|
|
<td><span class="field-display"></span></td>
|
|
|
|
|
<td><span class="addr-display"></span></td>
|
|
|
|
|
<td><span class="idt-display"></span></td>
|
|
|
|
|
<td><span class="odt-display"></span></td>
|
|
|
|
|
<td><span class="dv-display"></span></td>
|
|
|
|
|
<td><span class="shift-display"></span></td>
|
|
|
|
|
<td><span class="expr-display"></span></td>
|
|
|
|
|
<td><span class="mask-display"></span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<section id="page-modbus" class="page" style="display:none;">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h3>Modbus Configuration</h3>
|
|
|
|
|
<div class="modbus-table-container">
|
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Field</th>
|
|
|
|
|
<th>Address</th>
|
|
|
|
|
<th>IDT</th>
|
|
|
|
|
<th>ODT</th>
|
|
|
|
|
<th>Default Value</th>
|
|
|
|
|
<th>Shift</th>
|
|
|
|
|
<th>Expression</th>
|
|
|
|
|
<th>Mask</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="modbus-field-list">
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="modbus-input-area">
|
|
|
|
|
<label>Field</label>
|
|
|
|
|
<input type="text" id="modbus_field_input" placeholder="Enter field name">
|
|
|
|
|
<label class="nowrap">idt (Input Data Type)</label>
|
|
|
|
|
<select id="modbus_idt_input">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="integer">integer</option>
|
|
|
|
|
<option value="float64">float64</option>
|
|
|
|
|
<option value="boolean">boolean</option>
|
|
|
|
|
<option value="unsignedInteger">unsignedInteger</option>
|
|
|
|
|
<option value="short">short</option>
|
|
|
|
|
<option value="unsignedShort">unsignedShort</option>
|
|
|
|
|
</select>
|
|
|
|
|
<label>addr</label>
|
|
|
|
|
<input type="text" id="modbus_addr_input" placeholder="Enter address">
|
|
|
|
|
<label class="nowrap">odt (Output Data Type)</label>
|
|
|
|
|
<select id="modbus_odt_input">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="integer">integer</option>
|
|
|
|
|
<option value="float64">float64</option>
|
|
|
|
|
<option value="boolean">boolean</option>
|
|
|
|
|
<option value="string">string</option>
|
|
|
|
|
</select>
|
|
|
|
|
<label>shift</label>
|
|
|
|
|
<input type="text" id="modbus_shift_input" placeholder="Enter shift value">
|
|
|
|
|
<label class="nowrap">dv</label>
|
|
|
|
|
<input type="text" id="modbus_dv_input" placeholder="Enter default value">
|
|
|
|
|
<label>expr</label>
|
|
|
|
|
<input type="text" id="modbus_expr_input" placeholder="Enter expression">
|
|
|
|
|
<label class="nowrap">mask</label>
|
|
|
|
|
<input type="text" id="modbus_mask_input" placeholder="Enter mask">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="modbus-actions">
|
|
|
|
|
<button id="modbus_add_btn">Add</button>
|
|
|
|
|
<button id="modbus_modify_btn">Modify</button>
|
|
|
|
|
<button id="modbus_delete_btn">Delete</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="modbus-bottom-actions">
|
|
|
|
|
<div class="file-upload-wrapper-protocol"
|
|
|
|
|
data-file-type="protocol"
|
|
|
|
|
data-expected-filename="protocolConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/protocol"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol">
|
|
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-protocol-file">Download Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<template id="opcua-row-template">
|
|
|
|
|
<tr class="opcua-row">
|
|
|
|
|
<td><span class="opcua-field-display"></span></td>
|
|
|
|
|
<td><span class="opcua-addr-display"></span></td>
|
|
|
|
|
<td><span class="opcua-ns-display"></span></td>
|
|
|
|
|
<td><span class="opcua-odt-display"></span></td>
|
|
|
|
|
<td><span class="opcua-dv-display"></span></td>
|
|
|
|
|
<td><span class="opcua-shift-display"></span></td>
|
|
|
|
|
<td><span class="opcua-expr-display"></span></td>
|
|
|
|
|
<td><span class="opcua-mask-display"></span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<section id="page-opcua" class="page" style="display:none;">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h3>OPC_UA Configuration</h3>
|
|
|
|
|
<div class="opcua-table-container">
|
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Field</th>
|
|
|
|
|
<th>Address</th>
|
|
|
|
|
<th>NS</th>
|
|
|
|
|
<th>ODT</th>
|
|
|
|
|
<th>Default Value</th>
|
|
|
|
|
<th>Shift</th>
|
|
|
|
|
<th>Expression</th>
|
|
|
|
|
<th>Mask</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="opcua-field-list">
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="opcua-input-area">
|
|
|
|
|
<label>field</label>
|
|
|
|
|
<input type="text" id="opcua_field_input" placeholder="Enter field name">
|
|
|
|
|
<label class="nowrap">ns(NameSpace)</label>
|
|
|
|
|
<input type="text" id="opcua_ns_input" placeholder="Enter NS">
|
|
|
|
|
<label>addr(address)</label>
|
|
|
|
|
<input type="text" id="opcua_addr_input" placeholder="Enter address">
|
|
|
|
|
<label class="nowrap">odt(Output Data Type)</label>
|
|
|
|
|
<select id="opcua_odt_input">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="integer">integer</option>
|
|
|
|
|
<option value="float64">float64</option>
|
|
|
|
|
<option value="boolean">boolean</option>
|
|
|
|
|
<option value="string">string</option>
|
|
|
|
|
</select>
|
|
|
|
|
<label>shift</label>
|
|
|
|
|
<input type="text" id="opcua_shift_input" placeholder="Enter shift value">
|
|
|
|
|
<label class="nowrap">dv(Default Value)</label>
|
|
|
|
|
<input type="text" id="opcua_dv_input" placeholder="Enter default value">
|
|
|
|
|
<label>expr</label>
|
|
|
|
|
<input type="text" id="opcua_expr_input" placeholder="Enter expression">
|
|
|
|
|
<label class="nowrap">mask</label>
|
|
|
|
|
<input type="text" id="opcua_mask_input" placeholder="Enter mask">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="opcua-actions">
|
|
|
|
|
<button id="opcua_add_btn">Add</button>
|
|
|
|
|
<button id="opcua_modify_btn">Modify</button>
|
|
|
|
|
<button id="opcua_delete_btn">Delete</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="opcUa-bottom-actions">
|
|
|
|
|
<div class="file-upload-wrapper-protocol"
|
|
|
|
|
data-file-type="protocol"
|
|
|
|
|
data-expected-filename="protocolConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/protocol"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol">
|
|
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-protocol-file">Download Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<template id="can-row-template">
|
|
|
|
|
<tr class="can-row">
|
|
|
|
|
<td><span class="can-field-display"></span></td>
|
|
|
|
|
<td><span class="can-id-display"></span></td>
|
|
|
|
|
<td><span class="can-odt-display"></span></td>
|
|
|
|
|
<td><span class="can-dv-display"></span></td>
|
|
|
|
|
<td><span class="can-shift-display"></span></td>
|
|
|
|
|
<td><span class="can-expr-display"></span></td>
|
|
|
|
|
<td><span class="can-mask-display"></span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<section id="page-can" class="page" style="display:none;">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h3>CAN Configuration</h3>
|
|
|
|
|
<div class="can-table-container">
|
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Field</th>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>ODT</th>
|
|
|
|
|
<th>Default Value</th>
|
|
|
|
|
<th>Shift</th>
|
|
|
|
|
<th>Expression</th>
|
|
|
|
|
<th>Mask</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="can-field-list">
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="can-input-area">
|
|
|
|
|
<label>Field</label>
|
|
|
|
|
<input type="text" id="can_field_input" placeholder="Enter field name">
|
|
|
|
|
<label>ID</label>
|
|
|
|
|
<input type="text" id="can_id_input" placeholder="Enter ID">
|
|
|
|
|
<label>odt (Output Data Type)</label>
|
|
|
|
|
<select id="can_odt_input">
|
|
|
|
|
<option selected disabled hidden="hidden"></option>
|
|
|
|
|
<option value="integer">integer</option>
|
|
|
|
|
<option value="float64">float64</option>
|
|
|
|
|
<option value="boolean">boolean</option>
|
|
|
|
|
<option value="string">string</option>
|
|
|
|
|
</select>
|
|
|
|
|
<label>dv(Default Value)</label>
|
|
|
|
|
<input type="text" id="can_dv_input" placeholder="Enter default value">
|
|
|
|
|
<label>shift</label>
|
|
|
|
|
<input type="text" id="can_shift_input" placeholder="Enter shift value">
|
|
|
|
|
<label>expr</label>
|
|
|
|
|
<input type="text" id="can_expr_input" placeholder="Enter expression">
|
|
|
|
|
<label>mask</label>
|
|
|
|
|
<input type="text" id="can_mask_input" placeholder="Enter mask">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="can-actions">
|
|
|
|
|
<button id="can_add_btn">Add</button>
|
|
|
|
|
<button id="can_modify_btn">Modify</button>
|
|
|
|
|
<button id="can_delete_btn">Delete</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="can-bottom-actions">
|
|
|
|
|
<div class="file-upload-wrapper-protocol"
|
|
|
|
|
data-file-type="protocol"
|
|
|
|
|
data-expected-filename="protocolConfig.json"
|
|
|
|
|
data-upload-url="http://localhost:8080/setting/protocol"
|
|
|
|
|
data-get-url="http://localhost:8080/setting/get-protocol">
|
|
|
|
|
<input type="file" class="upload-input-protocol" accept=".json" style="display: none;">
|
|
|
|
|
<button class="select-file-btn-protocol">Load Local Protocol File</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="download-protocol-file">Download Device File</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- ───── Script ───── -->
|
|
|
|
|
<script>
|
|
|
|
|
const ssidList = document.getElementById('ssid-list');
|
|
|
|
|
const ssidTmpl = document.getElementById('ssid-template');
|
|
|
|
|
const data = {
|
|
|
|
|
/* Wi-Fi */
|
|
|
|
|
wifi_static: '', wifi_ip: '', wifi_netmask: '', wifi_gateway: '', wifi_dns1: '', wifi_dns2: '',
|
|
|
|
|
|
|
|
|
|
/* Ethernet */
|
|
|
|
|
eth_ip: '', eth_netmask: '', eth_gateway: '',
|
|
|
|
|
|
|
|
|
|
/* servers */
|
|
|
|
|
protocol_server_ip: '', protocol_server_port: null,
|
|
|
|
|
update_server_ip: '',
|
|
|
|
|
update_server_port: null,
|
|
|
|
|
rtcm_server_ip: '',
|
|
|
|
|
rtcm_server_port: null,
|
|
|
|
|
opc_ua_server_ip: '',
|
|
|
|
|
opc_ua_server_port: null,
|
|
|
|
|
modbus_server_ip: '',
|
|
|
|
|
modbus_server_port: null,
|
|
|
|
|
|
|
|
|
|
/* CAN / RS-485 */
|
|
|
|
|
can_bus_type: '', can_baudrate: null,
|
|
|
|
|
rs485_mode: '', rs485_baudrate: null, rs485_data_bits: null,
|
|
|
|
|
rs485_parity: '', rs485_stop_bits: null,
|
|
|
|
|
|
|
|
|
|
/* IMU (축 + 부호) */
|
|
|
|
|
imu_remap_x: '', imu_remap_y: '', imu_remap_z: '',
|
|
|
|
|
imu_remap_x_sign: '', imu_remap_y_sign: '', imu_remap_z_sign: '',
|
|
|
|
|
|
|
|
|
|
/* LTE 설정 */
|
|
|
|
|
lte_ip: '', lte_netmask: '', lte_gateway: '', lte_port: null,
|
|
|
|
|
/* SSID 목록 */
|
|
|
|
|
WIFI_SSID: [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const configIdMap = {
|
|
|
|
|
'reg_analog_input_level': 'analog_input_level',
|
|
|
|
|
'ai0': 'ai0',
|
|
|
|
|
'ai1': 'ai1',
|
|
|
|
|
'ai2': 'ai2',
|
|
|
|
|
'ai3': 'ai3',
|
|
|
|
|
'di0': 'di0',
|
|
|
|
|
'di1': 'di1',
|
|
|
|
|
'di2': 'di2',
|
|
|
|
|
'di3': 'di3',
|
|
|
|
|
'reg_can_input': 'can_input',
|
|
|
|
|
'reg_device_type': 'device_type',
|
|
|
|
|
'reg_dr_on_off': 'dr_on',
|
|
|
|
|
'reg_equipment': 'equipment',
|
|
|
|
|
'reg_two_byte_order': 'two_byte',
|
|
|
|
|
'reg_four_byte_order': 'four_byte',
|
|
|
|
|
'reg_protocol': 'protocol',
|
|
|
|
|
'reg_speed_data_source': 'speed_data_source',
|
|
|
|
|
'reg_version': 'version',
|
|
|
|
|
'reg_equipment_id': 'equipment_id',
|
|
|
|
|
'reg_meid': 'meid',
|
|
|
|
|
'reg_heading_on': 'heading_on',
|
|
|
|
|
'reg_heading_imu_on': 'heading_imu_on',
|
|
|
|
|
'reg_fixmode_on': 'fixmode_on'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let lastLoadedRegisterData = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const pages = {
|
|
|
|
|
ssid: document.getElementById('page-ssid'),
|
|
|
|
|
network: document.getElementById('page-network'),
|
|
|
|
|
io: document.getElementById('page-io'),
|
|
|
|
|
reg: document.getElementById('page-reg'),
|
|
|
|
|
modbus: document.getElementById('page-modbus'),
|
|
|
|
|
opcua: document.getElementById('page-opcua'),
|
|
|
|
|
can: document.getElementById('page-can')
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*----DOM 인터페이스 초기화----*/
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
const opcUaList = document.getElementById('opcua-field-list');
|
|
|
|
|
const modbusList = document.getElementById('modbus-field-list');
|
|
|
|
|
const fileUploadWrappersDevice = document.querySelectorAll('.file-upload-wrapper-device');
|
|
|
|
|
const modbusAddBtn = document.getElementById('modbus_add_btn');
|
|
|
|
|
const modbusFieldList = document.getElementById('modbus-field-list');
|
|
|
|
|
const modbusRowTemplate = document.getElementById('modbus-row-template');
|
|
|
|
|
const opcUaAddBtn = document.getElementById('opcua_add_btn');
|
|
|
|
|
const opcUaFieldList = document.getElementById('opcua-field-list');
|
|
|
|
|
const opcUaRowTemplate = document.getElementById('opcua-row-template');
|
|
|
|
|
const canRowTemplate = document.getElementById('can-row-template');
|
|
|
|
|
const canFieldList = document.getElementById('can-field-list');
|
|
|
|
|
|
|
|
|
|
// 페이지 로드 시 자동으로 JSON 파일들 로드
|
|
|
|
|
loadInitialData();
|
|
|
|
|
|
|
|
|
|
// Device 파일 업로드
|
|
|
|
|
// 모든 파일 업로드 래퍼 섹션을 가져옵니다.
|
|
|
|
|
fileUploadWrappersDevice.forEach(wrapper => {
|
|
|
|
|
// 데이터 초기화 함수 정의
|
|
|
|
|
|
|
|
|
|
// 현재 래퍼 내에서 input[type="file"]과 버튼을 찾습니다.
|
|
|
|
|
const uploadDeviceFileInput = wrapper.querySelector('.upload-input-device');
|
|
|
|
|
const selectDeviceFileBtn = wrapper.querySelector('.select-file-btn-device');
|
|
|
|
|
|
|
|
|
|
const expectedDeviceFileName = wrapper.dataset.expectedFilename;
|
|
|
|
|
const uploadDeviceUrl = wrapper.dataset.uploadUrl;
|
|
|
|
|
const getDeviceUrl = wrapper.dataset.getUrl;
|
|
|
|
|
const DeviceFileType = wrapper.dataset.fileType;
|
|
|
|
|
|
|
|
|
|
if (!uploadDeviceFileInput || !selectDeviceFileBtn) {
|
|
|
|
|
console.warn(`File upload elements not found in wrapper for type: ${DeviceFileType}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectDeviceFileBtn.addEventListener('click', () => {
|
|
|
|
|
uploadDeviceFileInput.value = '';
|
|
|
|
|
uploadDeviceFileInput.click();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
uploadDeviceFileInput.addEventListener('change', async (event) => {
|
|
|
|
|
if (event.target.files.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectedDeviceFile = event.target.files[0];
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
|
|
|
|
reader.onload = async (e) => {
|
|
|
|
|
try {
|
|
|
|
|
const DeviceFileContent = e.target.result;
|
|
|
|
|
const jsonData = JSON.parse(DeviceFileContent);
|
|
|
|
|
|
|
|
|
|
clearAllInputs();
|
|
|
|
|
const data = getInitialData();
|
|
|
|
|
loadFromData(jsonData);
|
|
|
|
|
|
|
|
|
|
console.log(`Loaded ${DeviceFileType} data from local file`, jsonData);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Error uploading file (${DeviceFileType}):`, error);
|
|
|
|
|
alert(`Error uploading file: ${error.message} (${DeviceFileType})`);
|
|
|
|
|
} finally {
|
|
|
|
|
uploadDeviceFileInput.value = '';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
reader.readAsText(selectedDeviceFile);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Protocol 파일 업로드
|
|
|
|
|
// 모든 파일 업로드 래퍼 섹션을 가져옵니다.
|
|
|
|
|
const fileUploadWrappers = document.querySelectorAll('.file-upload-wrapper-protocol');
|
|
|
|
|
|
|
|
|
|
fileUploadWrappers.forEach(wrapper => {
|
|
|
|
|
// 현재 래퍼 내에서 input[type="file"]과 버튼을 찾습니다.
|
|
|
|
|
const uploadFileInput = wrapper.querySelector('.upload-input-protocol');
|
|
|
|
|
const selectFileBtn = wrapper.querySelector('.select-file-btn-protocol');
|
|
|
|
|
|
|
|
|
|
// 해당 래퍼의 data 속성들을 가져옵니다.
|
|
|
|
|
const uploadUrl = wrapper.dataset.uploadUrl;
|
|
|
|
|
const getUrl = wrapper.dataset.getUrl;
|
|
|
|
|
const fileType = wrapper.dataset.fileType; // 'protocol', 'modbus', 'opcua', 'can'
|
|
|
|
|
|
|
|
|
|
// null 체크 (요소가 존재하지 않을 수도 있는 경우를 대비)
|
|
|
|
|
if (!uploadFileInput || !selectFileBtn) {
|
|
|
|
|
console.warn(`File upload elements not found in wrapper for type: ${fileType}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "파일 선택" 버튼 클릭 시 숨겨진 input[type="file"] 클릭 이벤트 발생
|
|
|
|
|
selectFileBtn.addEventListener('click', () => {
|
|
|
|
|
uploadFileInput.value = ''; // 이전에 선택된 파일 초기화
|
|
|
|
|
uploadFileInput.click();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 파일 선택 시 (change 이벤트 발생) 바로 업로드 로직 시작
|
|
|
|
|
uploadFileInput.addEventListener('change', async (event) => {
|
|
|
|
|
const selectedFile = event.target.files[0];
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
|
|
|
|
if (event.target.files.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reader.onload = async (e) => {
|
|
|
|
|
try {
|
|
|
|
|
const fileContent = e.target.result;
|
|
|
|
|
const jsonData = JSON.parse(fileContent);
|
|
|
|
|
|
|
|
|
|
// 업로드 유효성 검사 로직
|
|
|
|
|
if (!isValidRegisterConfig(jsonData)){ // 예를 들어, isValidModbusConfig(jsonData) 등
|
|
|
|
|
alert(`The contents of the selected file are not in a valid ${fileType} setting file format.`);
|
|
|
|
|
uploadFileInput.value = '';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
loadFromRegisterJson(jsonData,fileType);
|
|
|
|
|
console.log(`Loaded ${fileType} data from local file (not saved).`, jsonData);
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
alert(`Error uploading file: ${error.message} (${fileType})`);
|
|
|
|
|
uploadFileInput.value = '';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
reader.readAsText(selectedFile);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*---저장 버튼에 대한 로직---*/
|
|
|
|
|
document.querySelectorAll('.save-file').forEach(btn=>btn.addEventListener('click', () => {saveToConfig()}))
|
|
|
|
|
|
|
|
|
|
/*---device 다운로드 버튼에 대한 로직---*/
|
|
|
|
|
document.querySelectorAll('.download-device-file').forEach(btn=>{btn.addEventListener('click', () => {
|
|
|
|
|
downloadDeviceJson(collectDeviceData(data));
|
|
|
|
|
})})
|
|
|
|
|
|
|
|
|
|
/*---protocol 다운로드 버튼에 대한 로직---*/
|
|
|
|
|
document.querySelectorAll('.download-protocol-file').forEach(btn=>{btn.addEventListener('click', () => {
|
|
|
|
|
downloadProtocolJson(collectProtocolData(data));
|
|
|
|
|
})})
|
|
|
|
|
|
|
|
|
|
document.getElementById('reg_can_input')?.addEventListener('change', toggleProtocolMenus);
|
|
|
|
|
|
|
|
|
|
/*---modbus 테이블에 행 추가 로직---*/
|
|
|
|
|
document.getElementById('modbus-field-list').addEventListener('click', function (e) {
|
|
|
|
|
let tr = e.target.closest('tr');
|
|
|
|
|
if (!tr) return;
|
|
|
|
|
|
|
|
|
|
Array.from(this.children).forEach(row => row.classList.remove('selected-row'));
|
|
|
|
|
tr.classList.add('selected-row');
|
|
|
|
|
selectedRow = tr;
|
|
|
|
|
|
|
|
|
|
document.getElementById('modbus_field_input').value = tr.querySelector('.field-display').textContent;
|
|
|
|
|
document.getElementById('modbus_addr_input').value = tr.querySelector('.addr-display').textContent;
|
|
|
|
|
document.getElementById('modbus_idt_input').value = tr.querySelector('.idt-display').textContent;
|
|
|
|
|
document.getElementById('modbus_odt_input').value = tr.querySelector('.odt-display').textContent;
|
|
|
|
|
document.getElementById('modbus_dv_input').value = tr.querySelector('.dv-display').textContent;
|
|
|
|
|
document.getElementById('modbus_shift_input').value = tr.querySelector('.shift-display').textContent;
|
|
|
|
|
document.getElementById('modbus_expr_input').value = tr.querySelector('.expr-display').textContent;
|
|
|
|
|
document.getElementById('modbus_mask_input').value = tr.querySelector('.mask-display').textContent;
|
|
|
|
|
});
|
|
|
|
|
// 처음에 행이 없으면 빈 행을 하나 추가
|
|
|
|
|
if (modbusList.children.length === 0) {
|
|
|
|
|
const modbusRowTemplate = document.getElementById('modbus-row-template');
|
|
|
|
|
if (modbusRowTemplate) {
|
|
|
|
|
const newRow = modbusRowTemplate.content.cloneNode(true);
|
|
|
|
|
modbusList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let selectedRow = null;
|
|
|
|
|
/*-------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*---opcua 테이블에 행 추가 로직---*/
|
|
|
|
|
document.getElementById('opcua-field-list').addEventListener('click', function (e) {
|
|
|
|
|
let tr = e.target.closest('tr');
|
|
|
|
|
if (!tr) return;
|
|
|
|
|
|
|
|
|
|
Array.from(this.children).forEach(row => row.classList.remove('selected-row'));
|
|
|
|
|
tr.classList.add('selected-row');
|
|
|
|
|
selectedRow = tr;
|
|
|
|
|
|
|
|
|
|
document.getElementById('opcua_field_input').value = tr.querySelector('.opcua-field-display').textContent;
|
|
|
|
|
document.getElementById('opcua_addr_input').value = tr.querySelector('.opcua-addr-display').textContent;
|
|
|
|
|
document.getElementById('opcua_ns_input').value = tr.querySelector('.opcua-ns-display').textContent;
|
|
|
|
|
document.getElementById('opcua_odt_input').value = tr.querySelector('.opcua-odt-display').textContent;
|
|
|
|
|
document.getElementById('opcua_dv_input').value = tr.querySelector('.opcua-dv-display').textContent;
|
|
|
|
|
document.getElementById('opcua_shift_input').value = tr.querySelector('.opcua-shift-display').textContent;
|
|
|
|
|
document.getElementById('opcua_expr_input').value = tr.querySelector('.opcua-expr-display').textContent;
|
|
|
|
|
document.getElementById('opcua_mask_input').value = tr.querySelector('.opcua-mask-display').textContent;
|
|
|
|
|
});
|
|
|
|
|
// 처음에 행이 없으면 빈 행을 하나 추가
|
|
|
|
|
if (opcUaList.children.length === 0) {
|
|
|
|
|
const opcUaRowTemplate = document.getElementById('opcua-row-template');
|
|
|
|
|
if (opcUaRowTemplate) {
|
|
|
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true);
|
|
|
|
|
opcUaList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*---can 테이블에 행 추가 로직---*/
|
|
|
|
|
document.getElementById('can-field-list').addEventListener('click', function (e) {
|
|
|
|
|
let tr = e.target.closest('tr');
|
|
|
|
|
if (!tr) return;
|
|
|
|
|
|
|
|
|
|
Array.from(this.children).forEach(row => row.classList.remove('selected-row'));
|
|
|
|
|
tr.classList.add('selected-row');
|
|
|
|
|
selectedRow = tr;
|
|
|
|
|
|
|
|
|
|
document.getElementById('can_field_input').value = tr.querySelector('.can-field-display').textContent;
|
|
|
|
|
document.getElementById('can_id_input').value = tr.querySelector('.can-id-display').textContent;
|
|
|
|
|
document.getElementById('can_odt_input').value = tr.querySelector('.can-odt-display').textContent;
|
|
|
|
|
document.getElementById('can_dv_input').value = tr.querySelector('.can-dv-display').textContent;
|
|
|
|
|
document.getElementById('can_shift_input').value = tr.querySelector('.can-shift-display').textContent;
|
|
|
|
|
document.getElementById('can_expr_input').value = tr.querySelector('.can-expr-display').textContent;
|
|
|
|
|
document.getElementById('can_mask_input').value = tr.querySelector('.can-mask-display').textContent;
|
|
|
|
|
});
|
|
|
|
|
// 처음에 행이 없으면 빈 행을 하나 추가
|
|
|
|
|
if (canFieldList.children.length === 0) {
|
|
|
|
|
const canRowTemplate = document.getElementById('can-row-template');
|
|
|
|
|
if (canRowTemplate) {
|
|
|
|
|
const newRow = canRowTemplate.content.cloneNode(true);
|
|
|
|
|
opcUaList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modbusAddBtn.addEventListener('click', () => {
|
|
|
|
|
const field = document.getElementById('modbus_field_input').value;
|
|
|
|
|
const addr = document.getElementById('modbus_addr_input').value;
|
|
|
|
|
const idt = document.getElementById('modbus_idt_input').value;
|
|
|
|
|
const odt = document.getElementById('modbus_odt_input').value;
|
|
|
|
|
const defaultValue = document.getElementById('modbus_dv_input').value;
|
|
|
|
|
const shift = document.getElementById('modbus_shift_input').value;
|
|
|
|
|
const expr = document.getElementById('modbus_expr_input').value;
|
|
|
|
|
const mask = document.getElementById('modbus_mask_input').value;
|
|
|
|
|
|
|
|
|
|
const firstRow = modbusFieldList.querySelector('tr');
|
|
|
|
|
|
|
|
|
|
// 첫 행이 비어있다면 그 자리에 값을 넣기
|
|
|
|
|
if (firstRow && firstRow.querySelector('.field-display').textContent.trim() === '') {
|
|
|
|
|
firstRow.querySelector('.field-display').textContent = field;
|
|
|
|
|
firstRow.querySelector('.addr-display').textContent = addr;
|
|
|
|
|
firstRow.querySelector('.idt-display').textContent = idt;
|
|
|
|
|
firstRow.querySelector('.odt-display').textContent = odt;
|
|
|
|
|
firstRow.querySelector('.dv-display').textContent = defaultValue;
|
|
|
|
|
firstRow.querySelector('.shift-display').textContent = shift;
|
|
|
|
|
firstRow.querySelector('.expr-display').textContent = expr;
|
|
|
|
|
firstRow.querySelector('.mask-display').textContent = mask;
|
|
|
|
|
} else {
|
|
|
|
|
// 새로운 행을 추가
|
|
|
|
|
const newRow = modbusRowTemplate.content.cloneNode(true);
|
|
|
|
|
newRow.querySelector('.field-display').textContent = field;
|
|
|
|
|
newRow.querySelector('.addr-display').textContent = addr;
|
|
|
|
|
newRow.querySelector('.idt-display').textContent = idt;
|
|
|
|
|
newRow.querySelector('.odt-display').textContent = odt;
|
|
|
|
|
newRow.querySelector('.dv-display').textContent = defaultValue;
|
|
|
|
|
newRow.querySelector('.shift-display').textContent = shift;
|
|
|
|
|
newRow.querySelector('.expr-display').textContent = expr;
|
|
|
|
|
newRow.querySelector('.mask-display').textContent = mask;
|
|
|
|
|
|
|
|
|
|
modbusFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 입력 필드 초기화
|
|
|
|
|
document.getElementById('modbus_field_input').value = '';
|
|
|
|
|
document.getElementById('modbus_addr_input').value = '';
|
|
|
|
|
document.getElementById('modbus_idt_input').value = '';
|
|
|
|
|
document.getElementById('modbus_odt_input').value = '';
|
|
|
|
|
document.getElementById('modbus_dv_input').value = '';
|
|
|
|
|
document.getElementById('modbus_shift_input').value = '';
|
|
|
|
|
document.getElementById('modbus_expr_input').value = '';
|
|
|
|
|
document.getElementById('modbus_mask_input').value = '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('modbus_modify_btn').onclick = function () {
|
|
|
|
|
if (!selectedRow) return alert('Please select the row you want to modify');
|
|
|
|
|
selectedRow.querySelector('.field-display').textContent = document.getElementById('modbus_field_input').value;
|
|
|
|
|
selectedRow.querySelector('.addr-display').textContent = document.getElementById('modbus_addr_input').value;
|
|
|
|
|
selectedRow.querySelector('.idt-display').textContent = document.getElementById('modbus_idt_input').value;
|
|
|
|
|
selectedRow.querySelector('.odt-display').textContent = document.getElementById('modbus_odt_input').value;
|
|
|
|
|
selectedRow.querySelector('.dv-display').textContent = document.getElementById('modbus_dv_input').value;
|
|
|
|
|
selectedRow.querySelector('.shift-display').textContent = document.getElementById('modbus_shift_input').value;
|
|
|
|
|
selectedRow.querySelector('.expr-display').textContent = document.getElementById('modbus_expr_input').value;
|
|
|
|
|
selectedRow.querySelector('.mask-display').textContent = document.getElementById('modbus_mask_input').value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 삭제 버튼
|
|
|
|
|
document.getElementById('modbus_delete_btn').onclick = function () {
|
|
|
|
|
if (!selectedRow) return alert('Select the row you want to delete');
|
|
|
|
|
const list = document.getElementById('modbus-field-list');
|
|
|
|
|
selectedRow.remove();
|
|
|
|
|
selectedRow = null;
|
|
|
|
|
document.querySelectorAll('.modbus-input-area input').forEach(i => i.value = '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
opcUaAddBtn.addEventListener('click', () => {
|
|
|
|
|
const field = document.getElementById('opcua_field_input').value;
|
|
|
|
|
const addr = document.getElementById('opcua_addr_input').value;
|
|
|
|
|
const ns = document.getElementById('opcua_ns_input').value;
|
|
|
|
|
const odt = document.getElementById('opcua_odt_input').value;
|
|
|
|
|
const defaultValue = document.getElementById('opcua_dv_input').value;
|
|
|
|
|
const shift = document.getElementById('opcua_shift_input').value;
|
|
|
|
|
const expr = document.getElementById('opcua_expr_input').value;
|
|
|
|
|
const mask = document.getElementById('opcua_mask_input').value;
|
|
|
|
|
|
|
|
|
|
const firstRow = opcUaFieldList.querySelector('tr');
|
|
|
|
|
|
|
|
|
|
// 첫 행이 비어있다면 그 자리에 값을 넣기
|
|
|
|
|
if (firstRow && firstRow.querySelector('.opcua-field-display').textContent.trim() === '') {
|
|
|
|
|
firstRow.querySelector('.opcua-field-display').textContent = field;
|
|
|
|
|
firstRow.querySelector('.opcua-addr-display').textContent = addr;
|
|
|
|
|
firstRow.querySelector('.opcua-ns-display').textContent = ns;
|
|
|
|
|
firstRow.querySelector('.opcua-odt-display').textContent = odt;
|
|
|
|
|
firstRow.querySelector('.opcua-dv-display').textContent = defaultValue;
|
|
|
|
|
firstRow.querySelector('.opcua-shift-display').textContent = shift;
|
|
|
|
|
firstRow.querySelector('.opcua-expr-display').textContent = expr;
|
|
|
|
|
firstRow.querySelector('.opcua-mask-display').textContent = mask;
|
|
|
|
|
} else {
|
|
|
|
|
// 새로운 행을 추가
|
|
|
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true);
|
|
|
|
|
newRow.querySelector('.opcua-field-display').textContent = field;
|
|
|
|
|
newRow.querySelector('.opcua-addr-display').textContent = addr;
|
|
|
|
|
newRow.querySelector('.opcua-ns-display').textContent = ns;
|
|
|
|
|
newRow.querySelector('.opcua-odt-display').textContent = odt;
|
|
|
|
|
newRow.querySelector('.opcua-dv-display').textContent = defaultValue;
|
|
|
|
|
newRow.querySelector('.opcua-shift-display').textContent = shift;
|
|
|
|
|
newRow.querySelector('.opcua-expr-display').textContent = expr;
|
|
|
|
|
newRow.querySelector('.opcua-mask-display').textContent = mask;
|
|
|
|
|
|
|
|
|
|
opcUaFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 입력 필드 초기화
|
|
|
|
|
document.getElementById('opcua_field_input').value = '';
|
|
|
|
|
document.getElementById('opcua_addr_input').value = '';
|
|
|
|
|
document.getElementById('opcua_ns_input').value = '';
|
|
|
|
|
document.getElementById('opcua_odt_input').value = '';
|
|
|
|
|
document.getElementById('opcua_dv_input').value = '';
|
|
|
|
|
document.getElementById('opcua_shift_input').value = '';
|
|
|
|
|
document.getElementById('opcua_expr_input').value = '';
|
|
|
|
|
document.getElementById('opcua_mask_input').value = '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('opcua_modify_btn').onclick = function () {
|
|
|
|
|
if (!selectedRow) return alert('Please select the row you want to modify');
|
|
|
|
|
selectedRow.querySelector('.opcua-field-display').textContent = document.getElementById('opcua_field_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-addr-display').textContent = document.getElementById('opcua_addr_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-ns-display').textContent = document.getElementById('opcua_ns_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-odt-display').textContent = document.getElementById('opcua_odt_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-dv-display').textContent = document.getElementById('opcua_dv_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-shift-display').textContent = document.getElementById('opcua_shift_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-expr-display').textContent = document.getElementById('opcua_expr_input').value;
|
|
|
|
|
selectedRow.querySelector('.opcua-mask-display').textContent = document.getElementById('opcua_mask_input').value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 삭제 버튼
|
|
|
|
|
document.getElementById('opcua_delete_btn').onclick = function () {
|
|
|
|
|
if (!selectedRow) return alert('Select the row you want to delete');
|
|
|
|
|
const list = document.getElementById('opcua-field-list');
|
|
|
|
|
selectedRow.remove();
|
|
|
|
|
selectedRow = null;
|
|
|
|
|
document.querySelectorAll('.opcua-input-area input').forEach(i => i.value = '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (canFieldList.children.length === 0) {
|
|
|
|
|
const canRowTemplate = document.getElementById('can-row-template');
|
|
|
|
|
if (canRowTemplate) {
|
|
|
|
|
const newRow = canRowTemplate.content.cloneNode(true);
|
|
|
|
|
canFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CAN 테이블 추가 로직
|
|
|
|
|
document.getElementById('can_add_btn').addEventListener('click', () => {
|
|
|
|
|
const field = document.getElementById('can_field_input').value;
|
|
|
|
|
const id = document.getElementById('can_id_input').value;
|
|
|
|
|
const odt = document.getElementById('can_odt_input').value;
|
|
|
|
|
const dv = document.getElementById('can_dv_input').value;
|
|
|
|
|
const shift = document.getElementById('can_shift_input').value;
|
|
|
|
|
const expr = document.getElementById('can_expr_input').value;
|
|
|
|
|
const mask = document.getElementById('can_mask_input').value;
|
|
|
|
|
const canRowTemplate = document.getElementById('can-row-template');
|
|
|
|
|
const firstRow = canFieldList.querySelector('tr');
|
|
|
|
|
|
|
|
|
|
// 첫 행이 비어있다면 그 자리에 값을 넣기
|
|
|
|
|
if (firstRow && firstRow.querySelector('.can-field-display').textContent.trim() === '') {
|
|
|
|
|
firstRow.querySelector('.can-field-display').textContent = field;
|
|
|
|
|
firstRow.querySelector('.can-id-display').textContent = id;
|
|
|
|
|
firstRow.querySelector('.can-odt-display').textContent = odt;
|
|
|
|
|
firstRow.querySelector('.can-dv-display').textContent = dv;
|
|
|
|
|
firstRow.querySelector('.can-shift-display').textContent = shift;
|
|
|
|
|
firstRow.querySelector('.can-expr-display').textContent = expr;
|
|
|
|
|
firstRow.querySelector('.can-mask-display').textContent = mask;
|
|
|
|
|
} else {
|
|
|
|
|
// 새로운 행을 추가
|
|
|
|
|
const newRow = canRowTemplate.content.cloneNode(true);
|
|
|
|
|
newRow.querySelector('.can-field-display').textContent = field;
|
|
|
|
|
newRow.querySelector('.can-id-display').textContent = id;
|
|
|
|
|
newRow.querySelector('.can-odt-display').textContent = odt;
|
|
|
|
|
newRow.querySelector('.can-dv-display').textContent = dv;
|
|
|
|
|
newRow.querySelector('.can-shift-display').textContent = shift;
|
|
|
|
|
newRow.querySelector('.can-expr-display').textContent = expr;
|
|
|
|
|
newRow.querySelector('.can-mask-display').textContent = mask;
|
|
|
|
|
|
|
|
|
|
canFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 입력 필드 초기화
|
|
|
|
|
document.getElementById('can_field_input').value = '';
|
|
|
|
|
document.getElementById('can_id_input').value = '';
|
|
|
|
|
document.getElementById('can_odt_input').value = '';
|
|
|
|
|
document.getElementById('can_dv_input').value = '';
|
|
|
|
|
document.getElementById('can_shift_input').value = '';
|
|
|
|
|
document.getElementById('can_expr_input').value = '';
|
|
|
|
|
document.getElementById('can_mask_input').value = '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// can 테이블 수정 로직
|
|
|
|
|
document.getElementById('can_modify_btn').onclick = function () {
|
|
|
|
|
if (!selectedRow) return alert('Please select the row you want to modify');
|
|
|
|
|
selectedRow.querySelector('.can-field-display').textContent = document.getElementById('can_field_input').value;
|
|
|
|
|
selectedRow.querySelector('.can-id-display').textContent = document.getElementById('can_id_input').value;
|
|
|
|
|
selectedRow.querySelector('.can-odt-display').textContent = document.getElementById('can_odt_input').value;
|
|
|
|
|
selectedRow.querySelector('.can-dv-display').textContent = document.getElementById('can_dv_input').value;
|
|
|
|
|
selectedRow.querySelector('.can-shift-display').textContent = document.getElementById('can_shift_input').value;
|
|
|
|
|
selectedRow.querySelector('.can-expr-display').textContent = document.getElementById('can_expr_input').value;
|
|
|
|
|
selectedRow.querySelector('.can-mask-display').textContent = document.getElementById('can_mask_input').value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//can 테이블 삭제 로직
|
|
|
|
|
document.getElementById('can_delete_btn').onclick = function () {
|
|
|
|
|
if (!selectedRow) return alert('Select the row you want to delete');
|
|
|
|
|
const list = document.getElementById('can-field-list');
|
|
|
|
|
selectedRow.remove();
|
|
|
|
|
selectedRow = null;
|
|
|
|
|
document.querySelectorAll('.can-input-area input').forEach(i => i.value = '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 프로토콜에 따른 사이드바 변경 로직
|
|
|
|
|
regProtocolSelect = document.getElementById('reg_protocol');
|
|
|
|
|
if (regProtocolSelect) {
|
|
|
|
|
regProtocolSelect.addEventListener('change', () => {
|
|
|
|
|
toggleProtocolMenus();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
toggleProtocolMenus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*----서버로 데이터 전송하는 함수----*/
|
|
|
|
|
function saveToConfig() {
|
|
|
|
|
const deviceData = collectDeviceData();
|
|
|
|
|
const protocolData = collectProtocolData();
|
|
|
|
|
|
|
|
|
|
if (!isValidRegisterConfig(protocolData)) {
|
|
|
|
|
alert("Register settings are currently invalid. Cannot be saved.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
//device
|
|
|
|
|
const deviceReq = fetch('http://localhost:8080/setting/device', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify(deviceData)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//protocol
|
|
|
|
|
const protocolReq = fetch('http://localhost:8080/setting/protocol', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify(protocolData)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 모든 요청이 성공하면 성공 알림
|
|
|
|
|
Promise.all([deviceReq, protocolReq])
|
|
|
|
|
.then(([res1, res2]) => {
|
|
|
|
|
if (res1.ok && res2.ok) {
|
|
|
|
|
alert('saved successfully');
|
|
|
|
|
} else {
|
|
|
|
|
alert('save failure');
|
|
|
|
|
console.error('device response:', res1.status, 'protocol response:', res2.status);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
console.error('Error during transfer:', err);
|
|
|
|
|
alert('save failure');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//네이게이션 바 클릭시 해당 페이지로 이동 로직
|
|
|
|
|
document.querySelectorAll('.nav-items button').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
showPage(btn.dataset.page);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//ssid 행 추가 버튼
|
|
|
|
|
document.getElementById('add-ssid').addEventListener('click', addSsidRow);
|
|
|
|
|
|
|
|
|
|
function clearAllInputs() {
|
|
|
|
|
// 모든 input[type="text"], [type="number"], [type="password"] 등 초기화
|
|
|
|
|
document.querySelectorAll('input').forEach(input => {
|
|
|
|
|
if (input.type === 'checkbox' || input.type === 'radio') {
|
|
|
|
|
input.checked = false;
|
|
|
|
|
} else {
|
|
|
|
|
input.value = '';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 모든 select 초기화
|
|
|
|
|
document.querySelectorAll('select').forEach(select => {
|
|
|
|
|
select.selectedIndex = 0;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// textarea 초기화 (필요 시)
|
|
|
|
|
document.querySelectorAll('textarea').forEach(textarea => {
|
|
|
|
|
textarea.value = '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 동적 SSID 목록 등도 비워야 한다면
|
|
|
|
|
const ssidList = document.getElementById('ssid-list');
|
|
|
|
|
if (ssidList) ssidList.innerHTML = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getInitialData(){
|
|
|
|
|
return {
|
|
|
|
|
wifi_static: '', wifi_ip: '', wifi_netmask: '', wifi_gateway: '', wifi_dns1: '', wifi_dns2: '',
|
|
|
|
|
eth_ip: '', eth_netmask: '', eth_gateway: '',
|
|
|
|
|
protocol_server_ip: '', protocol_server_port: null,
|
|
|
|
|
update_server_ip: '', update_server_port: null,
|
|
|
|
|
rtcm_server_ip: '', rtcm_server_port: null,
|
|
|
|
|
opc_ua_server_ip: '', opc_ua_server_port: null,
|
|
|
|
|
modbus_server_ip: '', modbus_server_port: null,
|
|
|
|
|
can_bus_type: '', can_baudrate: null,
|
|
|
|
|
rs485_mode: '', rs485_baudrate: null, rs485_data_bits: null,
|
|
|
|
|
rs485_parity: '', rs485_stop_bits: null,
|
|
|
|
|
imu_remap_x: '', imu_remap_y: '', imu_remap_z: '',
|
|
|
|
|
imu_remap_x_sign: '', imu_remap_y_sign: '', imu_remap_z_sign: '',
|
|
|
|
|
lte_ip: '', lte_netmask: '', lte_gateway: '', lte_port: null,
|
|
|
|
|
WIFI_SSID: [{wifi_ssid: '', wifi_password: '', wifi_security: 'none'}],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
/*-----device input데이터 받는 함수------*/
|
|
|
|
|
function collectDeviceData() {
|
|
|
|
|
const res = { ...data };
|
|
|
|
|
|
|
|
|
|
res.wifi_static = document.querySelector('input[name="ipMode"]:checked')?.value === 'static'
|
|
|
|
|
? 'on' : 'off';
|
|
|
|
|
|
|
|
|
|
for (const k in res) {
|
|
|
|
|
const el = document.getElementById(k);
|
|
|
|
|
if (el) {
|
|
|
|
|
const val = el.value.trim();
|
|
|
|
|
if (['can_baudrate', 'rs485_baudrate'].includes(k)) {
|
|
|
|
|
res[k] = val === '' ? null : Number(val);
|
|
|
|
|
} else {
|
|
|
|
|
res[k] = val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//x,y,z,부호 처리
|
|
|
|
|
['x', 'y', 'z'].forEach(a => {
|
|
|
|
|
const { axis, sign } = axisValueToPair(document.getElementById(`imu_remap_${a}`).value);
|
|
|
|
|
res[`imu_remap_${a}`] = axis;
|
|
|
|
|
res[`imu_remap_${a}_sign`] = sign;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.WIFI_SSID = gatherSsidRows();
|
|
|
|
|
|
|
|
|
|
//null, 공백 없이 데이터 전처리
|
|
|
|
|
const filtered = {};
|
|
|
|
|
for (const [k, v] of Object.entries(res)) {
|
|
|
|
|
if (
|
|
|
|
|
v !== null &&
|
|
|
|
|
!(typeof v === 'string' && v.trim() === '')
|
|
|
|
|
) {
|
|
|
|
|
filtered[k] = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return filtered;
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*--------프로토콜 input 데이터 받는 함수-----------*/
|
|
|
|
|
function collectProtocolData() {
|
|
|
|
|
const collectedRegister = {...lastLoadedRegisterData};
|
|
|
|
|
// configIdMap을 순회하며 HTML ID로부터 값을 가져와 DTO 필드명에 매핑
|
|
|
|
|
for (const htmlId in configIdMap) {
|
|
|
|
|
const dtoKey = configIdMap[htmlId];
|
|
|
|
|
const el = document.getElementById(htmlId);
|
|
|
|
|
if (el) {
|
|
|
|
|
collectedRegister[dtoKey] = el.value !== null && el.value !== undefined ? el.value.trim() : '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 체크박스 필드 (HTML ID와 DTO 필드명이 같음)
|
|
|
|
|
['ai0', 'ai1', 'ai2', 'ai3', 'di0', 'di1', 'di2', 'di3'].forEach(id => {
|
|
|
|
|
const el = document.getElementById(id);
|
|
|
|
|
if (el && el.type === 'checkbox') {
|
|
|
|
|
collectedRegister[id] = el.checked ? "1" : "0";
|
|
|
|
|
} else {
|
|
|
|
|
if (collectedRegister[id] === undefined) {
|
|
|
|
|
collectedRegister[id] = "0";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// MODBUS 테이블 수집 (빈 값은 필드에서 제외)
|
|
|
|
|
const modbusRows = document.querySelectorAll('#modbus-field-list tr');
|
|
|
|
|
collectedRegister.MODBUS = [];
|
|
|
|
|
modbusRows.forEach(row => {
|
|
|
|
|
const field = row.querySelector('.field-display')?.textContent.trim();
|
|
|
|
|
if (field) {
|
|
|
|
|
const obj = {};
|
|
|
|
|
obj.field = field;
|
|
|
|
|
const addr = row.querySelector('.addr-display')?.textContent.trim();
|
|
|
|
|
const idt = row.querySelector('.idt-display')?.textContent.trim();
|
|
|
|
|
const odt = row.querySelector('.odt-display')?.textContent.trim();
|
|
|
|
|
const dv = row.querySelector('.dv-display')?.textContent.trim();
|
|
|
|
|
const shift = row.querySelector('.shift-display')?.textContent.trim();
|
|
|
|
|
const expr = row.querySelector('.expr-display')?.textContent.trim();
|
|
|
|
|
const mask = row.querySelector('.mask-display')?.textContent.trim();
|
|
|
|
|
if (addr) obj.addr = addr;
|
|
|
|
|
if (idt) obj.idt = idt;
|
|
|
|
|
if (odt) obj.odt = odt;
|
|
|
|
|
if (dv) obj.dv = dv;
|
|
|
|
|
if (shift) obj.shift = shift;
|
|
|
|
|
if (expr) obj.expr = expr;
|
|
|
|
|
if (mask) obj.mask = mask;
|
|
|
|
|
collectedRegister.MODBUS.push(obj);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// OPC_UA 테이블 수집 (빈 값은 필드에서 제외)
|
|
|
|
|
const opcuaRows = document.querySelectorAll('#opcua-field-list tr');
|
|
|
|
|
collectedRegister.OPC_UA = [];
|
|
|
|
|
opcuaRows.forEach(row => {
|
|
|
|
|
const field = row.querySelector('.opcua-field-display')?.textContent.trim();
|
|
|
|
|
if (field) {
|
|
|
|
|
const obj = {};
|
|
|
|
|
obj.field = field;
|
|
|
|
|
const addr = row.querySelector('.opcua-addr-display')?.textContent.trim();
|
|
|
|
|
const ns = row.querySelector('.opcua-ns-display')?.textContent.trim();
|
|
|
|
|
const odt = row.querySelector('.opcua-odt-display')?.textContent.trim();
|
|
|
|
|
const dv = row.querySelector('.opcua-dv-display')?.textContent.trim();
|
|
|
|
|
const shift = row.querySelector('.opcua-shift-display')?.textContent.trim();
|
|
|
|
|
const expr = row.querySelector('.opcua-expr-display')?.textContent.trim();
|
|
|
|
|
const mask = row.querySelector('.opcua-mask-display')?.textContent.trim();
|
|
|
|
|
if (addr) obj.addr = addr;
|
|
|
|
|
if (ns) obj.ns = ns;
|
|
|
|
|
if (odt) obj.odt = odt;
|
|
|
|
|
if (dv) obj.dv = dv;
|
|
|
|
|
if (shift) obj.shift = shift;
|
|
|
|
|
if (expr) obj.expr = expr;
|
|
|
|
|
if (mask) obj.mask = mask;
|
|
|
|
|
collectedRegister.OPC_UA.push(obj);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// CAN 테이블 수집 (빈 값은 필드에서 제외)
|
|
|
|
|
const canRows = document.querySelectorAll('#can-field-list tr');
|
|
|
|
|
collectedRegister.CAN = [];
|
|
|
|
|
canRows.forEach(row => {
|
|
|
|
|
const field = row.querySelector('.can-field-display')?.textContent.trim();
|
|
|
|
|
if (field) {
|
|
|
|
|
const obj = {};
|
|
|
|
|
obj.field = field;
|
|
|
|
|
const id = row.querySelector('.can-id-display')?.textContent.trim();
|
|
|
|
|
const odt = row.querySelector('.can-odt-display')?.textContent.trim();
|
|
|
|
|
const dv = row.querySelector('.can-dv-display')?.textContent.trim();
|
|
|
|
|
const shift = row.querySelector('.can-shift-display')?.textContent.trim();
|
|
|
|
|
const expr = row.querySelector('.can-expr-display')?.textContent.trim();
|
|
|
|
|
const mask = row.querySelector('.can-mask-display')?.textContent.trim();
|
|
|
|
|
if (id) obj.id = id;
|
|
|
|
|
if (odt) obj.odt = odt;
|
|
|
|
|
if (dv) obj.dv = dv;
|
|
|
|
|
if (shift) obj.shift = shift;
|
|
|
|
|
if (expr) obj.expr = expr;
|
|
|
|
|
if (mask) obj.mask = mask;
|
|
|
|
|
collectedRegister.CAN.push(obj);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return collectedRegister;
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*--------페이지 제목설정 함수-----------*/
|
|
|
|
|
function showPage(pageName) {
|
|
|
|
|
const titleEl = document.getElementById('page-title');
|
|
|
|
|
Object.values(pages).forEach(p => p.style.display = 'none');
|
|
|
|
|
pages[pageName].style.display = 'block';
|
|
|
|
|
// 버튼 텍스트를 페이지 제목으로 설정
|
|
|
|
|
const btn = document.querySelector(`.nav-items button[data-page="${pageName}"]`);
|
|
|
|
|
if (btn) {
|
|
|
|
|
titleEl.textContent = btn.textContent;
|
|
|
|
|
} else {
|
|
|
|
|
titleEl.textContent = pageName.charAt(0).toUpperCase() + pageName.slice(1); // 기본값
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 페이지 전환 시 항상 마지막으로 로드된 전체 데이터를 UI에 반영
|
|
|
|
|
// if (lastLoadedRegisterData && Object.keys(lastLoadedRegisterData).length > 0) {
|
|
|
|
|
// loadFromRegisterJson(lastLoadedRegisterData);
|
|
|
|
|
// }
|
|
|
|
|
toggleNetInputs(); // Network 페이지 로드 시 IP 모드에 따른 입력 필드 활성화/비활성화
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-----Device에 설정 데이터를 deviceConfig.json 파일로 변경하여 유저가 다운로드 할수 있게 하는 함수-----*/
|
|
|
|
|
function downloadDeviceJson(obj, filename='deviceConfig.json') {
|
|
|
|
|
const json = JSON.stringify(obj, null, 2); // 보기 좋게 들여쓰기 2칸
|
|
|
|
|
const blob = new Blob([json], {type: 'application/json'});
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = filename;
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
a.click();
|
|
|
|
|
document.body.removeChild(a);
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-----protocol에 설정 데이터를 protocolConfig.json 파일로 변경하여 유저가 다운로드 할수 있게 하는 함수-----*/
|
|
|
|
|
function downloadProtocolJson(obj, filename='protocolConfig.json') {
|
|
|
|
|
const json = JSON.stringify(obj, null, 2); // 보기 좋게 들여쓰기 2칸
|
|
|
|
|
const blob = new Blob([json], {type: 'application/json'});
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = filename;
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
a.click();
|
|
|
|
|
document.body.removeChild(a);
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-------페이지 로드시 서버에 있는 데이터를 불러오는 함수---------*/
|
|
|
|
|
async function loadInitialData() {
|
|
|
|
|
// 서버에서 파일 목록 조회 및 로드 (Device)
|
|
|
|
|
try {
|
|
|
|
|
const deviceResponse = await fetch('http://localhost:8080/setting/get-device');
|
|
|
|
|
if (deviceResponse.ok) {
|
|
|
|
|
const deviceData = await deviceResponse.json();
|
|
|
|
|
if (deviceData) {
|
|
|
|
|
loadFromData(deviceData);
|
|
|
|
|
console.log('Device Load completed (Default file: deviceConfig.json)');
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('Device response received, but no body found.');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`Device load failed: HTTP ${deviceResponse.status}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Device load failed:', error.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const protocolResponse = await fetch('http://localhost:8080/setting/get-protocol');
|
|
|
|
|
if (protocolResponse.ok) {
|
|
|
|
|
const protocolData = await protocolResponse.json();
|
|
|
|
|
if (protocolData) {
|
|
|
|
|
loadFromRegisterJson(protocolData);
|
|
|
|
|
console.log('Protocol Load completed (Default file: protocolConfig.json)');
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('Protocol response received, but no body found.');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`Protocol load failed: HTTP ${protocolResponse.status}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('./Protocol load failed:', error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*------데이터상 ssid 추가 함수--------*/
|
|
|
|
|
function createSsidRow(obj = {wifi_ssid: '', wifi_password: '', wifi_security: 'none'}) {
|
|
|
|
|
const node = ssidTmpl.content.firstElementChild.cloneNode(true);
|
|
|
|
|
node.querySelector('.ssid-name').value = obj.wifi_ssid ?? '';
|
|
|
|
|
node.querySelector('.ssid-pw').value = obj.wifi_password ?? '';
|
|
|
|
|
node.querySelector('select').value = (obj.wifi_security ?? 'none').toLowerCase();
|
|
|
|
|
|
|
|
|
|
const deleteBtn = document.createElement('button');
|
|
|
|
|
deleteBtn.textContent = '➖';
|
|
|
|
|
deleteBtn.dataset.action = 'delete-ssid';
|
|
|
|
|
deleteBtn.title = 'delete ssid';
|
|
|
|
|
deleteBtn.classList.add('delete-ssid');
|
|
|
|
|
|
|
|
|
|
node.appendChild(deleteBtn);
|
|
|
|
|
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-------ssid password 숨김 함수-----------*/
|
|
|
|
|
function addSsidRow() {
|
|
|
|
|
ssidList.appendChild(createSsidRow());
|
|
|
|
|
}
|
|
|
|
|
function removeSsidRow(row) {
|
|
|
|
|
row.remove();
|
|
|
|
|
}
|
|
|
|
|
ssidList.addEventListener('click', e => {
|
|
|
|
|
if (e.target.classList.contains('toggle-pw')) {
|
|
|
|
|
const pw = e.target.closest('.ssid-row').querySelector('.ssid-pw');
|
|
|
|
|
pw.type = pw.type === 'password' ? 'text' : 'password';
|
|
|
|
|
e.target.textContent = pw.type === 'password' ? 'show' : 'hide';
|
|
|
|
|
} else if (e.target.dataset.action === 'delete-ssid') {
|
|
|
|
|
const ssidRow = e.target.closest('.ssid-row');
|
|
|
|
|
if (ssidRow) {
|
|
|
|
|
removeSsidRow(ssidRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-------ssid 리스트 병합 함수-------------*/
|
|
|
|
|
function gatherSsidRows() {
|
|
|
|
|
const arr = [];
|
|
|
|
|
ssidList.querySelectorAll('.ssid-row').forEach(row => {
|
|
|
|
|
const ssid = row.querySelector('.ssid-name').value.trim();
|
|
|
|
|
const pw = row.querySelector('.ssid-pw').value.trim();
|
|
|
|
|
const sec = row.querySelector('select').value;
|
|
|
|
|
if (ssid === '' && pw === '') return;
|
|
|
|
|
if (ssid) arr.push({wifi_ssid: ssid, wifi_password: pw, wifi_security: sec});
|
|
|
|
|
});
|
|
|
|
|
return arr
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*------프로토콜 데이터 유효성 검사 로직---------*/
|
|
|
|
|
function isValidRegisterConfig(data) {
|
|
|
|
|
// 1. data가 객체이고 null이 아닌지 확인
|
|
|
|
|
if (typeof data !== 'object' || data === null) {
|
|
|
|
|
console.error("Validation Error: Data is not an object or is null.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegisterDTO의 필수 필드 (백엔드 snake_case와 일치시키세요)
|
|
|
|
|
const topLevelRequiredFields = [
|
|
|
|
|
'protocol',
|
|
|
|
|
'analog_input_level',
|
|
|
|
|
'can_input',
|
|
|
|
|
'device_type',
|
|
|
|
|
'dr_on',
|
|
|
|
|
'equipment',
|
|
|
|
|
'two_byte',
|
|
|
|
|
'four_byte',
|
|
|
|
|
'speed_data_source',
|
|
|
|
|
'version',
|
|
|
|
|
'equipment_id',
|
|
|
|
|
'meid',
|
|
|
|
|
'heading_on',
|
|
|
|
|
'heading_imu_on',
|
|
|
|
|
'fixmode_on'
|
|
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 2. 모든 최상위 필수 필드가 존재하는지 확인
|
|
|
|
|
for (const field of topLevelRequiredFields) {
|
|
|
|
|
if (!data.hasOwnProperty(field)) {
|
|
|
|
|
console.error(`Validation Error: Missing required top-level field: '${field}'`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 각 필드의 값 타입 및 유효성 검증
|
|
|
|
|
|
|
|
|
|
// protocol 필드 유효성 검사
|
|
|
|
|
const validProtocols = ['MODBUS', 'OPC_UA', 'CAN', 'NONE'];
|
|
|
|
|
if (typeof data.protocol !== 'string' || !validProtocols.includes(data.protocol)) {
|
|
|
|
|
console.error(`Validation Error: Invalid or missing 'protocol' field. Expected one of ${validProtocols.join(', ')}, got '${data.protocol}'`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ON/OFF 상태를 나타내는 필드 검사
|
|
|
|
|
const boolLikeFields = ['dr_on', 'heading_on', 'heading_imu_on', 'fixmode_on'];
|
|
|
|
|
for (const field of boolLikeFields) {
|
|
|
|
|
if (typeof data[field] !== 'string' || (data[field] !== 'on' && data[field] !== 'off')) {
|
|
|
|
|
console.error(`Validation Error: Invalid value for boolean-like field '${field}'. Expected 'ON' or 'OFF', got '${data[field]}'`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 기타 최상위 필드의 타입 검사 (예시, 필요에 따라 추가)
|
|
|
|
|
if (typeof data.analog_input_level !== 'string' && typeof data.analog_input_level !== 'number') {
|
|
|
|
|
console.error("Validation Error: 'analog_input_level' is not a string or number.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 리스트 내부 필드 유효성 검사
|
|
|
|
|
|
|
|
|
|
// MODBUS 리스트 검증
|
|
|
|
|
if (data.protocol === 'MODBUS') {
|
|
|
|
|
// protocol이 'MODBUS'일 경우, MODBUS 배열은 필수
|
|
|
|
|
if (!data.hasOwnProperty('MODBUS') || !Array.isArray(data.MODBUS)) {
|
|
|
|
|
console.error("Validation Error: 'MODBUS' array is required and must be an array when protocol is 'MODBUS'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// MODBUS 배열 내용 유효성 검사
|
|
|
|
|
const modbusRequiredFields = ['field', 'idt', 'addr', 'odt', 'dv'];
|
|
|
|
|
for (const [index, item] of data.MODBUS.entries()) {
|
|
|
|
|
if (typeof item !== 'object' || item === null) {
|
|
|
|
|
console.error(`Validation Error: MODBUS item at index ${index} is not an object.`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (const field of modbusRequiredFields) {
|
|
|
|
|
if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') {
|
|
|
|
|
console.error(`Validation Error: MODBUS item at index ${index} missing or empty required field: '${field}'`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// protocol이 'MODBUS'가 아닌 경우, MODBUS 배열은 존재하지 않거나 빈 배열이어야 함
|
|
|
|
|
if (data.hasOwnProperty('MODBUS') && data.MODBUS.length > 0) {
|
|
|
|
|
console.error("Validation Error: 'MODBUS' array must be empty or absent when protocol is not 'MODBUS'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 만약 존재한다면 배열 타입이어야 함
|
|
|
|
|
if (data.hasOwnProperty('MODBUS') && !Array.isArray(data.MODBUS)) {
|
|
|
|
|
console.error("Validation Error: 'MODBUS' field must be an array if present, even when protocol is not 'MODBUS'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// OPC_UA 리스트 검증
|
|
|
|
|
if (data.protocol === 'OPC_UA') {
|
|
|
|
|
// protocol이 'OPC_UA'일 경우, OPC_UA 배열은 필수
|
|
|
|
|
if (!data.hasOwnProperty('OPC_UA') || !Array.isArray(data.OPC_UA)) {
|
|
|
|
|
console.error("Validation Error: 'OPC_UA' array is required and must be an array when protocol is 'OPC_UA'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// OPC_UA 배열 내용 유효성 검사
|
|
|
|
|
const opcUaRequiredFields = ['field', 'ns', 'addr', 'odt', 'dv'];
|
|
|
|
|
for (const [index, item] of data.OPC_UA.entries()) {
|
|
|
|
|
if (typeof item !== 'object' || item === null) {
|
|
|
|
|
console.error(`Validation Error: OPC_UA item at index ${index} is not an object.`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (const field of opcUaRequiredFields) {
|
|
|
|
|
if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') {
|
|
|
|
|
console.error(`Validation Error: OPC_UA item at index ${index} missing or empty required field: '${field}'`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// protocol이 'OPC_UA'가 아닌 경우, OPC_UA 배열은 존재하지 않거나 빈 배열이어야 함
|
|
|
|
|
if (data.hasOwnProperty('OPC_UA') && data.OPC_UA.length > 0) {
|
|
|
|
|
console.error("Validation Error: 'OPC_UA' array must be empty or absent when protocol is not 'OPC_UA'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 만약 존재한다면 배열 타입이어야 함
|
|
|
|
|
if (data.hasOwnProperty('OPC_UA') && !Array.isArray(data.OPC_UA)) {
|
|
|
|
|
console.error("Validation Error: 'OPC_UA' field must be an array if present, even when protocol is not 'OPC_UA'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CAN 리스트 검증 (can_input이 'on'인 경우 필수 - protocol 값과는 독립적)
|
|
|
|
|
if (data.can_input.toLowerCase() === 'on') {
|
|
|
|
|
if (!data.hasOwnProperty('CAN') || !Array.isArray(data.CAN)) {
|
|
|
|
|
console.error("Validation Error: 'CAN' is required when can_input is 'on', and must be an array.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const canRequiredFields = ['field', 'id', 'odt', 'dv'];
|
|
|
|
|
for (const [index, item] of data.CAN.entries()) {
|
|
|
|
|
if (typeof item !== 'object' || item === null) {
|
|
|
|
|
console.error(`Validation Error: CAN item at index ${index} is not an object.`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (const field of canRequiredFields) {
|
|
|
|
|
if (!item.hasOwnProperty(field) || String(item[field]).trim() === '') {
|
|
|
|
|
console.error(`Validation Error: CAN item at index ${index} missing or empty required field: '${field}'`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// can_input이 'off'일 경우 CAN이 존재하면 배열이어야 함 (선택 사항)
|
|
|
|
|
if (data.hasOwnProperty('CAN') && data.CAN.length > 0) {
|
|
|
|
|
console.error("Validation Error: 'CAN' array must be empty or absent when can_input is 'off'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (data.hasOwnProperty('CAN') && !Array.isArray(data.CAN)) {
|
|
|
|
|
console.error("Validation Error: 'CAN' field should be an array if present, even when can_input is 'off'.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 모든 검사를 통과하면 유효함
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-------network dhcp 이면 input field 잠금------------*/
|
|
|
|
|
document.querySelectorAll('input[name="ipMode"]').forEach(r => r.addEventListener('change', toggleNetInputs));
|
|
|
|
|
|
|
|
|
|
const netFields = ['wifi_ip', 'wifi_netmask', 'wifi_gateway', 'wifi_dns1', 'wifi_dns2']
|
|
|
|
|
.map(id => document.getElementById(id));
|
|
|
|
|
|
|
|
|
|
function toggleNetInputs() {
|
|
|
|
|
const isStatic = document.querySelector('input[name="ipMode"]:checked')?.value === 'static';
|
|
|
|
|
netFields.forEach(el => {
|
|
|
|
|
el.disabled = !isStatic;
|
|
|
|
|
el.classList.toggle('disabled', !isStatic);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*-------imu-remap 정수와 부호 분리 함수-------------*/
|
|
|
|
|
function axisValueToPair(val) {
|
|
|
|
|
if (!val || val === '') return { axis: '', sign: '' };
|
|
|
|
|
return val.startsWith('-')
|
|
|
|
|
? { axis: val.slice(1), sign: 'minus' }
|
|
|
|
|
: { axis: val, sign: 'plus' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pairToAxisValue(axis, sign) {
|
|
|
|
|
return sign === 'minus' ? `-${axis}` : axis;
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*--------초기 랜더링 화면 함수------------*/
|
|
|
|
|
function loadFromData(obj) {
|
|
|
|
|
/* Wi-Fi 모드 radio */
|
|
|
|
|
document.querySelector(`input[name="ipMode"][value="${obj.wifi_static === 'on' ? 'static' : 'dhcp'}"]`).checked = true;
|
|
|
|
|
|
|
|
|
|
/* 일반 input/select */
|
|
|
|
|
for (const k in obj) {
|
|
|
|
|
const el = document.getElementById(k);
|
|
|
|
|
if (el) el.value = obj[k] ?? '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* IMU select 값 재구성 */
|
|
|
|
|
['x', 'y', 'z'].forEach(a => {
|
|
|
|
|
const axis = obj[`imu_remap_${a}`] || '';
|
|
|
|
|
const sign = obj[`imu_remap_${a}_sign`] || '';
|
|
|
|
|
document.getElementById(`imu_remap_${a}`).value = pairToAxisValue(axis, sign);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* SSID */
|
|
|
|
|
ssidList.innerHTML = '';
|
|
|
|
|
(obj.WIFI_SSID || []).forEach(o => ssidList.appendChild(createSsidRow(o)));
|
|
|
|
|
if (!ssidList.children.length) addSsidRow();
|
|
|
|
|
|
|
|
|
|
toggleNetInputs(); // 초기 잠금 상태
|
|
|
|
|
}
|
|
|
|
|
loadFromData(data);
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*--------토굴 프로토콜 and can input 함수------------*/
|
|
|
|
|
let regProtocolSelect;
|
|
|
|
|
|
|
|
|
|
function toggleProtocolMenus() {
|
|
|
|
|
if (!regProtocolSelect) return;
|
|
|
|
|
const selectProtocol = regProtocolSelect.value;
|
|
|
|
|
const modbusSidebarBtn = document.getElementById('modbusSidebarBtn');
|
|
|
|
|
const opcUaSidebarBtn = document.getElementById('opcUaSidebarBtn');
|
|
|
|
|
const canSidebarBtn = document.getElementById('canSidebarBtn');
|
|
|
|
|
if (modbusSidebarBtn) modbusSidebarBtn.style.display = 'none';
|
|
|
|
|
if (opcUaSidebarBtn) opcUaSidebarBtn.style.display = 'none';
|
|
|
|
|
if (canSidebarBtn) canSidebarBtn.style.display = 'none';
|
|
|
|
|
if (selectProtocol === 'MODBUS') {
|
|
|
|
|
if (modbusSidebarBtn) modbusSidebarBtn.style.display = 'block';
|
|
|
|
|
} else if (selectProtocol === 'OPC_UA') {
|
|
|
|
|
if (opcUaSidebarBtn) opcUaSidebarBtn.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
// CAN Input이 on일 때만 CAN 메뉴 보이기
|
|
|
|
|
const canInputEl = document.getElementById('reg_can_input');
|
|
|
|
|
if (canInputEl && canInputEl.value === 'on') {
|
|
|
|
|
if (canSidebarBtn) canSidebarBtn.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
// Protocol configuration 메뉴 자동 펼치기
|
|
|
|
|
const protocolDetails = document.querySelectorAll('details.nav')[1];
|
|
|
|
|
if (protocolDetails) protocolDetails.open = true;
|
|
|
|
|
}
|
|
|
|
|
/*-----------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
/*--------서버 DTO 기준 json 데이터 불러오는 함수------------------*/
|
|
|
|
|
function loadFromRegisterJson(register) { // register.json 데이터 로드
|
|
|
|
|
if (!register) {
|
|
|
|
|
console.error("Register data is null or undefined.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lastLoadedRegisterData = {...register};
|
|
|
|
|
console.log("Extracted Body Data:", lastLoadedRegisterData); // DTO 객체 로깅
|
|
|
|
|
|
|
|
|
|
// 1. configIdMap을 순회하며 DTO의 camelCase 필드명과 HTML ID 매핑을 사용하여 UI 업데이트
|
|
|
|
|
for (const htmlId in configIdMap) {
|
|
|
|
|
const dtoKey = configIdMap[htmlId]; // DTO의 camelCase 필드명
|
|
|
|
|
const el = document.getElementById(htmlId);
|
|
|
|
|
|
|
|
|
|
if (el && lastLoadedRegisterData.hasOwnProperty(dtoKey)) {
|
|
|
|
|
const value = lastLoadedRegisterData[dtoKey];
|
|
|
|
|
|
|
|
|
|
if (el.type === 'checkbox') {
|
|
|
|
|
// AnalogPort Enum은 "0", "1" 문자열 또는 boolean으로 파싱될 수 있습니다.
|
|
|
|
|
// UI 체크박스는 boolean 값을 필요로 하므로, "1"이면 체크, "0"이면 체크 해제.
|
|
|
|
|
el.checked = (value === "1" || value === true);
|
|
|
|
|
} else {
|
|
|
|
|
// 기타 입력 필드 (select, text input)
|
|
|
|
|
// null 또는 undefined 값에 대비하여 빈 문자열로 처리
|
|
|
|
|
el.value = value !== null && value !== undefined ? value : '';
|
|
|
|
|
}
|
|
|
|
|
} else if (el) {
|
|
|
|
|
// DTO에 해당 필드가 없으면 UI 요소 초기화 (선택 사항)
|
|
|
|
|
if (el.type === 'checkbox') {
|
|
|
|
|
el.checked = false;
|
|
|
|
|
} else {
|
|
|
|
|
el.value = '';
|
|
|
|
|
}
|
|
|
|
|
console.warn(`DTO field '${dtoKey}' (mapped from HTML ID '${htmlId}') not found in loaded data.`);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`HTML element with ID '${htmlId}' not found for DTO key '${dtoKey}'.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// protocol 값이 있으면 select 값을 변경 (데이터 로드 시에만)
|
|
|
|
|
if (register.protocol && regProtocolSelect) {
|
|
|
|
|
regProtocolSelect.value = register.protocol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 프로토콜 값에 따라 사이드바 버튼 토글
|
|
|
|
|
toggleProtocolMenus();
|
|
|
|
|
|
|
|
|
|
// MODBUS UI 테이블 렌더링
|
|
|
|
|
const modbusFieldList = document.getElementById('modbus-field-list');
|
|
|
|
|
const modbusRowTemplate = document.getElementById('modbus-row-template');
|
|
|
|
|
if (modbusFieldList && modbusRowTemplate) {
|
|
|
|
|
modbusFieldList.innerHTML = '';
|
|
|
|
|
if (Array.isArray(register.MODBUS) && register.MODBUS.length > 0) {
|
|
|
|
|
register.MODBUS.forEach(field => {
|
|
|
|
|
const newRow = modbusRowTemplate.content.cloneNode(true);
|
|
|
|
|
newRow.querySelector('.field-display').textContent = field.field ?? '';
|
|
|
|
|
newRow.querySelector('.addr-display').textContent = field.addr ?? '';
|
|
|
|
|
newRow.querySelector('.idt-display').textContent = field.idt ?? '';
|
|
|
|
|
newRow.querySelector('.odt-display').textContent = field.odt ?? '';
|
|
|
|
|
newRow.querySelector('.dv-display').textContent = field.dv ?? '';
|
|
|
|
|
newRow.querySelector('.shift-display').textContent = field.shift ?? '';
|
|
|
|
|
newRow.querySelector('.expr-display').textContent = field.expr ?? '';
|
|
|
|
|
newRow.querySelector('.mask-display').textContent = field.mask ?? '';
|
|
|
|
|
modbusFieldList.appendChild(newRow);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// MODBUS 데이터가 없으면 빈 행 하나 추가
|
|
|
|
|
const newRow = modbusRowTemplate.content.cloneNode(true);
|
|
|
|
|
modbusFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// OPC_UA UI 테이블 렌더링
|
|
|
|
|
const opcUaFieldList = document.getElementById('opcua-field-list');
|
|
|
|
|
const opcUaRowTemplate = document.getElementById('opcua-row-template');
|
|
|
|
|
if (opcUaFieldList && opcUaRowTemplate) {
|
|
|
|
|
opcUaFieldList.innerHTML = '';
|
|
|
|
|
if (Array.isArray(register.OPC_UA) && register.OPC_UA.length > 0) {
|
|
|
|
|
register.OPC_UA.forEach(field => {
|
|
|
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true);
|
|
|
|
|
newRow.querySelector('.opcua-field-display').textContent = field.field ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-addr-display').textContent = field.addr ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-ns-display').textContent = field.ns ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-odt-display').textContent = field.odt ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-dv-display').textContent = field.dv ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-shift-display').textContent = field.shift ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-expr-display').textContent = field.expr ?? '';
|
|
|
|
|
newRow.querySelector('.opcua-mask-display').textContent = field.mask ?? '';
|
|
|
|
|
opcUaFieldList.appendChild(newRow);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// OPC_UA 데이터가 없으면 빈 행 하나 추가
|
|
|
|
|
const newRow = opcUaRowTemplate.content.cloneNode(true);
|
|
|
|
|
opcUaFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CAN 테이블 렌더링
|
|
|
|
|
const canFieldList = document.getElementById('can-field-list');
|
|
|
|
|
const canRowTemplate = document.getElementById('can-row-template');
|
|
|
|
|
canFieldList.innerHTML = '';
|
|
|
|
|
if (Array.isArray(register.CAN) && register.CAN.length > 0) {
|
|
|
|
|
register.CAN.forEach(field => {
|
|
|
|
|
const newRow = canRowTemplate.content.cloneNode(true);
|
|
|
|
|
newRow.querySelector('.can-field-display').textContent = field.field ?? '';
|
|
|
|
|
newRow.querySelector('.can-id-display').textContent = field.id ?? '';
|
|
|
|
|
newRow.querySelector('.can-odt-display').textContent = field.odt ?? '';
|
|
|
|
|
newRow.querySelector('.can-dv-display').textContent = field.dv ?? '';
|
|
|
|
|
newRow.querySelector('.can-shift-display').textContent = field.shift ?? '';
|
|
|
|
|
newRow.querySelector('.can-expr-display').textContent = field.expr ?? '';
|
|
|
|
|
newRow.querySelector('.can-mask-display').textContent = field.mask ?? '';
|
|
|
|
|
canFieldList.appendChild(newRow);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
const newRow = canRowTemplate.content.cloneNode(true);
|
|
|
|
|
canFieldList.appendChild(newRow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|