1.修改部分代码风格,实现风格统一 2.添加数据刷新时间到config中 3.将html单独放到文件中并且添加一些相关说明

This commit is contained in:
2025-08-14 15:04:11 +08:00
parent 04bdb5f52b
commit 10959132b7
179 changed files with 1499 additions and 44780 deletions

View File

@ -0,0 +1,670 @@
<html>
<head>
<title>PLC Gateway API Documentation</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1, h2, h3 { color: #2c3e50; }
.endpoint {
background-color: #f8f9fa;
border-left: 4px solid #3498db;
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
.method {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
color: white;
font-weight: bold;
margin-right: 10px;
}
.method-get { background-color: #27ae60; }
.method-post { background-color: #3498db; }
.method-put { background-color: #f39c12; }
.method-delete { background-color: #e74c3c; }
table {
border-collapse: collapse;
width: 100%;
margin: 15px 0;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
code {
background-color: #f5f5f5;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
}
.example {
background-color: #f9f9f9;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-family: monospace;
}
.nav {
background-color: #e9f7fe;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
}
.nav a {
margin-right: 15px;
text-decoration: none;
color: #3498db;
}
</style>
</head>
<body>
<h1>PLC Gateway API Documentation</h1>
<div class="nav">
<a href="#status-api">Status API</a> |
<a href="#data-api">Data API</a> |
<a href="#config-api">Configuration API</a>
</div>
<h2 id="status-api">Status API</h2>
<div class="endpoint">
<h3>System Status</h3>
<div>
<span class="method method-get">GET</span>
<code>/api/status</code>
</div>
<p>获取系统状态信息包括启动时间、PLC数量和缓存大小。</p>
<h4>响应示例</h4>
<div class="example">
{
"status": "running",
"start_time": "2023-10-30 14:30:22",
"plc_count": 2,
"cache_size": 11000,
"plc_statuses": {
"PLC1": {
"status": "connected",
"last_connected": "2023-10-30 14:35:10"
},
"PLC2": {
"status": "disconnected",
"last_connected": "Never"
}
}
}
</div>
</div>
<div class="endpoint">
<h3>Area Status</h3>
<div>
<span class="method method-get">GET</span>
<code>/api/status/<plc_name>/<area_name></code>
</div>
<p>获取指定PLC区域的状态信息。</p>
<h4>路径参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>PLC名称如PLC1</td>
</tr>
<tr>
<td>area_name</td>
<td>区域名称如DB100_Read</td>
</tr>
</table>
<h4>响应示例</h4>
<div class="example">
{
"status": "connected",
"plc_connection_status": "connected",
"last_update": 1698754321.456,
"last_update_formatted": "2023-10-30 14:12:01",
"size": 4000,
"type": "read"
}
</div>
</div>
<h2 id="data-api">Data API</h2>
<div class="endpoint">
<h3>Single Read</h3>
<div>
<span class="method method-get">GET</span>
<code>/api/read/<plc_name>/<area_name>/<offset>/<length></code>
</div>
<p>从指定区域读取数据。</p>
<h4>路径参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>PLC名称如PLC1</td>
</tr>
<tr>
<td>area_name</td>
<td>区域名称如DB100_Read</td>
</tr>
<tr>
<td>offset</td>
<td>起始偏移量(字节)</td>
</tr>
<tr>
<td>length</td>
<td>读取长度(字节)</td>
</tr>
</table>
<h4>响应示例</h4>
<div class="example">
{
"status": "success",
"plc_name": "PLC1",
"area_name": "DB100_Read",
"offset": 0,
"length": 4,
"data": [0, 0, 123, 45],
"plc_connection_status": "connected",
"last_update": 1698754321.456,
"last_update_formatted": "2023-10-30 14:12:01"
}
</div>
</div>
<div class="endpoint">
<h3>Single Write</h3>
<div>
<span class="method method-post">POST</span>
<code>/api/write/<plc_name>/<area_name>/<offset></code>
</div>
<p>向指定区域写入数据。</p>
<h4>路径参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>PLC名称如PLC1</td>
</tr>
<tr>
<td>area_name</td>
<td>区域名称如DB100_Write</td>
</tr>
<tr>
<td>offset</td>
<td>起始偏移量(字节)</td>
</tr>
</table>
<h4>请求体</h4>
<p>原始二进制数据</p>
<h4>响应示例</h4>
<div class="example">
{
"status": "success",
"plc_name": "PLC1",
"area_name": "DB100_Write",
"offset": 0,
"length": 4,
"plc_connection_status": "connected",
"last_update": 1698754350.789,
"last_update_formatted": "2023-10-30 14:12:30"
}
</div>
</div>
<div class="endpoint">
<h3>Single Read Bool</h3>
<div>
<span class="method method-get">GET</span>
<code>/api/read_bool/<plc_name>/<area_name>/<offset>/<length></code>
</div>
<p>从指定区域读取数据。</p>
<h4>路径参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>PLC名称如PLC1</td>
</tr>
<tr>
<td>area_name</td>
<td>区域名称如DB100_Read</td>
</tr>
<tr>
<td>offset</td>
<td>起始偏移量(字节)</td>
</tr>
<tr>
<td>length</td>
<td>读取长度(字节)</td>
</tr>
</table>
<h4>响应示例</h4>
<div class="example">
{
"status": "success",
"plc_name": "PLC1",
"area_name": "DB100_Read",
"offset": 0,
"length": 2,
"data": [0:False, 1:False],
"plc_connection_status": "connected",
"last_update": 1698754321.456,
"last_update_formatted": "2023-10-30 14:12:01"
}
</div>
</div>
<div class="endpoint">
<h3>Single Write Bool</h3>
<div>
<span class="method method-post">POST</span>
<code>/api/write_bool/<plc_name>/<area_name>/<offset></code>
</div>
<p>向指定区域写入数据。</p>
<h4>路径参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>PLC名称如PLC1</td>
</tr>
<tr>
<td>area_name</td>
<td>区域名称如DB100_Write</td>
</tr>
<tr>
<td>offset</td>
<td>起始偏移量(字节)</td>
</tr>
</table>
<h4>请求体</h4>
<p>{0:True}</p>
<h4>响应示例</h4>
<div class="example">
{
"status": "success",
"plc_name": "PLC1",
"area_name": "DB100_Write",
"offset": 0,
"length": 1,
"plc_connection_status": "connected",
"last_update": 1698754350.789,
"last_update_formatted": "2023-10-30 14:12:30"
}
</div>
</div>
<div class="endpoint">
<h3>Batch Read</h3>
<div>
<span class="method method-post">POST</span>
<code>/api/batch_read</code>
</div>
<p>批量读取多个区域的数据。</p>
<h4>请求体</h4>
<table>
<tr>
<th>字段</th>
<th>类型</th>
<th>必需</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>string</td>
<td></td>
<td>PLC名称与配置中一致</td>
</tr>
<tr>
<td>area_name</td>
<td>string</td>
<td></td>
<td>区域名称(与配置中一致)</td>
</tr>
<tr>
<td>offset</td>
<td>number</td>
<td></td>
<td>起始偏移量字节默认为0</td>
</tr>
<tr>
<td>length</td>
<td>number</td>
<td></td>
<td>读取长度(字节),不提供则读取整个区域</td>
</tr>
</table>
<h4>请求示例</h4>
<div class="example">
[
{
"plc_name": "PLC1",
"area_name": "DB100_Read",
"offset": 0,
"length": 4
},
{
"plc_name": "PLC1",
"area_name": "DB202_Params",
"offset": 10,
"length": 2
}
]
</div>
<h4>响应示例</h4>
<div class="example">
[
{
"plc_name": "PLC1",
"area_name": "DB100_Read",
"status": "success",
"plc_connection_status": "connected",
"last_update": 1698754321.456,
"last_update_formatted": "2023-10-30 14:12:01",
"offset": 0,
"length": 4,
"data": [0, 0, 123, 45]
},
{
"plc_name": "PLC1",
"area_name": "DB202_Params",
"status": "success",
"plc_connection_status": "connected",
"last_update": 1698754322.123,
"last_update_formatted": "2023-10-30 14:12:02",
"offset": 10,
"length": 2,
"data": [255, 0]
}
]
</div>
</div>
<div class="endpoint">
<h3>Batch Write</h3>
<div>
<span class="method method-post">POST</span>
<code>/api/batch_write</code>
</div>
<p>批量写入多个区域的数据。</p>
<h4>请求体</h4>
<table>
<tr>
<th>字段</th>
<th>类型</th>
<th>必需</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>string</td>
<td></td>
<td>PLC名称与配置中一致</td>
</tr>
<tr>
<td>area_name</td>
<td>string</td>
<td></td>
<td>区域名称(与配置中一致)</td>
</tr>
<tr>
<td>offset</td>
<td>number</td>
<td></td>
<td>起始偏移量(字节)</td>
</tr>
<tr>
<td>data</td>
<td>array</td>
<td></td>
<td>要写入的数据(字节数组)</td>
</tr>
</table>
<h4>请求示例</h4>
<div class="example">
[
{
"plc_name": "PLC1",
"area_name": "DB100_Write",
"offset": 0,
"data": [1, 2, 3, 4]
},
{
"plc_name": "PLC1",
"area_name": "DB202_Params",
"offset": 10,
"data": [255, 0]
}
]
</div>
<h4>响应示例</h4>
<div class="example">
[
{
"plc_name": "PLC1",
"area_name": "DB100_Write",
"status": "success",
"plc_connection_status": "connected",
"last_update": 1698754350.789,
"last_update_formatted": "2023-10-30 14:12:30",
"offset": 0,
"length": 4
},
{
"plc_name": "PLC1",
"area_name": "DB202_Params",
"status": "success",
"plc_connection_status": "connected",
"last_update": 1698754351.234,
"last_update_formatted": "2023-10-30 14:12:31",
"offset": 10,
"length": 2
}
]
</div>
</div>
<div class="endpoint">
<h3>Parsed Data</h3>
<div>
<span class="method method-get">GET</span>
<code>/api/data/<plc_name>/<area_name></code>
</div>
<p>获取解析后的数据(如果配置了结构)。</p>
<h4>路径参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>plc_name</td>
<td>PLC名称如PLC1</td>
</tr>
<tr>
<td>area_name</td>
<td>区域名称如DB100_Read</td>
</tr>
</table>
<h4>响应示例(配置了解析结构)</h4>
<div class="example">
{
"parsed": {
"temperature": 25.5,
"pressure": 100,
"status": true
},
"raw_data": [0, 0, 128, 65, 0, 100],
"status": "connected",
"plc_connection_status": "connected",
"last_update": 1698754321.456,
"last_update_formatted": "2023-10-30 14:12:01"
}
</div>
<h4>响应示例(未配置解析结构)</h4>
<div class="example">
{
"raw_data": [0, 0, 128, 65, 0, 100],
"status": "connected",
"plc_connection_status": "connected",
"last_update": 1698754321.456,
"last_update_formatted": "2023-10-30 14:12:01"
}
</div>
</div>
<h2 id="config-api">Configuration API</h2>
<div class="endpoint">
<h3>Get Configuration</h3>
<div>
<span class="method method-get">GET</span>
<code>/api/config</code>
</div>
<p>获取当前配置。</p>
<h4>认证要求</h4>
<p>需要Basic Auth认证</p>
<h4>响应示例</h4>
<div class="example">
{
"plcs": [
{
"name": "PLC1",
"ip": "192.168.0.10",
"rack": 0,
"slot": 1,
"areas": [
{
"name": "DB100_Read",
"type": "read",
"db_number": 100,
"offset": 0,
"size": 4000,
"structure": [
{"name": "temperature", "type": "real", "offset": 0},
{"name": "pressure", "type": "int", "offset": 4}
]
}
]
}
]
}
</div>
</div>
<div class="endpoint">
<h3>Validate Configuration</h3>
<div>
<span class="method method-post">POST</span>
<code>/api/config/validate</code>
</div>
<p>验证配置是否有效。</p>
<h4>认证要求</h4>
<p>需要Basic Auth认证</p>
<h4>请求体</h4>
<p>要验证的配置JSON</p>
<h4>响应示例(有效)</h4>
<div class="example">
{
"valid": true
}
</div>
<h4>响应示例(无效)</h4>
<div class="example">
{
"valid": false,
"message": "Invalid configuration: 'ip' is a required property"
}
</div>
</div>
<div class="endpoint">
<h3>Save Configuration</h3>
<div>
<span class="method method-post">POST</span>
<code>/api/config</code>
</div>
<p>保存配置。</p>
<h4>查询参数</h4>
<table>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
<tr>
<td>reload</td>
<td>是否立即重载配置true/false</td>
</tr>
</table>
<h4>认证要求</h4>
<p>需要Basic Auth认证</p>
<h4>请求体</h4>
<p>要保存的配置JSON</p>
<h4>响应示例</h4>
<div class="example">
{
"success": true,
"message": "Configuration saved and reload requested"
}
</div>
</div>
<div class="footer" style="margin-top: 40px; padding-top: 10px; border-top: 1px solid #ddd; color: #777;">
<p>PLC Gateway v1.0 | <a href="/">Back to Status Page</a></p>
</div>
</body>
</html>

View File

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html>
<head>
<title>PLC Gateway Configuration</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #2c3e50; }
.config-container {
display: flex;
flex-direction: column;
max-width: 1200px;
}
textarea {
width: 100%;
height: 500px;
font-family: monospace;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.button-group {
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #2980b9;
}
.status-message {
padding: 10px;
margin-top: 10px;
border-radius: 4px;
display: none;
}
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
.info { background-color: #d1ecf1; color: #0c5460; }
.config-help {
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-left: 4px solid #3498db;
}
.config-help h3 {
margin-top: 0;
}
.doc-link {
margin-top: 10px;
padding: 10px;
background-color: #e9f7fe;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>PLC Gateway Configuration</h1>
<div class="doc-link">
<p><a href="/api/doc">View API documentation</a> - Learn how to use the API</p>
</div>
<div class="config-container">
<p>Edit the configuration JSON below. Be careful with the syntax.</p>
<form id="configForm">
<textarea id="configEditor" name="config">{{ config_json }}</textarea>
<div class="button-group">
<button type="button" onclick="validateConfig()">Validate</button>
<button type="button" onclick="saveConfig(false)">Save</button>
<button type="button" onclick="saveConfig(true)">Save & Reload</button>
</div>
</form>
<div id="statusMessage" class="status-message"></div>
<div class="config-help">
<h3>Configuration Guide</h3>
<p><strong>PLC Configuration:</strong></p>
<ul>
<li><code>name</code>: Unique name for the PLC</li>
<li><code>ip</code>: IP address of the PLC</li>
<li><code>rack</code>: Rack number (usually 0)</li>
<li><code>slot</code>: Slot number (usually 1 for S7-1200)</li>
</ul>
<p><strong>Data Area Configuration:</strong></p>
<ul>
<li><code>name</code>: Name of the data area</li>
<li><code>type</code>: <code>read</code>, <code>write</code>, or <code>read_write</code></li>
<li><code>db_number</code>: DB number (e.g., 100 for DB100)</li>
<li><code>offset</code>: Starting byte offset</li>
<li><code>size</code>: Size in bytes</li>
<li><code>structure</code> (optional): Define how to parse the data</li>
</ul>
<p><strong>Example:</strong></p>
<pre>{
"plcs": [
{
"name": "PLC1",
"ip": "192.168.0.10",
"rack": 0,
"slot": 1,
"areas": [
{
"name": "DB100_Read",
"type": "read",
"db_number": 100,
"offset": 0,
"size": 4000,
"structure": [
{"name": "temperature", "type": "real", "offset": 0},
{"name": "pressure", "type": "int", "offset": 4}
]
}
]
}
]
}</pre>
</div>
</div>
<script>
function showStatus(message, type) {
const statusDiv = document.getElementById('statusMessage');
statusDiv.textContent = message;
statusDiv.className = 'status-message ' + type;
statusDiv.style.display = 'block';
}
function validateConfig() {
try {
const config = JSON.parse(document.getElementById('configEditor').value);
fetch('/api/config/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('{{ username }}:{{ password }}')
},
body: JSON.stringify(config)
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message || 'Validation failed'); });
}
return response.json();
})
.then(data => {
if (data.valid) {
showStatus('Configuration is valid!', 'success');
} else {
showStatus('Validation error: ' + data.message, 'error');
}
})
.catch(error => {
showStatus('Validation error: ' + error.message, 'error');
});
} catch (e) {
showStatus('JSON error: ' + e.message, 'error');
}
}
function saveConfig(reload) {
try {
const config = JSON.parse(document.getElementById('configEditor').value);
const url = reload ? '/api/config?reload=true' : '/api/config';
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('{{ username }}:{{ password }}')
},
body: JSON.stringify(config)
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message || 'Save failed'); });
}
return response.json();
})
.then(data => {
if (data.success) {
const msg = reload ?
'Configuration saved and reloaded successfully!' :
'Configuration saved successfully. Restart to apply changes.';
showStatus(msg, 'success');
} else {
showStatus('Save error: ' + data.message, 'error');
}
})
.catch(error => {
showStatus('Error: ' + error.message, 'error');
});
} catch (e) {
showStatus('JSON error: ' + e.message, 'error');
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PLC Gateway Status</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #2c3e50; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.status-connected { color: green; font-weight: bold; }
.status-disconnected { color: red; }
.status-never-connected { color: orange; }
.plc-connected { background-color: #d4edda; }
.plc-disconnected { background-color: #f8d7da; }
.plc-never-connected { background-color: #fff3cd; }
.api-section { margin-top: 30px; }
.api-endpoint { background-color: #f9f9f9; padding: 10px; margin: 5px 0; border-radius: 4px; }
.config-link { margin-top: 20px; padding: 10px; background-color: #e9f7fe; border-radius: 4px; }
.footer { margin-top: 40px; padding-top: 10px; border-top: 1px solid #ddd; color: #777; }
.doc-link { margin-top: 10px; padding: 10px; background-color: #e9f7fe; border-radius: 4px; }
</style>
</head>
<body>
<h1>PLC Gateway Status</h1>
<p>Gateway running since: {{ start_time }}</p>
{% for plc_name, areas in summary.items() %}
{% set plc_status = plc_statuses.get(plc_name, "unknown") %}
{% set plc_class = {
'connected': 'plc-connected',
'disconnected': 'plc-disconnected'
}.get(plc_status, 'plc-never-connected') %}
<h2 class="{{plc_class}}">PLC:{{plc_name}} (Status: {{plc_status}})</h2>
<table>
<tr>
<th>Area Name</th>
<th>Type</th>
<th>Size (bytes)</th>
<th>Status</th>
<th>PLC Connection</th>
<th>Last Update</th>
</tr>
{% for area_name, area in areas.items() %}
{% set status_class = {
'connected': 'status-connected',
'disconnected': 'status-disconnected',
'never_connected': 'status-never-connected'
}.get(area.status, 'status-disconnected') %}
{% set status_text = {
'connected': 'Connected',
'disconnected': 'Disconnected',
'never_connected': 'Never connected'
}.get(area.status, area.status) %}
<tr>
<td>{{area_name}}</td>
<td>{{area['type']}}</td>
<td>{{area['size']}}</td>
<td class="{{status_class}}">{{status_text}}</td>
<td>{{area['plc_connection_status']}}</td>
<td>{{area['last_update']}}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
<div class="api-section">
<h2>API Endpoints</h2>
<div class="api-endpoint">
<strong>Single Read:</strong> GET /api/read/&lt;plc_name&gt;/&lt;area_name&gt;/&lt;offset&gt;/&lt;length&gt;<br>
Example: /api/read/PLC1/DB100_Read/10/4
</div>
<div class="api-endpoint">
<strong>Single Write:</strong> POST /api/write/&lt;plc_name&gt;/&lt;area_name&gt;/&lt;offset&gt;<br>
Body: Raw binary data<br>
Example: POST /api/write/PLC1/DB100_Write/10 with 4 bytes of data
</div>
<div class="api-endpoint">
<strong>Single Read_Bool:</strong> GET /api/read_bool/&lt;plc_name&gt;/&lt;area_name&gt;/&lt;offset&gt;/&lt;length&gt;<br>
Example: /api/read_bool/PLC1/DB100_Read/0/2
</div>
<div class="api-endpoint">
<strong>Single Write_Bool:</strong> POST /api/write_bool/&lt;plc_name&gt;/&lt;area_name&gt;/&lt;offset&gt;<br>
Body: Raw binary data<br>
Example: POST /api/write_bool/PLC1/DB100_Write/0
</div>
<div class="api-endpoint">
<strong>Batch Read:</strong> POST /api/batch_read<br>
Body: JSON array of read requests<br>
Example: [{"plc_name":"PLC1", "area_name":"DB100_Read", "offset":0, "length":4}]
</div>
<div class="api-endpoint">
<strong>Batch Write:</strong> POST /api/batch_write<br>
Body: JSON array of write requests<br>
Example: [{"plc_name":"PLC1", "area_name":"DB100_Write", "offset":0, "data":[1,2,3,4]}]
</div>
<div class="api-endpoint">
<strong>Configuration:</strong> GET/POST /api/config<br>
Manage gateway configuration
</div>
</div>
<div class="doc-link">
<h2>API Documentation</h2>
<p><a href="/api/doc">View detailed API documentation</a> - Full description of all API endpoints</p>
</div>
<div class="config-link">
<h2>Configuration</h2>
<p><a href="/config">Edit configuration</a> - Modify PLC connections and data areas</p>
</div>
<div class="footer">
<p>PLC Gateway v1.0 | <a href="/api/status">System Status</a></p>
</div>
</body>
</html>