461 lines
17 KiB
HTML
461 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>PLC数据转换工具</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
max-width: 1000px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
color: #333;
|
|
}
|
|
|
|
.container {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
padding: 30px;
|
|
border-radius: 20px;
|
|
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
h1 {
|
|
color: #4a5568;
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
font-size: 2.2em;
|
|
font-weight: 300;
|
|
}
|
|
|
|
.section {
|
|
background: #f8fafc;
|
|
border-radius: 12px;
|
|
padding: 25px;
|
|
margin-bottom: 25px;
|
|
border: 1px solid #e2e8f0;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.section:hover {
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.section h3 {
|
|
color: #2d3748;
|
|
margin-bottom: 15px;
|
|
font-size: 1.3em;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
|
border-radius: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
textarea {
|
|
width: 100%;
|
|
min-height: 80px;
|
|
padding: 15px;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 14px;
|
|
resize: vertical;
|
|
transition: border-color 0.3s ease;
|
|
}
|
|
|
|
textarea:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
}
|
|
|
|
button {
|
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
margin: 10px 5px;
|
|
}
|
|
|
|
button:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
button:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.result {
|
|
background: linear-gradient(135deg, #e8f5e8, #f0f8ff);
|
|
border: 2px solid #48bb78;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-top: 15px;
|
|
font-family: 'Courier New', monospace;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.error {
|
|
background: linear-gradient(135deg, #fed7d7, #ffeaea);
|
|
border: 2px solid #f56565;
|
|
color: #c53030;
|
|
}
|
|
|
|
.explanation {
|
|
background: linear-gradient(135deg, #e6fffa, #f0fff4);
|
|
border: 2px solid #38b2ac;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-top: 20px;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.explanation h4 {
|
|
color: #2c7a7b;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.byte-order-options {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin: 15px 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.radio-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 15px;
|
|
background: white;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.radio-group:hover {
|
|
border-color: #667eea;
|
|
background: #f7fafc;
|
|
}
|
|
|
|
.radio-group input[type="radio"] {
|
|
margin: 0;
|
|
}
|
|
|
|
.radio-group.selected {
|
|
border-color: #667eea;
|
|
background: #eef2ff;
|
|
}
|
|
|
|
.example {
|
|
background: rgba(102, 126, 234, 0.1);
|
|
border-left: 4px solid #667eea;
|
|
padding: 15px;
|
|
margin: 15px 0;
|
|
border-radius: 0 8px 8px 0;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 13px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔧 PLC数据转换工具</h1>
|
|
|
|
<div class="section">
|
|
<h3><span class="icon">📥</span>输入ushort数据</h3>
|
|
<textarea id="input" placeholder="请输入读取的ushort数组,用逗号分隔,例如:256,0,256,0,256,0,256,0,256,0">[256,0,256,0,256,0,256,0,256,0]</textarea>
|
|
|
|
<div>
|
|
<h4 style="margin-top: 20px; margin-bottom: 10px;">选择字节序模式:</h4>
|
|
<div class="byte-order-options">
|
|
<label class="radio-group selected">
|
|
<input type="radio" name="byteOrder" value="ABCD" checked>
|
|
<span>ABCD (大端序)</span>
|
|
</label>
|
|
<label class="radio-group">
|
|
<input type="radio" name="byteOrder" value="DCBA">
|
|
<span>DCBA (小端序)</span>
|
|
</label>
|
|
<label class="radio-group">
|
|
<input type="radio" name="byteOrder" value="BADC">
|
|
<span>BADC (混合序1)</span>
|
|
</label>
|
|
<label class="radio-group">
|
|
<input type="radio" name="byteOrder" value="CDAB">
|
|
<span>CDAB (混合序2)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<button onclick="convertData()">🔄 转换数据</button>
|
|
<button onclick="testAllFormats()">🧪 测试所有格式</button>
|
|
<button onclick="clearAll()">🗑️ 清空</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3><span class="icon">📤</span>转换结果</h3>
|
|
<div id="result"></div>
|
|
</div>
|
|
|
|
<div class="explanation">
|
|
<h4>📚 转换原理说明</h4>
|
|
<p><strong>问题分析:</strong></p>
|
|
<p>你写入了5个int值[1,1,1,1,1],但读取时得到了10个ushort值[256,0,256,0,256,0,256,0,256,0]。这是因为:</p>
|
|
<ul>
|
|
<li>每个32位int被拆分成2个16位ushort读取</li>
|
|
<li>存在字节序转换问题</li>
|
|
<li>256 (0x0100) 表明原本的0x01字节被放在了高位</li>
|
|
</ul>
|
|
|
|
<div class="example">
|
|
原始int: 1 (0x00000001)
|
|
内存布局: [01][00][00][00] (小端序)
|
|
按ushort读取:
|
|
- 第1个ushort: [00][01] → 256
|
|
- 第2个ushort: [00][00] → 0
|
|
</div>
|
|
|
|
<p><strong>字节序说明:</strong></p>
|
|
<ul>
|
|
<li><strong>ABCD (大端序):</strong> 高字节在前,低字节在后</li>
|
|
<li><strong>DCBA (小端序):</strong> 低字节在前,高字节在后</li>
|
|
<li><strong>BADC/CDAB:</strong> 字节对调的混合模式</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 更新单选按钮样式
|
|
document.querySelectorAll('input[name="byteOrder"]').forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
document.querySelectorAll('.radio-group').forEach(group => {
|
|
group.classList.remove('selected');
|
|
});
|
|
this.closest('.radio-group').classList.add('selected');
|
|
});
|
|
});
|
|
|
|
function convertData() {
|
|
const input = document.getElementById('input').value.trim();
|
|
const byteOrder = document.querySelector('input[name="byteOrder"]:checked').value;
|
|
const resultDiv = document.getElementById('result');
|
|
|
|
try {
|
|
// 解析输入数据
|
|
let ushortArray;
|
|
if (input.startsWith('[') && input.endsWith(']')) {
|
|
ushortArray = JSON.parse(input);
|
|
} else {
|
|
ushortArray = input.split(',').map(x => parseInt(x.trim()));
|
|
}
|
|
|
|
if (ushortArray.length % 2 !== 0) {
|
|
throw new Error('ushort数组长度必须是偶数');
|
|
}
|
|
|
|
const intArray = convertUshortToInt(ushortArray, byteOrder);
|
|
|
|
resultDiv.innerHTML = `<div class="result">
|
|
<strong>转换结果:</strong>
|
|
原始ushort数组: [${ushortArray.join(', ')}]
|
|
转换后的int数组: [${intArray.join(', ')}]
|
|
|
|
<strong>详细转换过程:</strong>
|
|
${getConversionDetails(ushortArray, intArray, byteOrder)}
|
|
|
|
<strong>验证:</strong>
|
|
${verifyConversion(intArray, ushortArray, byteOrder)}
|
|
</div>`;
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="result error">错误: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
function convertUshortToInt(ushortArray, byteOrder) {
|
|
const intArray = [];
|
|
|
|
for (let i = 0; i < ushortArray.length; i += 2) {
|
|
const low = ushortArray[i];
|
|
const high = ushortArray[i + 1];
|
|
|
|
let intValue;
|
|
switch (byteOrder) {
|
|
case 'ABCD': // 大端序:高字在前
|
|
intValue = (high << 16) | low;
|
|
break;
|
|
case 'DCBA': // 小端序:低字在前
|
|
intValue = (low << 16) | high;
|
|
break;
|
|
case 'BADC': // 字节对调1
|
|
intValue = ((low & 0xFF) << 24) | (((low >> 8) & 0xFF) << 16) |
|
|
((high & 0xFF) << 8) | ((high >> 8) & 0xFF);
|
|
break;
|
|
case 'CDAB': // 字节对调2
|
|
intValue = ((high & 0xFF) << 24) | (((high >> 8) & 0xFF) << 16) |
|
|
((low & 0xFF) << 8) | ((low >> 8) & 0xFF);
|
|
break;
|
|
}
|
|
|
|
// 处理有符号整数
|
|
if (intValue > 0x7FFFFFFF) {
|
|
intValue = intValue - 0x100000000;
|
|
}
|
|
|
|
intArray.push(intValue);
|
|
}
|
|
|
|
return intArray;
|
|
}
|
|
|
|
function getConversionDetails(ushortArray, intArray, byteOrder) {
|
|
let details = '';
|
|
for (let i = 0; i < ushortArray.length; i += 2) {
|
|
const low = ushortArray[i];
|
|
const high = ushortArray[i + 1];
|
|
const intValue = intArray[i / 2];
|
|
|
|
details += `第${i/2 + 1}个int: ushort[${i}]=${low}(0x${low.toString(16).padStart(4, '0')}), ushort[${i+1}]=${high}(0x${high.toString(16).padStart(4, '0')}) → int=${intValue}(0x${(intValue >>> 0).toString(16).padStart(8, '0')})\n`;
|
|
}
|
|
return details;
|
|
}
|
|
|
|
function verifyConversion(intArray, originalUshort, byteOrder) {
|
|
// 反向验证:将转换后的int再转回ushort看是否匹配
|
|
const backToUshort = [];
|
|
|
|
intArray.forEach(intValue => {
|
|
const uintValue = intValue >>> 0; // 转为无符号
|
|
|
|
switch (byteOrder) {
|
|
case 'ABCD':
|
|
backToUshort.push(uintValue & 0xFFFF);
|
|
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
|
break;
|
|
case 'DCBA':
|
|
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
|
backToUshort.push(uintValue & 0xFFFF);
|
|
break;
|
|
case 'BADC':
|
|
const b1 = (uintValue >> 24) & 0xFF;
|
|
const b2 = (uintValue >> 16) & 0xFF;
|
|
const b3 = (uintValue >> 8) & 0xFF;
|
|
const b4 = uintValue & 0xFF;
|
|
backToUshort.push((b1 << 8) | b2);
|
|
backToUshort.push((b3 << 8) | b4);
|
|
break;
|
|
case 'CDAB':
|
|
const c1 = (uintValue >> 24) & 0xFF;
|
|
const c2 = (uintValue >> 16) & 0xFF;
|
|
const c3 = (uintValue >> 8) & 0xFF;
|
|
const c4 = uintValue & 0xFF;
|
|
backToUshort.push((c3 << 8) | c4);
|
|
backToUshort.push((c1 << 8) | c2);
|
|
break;
|
|
}
|
|
});
|
|
|
|
const matches = JSON.stringify(backToUshort) === JSON.stringify(originalUshort);
|
|
return `反向转换: [${backToUshort.join(', ')}]\n匹配原始数据: ${matches ? '✅ 是' : '❌ 否'}`;
|
|
}
|
|
|
|
function testAllFormats() {
|
|
const input = document.getElementById('input').value.trim();
|
|
const resultDiv = document.getElementById('result');
|
|
|
|
try {
|
|
let ushortArray;
|
|
if (input.startsWith('[') && input.endsWith(']')) {
|
|
ushortArray = JSON.parse(input);
|
|
} else {
|
|
ushortArray = input.split(',').map(x => parseInt(x.trim()));
|
|
}
|
|
|
|
if (ushortArray.length % 2 !== 0) {
|
|
throw new Error('ushort数组长度必须是偶数');
|
|
}
|
|
|
|
let result = '<div class="result"><strong>🧪 所有字节序格式测试结果:</strong>\n\n';
|
|
|
|
['ABCD', 'DCBA', 'BADC', 'CDAB'].forEach(byteOrder => {
|
|
const intArray = convertUshortToInt(ushortArray, byteOrder);
|
|
const backToUshort = [];
|
|
|
|
// 反向验证
|
|
intArray.forEach(intValue => {
|
|
const uintValue = intValue >>> 0;
|
|
switch (byteOrder) {
|
|
case 'ABCD':
|
|
backToUshort.push(uintValue & 0xFFFF);
|
|
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
|
break;
|
|
case 'DCBA':
|
|
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
|
backToUshort.push(uintValue & 0xFFFF);
|
|
break;
|
|
case 'BADC':
|
|
const b1 = (uintValue >> 24) & 0xFF;
|
|
const b2 = (uintValue >> 16) & 0xFF;
|
|
const b3 = (uintValue >> 8) & 0xFF;
|
|
const b4 = uintValue & 0xFF;
|
|
backToUshort.push((b1 << 8) | b2);
|
|
backToUshort.push((b3 << 8) | b4);
|
|
break;
|
|
case 'CDAB':
|
|
const c1 = (uintValue >> 24) & 0xFF;
|
|
const c2 = (uintValue >> 16) & 0xFF;
|
|
const c3 = (uintValue >> 8) & 0xFF;
|
|
const c4 = uintValue & 0xFF;
|
|
backToUshort.push((c3 << 8) | c4);
|
|
backToUshort.push((c1 << 8) | c2);
|
|
break;
|
|
}
|
|
});
|
|
|
|
const matches = JSON.stringify(backToUshort) === JSON.stringify(ushortArray);
|
|
result += `${byteOrder}格式: [${intArray.join(', ')}] ${matches ? '✅' : '❌'}\n`;
|
|
});
|
|
|
|
result += '</div>';
|
|
resultDiv.innerHTML = result;
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="result error">错误: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
function clearAll() {
|
|
document.getElementById('input').value = '';
|
|
document.getElementById('result').innerHTML = '';
|
|
}
|
|
|
|
// 页面加载时自动转换示例数据
|
|
window.onload = function() {
|
|
convertData();
|
|
};
|
|
</script>
|
|
</body>
|
|
</html> |