4. Structure and Modules Description
/* ── Disable XWiki automatic heading counters inside our content ── */
.doc-content h1::before,.doc-content h2::before,
.doc-content h3::before,.doc-content h4::before,
.doc-content h5::before,.doc-content h6::before {
content:none !important; display:none !important;
}
.doc-content { counter-reset:none !important; }
/* ── Reading progress bar ── */
#rn-prog { position:fixed;top:0;left:0;height:3px;width:0;background:#005AB4;z-index:9999;pointer-events:none; }
/* ── Page content wrapper ── */
.doc-wrap { position:relative; }
.doc-content {
margin-right:224px;
border-left:1px solid #E8E8E8;
border-right:1px solid #E8E8E8;
padding:0 32px;
}
/* ── Right "On This Page" TOC ── */
.doc-toc {
position:absolute;top:0;right:0;width:200px;
font-size:12.5px;line-height:1.55;
padding:20px 0 20px 16px;
}
.doc-toc.is-fixed { position:fixed;width:200px;overflow-y:auto;z-index:10; }
.toc-header { display:flex;justify-content:space-between;align-items:center;margin-bottom:10px; }
.toc-header strong { font-size:13px;font-weight:700;color:#222; }
.toc-toggle { background:none;border:none;cursor:pointer;color:#555;font-size:14px;padding:0 2px;line-height:1; }
.toc-toggle:hover { color:#222; }
.doc-toc ul { list-style:none;margin:0;padding:0; }
.doc-toc li { margin:0; }
.doc-toc a { display:block;padding:3px 0;color:#555;text-decoration:none;border-left:2px solid transparent;transition:color .12s; }
.doc-toc a:hover { color:#222;text-decoration:underline; }
.doc-toc a.toc-on { color:#222;font-weight:700;border-left:2px solid #005AB4;padding-left:5px; }
.tl1 > a { font-weight:600;color:#222; }
.tl2 > a { padding-left:14px; }
.tl3 > a { padding-left:26px;font-size:12px; }
/* ── Typography ── */
.doc-content h1 { font-size:1.6em;font-weight:700;color:#222;margin:0 0 6px 0;border:none; }
.rn-meta { font-size:.8em;color:#555;margin:0 0 28px 0; }
.doc-content h2 { font-size:1.15em;font-weight:700;color:#222;margin:36px 0 10px 0;display:flex;align-items:baseline;gap:7px;scroll-margin-top:16px; }
.doc-content h3 { font-size:1em;font-weight:700;color:#222;margin:22px 0 8px 0;display:flex;align-items:baseline;gap:6px;scroll-margin-top:16px; }
.doc-content h4 { font-size:.93em;font-weight:700;color:#222;margin:14px 0 6px 0;display:flex;align-items:baseline;gap:5px;scroll-margin-top:16px; }
.doc-content p { font-size:.93em;color:#333;line-height:1.75;margin:0 0 12px 0; }
.doc-content strong { color:#222; }
.doc-content code { background:#f5f5f5;border:1px solid #E8E8E8;border-radius:3px;padding:1px 5px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:.82em;color:#222;line-height:1; }
/* ── Anchor icon ── */
.anchor-icon { color:#E8E8E8;text-decoration:none;opacity:0;transition:opacity .15s;vertical-align:middle;margin-left:4px;display:inline-flex;align-items:center; }
h2:hover .anchor-icon,h3:hover .anchor-icon,h4:hover .anchor-icon { opacity:1; }
.anchor-icon:hover { color:#005AB4; }
/* ── Callout notes ── */
.note { border-radius:6px;padding:12px 16px;margin:14px 0;border:1px solid transparent; }
.note-info { background:#e1f1f9 !important;border-color:#b3d4ee !important;border-left:4px solid #005AB4 !important; }
.note-warning { background:#fff4ec !important;border-color:#f5cba7 !important;border-left:4px solid #DD5600 !important; }
.note-tip { background:#e5f3df !important;border-color:#b7dba5 !important;border-left:4px solid #73A839 !important; }
.note-hd { font-weight:700;font-size:.85em;display:flex;align-items:center;gap:5px;margin-bottom:5px; }
.note-info .note-hd { color:#003d80 !important; }
.note-warning .note-hd { color:#a03d00 !important; }
.note-tip .note-hd { color:#3d6b1a !important; }
.note-body { font-size:.875em;color:#222 !important;line-height:1.65; }
/* ── Section index list ── */
.section-index { list-style:disc;padding-left:20px;margin:0 0 20px 0; }
.section-index li { margin-bottom:12px; }
.section-index li a { color:#005AB4;font-weight:600;font-size:.93em;text-decoration:underline; }
.section-index li p { margin:3px 0 0 0;font-size:.875em;color:#333;line-height:1.6; }
/* ── Parent topic ── */
.parent-topic { font-size:.82em;color:#555;margin:18px 0 24px 0;border-top:1px solid #E8E8E8;padding-top:10px; }
.parent-topic a { color:#005AB4; }
/* ── Accordion ── */
details.acc { border:1px solid #E8E8E8;border-radius:4px;margin-bottom:5px; }
details.acc summary { display:flex;align-items:center;gap:8px;padding:9px 12px;cursor:pointer;list-style:none;background:none;user-select:none; }
details.acc summary::-webkit-details-marker { display:none; }
details.acc summary::before { content:'▶';font-size:.58em;color:#555;transition:transform .18s;flex-shrink:0; }
details.acc[open] summary::before { transform:rotate(90deg); }
details.acc summary:hover { background:#f5f5f5; }
.acc-num { font-size:.8em;color:#555;min-width:28px;font-weight:600; }
.acc-title { font-size:.9em;font-weight:700;color:#222;flex:1; }
.acc-body { padding:10px 14px 12px 14px;font-size:.88em;color:#333;line-height:1.7; }
/* ── Images ── */
.fig { margin:12px auto 24px auto;width:fit-content;max-width:100%; }
.fig img { border-radius:4px 4px 0 0;border:1px solid #E8E8E8;display:block;cursor:zoom-in;max-width:100%;box-shadow:0 1px 4px rgba(0,0,0,.10);transition:box-shadow .15s; }
.fig img:hover { box-shadow:0 3px 12px rgba(0,90,180,.18); }
.fig-cap { font-size:.78em;color:#555;margin:0;padding:5px 8px;background:#f5f5f5;border:1px solid #E8E8E8;border-top:none;border-radius:0 0 4px 4px;display:block;width:100%;box-sizing:border-box; }
/* ── Lightbox ── */
#rn-lb { display:none;position:fixed;inset:0;z-index:9998;background:rgba(0,0,0,.8);cursor:zoom-out;align-items:center;justify-content:center; }
#rn-lb.open { display:flex; }
#rn-lb-wrap { position:relative;cursor:default;max-width:92vw;max-height:90vh; }
#rn-lb-wrap img { max-width:100%;max-height:90vh;border-radius:5px;display:block; }
#rn-lb-x { position:absolute;top:-13px;right:-13px;background:#005AB4;color:#fff;width:26px;height:26px;border-radius:50%;border:none;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center; }
#rn-lb-cap { position:fixed;bottom:18px;left:50%;transform:translateX(-50%);color:#ddd;font-size:.78em;background:rgba(0,0,0,.55);padding:3px 12px;border-radius:14px;pointer-events:none; }
/* ── Back to top ── */
#rn-top { position:fixed;bottom:60px;right:60px;z-index:9997;width:36px;height:36px;border-radius:50%;background:#005AB4;color:#fff;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:0;transform:translateY(8px);transition:opacity .22s,transform .22s;pointer-events:none;box-shadow:0 2px 8px rgba(0,90,180,.3); }
#rn-top.on { opacity:1;transform:translateY(0);pointer-events:auto; }
#rn-top:hover { background:#0066cc; }
/* ── Toast ── */
.rn-toast { position:fixed;bottom:66px;right:24px;z-index:9999;background:#222;color:#fff;font-size:.74em;padding:4px 11px;border-radius:4px;pointer-events:none;opacity:0;transition:opacity .18s; }
.rn-toast.on { opacity:1; }
/* ── Tables ── */
.rn-table { width:100%;border-collapse:collapse;margin:8px 0 18px 0;font-size:.875em; }
.rn-table th { background:#f5f5f5;text-align:left;padding:7px 12px;font-size:.78em;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#555;border-bottom:2px solid #E8E8E8; }
.rn-table td { padding:7px 12px;border-bottom:1px solid #E8E8E8;color:#333;vertical-align:top; }
.rn-table tr:last-child td { border-bottom:none; }
.rn-table tr:hover td { background:#f5f5f5; }
.sc { display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;background:#deeaf7;color:#005AB4;border-radius:3px;font-size:.8em;font-weight:700; }
- 1. Introduction
CENTO platform overview and purpose of this document.
- 2. CENTO Structure
General architecture: web part and core system components.
- 3. Composition and Description
Detailed description of all 140 CENTO services grouped by function.
1. Introduction
CENTO is a multifunctional software platform for data collection, processing, and display. It can be used to build various automated systems, including dispatching, electricity and energy resource metering, emergency event registration, power quality control, monitoring, and diagnostics.
This document describes the CENTO structure and lists all CENTO components, each with a brief description.
2. CENTO Structure
The general structure of CENTO is shown in Fig. 2.1. CENTO consists of two main blocks:
- Web part — provides data to users and contains system configuration tools. Information is delivered via a web browser.
- Core of the system — a set of applications running all CENTO components, responsible for collecting, transmitting, processing, and storing data.
Fig. 2.1 – The general structure of CENTO
3. Composition and Description of CENTO Components
3.1. Web Part
The CENTO web interface is built on the nginx web server, web application modules (PHP and executable services), and a web browser on the client side. Key infrastructure components:
- MongoDB — NoSQL database storing system configuration, event logs, mimic diagrams, and their components.
- InfluxDB — NoSQL time-series database storing data collected by the system.
- NATS — message broker for inter-component data exchange.
3.2. The "Core" of the System
The core consists of polling and data transmission services, processing services, and auxiliary services.
Polling and data transmission services collect data from information sources and transmit it to external systems via the message broker. Certain polling services include binary files sed_metrology_XXX.bin — the metrologically significant part containing data extraction mechanisms and register configurations for the polled devices.
Fig. 3.1 – Updating checksums of metrologically significant files
Data processing services receive data from the message broker, process it, and write it to the database. Processing services include metrologically significant files sed_metrology_YYY.bin containing algorithms for calculating measured values. The sed_calc_arch service additionally has sed_metrology_formulas.bin for custom pre-calculation formulas marked as metrologically significant.
Auxiliary services ensure the main CENTO services can perform their functions (license management, watchdog, scheduling, backup, replication, etc.).
The composition of CENTO services for a given installation depends on its configuration and is shown on the System Information tab.
Table 1 — CENTO software components (140 services)
5Data Collection and Transmission Services
| № | Service | Function |
|---|---|---|
| 1 | sed_bus_dispatcher | Sequential Device Polling Management Service |
| 2 | sed_xml_reader | Service for processing received XML files 80020 |
| 3 | sed_xml_send | XML file transfer service 80020 |
| 4 | sed_archive_webapi | Web API service for transmitting archived data per CENTO protocol |
| 5 | sed_s2s_configuration | Configuration transmission service between CENTO servers |
16Standard Protocol Services
| № | Service | Function | Metrologically significant |
|---|---|---|---|
| 6 | sed_modbus_rtd | Real-time data from devices using Modbus TCP / RTU over TCP | sed_metrology_001.bin |
| 7 | sed_modbus_rtd_send | Send real-time data to third-party devices via Modbus TCP | |
| 8 | sed_modbus_sync | Device time synchronisation via Modbus TCP / RTU over TCP | |
| 9 | sed_snmp_rtd | Real-time data via SNMP (v1, v2c, v3) and ICMP | |
| 10 | sed_iec103_waveform | Waveform files from devices via IEC 60870-5-103 | sed_metrology_393.bin |
| 11 | sed_iec104_rtd | Real-time data via IEC 60870-5-104 | sed_metrology_011.bin |
| 12 | sed_iec104_rtd_send | Send real-time data to third-party devices via IEC 60870-5-104 | sed_metrology_012.bin |
| 13 | sed_opcda_rtd | Real-time data via OPC DA | |
| 14 | sed_opcua_rtd | Real-time data via OPC UA | |
| 15 | sed_opcua_arch | Archived data via OPC UA | sed_metrology_402.bin |
| 16 | sed_opcua_server | Data transmission via OPC UA | |
| 17 | sed_dnp3_rtd | Real-time data via DNP3 | |
| 18 | sed_ftp_waveform | Waveform files via FTP | sed_metrology_380.bin |
| 19 | sed_iec61107_rtd | Real-time data via IEC 62056-21 | sed_metrology_61107.bin |
| 20 | sed_iec61850_arch | Archived data via IEC 61850 | |
| 21 | sed_iec61850_rtd | Real-time data via IEC 61850 | |
45Proprietary Protocol Services
| № | Service | Function | Metrologically significant |
|---|---|---|---|
| 22 | sed_satec_arch | Archived data and event logs from SATEC devices | sed_metrology_7_8.bin |
| 23 | sed_satec_pq_journal | Power quality event logs from SATEC devices | sed_metrology_106.bin |
| 24 | sed_satec_pq_report | Power quality reports from SATEC devices | sed_metrology_106.bin |
| 25 | sed_satec_waveforms | Waveforms from SATEC via Modbus TCP / RTU over TCP | sed_metrology_108_109.bin |
| 26 | sed_iec103_rtd | Real-time data from TOR 200 terminals via IEC 60870-5-103 | sed_metrology_391.bin |
| 27 | sed_sepam_waveform | Waveforms and time sync from Sepam via Modbus RTU over TCP | sed_metrology_298_299.bin |
| 28 | sed_rtu_over_tcp_logikamp_rtd | Real-time data via LogicaMP RTU over TCP | sed_metrology_121.bin |
| 29 | sed_rtu_over_tcp_logikamp_arch | Archived data via LogicaMP RTU over TCP | sed_metrology_122.bin |
| 30 | sed_axis_video_journal | AXIS camera recording service | sed_metrology_435.bin |
| 31 | sed_vzlyotmb_arch | Archives and event logs from Vzlet TSRV-043/027, IVK-TER | sed_metrology_25_133.bin |
| 32 | sed_micom_waveforms | Waveforms from Micom via Modbus RTU over TCP | sed_metrology_370.bin |
| 33 | sed_pulsar_arch | Archives from Pulsar via Modbus | sed_metrology_342.bin |
| 34 | sed_rtu_over_tcp_a1800_arch | Archived data from A1800 meters via Modbus RTU over TCP | sed_metrology_232.bin |
| 35 | sed_rtu_over_tcp_a1800_rtd | Real-time data from A1800 meters via Modbus RTU over TCP | sed_metrology_231.bin |
| 36 | sed_rtu_over_tcp_acron_arch | Archived data from Acron meters via Modbus RTU over TCP | sed_metrology_142.bin |
| 37 | sed_rtu_over_tcp_acron_rtd | Real-time data from Acron meters via Modbus RTU over TCP | sed_metrology_141.bin |
| 38 | sed_rtu_over_tcp_bmrz_waveform | Waveforms from BMRZ terminals via Modbus RTU over TCP | sed_metrology_110.bin |
| 39 | sed_rtu_over_tcp_bzp03_waveform | Waveforms from BZP-03 terminals via Modbus RTU over TCP | |
| 40 | sed_rtu_over_tcp_kmp_rtd | Multitical 601 polling via KMP protocol | sed_metrology_191.bin |
| 41 | sed_rtu_over_tcp_majac_arch | Archived data via Mayak protocol | sed_metrology_152.bin |
| 42 | sed_rtu_over_tcp_mercury200_rtd | Real-time data via Mercury 200 protocol | sed_metrology_251.bin |
| 43 | sed_rtu_over_tcp_mercury_arch | Archives and event logs from Mercury-230 via Mercury-230 protocol | sed_metrology_202.bin |
| 44 | sed_rtu_over_tcp_mercury_rtd | Real-time data via Mercury 230 protocol | sed_metrology_201.bin |
| 45 | sed_rtu_over_tcp_photon_rtd | Real-time data via Photon RTU over TCP | sed_metrology_221.bin |
| 46 | sed_rtu_over_tcp_ref542plus_waveform | Waveforms and time sync from ABB REF 542plus via Modbus RTU over TCP | |
| 47 | sed_rtu_over_tcp_set4tm_arch | Archive polling for SET-4TM / PSC meters | sed_metrology_112.bin |
| 48 | sed_rtu_over_tcp_set4tm_rtd | Real-time data via SET-4TM RTU over TCP | sed_metrology_111.bin |
| 49 | sed_rtu_over_tcp_tem104_arch | Archived data via TEM104 protocol | sed_metrology_162.bin |
| 50 | sed_rtu_over_tcp_tem104_rtd | Real-time data via TEM104 protocol | sed_metrology_161.bin |
| 51 | sed_rtu_over_tcp_vist_arch | Archived data via VIS.T protocol | sed_metrology_172.bin |
| 52 | sed_rtu_over_tcp_vkt5_arch | Archived data from VKT-5 devices | sed_metrology_312.bin |
| 53 | sed_rtu_over_tcp_vkt5_rtd | Real-time data from VKT-5 devices | sed_metrology_311.bin |
| 54 | sed_vkt7_arch | Archived data from VKT-7 devices | |
| 55 | sed_vkt7_rtd | Real-time data from VKT-7 devices | |
| 56 | sed_rtu_over_tcp_vtd_arch | Archived data from VTD devices | sed_metrology_182.bin |
| 57 | sed_rtu_over_tcp_vtd_rtd | Real-time data from VTD devices | sed_metrology_181.bin |
| 58 | sed_uvp_arch | Archive polling for Promavtomatika UVP series devices | sed_metrology_352.bin |
| 59 | sed_ce_arch | Archived data from Energomera CE series via IEC61107 | sed_metrology_362.bin |
| 60 | sed_alpha_arch | Archived data from Alfa-3 (Alfamera) devices | sed_metrology_462_472.bin |
| 61 | sed_bug01_rtd | Archived data from BUG-01 devices | sed_metrology_491.bin |
| 62 | sed_dymetic_arch | Archived data from Dimetik devices | sed_metrology_552.bin |
| 63 | sed_irvis_arch | Archived data via Irvis protocol | sed_metrology_442.bin |
| 64 | sed_karat_arch | Archived data from Karat devices | sed_metrology_512.bin |
| 65 | sed_lis200_arch | Archived data from EK-260 / EK-270 (Elster) | sed_metrology_452.bin |
| 66 | sed_rtu_over_tcp_sepam_waveform | Waveforms from Sepam via Modbus RTU over TCP | sed_metrology_298.bin |
3SQL Database Services
| № | Service | Function | Metrologically significant |
|---|---|---|---|
| 67 | sed_sql_rtd | Telemetry data from MySQL | sed_metrology_211.bin |
| 68 | sed_sql_arch | Archived data from Microsoft SQL Server | |
| 69 | sed_sql_arch_send | Recording archived data in an external SQL database | |
8Data Processing Services
| № | Service | Function | Metrologically significant |
|---|---|---|---|
| 70 | sed_controller | Telemetry reception and processing → InfluxDB | sed_metrology_controller.bin |
| 71 | sed_events_calc | Event generation based on configured conditions | |
| 72 | sed_calc_arch | Metering channel calculation | sed_metrology_formulas.bin |
| 73 | sed_calc_rtd | Pre-calculated tag calculation | |
| 74 | sed_round_arch | Metering channel calculation with rounding | sed_metrology_round_arch.bin |
| 75 | sed_energy_calc | Server-level energy resource calculation | sed_metrology_411_421.bin |
| 76 | sed_loadflow_calc | Mode calculation based on information model | |
| 77 | sed_storage_writer | Archived data recording (metering and event logs) to InfluxDB and MongoDB | |
29Web Part Services
| № | Service | Function |
|---|---|---|
| 78 | php5-cgi | Web query service |
| 79 | nginx | Web server |
| 80 | sed_ui | Web resources processing service (js, css, html) |
| 81 | sed_rtd_analysis | Real-time data analysis |
| 82 | sed_auth | User authentication, authorisation, and access rights configuration |
| 83 | sed_auth_privileges | User privilege management |
| 84 | sed_api_gateway | Web request proxy to internal services |
| 85 | sed_dataset_configurator | Dataset management |
| 86 | sed_rtd | Real-time data subscription service |
| 87 | sed_rtd_archive | Archived data generation from real-time data |
| 88 | sed_system | System information user interface |
| 89 | sed_archive | Electricity and energy accounting analysis interfaces |
| 90 | sed_archive_integrity | Data completeness analysis interfaces |
| 91 | sed_pq_reports | Power quality reports |
| 92 | sed_pq_journal | Power quality logs |
| 93 | sed_ras_configurator | Emergency event logger configuration |
| 94 | sed_reports | Report generation, management, and user interfaces |
| 95 | sed_widgets | Drag-and-drop widget user interfaces |
| 96 | sed_menu | CENTO system menu display and configuration |
| 97 | sed_mnemonic_diagrams | Mimic diagram user interfaces |
| 98 | sed_configuration | System configuration interfaces (alerts, mail, formulas, device trees) |
| 99 | sed_devices_configurator | Device, tag, and channel configuration editing |
| 100 | sed_common_event_journal | Configurable event log interfaces |
| 101 | sed_device_event_journal | Device log service |
| 102 | sed_operative_journal | Real-time event log interface |
| 103 | sed_video_journal | Video camera log service |
| 104 | sed_marking | Dispatch tag management |
| 105 | sed_sounds | Sound alert management interface |
| 106 | sed_video_stream | Video streaming from cameras registered in the information model |
28System Services
| № | Service | Function |
|---|---|---|
| 107 | nats | Message broker and service registry (service discovery) |
| 108 | influxdb | Time series database |
| 109 | mongod | Configuration and structured data database (event logs, requests) |
| 110 | sed_signals | Internal signal service (remote control, config update, manual polling, recalculation) |
| 111 | sed_debug | Debugging information access service |
| 112 | sed_graphql | Device status subscription service |
| 113 | sed_energy_calc_configuration | Energy resource calculation configuration management |
| 114 | sed_backup | Data backup service (cron-based) |
| 115 | sed_channels | Channel subscription service |
| 116 | sed_channels_data | Protocol status and latest channel data subscription service |
| 117 | sed_emission_archive | Emissions metering data service |
| 118 | sed_profile_archive | Metering data from InfluxDB via gRPC |
| 119 | sed_web_storage | Archived metering data access service |
| 120 | sed_watchdog | CENTO services launch management |
| 121 | sed_scheduler | Scheduled task launcher |
| 122 | sed_license | Licensing service |
| 123 | sed_monitor | System services and application monitoring |
| 124 | sed_telecontrol | Remote control service |
| 125 | sed_notificator | User event notification by e-mail |
| 126 | sed_waveforms_journal | Waveform log service |
| 127 | sed_storage_configurator | InfluxDB database configuration |
| 128 | sed_system_configuration | CENTO system configuration editing |
| 129 | sed_user_settings | User settings configuration |
| 130 | sed_system_event_journal | System event registration and provision (business logic and administration) |
| 131 | sed_tech_event_journal | Technological event processing and management |
| 132 | sed_information_model | System information model management |
| 133 | sed_events_configurator | System event configuration |
| 134 | sed_information_model_editor | Information model editing |
6Server Backup Services
| № | Service | Function |
|---|---|---|
| 135 | sed_coordinator | Cluster host server selection via Bully leader election algorithm |
| 136 | sed_reserve | Backup server list display |
| 137 | sed_replicator | Log replication between cluster servers |
| 138 | sed_influx_replicator | Data replication (archives and telemetry) between cluster servers |
| 139 | sed_events_replicator | Event replication between cluster servers |
| 140 | sed_fs_synch | File replication (gob files, reports, waveforms) between cluster servers |
(function(){
var bar = document.getElementById('rn-prog');
var top = document.getElementById('rn-top');
window.addEventListener('scroll', function(){
var d = document.documentElement;
var pct = d.scrollTop / (d.scrollHeight - d.clientHeight);
bar.style.width = (pct*100)+'%';
d.scrollTop > 280 ? top.classList.add('on') : top.classList.remove('on');
}, {passive:true});
/* TOC active via IntersectionObserver */
var links = Array.from(document.querySelectorAll('[data-toc]'));
var secs = links.map(function(a){
return {el: document.getElementById(a.getAttribute('href').slice(1)), a:a};
}).filter(function(s){return s.el;});
var busy = false;
var obs = new IntersectionObserver(function(entries){
if (busy) return;
entries.forEach(function(e){
if (!e.isIntersecting) return;
var i = secs.findIndex(function(s){return s.el===e.target;});
if (i<0) return;
secs.forEach(function(s){s.a.classList.remove('toc-on');});
secs[i].a.classList.add('toc-on');
var toc = document.querySelector('.doc-toc');
var link = secs[i].a;
toc.scrollTop = link.offsetTop - toc.offsetTop - toc.clientHeight/2 + link.clientHeight/2;
});
}, {rootMargin:'-8% 0px -60% 0px', threshold:0});
secs.forEach(function(s){obs.observe(s.el);});
links.forEach(function(a){
a.addEventListener('click', function(e){
var id = a.getAttribute('href').slice(1);
var el = document.getElementById(id);
if (!el) return;
e.preventDefault();
busy = true;
secs.forEach(function(s){s.a.classList.remove('toc-on');});
a.classList.add('toc-on');
el.scrollIntoView({behavior:'smooth',block:'start'});
setTimeout(function(){busy=false;}, 900);
});
});
/* TOC collapse */
var tocList = document.getElementById('toc-list');
var togBtn = document.getElementById('toc-tog');
var collapsed = false;
togBtn && togBtn.addEventListener('click', function(){
collapsed = !collapsed;
tocList.style.display = collapsed ? 'none' : '';
togBtn.textContent = collapsed ? '»' : '«';
});
/* Lightbox */
var lb = document.getElementById('rn-lb');
var lbI = document.getElementById('rn-lb-img');
var lbC = document.getElementById('rn-lb-cap');
window.openLightbox = function(img){
lbI.src = img.src; lbI.alt = img.alt; lbC.textContent = img.alt;
lb.classList.add('open'); document.body.style.overflow='hidden';
};
window.closeLightbox = function(){
lb.classList.remove('open'); document.body.style.overflow='';
};
lb.addEventListener('click', function(e){if(e.target===lb)closeLightbox();});
document.addEventListener('keydown', function(e){if(e.key==='Escape')closeLightbox();});
/* Copy anchor */
var toast = document.getElementById('rn-toast');
var t;
window.copyAnchor = function(id){
var url = location.href.split('#')[0]+'#'+id;
if(navigator.clipboard) navigator.clipboard.writeText(url).catch(function(){});
else { var x=document.createElement('textarea');x.value=url;document.body.appendChild(x);x.select();document.execCommand('copy');document.body.removeChild(x); }
toast.classList.add('on');
clearTimeout(t);
t = setTimeout(function(){toast.classList.remove('on');}, 1600);
};
/* ── Sticky TOC via JS ──
Uses position:absolute→fixed switch. No placeholder needed because
.doc-content has a fixed margin-right — content width never changes. */
(function(){
var toc = document.querySelector('.doc-toc');
var wrap = document.querySelector('.doc-wrap');
if (!toc || !wrap) return;
var navbar = document.querySelector('.navbar, nav.navbar, #topBar, header');
var navH = navbar ? navbar.offsetHeight : 0;
var footer = document.querySelector('footer, .xwikifooter, #xwikifooter, .bottombar, .sticky-container');
function update() {
var wr = wrap.getBoundingClientRect();
if (wr.top <= navH) {
/* Doc-wrap has scrolled above navbar — fix the TOC */
var footerTop = footer ? footer.getBoundingClientRect().top : window.innerHeight;
var h = Math.max(0, Math.min(window.innerHeight, footerTop) - navH);
toc.classList.add('is-fixed');
toc.style.top = navH + 'px';
toc.style.left = (wr.right - 200) + 'px'; /* right edge of wrap minus TOC width */
toc.style.height = h + 'px';
} else {
/* Still in natural position */
toc.classList.remove('is-fixed');
toc.style.top = toc.style.left = toc.style.height = '';
}
}
requestAnimationFrame(function(){ requestAnimationFrame(function(){
update();
window.addEventListener('scroll', update, {passive:true});
window.addEventListener('resize', update, {passive:true});
}); });
})();
/* ── Left panel navigation hints ── */
(function(){
var hints = {
'Preface': 'Introduction to CENTO Software documentation',
'1. User Manual': 'Web interface guide — login, diagrams, analysis, logs, reports',
'2. Setup Manual': 'System configuration and setup for administrators',
'3. Metering Channels': 'Metering channel configuration and data collection setup',
'4. Structure and Modules Description': 'CENTO system architecture and module overview',
'5. Diagrams Development Manual': 'Creating and configuring SCADA mimic diagrams',
'6. Flexible Reports Guide': 'Building configurable tabular reports from metering data',
'7. Events Setup': 'Configuring event rules, thresholds, and notifications',
'8. Control Setup Manual': 'Setting up remote control and switching operations',
'Network Diagram (CLOUD)': 'CENTO deployment topology for cloud installations',
'Network Diagram (On-Prem)': 'CENTO deployment topology for on-premises installations',
'Server Configuration Example': 'Reference server setup and configuration example',
'Technical Requirements': 'Hardware and software requirements for CENTO deployment',
'DEMO server': 'Access the live CENTO demo environment',
'Release Notes': 'Changelog and version history for the CENTO platform',
};
document.querySelectorAll('#leftPanels a').forEach(function(a){
var text = a.textContent.trim();
var hint = hints[text] || hints[text.replace(/^\d+\.\s*/, '')];
if (hint) a.setAttribute('data-hint', hint);
});
})();
})();