refactor: 移除 in 信号类型 (全栈 — datacenter/libplc/webserver/前端/测试)

in 信号的核心功能 (获取 out 指针 + 变更回调) 已被
dc_signal_out_link_with_callback 完全覆盖, 其独立的 saddr 命名空间
没有实际业务价值.

后端:
- datacenter API: 删除 dc_signal_in/dc_signal_in_with_callback/dc_get_in_signal_info
- dc_signal_in.cpp 已删除, g_dc_in/dc_core_find_in 移除
- libplc: 2 处 dc_signal_in → dc_signal_out_link_with_callback
- webserver: 移除 WS_SIG_IN 枚举值, ws_sig_add_in 函数,
  g_ws_sig_names / dc_data 中的 in 分支

前端:
- router.js / signals.js / datacenter.js / ui.js / i18n.js / dashboard.js
  / signal-store.js / index.html: 移除所有 in 导航/页面/Tab/翻译
- 测试: test_datacenter.cpp 移除 test_in_signal
This commit is contained in:
ypc 2026-07-14 18:21:35 +08:00
parent efacf3eaa8
commit ccb26007e2
17 changed files with 193 additions and 395 deletions

View File

@ -70,7 +70,6 @@
<script>
(function() {
I18n.init();
SignalStore.restore();
document.getElementById('lang-select').value = I18n.current;
document.getElementById('nav-version').textContent = I18n.t('app.version');
document.getElementById('nav-title').textContent = I18n.t('app.title');
@ -87,10 +86,12 @@
if (!data || typeof data !== 'object') return;
MonitorRing.push('↓', JSON.stringify(data).substring(0, 200));
if (data.out || data.in || data.yk || data.ao || data.param) {
if (data.type === 'dc_data') {
if (typeof DcPage !== 'undefined' && DcPage.onDcData) DcPage.onDcData(data);
return;
}
if (data.out || data.yk || data.ao || data.param) {
SignalStore.update(data);
var sigPages = ['#out','#in','#yk','#ao','#param','#datacenter','#plc_debug'];
if (sigPages.indexOf(Router.currentHash) >= 0) Router.dispatch(Router.currentHash);
}
/* ---- 命令列表:渲染到终端面板 ---- */
if (data.type === 'cmd_list' && data.cmds) {

View File

@ -36,7 +36,6 @@ var I18n = {
'nav.section.tools': '工具',
'nav.dashboard': '仪表盘',
'nav.out': '开出信号',
'nav.in': '开入信号',
'nav.yk': '遥控信号',
'nav.ao': '参数设置',
'nav.param': '定值管理',
@ -65,7 +64,6 @@ var I18n = {
'dash.url': '地址',
'dash.status': '状态',
'dash.out.desc': '开出信号注册',
'dash.in.desc': '开入信号链接',
'dash.yk.desc': '遥控信号管理',
'dash.ao.desc': '模拟输出参数',
'dash.param.desc': '定值区参数',
@ -84,7 +82,6 @@ var I18n = {
'signal.saddr': '信号地址',
'signal.desc': '描述',
'signal.type': '数据类型',
'signal.link': '链接目标',
'signal.ctrl': '控制类型',
'signal.ctrl_direct': '直控',
'signal.ctrl_sbo': '选控 (SBO)',
@ -153,6 +150,7 @@ var I18n = {
'plc_config.help': '点击节点选中 | 拖拽移动 | 点击输出圆点→点击输入圆点连线 | 滚轮缩放 | 拖拽空白区平移',
/* Toast */
'toast.val_required': '请输入值',
'toast.ws_error': 'WebSocket 未连接',
'toast.saddr_required': '信号地址不能为空',
'toast.add_sent': '添加请求已发送',
@ -189,7 +187,6 @@ var I18n = {
'nav.section.tools': 'Tools',
'nav.dashboard': 'Dashboard',
'nav.out': 'Out Signals',
'nav.in': 'In Signals',
'nav.yk': 'YK Controls',
'nav.ao': 'AO Parameters',
'nav.param': 'Param Settings',
@ -218,7 +215,6 @@ var I18n = {
'dash.url': 'URL',
'dash.status': 'Status',
'dash.out.desc': 'Output signal registrations',
'dash.in.desc': 'Input signal links',
'dash.yk.desc': 'Remote control signals',
'dash.ao.desc': 'Analog output parameters',
'dash.param.desc': 'Setting zone parameters',
@ -237,7 +233,6 @@ var I18n = {
'signal.saddr': 'Signal Address',
'signal.desc': 'Description',
'signal.type': 'Data Type',
'signal.link': 'Link Target',
'signal.ctrl': 'Control Type',
'signal.ctrl_direct': 'Direct',
'signal.ctrl_sbo': 'SBO',
@ -305,6 +300,7 @@ var I18n = {
'plc_config.help': 'Click node to select | Drag to move | Click output pin → input pin to wire | Scroll to zoom | Drag empty area to pan',
/* Toast */
'toast.val_required': 'Value required',
'toast.ws_error': 'WebSocket not connected',
'toast.saddr_required': 'Signal address is required',
'toast.add_sent': 'Add request sent',

View File

@ -7,7 +7,6 @@
Router.register('#dashboard', function(c) {
var types = [
{ t: 'out', key: 'nav.out', descKey: 'dash.out.desc', color: 'var(--accent)' },
{ t: 'in', key: 'nav.in', descKey: 'dash.in.desc', color: 'var(--accent2)' },
{ t: 'yk', key: 'nav.yk', descKey: 'dash.yk.desc', color: 'var(--warning)' },
{ t: 'ao', key: 'nav.ao', descKey: 'dash.ao.desc', color: 'var(--success)' },
{ t: 'param', key: 'nav.param', descKey: 'dash.param.desc', color: '#a78bfa' },
@ -16,7 +15,7 @@ Router.register('#dashboard', function(c) {
var cards = types.map(function(t) {
var count = SignalStore.get(t.t).length;
return '<div class="dash-card" onclick="window.location.hash=\'#' + t.t + '\'">'
+ '<div class="dash-card-icon">' + Icon.get(t.icon === 'out' ? 'signal' : t.icon === 'in' ? 'link' : t.icon === 'yk' ? 'zap' : t.icon === 'ao' ? 'sliders' : 'settings') + '</div>'
+ '<div class="dash-card-icon">' + Icon.get(t.t === 'out' ? 'signal' : t.t === 'yk' ? 'zap' : t.t === 'ao' ? 'sliders' : 'settings') + '</div>'
+ '<div class="dash-card-count">' + count + '</div>'
+ '<div class="dash-card-label">' + I18n.t(t.key) + '</div>'
+ '<div style="font-size:11px;color:var(--text-muted);margin-top:4px;">' + I18n.t(t.descKey) + '</div>'
@ -51,7 +50,7 @@ Router.register('#dashboard', function(c) {
var DashActions = {
exportAll: function() {
var types = ['out', 'in', 'yk', 'ao', 'param'];
var types = ['out', 'yk', 'ao', 'param'];
var xml = '<?xml version="1.0" encoding="UTF-8"?>\n<root>\n';
var hasData = false;
types.forEach(function(st) {
@ -88,7 +87,7 @@ var DashActions = {
var count = 0;
nodes.forEach(function(el) {
var type = el.getAttribute('type');
if (['out', 'in', 'yk', 'ao', 'param'].indexOf(type) === -1) return;
if (['out', 'yk', 'ao', 'param'].indexOf(type) === -1) return;
el.querySelectorAll('signal').forEach(function(sig) {
var saddr = sig.getAttribute('saddr');
if (!saddr || !saddr.trim()) return;

View File

@ -1,6 +1,6 @@
/**
* @file datacenter.js
* @brief RTU_ALL_AI 数据中心 搜索过滤 + 复选框 + 批量添加到配置页
* @brief RTU_ALL_AI 数据中心 结构化 JSON 数据 + 搜索过滤 + 批量添加
*/
Router.register('#datacenter', function(c) { DcPage.render(c); });
@ -37,8 +37,8 @@ var DcPage = (function() {
}
function renderTabs() {
var types = ['out', 'in', 'yk', 'ao', 'param'];
var labels = { out: '注册 (out)', in: '链接注册 (in)', yk: '遥控 (yk)', ao: '参数 (ao)', param: '定值 (param)' };
var types = ['out', 'yk', 'ao', 'param'];
var labels = { out: '注册 (out)', yk: '遥控 (yk)', ao: '参数 (ao)', param: '定值 (param)' };
var el = document.getElementById('dc-tab-bar');
if (!el) return;
var h = '';
@ -80,10 +80,10 @@ var DcPage = (function() {
var thEl = document.getElementById('dc-thead');
if (thEl) thEl.innerHTML = '<th style="width:36px"><input type="checkbox" id="dc-cb-all"'
+ (allChk ? ' checked' : '') + ' onchange="DcPage._toggleAll()"></th>'
+ '<th>id</th><th>saddr</th><th>desc</th><th>val</th><th>type</th><th>ctrl</th><th>link</th>';
+ '<th>id</th><th>saddr</th><th>desc</th><th>type</th>';
if (data.length === 0) {
tbody.innerHTML = '<tr class="empty-row"><td colspan="8">' + I18n.t('dc.no_signal') + '</td></tr>';
tbody.innerHTML = '<tr class="empty-row"><td colspan="5">' + I18n.t('dc.no_signal') + '</td></tr>';
return;
}
@ -95,68 +95,36 @@ var DcPage = (function() {
+ '<td class="cell-mono">' + fmtVal(x.id) + '</td>'
+ '<td class="cell-mono">' + esc(x.saddr) + '</td>'
+ '<td>' + esc(x.desc || '') + '</td>'
+ '<td class="cell-val">' + fmtVal(x.val) + '</td>'
+ '<td class="cell-mono">' + esc(x.type || '') + '</td>'
+ '<td class="cell-mono">' + (x.ctrl_type == 1 ? 'SBO' : 'Direct') + '</td>'
+ '<td>' + esc(x.link_saddrs || '') + '</td></tr>';
+ '</tr>';
});
tbody.innerHTML = h;
}
function parseDcOutput(text) {
if (!text || text.length < 10) return;
var types = ['out', 'in', 'yk', 'ao', 'param'], currentType = '';
var lines = text.split('\n');
lines.forEach(function(line) {
types.forEach(function(t) {
if (line.indexOf(t + ' signals:') >= 0) currentType = t;
/**
* @brief 从后端 dc_data JSON 加载信号列表 (替代文本解析)
*/
function loadFromDcData(dc) {
allData = {};
['out', 'yk', 'ao', 'param'].forEach(function(t) {
if (!dc[t] || !Array.isArray(dc[t])) return;
allData[t] = dc[t].map(function(s) {
return {
id: s.id, saddr: s.saddr || '', desc: s.desc || '',
type: s.type || '', ctrl_type: s.ctrl_type || 0,
link_saddrs: s.link_saddrs || ''
};
});
if (!currentType) return;
if (line.indexOf('---') >= 0 || line.indexOf('saddr') >= 0 || line.indexOf(' id ') >= 0) return;
var trimmed = line.trim();
if (!trimmed) return;
var parts = trimmed.split(/\s+/);
if (parts.length < 4) return;
var item = {};
if (currentType === 'param') {
item = { id: parseInt(parts[0]), saddr: parts[1], desc: parts[2], type: parts[3] || '', ctrl_type: parseInt(parts[4]) || 0 };
} else if (currentType === 'in') {
item = { id: parseInt(parts[0]), saddr: parts[1], desc: parts[2], link_saddrs: parts[3] || '', type: parts[4] || '', val: parts[5] || '' };
} else {
item = { id: parseInt(parts[0]), saddr: parts[1], desc: parts[2], type: parts[3] || '', val: parts[4] || '' };
}
allData[currentType] = allData[currentType] || [];
allData[currentType].push(item);
});
DcPage._renderTable();
toast(I18n.t('dc.refreshed'), 'success');
}
function refreshAll() {
allData = { out: [], in: [], yk: [], ao: [], param: [] };
allData = { out: [], yk: [], ao: [], param: [] };
toast(I18n.t('dc.querying'), 'info');
var buf = '', timer = null, done = false;
var handler = function(data, binary) {
if (binary) {
buf += new TextDecoder().decode(binary);
clearTimeout(timer);
timer = setTimeout(function() {
if (done) return; done = true;
WsClient.msgCbs = WsClient.msgCbs.filter(function(cb) { return cb !== handler; });
parseDcOutput(buf);
}, 600);
}
};
WsClient.msgCbs.unshift(handler);
/* 发送 datacenter all 命令,后端会在响应中附加 dc_data JSON */
WsClient.send({ type: 'cmd', data: 'datacenter all' });
/* 兜底超时, 但只在 600ms 定时器未触发时生效 */
setTimeout(function() {
if (buf && !done) {
done = true;
WsClient.msgCbs = WsClient.msgCbs.filter(function(cb) { return cb !== handler; });
parseDcOutput(buf);
}
}, 6000);
}
return {
@ -176,13 +144,14 @@ var DcPage = (function() {
+ '<span class="dc-sel-label" id="dc-sel-label"></span>'
+ '<button class="btn btn-primary btn-sm" onclick="DcPage._addSelected()">' + I18n.t('dc.add_to_config') + '</button>'
+ '</div>'
+ '<div class="table-wrap"><table class="signal-table">'
+ '<div class="table-wrap"><table class="signal-table dc-table">'
+ '<thead id="dc-thead"></thead>'
+ '<tbody id="dc-tbody"></tbody>'
+ '</table></div></div>';
renderTabs();
renderTable();
},
onDcData: loadFromDcData,
refreshAll: refreshAll,
switchTab: function(t) {
activeTab = t; searchText = ''; selectedSet = {};

View File

@ -1,68 +1,23 @@
/**
* @file signals.js
* @brief 信号管理页面 out / in / yk / ao (统一渲染)
* @brief 信号管理页面 out / yk / ao (统一渲染, 标准列: # | saddr | desc | type | val | [ctrl] | 操作)
*/
/* ---- Out ---- */
/* ---- Out: 开出信号 (带复选框用于批量删除) ---- */
Router.register('#out', function(c) {
c.innerHTML = renderSignalPage('out',
['<input type="checkbox" style="pointer-events:none;opacity:0">', I18n.t('signal.saddr'), I18n.t('signal.desc'), I18n.t('signal.type')],
function(s) { return td(esc(s.type || '?'), 'cell-mono'); },
function(s) {
return '<div class="btn-group">'
+ '<button class="btn btn-sm" onclick="SignalActions.set(\'out\',\'' + esc(s.saddr) + '\')">' + Icon.get('edit') + ' ' + I18n.t('signal.set') + '</button>'
+ '<button class="btn btn-danger btn-sm" onclick="SignalActions.del(\'out\',\'' + esc(s.saddr) + '\')">' + Icon.get('trash') + '</button>'
+ '</div>';
}
);
[].slice.call(document.querySelectorAll('#tbody-out tr')).forEach(function(tr) {
var saddr = (tr.querySelector('.cell-mono') || {}).textContent || '';
tr.dataset.saddr = saddr;
tr.querySelector('td:first-child').innerHTML = '<input type="checkbox">';
});
c.innerHTML = renderSignalPage('out', true, null, null);
});
/* ---- In ---- */
Router.register('#in', function(c) {
c.innerHTML = renderSignalPage('in',
['<input type="checkbox" style="pointer-events:none;opacity:0">', I18n.t('signal.saddr'), I18n.t('signal.desc'), I18n.t('signal.link'), I18n.t('signal.type')],
function(s) { return td(esc(s.link_saddr || '\u2014'), 'cell-mono') + td(esc(s.type || '?'), 'cell-mono'); },
function(s) {
return '<button class="btn btn-danger btn-sm" onclick="SignalActions.del(\'in\',\'' + esc(s.saddr) + '\')">' + Icon.get('trash') + '</button>';
}
);
[].slice.call(document.querySelectorAll('#tbody-in tr')).forEach(function(tr) {
var saddr = (tr.querySelector('.cell-mono') || {}).textContent || '';
tr.dataset.saddr = saddr;
tr.querySelector('td:first-child').innerHTML = '<input type="checkbox">';
});
});
/* ---- YK ---- */
/* ---- YK: 遥控信号 (带控制类型列) ---- */
Router.register('#yk', function(c) {
c.innerHTML = renderSignalPage('yk',
[I18n.t('signal.saddr'), I18n.t('signal.desc'), I18n.t('signal.type'), I18n.t('signal.ctrl')],
function(s) { return td(esc(s.type || '?'), 'cell-mono') + td(s.ctrl_type == 1 ? I18n.t('signal.ctrl_sbo') : I18n.t('signal.ctrl_direct'), 'cell-mono'); },
function(s) {
return '<div class="btn-group">'
+ '<button class="btn btn-sm" onclick="SignalActions.set(\'yk\',\'' + esc(s.saddr) + '\')">' + Icon.get('edit') + ' ' + I18n.t('signal.set') + '</button>'
+ '<button class="btn btn-danger btn-sm" onclick="SignalActions.del(\'yk\',\'' + esc(s.saddr) + '\')">' + Icon.get('trash') + '</button>'
+ '</div>';
}
);
c.innerHTML = renderSignalPage('yk', false, I18n.t('signal.ctrl'), function(s) {
return '<td class="cell-mono">' + (s.ctrl_type == 1 ? I18n.t('signal.ctrl_sbo') : I18n.t('signal.ctrl_direct')) + '</td>';
});
});
/* ---- AO ---- */
/* ---- AO: 模拟输出 (带控制类型列) ---- */
Router.register('#ao', function(c) {
c.innerHTML = renderSignalPage('ao',
[I18n.t('signal.saddr'), I18n.t('signal.desc'), I18n.t('signal.type'), I18n.t('signal.ctrl')],
function(s) { return td(esc(s.type || '?'), 'cell-mono') + td(s.ctrl_type == 1 ? I18n.t('signal.ctrl_sbo') : I18n.t('signal.ctrl_direct'), 'cell-mono'); },
function(s) {
return '<div class="btn-group">'
+ '<button class="btn btn-sm" onclick="SignalActions.set(\'ao\',\'' + esc(s.saddr) + '\')">' + Icon.get('edit') + ' ' + I18n.t('signal.set') + '</button>'
+ '<button class="btn btn-danger btn-sm" onclick="SignalActions.del(\'ao\',\'' + esc(s.saddr) + '\')">' + Icon.get('trash') + '</button>'
+ '</div>';
}
);
c.innerHTML = renderSignalPage('ao', false, I18n.t('signal.ctrl'), function(s) {
return '<td class="cell-mono">' + (s.ctrl_type == 1 ? I18n.t('signal.ctrl_sbo') : I18n.t('signal.ctrl_direct')) + '</td>';
});
});

View File

@ -37,16 +37,24 @@ var Router = {
if (hash === this.currentHash) return;
this.currentHash = hash;
updateNavActive(hash);
this._render(hash);
},
/**
* @brief 强制重绘当前页面 (用于 WS 推送刷新, hash 不变)
*/
reload: function() {
if (this.currentHash) this._render(this.currentHash);
},
_render: function(hash) {
var page = this.pages[hash];
if (!page) { window.location.hash = '#dashboard'; return; }
var titleMap = {
'#dashboard': I18n.t('dash.title'),
'#out': I18n.t('nav.out'),
'#in': I18n.t('nav.in'),
'#yk': I18n.t('nav.yk'),
'#ao': I18n.t('nav.ao'),
'#param': I18n.t('nav.param'),
@ -70,7 +78,6 @@ var Router = {
var NAV = [
{ h: '#dashboard', key: 'nav.dashboard', icon: 'dashboard' },
{ h: '#out', key: 'nav.out', icon: 'signal', section: 'signal_mgmt' },
{ h: '#in', key: 'nav.in', icon: 'link' },
{ h: '#yk', key: 'nav.yk', icon: 'zap' },
{ h: '#ao', key: 'nav.ao', icon: 'sliders' },
{ h: '#param', key: 'nav.param', icon: 'settings' },

View File

@ -1,61 +1,84 @@
/**
* @file signal-store.js
* @brief 信号数据缓存 接收 WS 推送并更新内存 + localStorage 持久化
* @brief 信号数据缓存 纯内存, WS 推送驱动, 不持久化 + 增量 DOM 刷新
*/
var SignalStore = {
data: { out: [], in: [], yk: [], ao: [], param: [] },
data: { out: [], yk: [], ao: [], param: [] },
/**
* @brief 合并 WS 推送数据到缓存, 并持久化到 localStorage
* @param {object} push {out:[...], in:[...], ...}
* @brief 合并 WS 推送数据 + 增量更新可见 DOM (不重刷整页)
*/
update: function(push) {
var self = this;
['out', 'in', 'yk', 'ao', 'param'].forEach(function(type) {
if (push[type])
{
var map = {};
self.data[type].forEach(function(s) { map[s.saddr] = s; });
push[type].forEach(function(s) { map[s.saddr] = s; });
self.data[type] = Object.keys(map).map(function(k) { return map[k]; });
}
['out', 'yk', 'ao', 'param'].forEach(function(type) {
if (!push[type]) return;
var map = {};
self.data[type].forEach(function(s) { map[s.saddr] = s; });
push[type].forEach(function(s) { map[s.saddr] = s; });
self.data[type] = Object.keys(map).map(function(k) { return map[k]; });
/* 增量更新可见页面 DOM (仅更新变化的单元格,不重绘整页) */
var tbody = document.getElementById('tbody-' + type);
if (!tbody) return;
push[type].forEach(function(s) {
var row = tbody.querySelector('tr[data-saddr="' + s.saddr.replace(/"/g, '\\"') + '"]');
if (!row) return;
/* 更新值列 (class=cell-val) */
var valCell = row.querySelector('.cell-val');
if (valCell && s.val !== undefined) {
var nv = (s.val === null || s.val === '') ? '—' : String(s.val);
if (valCell.textContent.trim() !== nv) {
valCell.textContent = nv;
valCell.classList.add('changed');
setTimeout(function() { valCell.classList.remove('changed'); }, 600);
}
}
/* 更新 ctrl_type 列 (YK/AO) */
if (s.ctrl_type !== undefined) {
var ctrlCell = row.querySelector('.cell-ctrl');
if (ctrlCell) {
var ct = (s.ctrl_type == 1) ? (I18n.t('signal.ctrl_sbo') || 'SBO') : (I18n.t('signal.ctrl_direct') || 'Direct');
if (ctrlCell.textContent.trim() !== ct) ctrlCell.textContent = ct;
}
}
/* Param: 更新定值区值 */
if (type === 'param' && s.setting_zone_list) {
s.setting_zone_list.forEach(function(z) {
var zoneInput = row.querySelector('.param-zone-val[data-zone="' + z.id + '"]');
if (zoneInput) {
var zv = z.val || '';
if (zoneInput.textContent !== zv) zoneInput.textContent = zv;
}
});
}
});
});
this._save();
/* PLC Debug 页增量刷新 */
if (typeof PlcDebugPage !== 'undefined' && PlcDebugPage.incrRefresh) {
PlcDebugPage.incrRefresh();
}
},
get: function(type) {
return this.data[type] || [];
},
addFromPush: function(type, item) {
this.data[type].push(item);
this._save();
},
/** @brief 删除信号 (内存 + 立即移除 DOM不等后端确认) */
remove: function(type, saddr) {
this.data[type] = this.data[type].filter(function(s) { return s.saddr !== saddr; });
this._save();
/* 立即从页面移除对应行 */
var row = document.querySelector('#tbody-' + type + ' tr[data-saddr="' + saddr.replace(/"/g, '\\"') + '"]');
if (row) {
row.style.transition = 'opacity 0.2s';
row.style.opacity = '0';
setTimeout(function() { if (row.parentNode) row.parentNode.removeChild(row); }, 200);
}
},
/** @brief 从 localStorage 恢复缓存 */
restore: function() {
try {
var saved = localStorage.getItem('rtu_signals');
if (saved) {
var parsed = JSON.parse(saved);
if (parsed && parsed.out) { this.data = parsed; }
}
} catch (e) { /* ignore corrupt data */ }
},
/** @brief 持久化到 localStorage (debounced) */
_save: function() {
var self = this;
if (this._timer) clearTimeout(this._timer);
this._timer = setTimeout(function() {
try { localStorage.setItem('rtu_signals', JSON.stringify(self.data)); } catch (e) {}
}, 500);
/** @brief 清空所有缓存 (WebSocket 断连时调用) */
clear: function() {
this.data = { out: [], yk: [], ao: [], param: [] };
}
};

View File

@ -109,7 +109,6 @@ typedef struct
*
* ================================================================ */
extern dc_signal_map_t g_dc_out;
extern dc_signal_map_t g_dc_in;
extern dc_signal_map_t g_dc_yk;
extern dc_signal_map_t g_dc_ao;
extern dc_signal_map_t g_dc_param;
@ -135,7 +134,6 @@ extern std::unordered_map<std::string, dc_param_meta_t> g_param_metadata;
int dc_core_add(dc_signal_t &signal, dc_signal_map_t &smap);
dc_signal_t *dc_core_find(const char *saddr, dc_signal_map_t &smap);
dc_signal_t *dc_core_find_out(const char *saddr);
dc_signal_t *dc_core_find_in(const char *saddr);
dc_signal_t *dc_core_find_by_id(uint32_t id, dc_signal_map_t &smap);
void dc_core_mark_dirty(dc_signal_t *p_signal);

View File

@ -245,25 +245,6 @@ int dc_signal_out_link_with_callback(const char *saddr, void **p_data,
int dc_set_out_signal_val(const char *saddr, const void *set_data,
const char *module_id);
/* ==================================================================
* In out
* ================================================================== */
/**
* @brief
* @param link_saddr out
* @param p_data out
*/
int dc_signal_in(const char *saddr, const char *desc,
const char *link_saddr, void **p_data, const char *module_id);
/**
* @brief out
*/
int dc_signal_in_with_callback(const char *saddr, const char *desc,
const char *link_saddr, void **p_data,
dc_out_change_cb_t cb, const char *module_id);
/* ==================================================================
* YK
* ================================================================== */
@ -419,10 +400,6 @@ int dc_param_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb,
int dc_get_out_signal_info(const char *saddr, char *desc, int desc_len,
uint8_t *data_type, void **p_data);
/** @brief 查询 in 信号信息 */
int dc_get_in_signal_info(const char *saddr, char *desc, int desc_len,
uint8_t *data_type, void **p_data);
/** @brief 查询 AO 信号信息(含参数元数据) */
int dc_get_ao_signal_info(const char *saddr, char *desc, int desc_len,
uint8_t *data_type, dc_param_meta_t *p_meta,

View File

@ -2,7 +2,6 @@
#include "dc_internal.h"
dc_signal_map_t g_dc_out = {};
dc_signal_map_t g_dc_in = {};
dc_signal_map_t g_dc_yk = {};
dc_signal_map_t g_dc_ao = {};
dc_signal_map_t g_dc_param = {};
@ -100,12 +99,6 @@ dc_signal_t *dc_core_find_out(const char *saddr)
return dc_core_find(saddr, g_dc_out);
}
/** @brief 快捷查找 in 信号 */
dc_signal_t *dc_core_find_in(const char *saddr)
{
return dc_core_find(saddr, g_dc_in);
}
/** @brief 按 ID 查找信号O(1) */
dc_signal_t *dc_core_find_by_id(uint32_t id, dc_signal_map_t &smap)
{

View File

@ -1,79 +0,0 @@
/** @file dc_signal_in.cpp — In 信号注册 */
#include "dc_internal.h"
/** @brief In 信号注册内部实现:关联 out 信号、建立链接、追加回调 */
static int dc_in_register_impl(const char *saddr, const char *desc, const char *link_saddr, void **p_data, dc_out_change_cb_t cb, const char *module_id)
{
if (!g_initialized || !saddr || !link_saddr || !p_data || !module_id)
{
return DC_ERR_PARAM;
}
if (!link_saddr[0])
{
return DC_ERR_PARAM;
}
if (dc_core_find_in(saddr))
{
LOG_E("in %s exists\n", saddr);
return DC_ERR_EXISTS;
}
dc_signal_t *ps_out = dc_core_find_out(link_saddr);
if (!ps_out)
{
LOG_E("link out %s not found\n", link_saddr);
return DC_ERR_NOTFOUND;
}
*p_data = ps_out->vec_p_data[0];
dc_signal_t sig = {};
sig.hash = XXH3_128bits(saddr, strlen(saddr));
strncpy(sig.saddr, saddr, DC_SADDR_MAX_LEN - 1);
if (desc)
{
strncpy(sig.desc, desc, DC_DESC_MAX_LEN - 1);
}
sig.data_type = ps_out->data_type;
strncpy(sig.link_saddr, link_saddr, DC_SADDR_MAX_LEN - 1);
sig.vec_p_data = (void **)malloc(sizeof(void *));
sig.vec_p_data[0] = *p_data;
sig.vec_p_data_count = 1;
/* out 记录 in 的链接 */
char **nl = (char **)realloc(ps_out->link_saddrs, (ps_out->link_count + 1) * sizeof(char *));
nl[ps_out->link_count] = strdup(saddr);
ps_out->link_saddrs = nl;
ps_out->link_count++;
if (cb && ps_out->cb_count < DC_MAX_CALLBACKS)
{
strncpy(ps_out->cb_list[ps_out->cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1);
ps_out->cb_list[ps_out->cb_count].out_cb = cb;
ps_out->cb_count++;
}
return dc_core_add(sig, g_dc_in);
}
/** @brief 注册 in 信号(链接到 out无回调 */
int dc_signal_in(const char *saddr, const char *desc, const char *link_saddr, void **p_data, const char *module_id)
{
return dc_in_register_impl(saddr, desc, link_saddr, p_data, NULL, module_id);
}
/** @brief 注册 in 信号(链接到 out带变更回调 */
int dc_signal_in_with_callback(const char *saddr, const char *desc, const char *link_saddr, void **p_data, dc_out_change_cb_t cb, const char *module_id)
{
return dc_in_register_impl(saddr, desc, link_saddr, p_data, cb, module_id);
}

View File

@ -43,13 +43,6 @@ int dc_get_out_signal_info(const char *saddr, char *desc, int desc_len,
return dc_query_info(&g_dc_out, saddr, desc, desc_len, data_type, p_data);
}
/** @brief 查询 in 信号信息 */
int dc_get_in_signal_info(const char *saddr, char *desc, int desc_len,
uint8_t *data_type, void **p_data)
{
return dc_query_info(&g_dc_in, saddr, desc, desc_len, data_type, p_data);
}
/** @brief 查询 AO 信号信息(含参数元数据和控型) */
int dc_get_ao_signal_info(const char *saddr, char *desc, int desc_len,
uint8_t *data_type, dc_param_meta_t *p_meta, uint8_t *ctrl_type,
@ -220,10 +213,6 @@ int dc_get_signal_count(const char *type_str)
{
pmap = &g_dc_out;
}
else if (0 == strcmp(type_str, "in"))
{
pmap = &g_dc_in;
}
else if (0 == strcmp(type_str, "yk"))
{
pmap = &g_dc_yk;
@ -263,10 +252,6 @@ int dc_get_signal_info_by_id(const char *type_str, uint32_t id,
{
pmap = &g_dc_out;
}
else if (0 == strcmp(type_str, "in"))
{
pmap = &g_dc_in;
}
else if (0 == strcmp(type_str, "yk"))
{
pmap = &g_dc_yk;

View File

@ -99,11 +99,6 @@ static void cmd_dc_func(int argc, char *argv[])
printf("out signals:\n");
dc_show_signals(g_dc_out);
}
else if (0 == strcmp(argv[1], "in"))
{
printf("in signals:\n");
dc_show_signals(g_dc_in);
}
else if (0 == strcmp(argv[1], "yk"))
{
printf("yk signals:\n");
@ -123,8 +118,6 @@ static void cmd_dc_func(int argc, char *argv[])
{
printf("out signals:\n");
dc_show_signals(g_dc_out);
printf("in signals:\n");
dc_show_signals(g_dc_in);
printf("yk signals:\n");
dc_show_signals(g_dc_yk);
printf("ao signals:\n");

View File

@ -13,7 +13,7 @@
* - Kahn (, )
* - (clock_gettime CLOCK_MONOTONIC)
* - mtime (stat, XML )
* - self_ptl (dc_signal_in self_ptl )
* - self_ptl (dc_signal_out_link_with_callback self_ptl )
* - datacenter out (dc_set_out_signal_val)
*
* 线: X-Macro app_plc 线, EV_TIMER3(1s) PLC_Logic()
@ -1788,20 +1788,16 @@ int app_plc_init1(void *arg)
int app_plc_init2(void *arg)
{
int ret = 0;
ret |= dc_signal_in("plc.run_cnt_in", "plc.run_cnt_in", "plc.run_cnt", (void **)&gp_run_cnt_in, "plc");
ret |= dc_signal_out_link_with_callback("plc.run_cnt", (void **)&gp_run_cnt_in, NULL, "plc");
for (uint32_t i = 0; i < g_plc_cfg.size(); i++)
{
stru_plc_cfg *p = &g_plc_cfg[i];
char saddr[64];
char desc[64];
snprintf(saddr, sizeof(saddr), "plc.st.in.%d", i);
snprintf(desc, sizeof(desc), "plc链接遥信%d", i);
ret |= dc_signal_in(saddr, desc, p->base.saddr.c_str(), (void **)&p->p_data, "plc");
ret |= dc_signal_out_link_with_callback(p->base.saddr.c_str(), (void **)&p->p_data, NULL, "plc");
}
if (ret != 0)
{
LOG_E("app_plc_init2 dc_signal_in failed");
LOG_E("app_plc_init2 dc_signal_out_link_with_callback failed");
return -1;
}
return 0;

View File

@ -7,7 +7,7 @@
* :
* - HTTP packed_fs
* - WebSocket
* - 5 out/in/yk/ao/param
* - 4 out/yk/ao/param
* - stdout
* - /
* -
@ -29,10 +29,9 @@ extern "C"
typedef enum
{
WS_SIG_OUT = 0,
WS_SIG_IN,
WS_SIG_YK,
WS_SIG_AO,
WS_SIG_PARAM,
WS_SIG_YK = 1,
WS_SIG_AO = 2,
WS_SIG_PARAM = 3,
WS_SIG_MAX
} enum_ws_sig_type;

View File

@ -42,6 +42,9 @@ typedef struct
static std::map<unsigned long, stru_ws_session> g_ws_sessions;
static pthread_mutex_t g_ws_session_mutex = PTHREAD_MUTEX_INITIALIZER;
/* WebSocket 收发包日志开关(默认关闭,通过 web_log on/off 控制) */
static int g_ws_rx_log = 0;
/* ---- 前向声明 ---- */
static int ws_make_sig_json(stru_ws_session *s, cJSON *root, enum_ws_sig_type st, bool *has_change);
static void ws_sig_add(stru_ws_session *s, enum_ws_sig_type st, const char *saddr);
@ -52,7 +55,7 @@ static int ws_sig_set(stru_ws_session *s, enum_ws_sig_type st, const char *sadd
/* ---- 函数表: 信号类型 → 名称 ---- */
static const char *g_ws_sig_names[WS_SIG_MAX] =
{
"out", "in", "yk", "ao", "param"
"out", "yk", "ao", "param"
};
/* ================================================================
@ -112,33 +115,6 @@ static void ws_sig_add_out(stru_ws_session *s, const char *saddr)
vec.push_back(sig);
}
static void ws_sig_add_in(stru_ws_session *s, const char *saddr)
{
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_IN];
void *p_data = NULL;
if (ws_sig_find_idx(vec, saddr) >= 0)
{
return;
}
stru_ws_sig sig;
sig.saddr = saddr;
char desc[128] = {0};
if (0 != dc_get_in_signal_info(saddr, desc, sizeof(desc), &sig.data_type, &p_data))
{
LOG_E("dc_get_in_signal_info failed: %s", saddr);
return;
}
sig.desc = desc;
sig.vec_p_data.push_back(p_data);
vec.push_back(sig);
}
static void ws_sig_add_yk(stru_ws_session *s, const char *saddr)
{
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_YK];
@ -231,7 +207,6 @@ static void ws_sig_add(stru_ws_session *s, enum_ws_sig_type st, const char *sadd
switch (st)
{
case WS_SIG_OUT: ws_sig_add_out(s, saddr); break;
case WS_SIG_IN: ws_sig_add_in(s, saddr); break;
case WS_SIG_YK: ws_sig_add_yk(s, saddr); break;
case WS_SIG_AO: ws_sig_add_ao(s, saddr); break;
case WS_SIG_PARAM: ws_sig_add_param(s, saddr); break;
@ -274,12 +249,6 @@ static int ws_sig_set(stru_ws_session *s, enum_ws_sig_type st, const char *saddr
case WS_SIG_OUT:
return dc_set_out_signal_val(saddr, data, MODULE_WEB_SERVER);
case WS_SIG_IN:
/* in 信号只读 */
LOG_E("set in signal not supported: %s", saddr);
return -1;
case WS_SIG_YK:
{
dc_ctrl_t ctrl;
@ -483,6 +452,18 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
return;
}
{
char buf[256] = {0};
int copy_len = (int)rx_len < 255 ? (int)rx_len : 255;
memcpy(buf, p_rx, (size_t)copy_len);
buf[copy_len] = '\0';
if (g_ws_rx_log)
{
LOG_I("WS recv %uB: %s", rx_len, buf);
}
}
cJSON *root = cJSON_Parse(p_rx);
if (!root)
@ -529,13 +510,12 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
}
/* 构建信号元数据 JSON */
const char *dc_types[5];
const char *dc_types[4];
int dc_type_count = 0;
if (0 == strcmp(sub, "all") || 0 == strncmp(sub, "all ", 4))
{
dc_types[dc_type_count++] = "out";
dc_types[dc_type_count++] = "in";
dc_types[dc_type_count++] = "yk";
dc_types[dc_type_count++] = "ao";
dc_types[dc_type_count++] = "param";
@ -544,10 +524,6 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
{
dc_types[dc_type_count++] = "out";
}
else if (0 == strcmp(sub, "in") || 0 == strncmp(sub, "in ", 3))
{
dc_types[dc_type_count++] = "in";
}
else if (0 == strcmp(sub, "yk") || 0 == strncmp(sub, "yk ", 3))
{
dc_types[dc_type_count++] = "yk";
@ -564,7 +540,6 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
{
/* 默认返回全部 */
dc_types[dc_type_count++] = "out";
dc_types[dc_type_count++] = "in";
dc_types[dc_type_count++] = "yk";
dc_types[dc_type_count++] = "ao";
dc_types[dc_type_count++] = "param";
@ -614,7 +589,7 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
if (dc_tx)
{
ws_send_binary(c->id, dc_tx, strlen(dc_tx));
ws_send_one(c->id, dc_tx, strlen(dc_tx));
free(dc_tx);
}
@ -682,7 +657,7 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
if (0 == func_proc_self_dir(proc_dir, sizeof(proc_dir)))
{
snprintf(resolved, sizeof(resolved), "%s%s", proc_dir, jpath->valuestring);
snprintf(resolved, sizeof(resolved), "%s/%s", proc_dir, jpath->valuestring);
}
else
{
@ -753,7 +728,7 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
if (0 == func_proc_self_dir(proc_dir, sizeof(proc_dir)))
{
snprintf(resolved, sizeof(resolved), "%s%s", proc_dir, jpath->valuestring);
snprintf(resolved, sizeof(resolved), "%s/%s", proc_dir, jpath->valuestring);
}
else
{
@ -838,6 +813,7 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
if (0 == strcmp(curd, "add"))
{
if (g_ws_rx_log) LOG_I("WS add %s %s", g_ws_sig_names[st], saddr);
ws_sig_add(&sess, st, saddr);
/* 立即推送当前状态 */
@ -858,6 +834,7 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
}
else if (0 == strcmp(curd, "del"))
{
if (g_ws_rx_log) LOG_I("WS del %s %s", g_ws_sig_names[st], saddr);
ws_sig_del(&sess, st, saddr);
cJSON *push = cJSON_CreateObject();
@ -877,6 +854,7 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
}
else if (0 == strcmp(curd, "set"))
{
if (g_ws_rx_log) LOG_I("WS set %s %s = %s (zone=%u)", g_ws_sig_names[st], saddr, sig_data, zone);
ws_sig_set(&sess, st, saddr, zone, sig_data);
}
@ -897,3 +875,50 @@ void ws_session_destroy(struct mg_connection *c)
g_ws_sessions.erase(c->id);
pthread_mutex_unlock(&g_ws_session_mutex);
}
/* ================================================================
* web_log WebSocket
* ================================================================ */
/** @brief web_log 命令补全 */
LOCAL void cmd_web_log_complete(const char *buf, char ***completions, int *ncomp)
{
static const char *subs[] = {"on", "off", "status"};
cmd_sub_complete(buf, completions, ncomp, subs, sizeof(subs) / sizeof(subs[0]));
}
/** @brief web_log 命令处理 */
LOCAL void cmd_web_log(int argc, char *argv[])
{
const char *usage =
"web_log on 打开 WS 收发包日志\n"
"web_log off 关闭 WS 收发包日志\n"
"web_log status 查看当前状态\n";
if (argc < 2)
{
printf("%s", usage);
return;
}
if (0 == strcmp(argv[1], "on"))
{
g_ws_rx_log = 1;
printf("WS log: ON\n");
}
else if (0 == strcmp(argv[1], "off"))
{
g_ws_rx_log = 0;
printf("WS log: OFF\n");
}
else if (0 == strcmp(argv[1], "status"))
{
printf("WS log: %s\n", g_ws_rx_log ? "ON" : "OFF");
}
else
{
printf("未知子命令: %s\n%s", argv[1], usage);
}
}
CMD_REGISTER_C("web_log", cmd_web_log, "WebSocket 收发包日志开关", cmd_web_log_complete);

View File

@ -230,44 +230,6 @@ static void test_out_signal(void)
}
}
/**
* @brief 3in
*/
static void test_in_signal(void)
{
TEST("dc_signal_in()");
{
void *p = NULL;
int r = dc_signal_in("RPC1:YC:1:in", "test in", "RPC1:YC:1",
&p, "protocol");
if (DC_OK != r || !p)
{
FAIL("in register failed");
return;
}
OK();
}
TEST("dc_signal_in() bad link");
{
void *p = NULL;
int r = dc_signal_in("RPC1:YC:99:in", "bad", "RPC1:YC:99",
&p, "protocol");
if (DC_ERR_NOTFOUND != r)
{
FAIL("bad link should return NOTFOUND");
return;
}
OK();
}
}
/**
* @brief 4yk +
*/
@ -862,7 +824,6 @@ int main(void)
/* 信号注册 */
test_out_signal();
test_in_signal();
test_yk_signal();
test_ao_signal();
test_param_signal();