fix(acs): SSID hanya SSID1/SSID5, split task 9007, RX Power/LOS
Some checks failed
CI / test (push) Has been cancelled

- port updateSSID/updatePassword dari upstream
- tidak menyentuh SSID2/Guest
- setParameterValues per parameter
- RX power negatif/raw + LOS
- versi 0.6.9
This commit is contained in:
oxpoe
2026-11-06 11:00:29 +07:00
parent 5361c3a1c1
commit de120f08a9
5 changed files with 169 additions and 111 deletions

View File

@@ -2,6 +2,14 @@
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
## [0.6.9] — 2026-11-06 — "acs-wifi-rx"
### Diperbaiki
- **ACS/GenieACS WiFi**: SSID hanya mengubah SSID1 (2.4G) dan SSID5 (5G bila tersedia); tidak lagi menyentuh SSID2/Guest.
- `setParameterValues` dipecah per parameter agar tidak kena TR-069 fault 9007.
- Sinkronisasi nama WiFi baru ke data pelanggan setelah update.
- **RX Power**: dukung nilai negatif, konversi raw CTC EPON/GPON, dan tampilkan **LOS** saat sinyal putus.
## [0.6.8] — 2026-11-06 — "isolir-url-setting"
### Ditambahkan

View File

@@ -11,7 +11,7 @@
<p align="center">
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
<img src="https://img.shields.io/badge/versi-0.6.8_isolir--url-ADFF2F" alt="Versi 0.6.8 (isolir-url-setting)">
<img src="https://img.shields.io/badge/versi-0.6.9_acs--wifi-ADFF2F" alt="Versi 0.6.9 (acs-wifi-rx)">
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>

View File

@@ -1,6 +1,6 @@
{
"name": "open-isp",
"version": "0.6.8",
"version": "0.6.9",
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
"main": "app-customer.js",
"scripts": {

View File

@@ -696,11 +696,19 @@ function mapDeviceData(device, tag, isPppoeActive = false) {
} catch {}
let rxPower = getParameterWithPaths(device, parameterPaths.rxPower);
if (rxPower !== 'N/A' && rxPower !== '-' && rxPower !== '') {
const num = parseFloat(rxPower);
if (!isNaN(num) && num > 0) {
const dbVal = 30 + (Math.log10(num * Math.pow(10, -7)) * 10);
rxPower = (Math.ceil(dbVal * 100) / 100).toFixed(2);
if (rxPower !== 'N/A' && rxPower !== '-' && rxPower !== '' && rxPower !== null && rxPower !== undefined) {
const raw = parseFloat(rxPower);
if (!isNaN(raw)) {
if (raw === 0 || raw >= 65535) {
rxPower = 'LOS';
} else if (raw < 0 && raw > -50) {
rxPower = raw.toFixed(2);
} else if (raw <= -50) {
rxPower = (raw / 100).toFixed(2);
} else if (raw > 0) {
const dbm = 30 + (Math.log10(raw * 1e-7) * 10);
rxPower = (Math.round(dbm * 100) / 100).toFixed(2);
}
}
}
const pppoeIP = extractPppoeIp(device);
@@ -851,6 +859,7 @@ async function updateSSID(tag, newSSID, actor = null) {
try {
const ssidClean = String(newSSID ?? '').replace(/[\r\n\t]+/g, '').trim();
if (!ssidClean) { logger.warn('[updateSSID] SSID kosong, dibatalkan.'); return false; }
newSSID = ssidClean;
const device = await resolveDeviceToken(tag);
if (!device) return false;
const deviceId = encodeURIComponent(device._id);
@@ -863,62 +872,96 @@ async function updateSSID(tag, newSSID, actor = null) {
const tasksUrl = `/devices/${deviceId}/tasks`;
const parameterValues = [];
const igd = device.InternetGatewayDevice;
const dev181 = device.Device;
// Check supported paths in DB
const db = require('../config/database');
const row = db.prepare('SELECT params FROM acs_devices WHERE id = ?').get(device._id);
const flatParams = row && row.params ? JSON.parse(row.params) : null;
// Check supported paths directly from loaded device object
if (igd && igd.LANDevice && igd.LANDevice['1'] && igd.LANDevice['1'].WLANConfiguration) {
const wlan = igd.LANDevice['1'].WLANConfiguration;
// Selalu ubah SSID 1 (2.4GHz Utama)
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', newSSID, 'xsd:string']);
if (flatParams) {
// SSID 2.4G paths
const paths24G = [
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID',
'Device.WiFi.SSID.1.SSID'
];
paths24G.forEach(p => {
if (flatParams[p] !== undefined) {
parameterValues.push([p, ssidClean, 'xsd:string']);
}
});
// SSID 5G paths
const paths5G = [
'Device.WiFi.SSID.2.SSID'
];
for (const idx of [5, 6, 7, 8]) {
paths5G.push(`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.SSID`);
}
paths5G.forEach(p => {
if (flatParams[p] !== undefined) {
parameterValues.push([p, `${ssidClean}-5G`, 'xsd:string']);
}
});
}
// Fallback if no parameters match or device not bootstrapped yet
if (parameterValues.length === 0) {
parameterValues.push(
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', ssidClean, 'xsd:string'],
['Device.WiFi.SSID.1.SSID', ssidClean, 'xsd:string']
// HANYA jika parameter SSID 5G (WLANConfiguration.5) benar-benar ada di perangkat.
// JANGAN PERNAH mengubah WLANConfiguration.2 (SSID 2) karena itu adalah SSID2/Guest pada perangkat single-band!
const hasFiveG = Boolean(
wlan['5'] && (
wlan['5'].SSID !== undefined ||
wlan['5']._object === true ||
wlan['5'].Enable !== undefined ||
wlan['5'].KeyPassphrase !== undefined
)
);
if (hasFiveG) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.SSID', `${newSSID}-5G`, 'xsd:string']);
}
} else if (dev181 && dev181.WiFi) {
parameterValues.push(['Device.WiFi.SSID.1.SSID', newSSID, 'xsd:string']);
} else {
// Fallback check in local DB
const db = require('../config/database');
const row = db.prepare('SELECT params FROM acs_devices WHERE id = ?').get(device._id);
const flatParams = row && row.params ? JSON.parse(row.params) : null;
if (flatParams) {
if (flatParams['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID'] !== undefined) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', newSSID, 'xsd:string']);
} else if (flatParams['Device.WiFi.SSID.1.SSID'] !== undefined) {
parameterValues.push(['Device.WiFi.SSID.1.SSID', newSSID, 'xsd:string']);
}
if (flatParams['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.SSID'] !== undefined) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.SSID', `${newSSID}-5G`, 'xsd:string']);
}
}
if (parameterValues.length === 0) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', newSSID, 'xsd:string']);
}
}
let ok = false;
try {
await instance.post(tasksUrl, {
name: 'setParameterValues',
parameterValues: parameterValues
}, { timeout: 20000 });
ok = true;
} catch (e) {
logger.error(`[updateSSID] Failed to set SSID: ${e.message}`);
for (const pv of parameterValues) {
try {
await instance.post(tasksUrl, {
name: 'setParameterValues',
parameterValues: [pv]
}, { timeout: 15000 });
ok = true;
} catch (e) {
logger.error(`[updateSSID] Failed to set parameter ${pv[0]}: ${e.message}`);
}
}
// Refresh + inform dijalankan di latar belakang agar respons cepat (tidak menahan UI)
// Refresh objects untuk trigger update parameter dari ONU
try {
await instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 15000 });
} catch (e) {}
// Tunggu sesaat agar task dieksekusi oleh ONU
if (ok) {
instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 12000 }).catch(() => {});
instance.post(tasksUrl, { name: 'inform' }, { timeout: 12000 }).catch(() => {});
logger.info(`[updateSSID] Set SSID dikirim untuk ${tag}; refresh & inform berjalan di latar belakang`);
await new Promise(resolve => setTimeout(resolve, 3000));
// Sinkronkan nama WiFi (SSID) baru ke database pelanggan
try {
const db = require('../config/database');
const pppoeUser = extractPppoeUser(device);
const tagCandidate = (device._tags && device._tags[0]) || null;
const snCandidate = device._deviceId?._SerialNumber || null;
const idCandidate = device._id || null;
const cust = db.prepare(`
SELECT id FROM customers
WHERE id = ?
OR (pppoe_username IS NOT NULL AND pppoe_username != '' AND (pppoe_username = ? OR pppoe_username = ?))
OR (genieacs_tag IS NOT NULL AND genieacs_tag != '' AND (genieacs_tag = ? OR genieacs_tag = ?))
OR (mac_address IS NOT NULL AND mac_address != '' AND (mac_address = ? OR mac_address = ? OR mac_address = ?))
OR phone = ?
LIMIT 1
`).get(tag, tag, pppoeUser, tag, tagCandidate, tag, snCandidate, idCandidate, tag);
if (cust && cust.id) {
db.prepare('UPDATE customers SET wifi_ssid = ? WHERE id = ?').run(newSSID, cust.id);
}
} catch (err) {
logger.debug(`[updateSSID] Failed to sync customer wifi_ssid: ${err.message}`);
}
}
// Catat audit trail jika berhasil
@@ -970,71 +1013,78 @@ async function updatePassword(tag, newPassword, actor = null) {
logger.info(`[updatePassword] Setting password for device ${deviceId}, tag ${tag}`);
const parameterValues = [];
const igd = device.InternetGatewayDevice;
const dev181 = device.Device;
// Check supported paths in DB
const db = require('../config/database');
const row = db.prepare('SELECT params FROM acs_devices WHERE id = ?').get(device._id);
const flatParams = row && row.params ? JSON.parse(row.params) : null;
if (igd && igd.LANDevice && igd.LANDevice['1'] && igd.LANDevice['1'].WLANConfiguration) {
const wlan = igd.LANDevice['1'].WLANConfiguration;
const wlan1 = wlan['1'] || {};
if (flatParams) {
// 2.4G password paths
const paths24G = [
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase',
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.KeyPassphrase',
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey',
'Device.WiFi.AccessPoint.1.Security.KeyPassphrase',
'Device.WiFi.AccessPoint.1.Security.PreSharedKey'
];
paths24G.forEach(p => {
if (flatParams[p] !== undefined) {
parameterValues.push([p, pw, 'xsd:string']);
}
});
// 5G password paths
const paths5G = [
'Device.WiFi.AccessPoint.2.Security.KeyPassphrase',
'Device.WiFi.AccessPoint.2.Security.PreSharedKey'
];
for (const idx of [5, 6, 7, 8]) {
paths5G.push(
`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.KeyPassphrase`,
`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.PreSharedKey.1.KeyPassphrase`,
`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.PreSharedKey.1.PreSharedKey`
);
if (wlan1.KeyPassphrase !== undefined) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string']);
}
if (wlan1.PreSharedKey && typeof wlan1.PreSharedKey === 'object') {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
}
if (parameterValues.length === 0) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string']);
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
}
paths5G.forEach(p => {
if (flatParams[p] !== undefined) {
parameterValues.push([p, pw, 'xsd:string']);
}
});
}
// Fallback if no parameters match or device not bootstrapped yet
if (parameterValues.length === 0) {
parameterValues.push(
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string'],
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.KeyPassphrase', pw, 'xsd:string'],
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string'],
['Device.WiFi.AccessPoint.1.Security.KeyPassphrase', pw, 'xsd:string']
// 5G if available - HANYA jika WLANConfiguration.5 ada di perangkat. JANGAN PERNAH mengubah WLANConfiguration.2!
const hasFiveG = Boolean(
wlan['5'] && (
wlan['5'].SSID !== undefined ||
wlan['5'].KeyPassphrase !== undefined ||
wlan['5'].PreSharedKey !== undefined ||
wlan['5']._object === true ||
wlan['5'].Enable !== undefined
)
);
if (hasFiveG) {
const wlan5 = wlan['5'] || {};
if (wlan5.KeyPassphrase !== undefined) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.KeyPassphrase', pw, 'xsd:string']);
}
if (wlan5.PreSharedKey && typeof wlan5.PreSharedKey === 'object') {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
}
if (!wlan5.KeyPassphrase && (!wlan5.PreSharedKey || typeof wlan5.PreSharedKey !== 'object')) {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.KeyPassphrase', pw, 'xsd:string']);
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
}
}
} else if (dev181 && dev181.WiFi) {
parameterValues.push(['Device.WiFi.AccessPoint.1.Security.KeyPassphrase', pw, 'xsd:string']);
} else {
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string']);
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
}
let ok = false;
try {
await instance.post(tasksUrl, {
name: 'setParameterValues',
parameterValues: parameterValues
}, { timeout: 20000 });
ok = true;
} catch (e) {
logger.error(`[updatePassword] Failed to set password: ${e.message}`);
for (const pv of parameterValues) {
try {
await instance.post(tasksUrl, {
name: 'setParameterValues',
parameterValues: [pv]
}, { timeout: 15000 });
ok = true;
} catch (e) {
logger.debug(`[updatePassword] Param ${pv[0]} failed: ${e.message}`);
}
}
// Refresh di latar belakang agar respons cepat
instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 12000 }).catch(() => {});
// Refresh object - only refresh InternetGatewayDevice path yang lebih universal
try {
await instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 15000 });
} catch (e) {}
// Skip Device.WiFi.AccessPoint refresh karena tidak semua ONU support (CIOT tidak support)
// Tunggu sesaat agar task dieksekusi oleh ONU
if (ok) {
await new Promise(resolve => setTimeout(resolve, 3000));
}
// Catat audit trail jika berhasil
if (ok && actor) {
auditTrail.logAuditTrail({

View File

@@ -1 +1 @@
0.6.8
0.6.9