From de120f08a96ebfa0a22d72c7ef01accd43ade9a1 Mon Sep 17 00:00:00 2001 From: oxpoe Date: Fri, 6 Nov 2026 11:00:29 +0700 Subject: [PATCH] fix(acs): SSID hanya SSID1/SSID5, split task 9007, RX Power/LOS - port updateSSID/updatePassword dari upstream - tidak menyentuh SSID2/Guest - setParameterValues per parameter - RX power negatif/raw + LOS - versi 0.6.9 --- CHANGELOG.md | 8 + README.md | 2 +- package.json | 2 +- services/customerDeviceService.js | 266 ++++++++++++++++++------------ version.txt | 2 +- 5 files changed, 169 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0404a2..af674bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index e21b903..1a17282 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Lisensi: ISC - Versi 0.6.8 (isolir-url-setting) + Versi 0.6.9 (acs-wifi-rx) Node.js >= 20 Docker Demo diff --git a/package.json b/package.json index 57441df..5b380bc 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/services/customerDeviceService.js b/services/customerDeviceService.js index b69775d..4ae7b8c 100644 --- a/services/customerDeviceService.js +++ b/services/customerDeviceService.js @@ -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; - - 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']); - } - }); + // 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']); - // 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 = []; - - // 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 (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` - ); + const igd = device.InternetGatewayDevice; + const dev181 = device.Device; + + if (igd && igd.LANDevice && igd.LANDevice['1'] && igd.LANDevice['1'].WLANConfiguration) { + const wlan = igd.LANDevice['1'].WLANConfiguration; + const wlan1 = wlan['1'] || {}; + + if (wlan1.KeyPassphrase !== undefined) { + parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', 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'] + 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']); + } + + // 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({ diff --git a/version.txt b/version.txt index bc8443e..1864002 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.6.8 \ No newline at end of file +0.6.9 \ No newline at end of file