2026-09-03 21:30:32 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* base-url.js - 设备 IP / 基础 URL 公共助手
|
|
|
|
|
|
*
|
|
|
|
|
|
* 各页面共用 localStorage key dcu_esp_ip(OTA / CAN / 电机页一致)。
|
|
|
|
|
|
* 本模块只提供纯 localStorage + 默认值的通用实现;页面特有的输入框回退
|
|
|
|
|
|
* (如 CAN 页 el.toolIp、OTA 页 #espIp)由各自入口在此基础上包装。
|
2026-10-13 00:48:34 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 写入一律经 saveDeviceIp 的 IPv4 校验:这是"哪个端点算当前设备"的唯一落盘点,
|
|
|
|
|
|
* 脏值在这里被拒掉,比在每个调用方各判一次可靠(固件 AP/掉租约时会上报
|
|
|
|
|
|
* ip:"0.0.0.0",见 ESP32_COMM/main/server/http_handlers.cpp:1776)。
|
2026-09-03 21:30:32 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export var LS_ESP_IP = 'dcu_esp_ip';
|
|
|
|
|
|
export var DEFAULT_ESP_IP = '192.168.31.127';
|
2026-10-13 00:48:34 +08:00
|
|
|
|
/** @description 最近一次"选中"的设备快照(ip + 版本指纹),供端点一致性核对。 */
|
|
|
|
|
|
export var LS_ESP_DEVICE = 'dcu_esp_device';
|
|
|
|
|
|
/** @description 局域网发现结果缓存(带 observedAt),只给 UI 当候选种子用。 */
|
|
|
|
|
|
export var LS_LAN_DEVICES = 'dcu_lan_devices';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @description 严格点分十进制 IPv4,并拒掉不能当 API 目标的保留段。
|
|
|
|
|
|
* 0/8(含固件脏值 0.0.0.0)与 255.255.255.255 直接拒;169.254/16 是链路本地,
|
|
|
|
|
|
* 板子不会在那儿服务 HTTP,也拒——宁可让人重填,也不要网页打到一个
|
|
|
|
|
|
* 永远不通的地址上还显示"已连接"。
|
|
|
|
|
|
* @param {string} s 待校验字符串
|
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function isValidIpv4(s) {
|
|
|
|
|
|
var parts = String(s == null ? '' : s).trim().split('.');
|
|
|
|
|
|
if (parts.length !== 4) return false;
|
|
|
|
|
|
var nums = [];
|
|
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
|
|
|
|
var p = parts[i];
|
|
|
|
|
|
if (!/^\d{1,3}$/.test(p)) return false;
|
|
|
|
|
|
var n = parseInt(p, 10);
|
|
|
|
|
|
if (n > 255) return false;
|
|
|
|
|
|
nums.push(n);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nums[0] === 0 || nums[0] === 255) return false;
|
|
|
|
|
|
if (nums[0] === 169 && nums[1] === 254) return false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-09-03 21:30:32 +08:00
|
|
|
|
|
|
|
|
|
|
/** @description 读取 localStorage 中保存的设备 IP,无有效值返回空串。 */
|
|
|
|
|
|
export function getSavedDeviceIp() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var saved = localStorage.getItem(LS_ESP_IP);
|
|
|
|
|
|
if (saved && saved.trim()) return saved.trim();
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 00:48:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @description 保存设备 IP。非法值**拒绝且不改写现值**,返回 false——
|
|
|
|
|
|
* 不能用 DEFAULT_ESP_IP 去"兜住"脏输入,那会把配置自证成连接成功。
|
|
|
|
|
|
* @param {string} ip
|
|
|
|
|
|
* @returns {boolean} 是否真的写进去了
|
|
|
|
|
|
*/
|
2026-09-03 21:30:32 +08:00
|
|
|
|
export function saveDeviceIp(ip) {
|
2026-10-13 00:48:34 +08:00
|
|
|
|
var v = String(ip == null ? '' : ip).trim();
|
|
|
|
|
|
if (!isValidIpv4(v)) return false;
|
|
|
|
|
|
try { localStorage.setItem(LS_ESP_IP, v); } catch (_) { return false; }
|
|
|
|
|
|
return true;
|
2026-09-03 21:30:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 设备 IP:localStorage 优先,否则默认值。 */
|
|
|
|
|
|
export function getDeviceIp() {
|
|
|
|
|
|
return getSavedDeviceIp() || DEFAULT_ESP_IP;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 拼接设备基础 URL(http://<ip>)。 */
|
|
|
|
|
|
export function getBaseUrl() {
|
|
|
|
|
|
return 'http://' + getDeviceIp();
|
|
|
|
|
|
}
|
2026-10-13 00:48:34 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @description 读最近一次选中的设备指纹;结构不对一律当没有。
|
|
|
|
|
|
* @returns {{ip:string, version:string, git_hash:string, observedAt:number, source:string}|null}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getDeviceMeta() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var raw = localStorage.getItem(LS_ESP_DEVICE);
|
|
|
|
|
|
if (!raw) return null;
|
|
|
|
|
|
var o = JSON.parse(raw);
|
|
|
|
|
|
if (!o || typeof o !== 'object' || !isValidIpv4(o.ip)) return null;
|
|
|
|
|
|
return o;
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 写设备指纹(与 IP 同时机,由 deviceLink.selectDevice 统一调用)。 */
|
|
|
|
|
|
export function saveDeviceMeta(meta) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
localStorage.setItem(LS_ESP_DEVICE, JSON.stringify(meta || {}));
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @description 读发现结果缓存;返回数组(可能为空),坏数据直接丢弃不猜。
|
|
|
|
|
|
* @returns {Array<object>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getLanDevices() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
var raw = localStorage.getItem(LS_LAN_DEVICES);
|
|
|
|
|
|
if (!raw) return [];
|
|
|
|
|
|
var arr = JSON.parse(raw);
|
|
|
|
|
|
if (!Array.isArray(arr)) return [];
|
|
|
|
|
|
return arr.filter(function (d) { return d && isValidIpv4(d.ip); });
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 存发现结果缓存(上限 16 条,超了截断——它只是候选种子,不是台账)。 */
|
|
|
|
|
|
export function saveLanDevices(list) {
|
|
|
|
|
|
var arr = Array.isArray(list) ? list.slice(0, 16) : [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
localStorage.setItem(LS_LAN_DEVICES, JSON.stringify(arr));
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
}
|