Compare commits

...
17 Commits
Author SHA1 Message Date
efox 0da880ec86 feat(atsc_app): 更新138R射频状态展示与维护说明
将发布版本记录更新至138R,并在Overview中为SNR、LIMD和UIMD增加与Dashboard一致的阈值着色,同时统一LIMD/UIMD趋势顺序。
补充ORx QEC测试背景和DPD结束后FAGC恢复方案注释;恢复调用仍保持注释状态,不改变现有运行行为。
已执行暂存区差异、空白错误及冲突标记检查;未执行编译和设备验证。
2026-08-28 20:10:46 +08:00
efox f6e4f84c27 fix(atsc_app): 暂停FSSI刷新期间的IQ采集
FSSI Refresh开启后,网页提前改写全局状态,使频谱隐藏函数误判状态
未变化而直接返回;同时i.MX仍持续组装IQ窗口并执行FFT和眼图分析。

由频谱页状态函数统一完成前端切换。后端在A/B轮询期间丢弃IQ包,
清除待处理窗口和旧测量结果;关闭FSSI Refresh后,从pkt_idx=0的
新窗口恢复采集,避免混入A/B切换期间或内核积压的旧数据。

验证:node --check src/web/www/js/dashboard.js,通过。
验证:加载Yocto环境后执行make -C src/atsc_app -j2,编译通过。
未部署到192.168.1.143,未执行板端FSSI开关和恢复验证。
2026-08-28 20:06:02 +08:00
efox 61c85f1fdc feat(atsc_app): 增加ORx FSSI与实时IQ测量
V1.1平台的FAGC粗调死区内无法反映功率变化,且频谱页面关闭或DPD
更新期间,状态页面的SNR和左右带肩可能停止更新。

组合FAGC控制字和RMS残差计算FSSI,并保留A/B切换防抖;采用固定
500 kHz窗口实时计算SNR、LIMD和UIMD,后台测量不再依赖频谱页面。
同步网页无效值、A/B模式和左右带肩显示,并将DPD控制默认值设为
0x07,同时保持HardwareVersion参数解析顺序兼容。

验证:make -C src/atsc_app/tests check,全部通过。
验证:加载Yocto环境后执行make -C src/atsc_app -j2,编译通过。
未执行V1.1板端射频、AVQ及DPD训练后的实机验证。
2026-08-28 20:04:33 +08:00
rfeng 94a6772782 文档:补充射频恢复与校准说明 2026-08-28 13:31:56 +08:00
rfeng d76eff51c4 维护:更新 SMOD 至 V137R 的版本记录 2026-08-28 13:29:54 +08:00
rfeng 233866b5df 新增:射频性能阈值着色 2026-08-28 13:29:31 +08:00
rfeng 2cf8ca7a5a 修复:DPD 完成后恢复 ORx FAGC 跟踪 2026-08-28 13:29:16 +08:00
rfeng 46db56d45c 新增:扩展 V1.1 WE 校准范围 2026-08-28 13:29:04 +08:00
efoxandrfeng 9763f09b4d 新增:V1.1 功率控制与射频路径更新 2026-08-28 13:28:36 +08:00
efoxandrfeng 936a850c6b 新增:V1.1 硬件功率校准 2026-08-28 13:28:25 +08:00
rfeng eebdd22256 重构:优化 DPD 的 FAGC 与 DAGC 时序 2026-08-28 13:28:14 +08:00
rfeng 8243380474 新增:概览仪表盘及统一频谱页面 2026-08-28 13:27:39 +08:00
rfeng bded7b6836 修复:FSSI 刷新期间协调射频路径与 IQ 采集 2026-08-28 13:26:58 +08:00
rfeng 33d8f4f428 新增:FPGA 升级后热恢复射频链路 2026-08-28 13:26:45 +08:00
rfeng 47b782e976 修复:重启后保留 TSOIP 参数 2026-08-28 13:26:23 +08:00
rfeng fad8784550 修复:热重启时保持射频时钟 2026-08-28 13:25:54 +08:00
rfeng f939716614 修复:防止 ATSC 应用重复启动 2026-08-28 13:23:53 +08:00
53 changed files with 8713 additions and 4103 deletions
@@ -0,0 +1,108 @@
# PR17:DPD结束后恢复ORx FAGC实时跟踪
## 1. 背景
当前冷启动流程会调用 `boot_cfg_fagc_update()`,向 FPGA 的
`FPGA_ADDR_DPD_CTRL3` 写入 `DPD_CTRL3_FAGC_UPDATE`,因此在未执行
DPD Update 时,ORx FAGC保持实时跟踪。
执行DPD Update后,FAGC在训练阶段工作一段时间,然后被锁定。DPD完整结束后,
当前流程没有恢复冷启动时的FAGC工作状态,导致后续ORx反馈射频电平发生较大变化时,
FAGC不能再次收敛。
## 2. 需求
DPD Update完整结束后,恢复与设备冷启动后相同的ORx FAGC实时跟踪状态。
要求:
- 只修改i.MX6应用代码。
- 不修改FPGA RTL。
- 不修改ORx DAGC逻辑。
- 不改变DPD训练期间现有的FAGC启停和锁定时序。
- 不触发新的DPD训练,不清除已经生成的DPD系数。
## 3. 修改方案
目标文件:
```text
imx6/src/atsc_app/bsp/dpd_proc.c
```
复用现有函数:
```c
boot_cfg_fagc_update();
```
该函数定义于:
```text
imx6/src/atsc_app/bsp/fpga.c
```
如果 `dpd_proc.c` 中没有函数声明,则增加:
```c
extern void boot_cfg_fagc_update(void);
```
在DPD Update完整成功结束、即将退出更新状态机的位置,调用:
```c
/* 恢复冷启动后的ORx FAGC实时跟踪状态。 */
boot_cfg_fagc_update();
log_printf(LOG_INFO, I_USER_CFG,
"[DPD] update complete, ORx FAGC tracking restored\n");
```
如果该完整结束出口当前执行以下最终清零:
```c
fpga_byte_write(0x05, FPGA_ADDR_DPD_CTRL3, 0);
```
则将这一次最终清零替换为 `boot_cfg_fagc_update()`。
必须保留phase 1、phase 7以及其他DPD训练阶段内部用于锁定FAGC的
`FPGA_ADDR_DPD_CTRL3 = 0` 操作。这些操作属于现有DPD收敛流程,不能替换。
如果工程中存在多套DPD Update状态机,应分别检查它们的完整成功结束出口,确保最终都恢复FAGC。
## 4. 修改后的工作状态
1. 冷启动、不执行DPD Update:行为不变,FAGC持续实时跟踪。
2. DPD训练期间:行为不变,FAGC先收敛,再按现有流程锁定。
3. DPD Update完整结束:重新置位 `DPD_CTRL3_FAGC_UPDATE`。
4. 后续ORx反馈电平超出RTL现有死区时,FAGC再次调整ADRV9009 ORx增益。
5. DAGC控制方式和已完成的DPD系数保持不变。
## 5. 注意事项
- `boot_cfg_fagc_update()` 只有在ADRV9009 SPI控制权属于FPGA时才会写入;DPD结束时应确认SPI控制权已经交还FPGA。
- 不增加 `DPD_CTRL3_FAGC_SLOWDN`,确保恢复状态与当前冷启动后的状态完全一致。
- 不触发 `DPD_START`。
- 只修改整个DPD Update的最终出口,不修改phase之间的临时控制操作。
## 6. 验证步骤
1. 冷启动设备,不执行DPD Update,记录FAGC控制字或ORx `gain_index`的工作状态。
2. 执行一次完整DPD Update。
3. 确认DPD结束后日志出现:
```text
[DPD] update complete, ORx FAGC tracking restored
```
4. DPD结束后改变反馈链路衰减,使ORx输入电平变化超过现有约±1 dB死区。
5. 确认FAGC控制字及ADRV9009 ORx `gain_index`重新变化并最终稳定。
6. 确认DPD系数没有被清除或重新训练,发射输出保持正常。
## 7. 验收标准
- DPD Update结束后,FAGC工作状态与冷启动且未执行DPD时一致。
- 反馈射频电平明显变化后,FAGC能够重新收敛。
- DPD训练阶段原有的FAGC锁定逻辑不变。
- 不涉及RTL和DAGC修改。
Executable → Regular
+46
View File
@@ -39,7 +39,53 @@ SVN_VER_NUM=122T4
SVN_VER_NUM=123
SVN_VER_NUM=124
SVN_VER_NUM=125
SVN_VER_NUM=125T
SVN_VER_NUM=130R
#1.修正之前版本FWD/FSSI等显示问题;
#2.已经支持网页升级FPGA功能;
SVN_VER_NUM=131R
SVN_VER_NUM=132
#132R
#1.调整软件中AB反馈口的使用习惯和历史型号兼容;
#2.修正TSOIP参数开机加载问题;
#3.调整网页相关布局和显示内容(标题/频谱图/SNR/FSSI显示等内容);
SVN_VER_NUM=132R
SVN_VER_NUM=132RT1
SVN_VER_NUM=132RT2
#133R
#1.修正升级或执行网页System Reset可能导致频谱无法正常恢复问题;
#2.恢复网页端的LIMD和UIMD的固定显示偏置;
#3.调整 FSSI Refresh 开启时,频谱图和眼图停止更新;
#4.修正login页面title未正确显示问题;
SVN_VER_NUM=133R
#134R
#1.对RF IN A/B做了调整,适配最新版本260805的FPGA;
SVN_VER_NUM=134R
#1.合并补充overview页面,支持登录后自动跳转到overview页面;
SVN_VER_NUM=135R
#未正式发布,存在开机后SNR LIMD/UIMD不更新和显示问题
SVN_VER_NUM=136R
#加入偏置 切换频点降功率
SVN_VER_NUM=136RT
#加入偏置 切换频点不降功率
SVN_VER_NUM=136RT2
#更新473-887
SVN_VER_NUM=136RT3
SVN_VER_NUM=137R
SVN_VER_NUM=138R
OUT_FS_DIR := $(PWD)/../out/rootfs
UPGARDE_IMX_FS_DIR := SMOD
+7 -14
View File
@@ -20,8 +20,13 @@ echo "clk prepare..."
sleep 5
echo "clk rdy"
init_ad9528_reset_gpio.sh
imx_cfgad9528
if imx_cfgad9528 --check-ready; then
echo "AD9528 already configured and locked; preserve live RF clocks"
else
echo "AD9528 not ready; run cold-start reset and configuration"
init_ad9528_reset_gpio.sh
imx_cfgad9528
fi
mkdir -p /aw/anywave/dpd_log
@@ -148,15 +153,3 @@ echo " "
start_and_longlog /usr/sbin logrotateStart $MODULE_LOG_PATH logrotateStart.log 0 &
+3
View File
@@ -104,6 +104,9 @@ all: imx_atsc_app
install: all
cp imx_atsc_app ../../out/upgrade_imx_fs/SMOD/src/aw/anywave/app/
cp imx_atsc_app ../../out/rootfs/aw/anywave/app/
cp runme.sh ../../out/upgrade_imx_fs/SMOD/runme.sh
cp anywave_exc_start.sh ../../out/upgrade_imx_fs/SMOD/src/aw/anywave_exc_start.sh
cp anywave_exc_start.sh ../../out/rootfs/aw/anywave_exc_start.sh
@echo "\n\n\t ATSC_APP intall ok\n\n"
clean:
+7 -1
View File
@@ -340,7 +340,13 @@ int adrv9009_cfg(int which_9009)
TAL_ADC_STITCHING|
TAL_ORX_LO_DELAY | //add 260518 ORx 专用初始化校准
TAL_ORX_QEC_INIT //add 260518 ORx 专用初始化校准
/*
* PR16 hardware A/B test: keep the one-shot cold-boot
* QEC init, but do not enable TAL_TRACK_ORX1_QEC.
* Forced tracking reached IRR=58 dB / 2099 updates,
* while the exported ORx IQ stream became all zero.
*/
TAL_ORX_QEC_INIT
;
uint8_t errorFlag = 0;
taliseErr_t mykError = TAL_ERR_OK;
+155
View File
@@ -0,0 +1,155 @@
#!/bin/sh
### Auther : F
### Date : 2021-12-7
### Function: Start anywave work
echo " "
echo " "
echo "--------------------------------"
echo " ... HELLO , ANYWAVE ... "
echo "--------------------------------"
echo " "
echo " "
setnetmac.sh
ifconfig eth0 192.168.1.143
ifconfig eth1 192.168.2.143
route add default gw 192.168.1.1
echo "clk prepare..."
sleep 5
echo "clk rdy"
if imx_cfgad9528 --check-ready; then
echo "AD9528 already configured and locked; preserve live RF clocks"
else
echo "AD9528 not ready; run cold-start reset and configuration"
init_ad9528_reset_gpio.sh
imx_cfgad9528
fi
mkdir -p /aw/anywave/dpd_log
### Path for web upgrade
mkdir -p /aw/upgrade
mkdir -p /aw/tmp
mkdir -p /aw/anywave/cfg
mkdir -p /aw/anywave/cfg/bkp
mkdir -p /aw/anywave/dpd_curve
mkdir -p /aw/anywave/lib
mkdir -p /aw/log
mkdir -p /etc/logrotate.d/
echo "== web start =="
cd /aw/anywave/web/sh && ./start_web.sh
MODULE_LOG_PATH=/var/module_log/
mkdir -p $MODULE_LOG_PATH
echo 10 > /sys/class/gpio/export
echo 11 > /sys/class/gpio/export
echo 12 > /sys/class/gpio/export
echo 13 > /sys/class/gpio/export
echo 14 > /sys/class/gpio/export
echo 15 > /sys/class/gpio/export
insmod /aw/anywave/lib/key-bsp.ko
#logconf
LOG_CONF_FILE=/etc/logrotate.d/log.conf
rm -f $LOG_CONF_FILE
set_log_conf()
{
echo "$1$2{
$3
daily
rotate 3
size 1K
dateext
extension .log
compress
notifempty
sharedscripts
create 644 root root
sharedscripts
postrotate
$4
endscript
}
" >> $LOG_CONF_FILE
}
start_and_log()
{
if [ $# -lt 4 ];then
echo 'Usage:start_and_log command_path command_with_param log_path log_name enable_output &'
echo "For example:"
echo "start_and_log /app ./app /var/module_log/ app.log 0 &"
echo "or start_and_log /app ./app /var/module_log/ app.log 1 &"
return 1
fi
set_log_conf $3 $4
cd $1 && $2 2>&1 | loglog $3 $4 $5 $6
return 0
}
set_longlog_conf()
{
echo "$1$2{
$3
daily
rotate 3
size 500K
dateext
extension .log
compress
notifempty
create 644 root root
sharedscripts
postrotate
$4
endscript
}
" >> $LOG_CONF_FILE
}
start_and_longlog()
{
if [ $# -lt 4 ];then
echo 'Usage:start_and_longlog command_path command_with_param log_path log_name enable_output &'
echo "For example:"
echo "start_and_longlog /app ./app /var/module_log/ app.log 0 &"
echo "or start_and_longlog /app ./app /var/module_log/ app.log 1 &"
return 1
fi
set_longlog_conf $3 $4
cd $1 && $2 2>&1 | loglog $3 $4 $5 $6
return 0
}
echo "== app start =="
#cd /aw/anywave/app/ && ./imx_atsc_app&
start_and_log /aw/anywave/app ./imx_atsc_app $MODULE_LOG_PATH imx_atsc_app.log 1 &
echo "== daemon_system start =="
#daemon_system
start_and_longlog /usr/sbin daemon_system $MODULE_LOG_PATH daemon_system.log 1 &
echo " "
echo "== start finished =="
echo " "
#----------daemon_app-------------
#ProcNumber=`ps -ef |grep -w daemon_app|grep -v grep|wc -l`
#if [ $ProcNumber -le 0 ];then
#/usr/sbin/daemon_app &
#echo "daemon_app first run"
#else
#echo "daemon_app have been run"
#fi
start_and_longlog /usr/sbin logrotateStart $MODULE_LOG_PATH logrotateStart.log 0 &
Executable → Regular
+18 -8
View File
@@ -106,7 +106,7 @@ void reset_dpd_to_normal_state(void)
//printf("reset_dpd_to_normal_state\n");
u_dpdctl_r23.u8val = 0;
u_dpdctl_r23.dpd_bt.iqb_upd = 0;
u_dpdctl_r23.dpd_bt.rf_sel = 0; //A
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
u_dpdctl_r23.dpd_bt.sync_mode = 1;
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "reset set seg5-23 IQBval=0x%x\n",u_dpdctl_r23.u8val);
@@ -1185,6 +1185,7 @@ void dpd_exit(void)// 关闭dpd
fpga_byte_write(5,FPGA_ADDR_DPD_CTRL1, DPD_CTRL1_ALL_BYPS|DPD_CTRL1_DPD_ALLCLR);
fpga_byte_write(5,FPGA_ADDR_DPD_CTRL3, 0);
u_dpdctl_r23.u8val=0;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
fpga_byte_write(5,FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
reset_dpd_to_normal_state();
@@ -1264,7 +1265,7 @@ int boot_tsnr_update(uint8 rst_flg)
u_dpdctl_r23.u8val = 0;
u_dpdctl_r23.dpd_bt.iqb_upd = 0;
u_dpdctl_r23.dpd_bt.sync_mode = 1;
u_dpdctl_r23.dpd_bt.rf_sel = 0;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
log_printf(LOG_DEBUG,I_NOUSED,"[state] phase 7 ---------------A- 0x%x\n", u_dpdctl_r23.u8val);
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
@@ -1338,7 +1339,7 @@ int boot_tsnr_update(uint8 rst_flg)
u_dpdctl_r23.dpd_bt.iqb_upd = 1;
u_dpdctl_r23.dpd_bt.sync_mode = 1;
u_dpdctl_r23.dpd_bt.rf_sel = 0;// A
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
log_printf(LOG_DEBUG,I_NOUSED,"[state] phase 9 ---------------A- 0x%x\n", u_dpdctl_r23.u8val);
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
}
@@ -1428,7 +1429,7 @@ int update_tsnr_uimd_limd(uint8 rst_flg, uint8 *p_phase)
u_dpdctl_r23.u8val=0;
u_dpdctl_r23.dpd_bt.iqb_upd = 0;
u_dpdctl_r23.dpd_bt.sync_mode = 1;
u_dpdctl_r23.dpd_bt.rf_sel = 0;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "[state] phase 7 in---------------A- 0x%x\n", u_dpdctl_r23.u8val);
@@ -1502,7 +1503,7 @@ int update_tsnr_uimd_limd(uint8 rst_flg, uint8 *p_phase)
u_dpdctl_r23.dpd_bt.iqb_upd = 1;
u_dpdctl_r23.dpd_bt.sync_mode = 1;
u_dpdctl_r23.dpd_bt.rf_sel = 0;// A
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "[state] phase 9 in---------------A- 0x%x\n", u_dpdctl_r23.u8val);
}
@@ -1788,7 +1789,7 @@ uint8 conv_adpc_err_output(uint8 raw_dpd_err)
void set_ab_switch(E_AB_SEL ab_val)
{
u_dpdctl_r23.dpd_bt.rf_sel = ab_val; // 1 : B , 0 : A
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(ab_val);
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
}
@@ -2105,7 +2106,7 @@ void refresh_update_dpd(void *parg,void *parv)
u_dpdctl_r23.u8val = 0;
u_dpdctl_r23.dpd_bt.iqb_upd = 1;
u_dpdctl_r23.dpd_bt.sync_mode = 0;
u_dpdctl_r23.dpd_bt.rf_sel = 1;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_B);
fpga_byte_write(5,FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "[update] phase 1: 0x%x\n", u_dpdctl_r23.u8val);
@@ -2640,7 +2641,7 @@ void refresh_update_dpd(void *parg,void *parv)
u_dpdctl_r23.dpd_bt.iqb_upd =0;
u_dpdctl_r23.dpd_bt.sync_mode=1;
u_dpdctl_r23.dpd_bt.rf_sel =0;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
fpga_byte_write(5,FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "[update] phase 7: 0x%x\n", u_dpdctl_r23.u8val);
@@ -2862,6 +2863,7 @@ DPD_SAVE_PROC:
fpga_byte_write(0x05, FPGA_ADDR_DPD_CTRL3, 0);
u_dpdctl_r23.u8val = 0;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "[update] save data --- 0x%x\n", u_dpdctl_r23.u8val);
@@ -2928,6 +2930,7 @@ DPD_SAVE_PROC:
fpga_byte_write(5,FPGA_ADDR_DPD_CTRL1, 0);
fpga_byte_write(5,FPGA_ADDR_DPD_CTRL3, 0);
u_dpdctl_r23.u8val = 0;
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
fpga_byte_write(5,FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
log_printf(LOG_DEBUG, I_USER_CFG, "[update] success --- u_dpdctl_r23=0x%x\n", u_dpdctl_r23.u8val);
@@ -2982,6 +2985,13 @@ DPD_SAVE_PROC:
}
reset_dpd_to_normal_state();
/* 恢复冷启动后的ORx FAGC实时跟踪状态。 */
/*boot_cfg_fagc_update();
log_printf(LOG_INFO, I_USER_CFG,
"[DPD] update complete, ORx FAGC tracking restored\n");*/
psys->final_stage = 0xff;
//finished
Executable → Regular
+115 -28
View File
@@ -85,6 +85,26 @@ static const int32 we_power[]=
1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000
};
/* Hardware V1.1 VHF calibration, channels 2..13. */
static const uint16 we_power_v1_1_v[]=
{
143,98,66,38,28,52,52,59,62,64,66,74
};
/*
* Hardware V1.1 UHF calibration, channels 14..83 (473..887 MHz).
* Values are the raw WE register values measured with 563 MHz as 0 dB.
* 1 raw step = 0.05 dB; 300 = 0 dB. V1.1 may use values up to 720
* while V1.0 remains limited to 500.
*/
static const uint16 we_power_v1_1[]=
{
224,226,231,231,234,235,239,244,245,248,254,257,264,268,269,275,281,282,
292,292,295,301,304,312,317,322,329,335,340,345,347,350,354,358,361,366,
370,377,382,385,389,391,394,399,403,405,413,420,423,432,437,447,454,463,
480,495,512,528,547,565,590,614,635,662,687,715,500,500,500,500
};
/*auto ver
{
-405, -361, -349, -314, -332, -303, -295, -285, -172, -257,
@@ -192,6 +212,7 @@ void e2rom_save_sys(S_SYSTEM_PARA *ppara)
c256_bytewrite(EEPROM_ADRV9009_ORX_MANU_GAIN, ppara->orx_manu_gain&0xff);
c256_bytewrite(EEPROM_SPEC_ENABLE, ppara->spec_enable ? 1 : 0);
c256_bytewrite(EEPROM_HARDWARE_VERSION, ppara->hardware_version);
}
void e2rom_save_power(S_RF_PARA *ppara)
{
@@ -398,18 +419,36 @@ void e2rom_save_wd(S_WD_PARA *ppara)
c256_bytewrite(EEPROM_WD_ADDR+i,*pchar++);
}
}
uint16 get_we_power_max(void)
{
return (atsc_para.system_para.hardware_version == HARDWARE_VERSION_V1_1) ?
WE_POWER_MAX_V1_1 : WE_POWER_MAX;
}
static uint16 e2rom_legacy_power_addr(void)
{
return (atsc_para.wl_para.exc_5x == 0) ?
EEPROM_CH2_POWER_ADDR : EEPROM_CH1_POWER_ADDR;
}
static uint16 e2rom_active_power_addr(void)
{
if(atsc_para.system_para.hardware_version == HARDWARE_VERSION_V1_1)
return EEPROM_CH_POWER_V1_1_ADDR;
return e2rom_legacy_power_addr();
}
void e2rom_save_leak(uint32 ch, S_WE_PARA *ppara)
{
if(atsc_para.wl_para.exc_5x==0)
{
c256_bytewrite(EEPROM_CH2_POWER_ADDR+ch*2+0,ppara->power>>8);
c256_bytewrite(EEPROM_CH2_POWER_ADDR+ch*2+1,ppara->power);
}
else
{
c256_bytewrite(EEPROM_CH1_POWER_ADDR+ch*2+0,ppara->power>>8);
c256_bytewrite(EEPROM_CH1_POWER_ADDR+ch*2+1,ppara->power);
}
uint16 base = e2rom_active_power_addr();
uint16 power_max = get_we_power_max();
if(ppara->power > power_max)
ppara->power = power_max;
c256_bytewrite(base+ch*2+0,ppara->power>>8);
c256_bytewrite(base+ch*2+1,ppara->power);
}
void e2rom_save_we(S_WE_PARA *ppara)
{
@@ -1145,8 +1184,16 @@ void e2rom_read_system(S_SYSTEM_PARA *ppara)
ppara->orx_manu_gain = orx_gain_max;
ppara->spec_enable = 1;
ppara->hardware_version = c256_byteread(EEPROM_HARDWARE_VERSION);
if(ppara->hardware_version >= HARDWARE_VERSION_END)
{
ppara->hardware_version = DEFAULT_HARDWARE_VERSION;
c256_bytewrite(EEPROM_HARDWARE_VERSION, ppara->hardware_version);
}
printf("load agc_att=%d %fdb adrv9009_att=%d orx_manu_gain=%d spec_enable=%d\n", ppara->agc_att, -0.5*ppara->agc_att, ppara->adrv9009_att, ppara->orx_manu_gain, ppara->spec_enable);
printf("load agc_att=%d %fdb adrv9009_att=%d orx_manu_gain=%d spec_enable=%d hardware=V1.%d\n",
ppara->agc_att, -0.5*ppara->agc_att, ppara->adrv9009_att,
ppara->orx_manu_gain, ppara->spec_enable, ppara->hardware_version);
}
static void bak_e2rom_read_rf(S_RF_PARA *ppara)
{
@@ -2008,6 +2055,26 @@ static void e2rom_fac_power_rst_3x(void)
}
}
static void e2rom_fac_power_rst_v1_1(void)
{
uint32 i;
uint16 power;
for(i=FREQ_MIN_V1; i<=FREQ_MAX_V3; i++)
{
power = we_power_v1_1_v[i-FREQ_MIN_V1];
c256_bytewrite(EEPROM_CH_POWER_V1_1_ADDR+i*2+0, power>>8);
c256_bytewrite(EEPROM_CH_POWER_V1_1_ADDR+i*2+1, power);
}
for(i=FREQ_MIN_U; i<=FREQ_MAX_U; i++)
{
power = we_power_v1_1[i-FREQ_MIN_U];
c256_bytewrite(EEPROM_CH_POWER_V1_1_ADDR+i*2+0, power>>8);
c256_bytewrite(EEPROM_CH_POWER_V1_1_ADDR+i*2+1, power);
}
}
static uint16 e2rom_read_fac_power_default_3x(uint32 chn)
{
uint16 power;
@@ -2245,7 +2312,7 @@ static void e2rom_read_dpd_para(S_DPD_PARA *ppara)
//gl default val 0x0370
ppara->gl = c256_byteread_default(EEPROM_DPD_GAIN_LIMIT_H, 0x03)<<8 | c256_byteread_default(EEPROM_DPD_GAIN_LIMIT_L, 0x70);
ppara->dpd_ctl = c256_byteread_default(EEPROM_DPD_CTL_TST, 0x03);
ppara->dpd_ctl = c256_byteread_default(EEPROM_DPD_CTL_TST, 0x07);
}
@@ -2401,11 +2468,13 @@ void e2rom_read_tsoip(int tsoip_index, S_TSOIP_CONFIG_PARA *ppara)
log_printf(LOG_DEBUG,I_NOUSED,"TSOIP_%d ppara->receive_type=%s\n", tsoip_index+1, ppara->receive_type==0?"multi":"singl" );
ppara->fec = (c256_byteread(EEPROM_FEC_ADDR)<<8)|c256_byteread(EEPROM_FEC_ADDR+1);
if(ppara->fec >= 1) {
if(ppara->fec > 1) {
ppara->fec = 0;
}
ppara->delay = (c256_byteread(EEPROM_FEC_DLY_ADDR)<<8)|c256_byteread(EEPROM_FEC_DLY_ADDR+1);
ppara->row = (c256_byteread(EEPROM_ROW_ADDR)<<8)|c256_byteread(EEPROM_ROW_ADDR+1);
ppara->col = (c256_byteread(EEPROM_COL_ADDR)<<8)|c256_byteread(EEPROM_COL_ADDR+1);
ppara->sfn_mfn_sel = c256_byteread(EEPROM_SFN_MFN_SEL);
if(ppara->sfn_mfn_sel != NET_SFN && ppara->sfn_mfn_sel != NET_MFN) {
ppara->sfn_mfn_sel = NET_SFN;
@@ -2858,6 +2927,7 @@ void e2rom_load_default_param(unsigned char menu)
atsc_para.system_para.gps = 0;
atsc_para.system_para.test = TEST_OFF;
atsc_para.system_para.spec_enable = 1;
atsc_para.system_para.hardware_version = DEFAULT_HARDWARE_VERSION;
//atsc_para.system_para.net = 1; //MFN;
atsc_para.system_para.ctrl = CTRL_REMOTE; // remote
@@ -3218,7 +3288,7 @@ void e2rom_load_default_param(unsigned char menu)
atsc_para.dpd_para.phase6_minute=3;//TM
atsc_para.dpd_para.wpp_th =3900;//6000
atsc_para.dpd_para.gl =0x0370;//0x0A00
atsc_para.dpd_para.dpd_ctl =3;
atsc_para.dpd_para.dpd_ctl =0x07;
//cttb_para.dpd_para.amp_sat_sqr=0;//该值由amp_sat转换而成
e2rom_save_dpd_para(&atsc_para.dpd_para);
//----------------------------
@@ -3241,6 +3311,7 @@ void e2rom_load_default_param(unsigned char menu)
case MENU_POWER:
atsc_para.rf_para.power = RF_POWER_ZERO;
e2rom_fac_power_rst_v1_1();
if(atsc_para.wl_para.exc_5x==0)
{
atsc_para.rf_para.db = DEFAULT_RF_POWER_DB_3X;
@@ -3268,27 +3339,42 @@ void e2rom_load_default_param(unsigned char menu)
void e2rom_read_leak(uint32 chn,S_WE_PARA *ppara)
{
uint8 data = 0;
uint8 data;
uint16 base = e2rom_active_power_addr();
uint16 power_max = get_we_power_max();
if(atsc_para.wl_para.exc_5x==0)
data = c256_byteread(base+chn*2+0);
ppara->power = (data<<8)|c256_byteread(base+chn*2+1);
/*
* Populate an upgraded unit's missing V1.1 entry from the measured
* V1.1 defaults and persist it in the independent V1.1 EEPROM table.
*/
if((ppara->power>power_max) &&
(atsc_para.system_para.hardware_version == HARDWARE_VERSION_V1_1))
{
data = c256_byteread(EEPROM_CH2_POWER_ADDR+chn*2+0);
ppara->power = (data<<8)|c256_byteread(EEPROM_CH2_POWER_ADDR+chn*2+1);
if(ppara->power>WE_POWER_MAX)
if((chn>=FREQ_MIN_V1)&&(chn<=FREQ_MAX_V3))
{
ppara->power = e2rom_read_fac_power_default_3x(chn);
ppara->power = we_power_v1_1_v[chn-FREQ_MIN_V1];
}
else if((chn>=FREQ_MIN_U)&&(chn<=FREQ_MAX_U))
{
ppara->power = we_power_v1_1[chn-FREQ_MIN_U];
}
if(ppara->power<=power_max)
{
c256_bytewrite(EEPROM_CH_POWER_V1_1_ADDR+chn*2+0,ppara->power>>8);
c256_bytewrite(EEPROM_CH_POWER_V1_1_ADDR+chn*2+1,ppara->power);
}
}
else
{
data = c256_byteread(EEPROM_CH1_POWER_ADDR+chn*2+0);
ppara->power = (data<<8)|c256_byteread(EEPROM_CH1_POWER_ADDR+chn*2+1);
if(ppara->power>WE_POWER_MAX)
{
if(ppara->power>power_max)
{
if(atsc_para.wl_para.exc_5x==0)
ppara->power = e2rom_read_fac_power_default_3x(chn);
else
ppara->power = e2rom_read_fac_power_default_5x(chn);
}
}
}
extern void fpga_read_system(S_SYSTEM_PARA *ppara);
@@ -3441,6 +3527,7 @@ void e2rom_read_all_para(void)
e2rom_load_default_param(2);
e2rom_load_default_param(3);
e2rom_fac_power_rst_v1_1();
if(atsc_para.wl_para.exc_5x==0) //此处是将非当前标准的we功率也置为默认值.
{
e2rom_fac_power_rst_5x();
+32 -8
View File
@@ -80,11 +80,26 @@ void uart_debug(uint8 *pbuf,uint32 len)
#define HMC472_STEP_MDB 500
#define HMC472_STEPS_MAX 63
#define ADRV9009_ATT_BASE 8000
#define ADRV9009_ATT_MAX 40000
/*
* The post-reset measurements were made in V1.0 software mode and established
* that LCD -11 dB produces 0 dBm at the 563 MHz reference point. Therefore
* V1.1 needs 11 dB additional attenuation for LCD 0 dB to map to the same
* physical 0 dBm output.
*/
#define V1_1_OUTPUT_GAIN_MDB 11000
static int32 g_last_total_mdb = -1;
static uint16 g_last_adrv9009_att = 0xFFFF;
static volatile uint8 g_adrv9009_ready = 0;
static int32 hardware_output_gain_mdb(void)
{
return atsc_para.system_para.hardware_version == HARDWARE_VERSION_V1_1
? V1_1_OUTPUT_GAIN_MDB : 0;
}
/*
* Validate a stored power command only after the current channel's WE power
* has been loaded. A raw attenuation of zero is a valid physical maximum;
@@ -98,6 +113,7 @@ static uint8 normalize_new_power_rf_db(S_WE_PARA *pwe, S_RF_PARA *prf)
raw_total_mdb = (int32)RAW_MAX_OUTPUT_MDB
- ((int32)prf->db + (int32)pwe->power - (int32)REF_POWER_UNITS)
* (int32)PWR_UNIT_TO_MDB;
raw_total_mdb += hardware_output_gain_mdb();
if(raw_total_mdb >= 0)
return 0;
@@ -1057,6 +1073,7 @@ uint16 fpga_limit_power_target(S_WE_PARA *pwe, S_RF_PARA *prf, uint16 target_pow
total_mdb = (int32)RAW_MAX_OUTPUT_MDB
- ((int32)prf->db + (int32)pwe->power - (int32)REF_POWER_UNITS)
* (int32)PWR_UNIT_TO_MDB;
total_mdb += hardware_output_gain_mdb();
if(total_mdb <= 0)
return prf->power;
@@ -1087,12 +1104,13 @@ void fpga_save_power(S_WE_PARA *pwe,S_RF_PARA *prf)
total_mdb = (int32)RAW_MAX_OUTPUT_MDB
- ((int32)prf->db + (int32)pwe->power - (int32)REF_POWER_UNITS)
* (int32)PWR_UNIT_TO_MDB;
total_mdb += hardware_output_gain_mdb();
if(total_mdb < 0)
total_mdb = 0;
if(total_mdb > (int32)(ADRV9009_ATT_BASE + HMC472_STEPS_MAX * HMC472_STEP_MDB))
total_mdb = ADRV9009_ATT_BASE + HMC472_STEPS_MAX * HMC472_STEP_MDB;
if(total_mdb > (int32)(ADRV9009_ATT_MAX + HMC472_STEPS_MAX * HMC472_STEP_MDB))
total_mdb = ADRV9009_ATT_MAX + HMC472_STEPS_MAX * HMC472_STEP_MDB;
if(total_mdb >= (int32)ADRV9009_ATT_BASE)
{
@@ -1100,7 +1118,13 @@ void fpga_save_power(S_WE_PARA *pwe,S_RF_PARA *prf)
agc_att_val = (uint8)(hmc_extra / HMC472_STEP_MDB);
if(agc_att_val > HMC472_STEPS_MAX)
agc_att_val = HMC472_STEPS_MAX;
adrv9009_att_val = (uint16)(ADRV9009_ATT_BASE + (hmc_extra % HMC472_STEP_MDB));
/*
* Keep ADRV9009 near its 8000 mdB optimum while HMC472 has range.
* Once HMC472 reaches 63, use the remaining ADRV9009 range up to
* 40000 mdB instead of clipping the combined attenuation at 39500.
*/
adrv9009_att_val = (uint16)(total_mdb - agc_att_val * HMC472_STEP_MDB);
}
else
{
@@ -1141,6 +1165,8 @@ void fpga_save_power(S_WE_PARA *pwe,S_RF_PARA *prf)
}
g_last_total_mdb = total_mdb;
printf("[power] hardware=V1.%d offset_mdb=%d\n",
atsc_para.system_para.hardware_version, hardware_output_gain_mdb());
printf("[power] we=%3d db=%3d total_mdb=%5d | agc_att=%d adrv_att=%d\n",
pwe->power, prf->db, total_mdb, agc_att_val, adrv9009_att_val);
@@ -1717,11 +1743,9 @@ void config_fpga_para(void)
fpga_save_all_para(TRUE);
/* TSOIP ---20220214 s ---*/
// PR03: force TSoIP unicast defaults (192.168.2.200/single) onto BOTH cores every boot
extern void e2rom_load_default_param_t_tsoip(int tsoip_index, uint32 type);
e2rom_load_default_param_t_tsoip(TSOIP_1, 0);
e2rom_load_default_param_t_tsoip(TSOIP_2, 0);
/* TSOIP settings have already been restored by e2rom_read_all_para().
* Do not load defaults here: that routine also writes the defaults back to
* EEPROM and would erase the values set from the web page on every reboot. */
tsoip_byte_write(TSOIP_1, I2C_REG_ADDRES, 0x11);
delay_ms(200);//delay_ms(6000);
Executable → Regular
+1 -2
View File
@@ -980,8 +980,7 @@ void fpga_para_init(void)
{
if(atsc_para.rf_para.rf && (adrv9009_set_tx_path_enable(1) != OK))
{
log_printf(LOG_ERR, I_SYS_STATUS,
"ADRV9009 TX enable failed, continue restoring requested RF state\n");
log_printf(LOG_ERR, I_SYS_STATUS, "ADRV9009 TX enable failed, continue restoring requested RF state\n");
}
fpga_boot_rf_hold_release();
}
Executable → Regular
+27
View File
@@ -52,6 +52,33 @@ uint8 orx_fssi_from_gain_index(uint8 gi, uint8 gi_min, uint8 gi_max)
return (uint8)(pct + 0.5f);
}
uint8 orx_fssi_from_power(uint16 fagc_cw, uint16 rms, uint16 rms_ref,
uint8 gi_min, uint8 gi_max)
{
float span_db; /* 量程(dB): (gi_max-gi_min) 个 0.5 dB 档 */
float p_db; /* 相对设定点的 ORx 输入功率(dB) */
float pct;
if (gi_max <= gi_min)
return 0; /* 防除零的退化入参 */
if (rms_ref == 0)
rms_ref = 1; /* 防 log10(0); 调用方不应传 0 */
/* 粗档 0.5*cw_hi 与细档 10*log10(rms/rms_ref) 同向(见 orx_power_combine_db
* 的方向说明), 直接相加。rms==rms_ref 时细档为 0, 退化成纯 gain index 版。 */
p_db = orx_power_combine_db(fagc_cw, rms)
- 10.0f * log10f((float)rms_ref);
span_db = 0.5f * (float)(gi_max - gi_min);
pct = p_db * 100.0f / span_db;
if (pct <= 0.0f)
return 0;
if (pct >= 100.0f)
return 100;
return (uint8)(pct + 0.5f);
}
int16 orx_pwr_halfdb(uint16 rms)
{
float v = (float)rms;
+5
View File
@@ -45,6 +45,11 @@
#define USE_UART_DEV_LINK 1
/* IQ spectrum/eye-analysis periodic diagnostics on the debug UART.
* Keep disabled in normal operation; set to 1 only when timing/packet
* continuity troubleshooting is required. */
#define IQ_ANALYSIS_UART_DEBUG 0
/*
* Prefer the local key/LCD path when the single-core CPU is busy with IQ
* analysis. Set to 0 to restore the original thread priorities.
+9 -1
View File
@@ -93,7 +93,15 @@ enum{
//#define DPD_IQB_SYNC_MASK (0x0001<<2)
#define DPD_SYNC_MODE_MASK (0x0001<<2)
#define DPD_RF_SEL_BEFORE (0x0001<<3)
/*
* Hardware order is exchanged again: logical/silkscreen A -> rf_sel 0,
* logical/silkscreen B -> rf_sel 1. Keep every RF input selection routed
* through this mapping so DPD/FWD/FSSI use the same A/B interpretation.
*/
#define DPD_RF_SEL_HW_A 0U
#define DPD_RF_SEL_HW_B 1U
#define DPD_RF_SEL_VALUE(sel) (((sel) == E_SELECT_B) ? DPD_RF_SEL_HW_B : DPD_RF_SEL_HW_A)
#define DPD_RF_SEL_AFTER (0x0001<<3)
#define DPD_SET_100M (0x00)
#define DPD_SET_25M (0x01)
+3
View File
@@ -38,6 +38,7 @@ typedef enum
EEPROM_ADRV9009_ATT_L,
EEPROM_ADRV9009_ORX_MANU_GAIN,
EEPROM_SPEC_ENABLE,
EEPROM_HARDWARE_VERSION,
EEPROM_CHANNEL_ADDR = 0x50, //RF 相关
@@ -188,6 +189,8 @@ typedef enum
//工厂菜单当前WK为0x220, 工厂菜单power定义到0x300,中间预留部分空间
EEPROM_CH1_POWER_ADDR = 0x300, //约60多字节
EEPROM_CH2_POWER_ADDR = 0X400, //1.42(3X+)功率控制字
// V1.1独立频点WE表;0x500~0x5A7,未配置项回退到V1.0表
EEPROM_CH_POWER_V1_1_ADDR = 0x500,
EEPROM_PSTS_TS_EDIT = 0x600, //开关
EEPROM_PSTS_TSIDH, //tsid
Executable → Regular
+4 -6
View File
@@ -11,17 +11,15 @@ void iq_spec_set_enable(int enable);
/*
* Newest UIMD/LIMD measured on the imx6's own 10 MSPS ORx window.
*
* Same taps as the FPGA's imd_imd_fft_meas -- centre bin at +24.414 kHz and the two
* shoulder bins at +-3.1494 MHz -- but averaged over 11 bins (+-12.2 kHz) each and
* Welch-averaged over the whole 26.2 ms window. LIMD is always the shoulder on the
* pilot's side of the carrier; which one that is follows the measured pilot, so a
* spectral inversion cannot silently swap the two.
* PR19 uses three fixed 500 kHz measurement windows. P0 is the mean linear power
* from -0.25 to +0.25 MHz, PL is the mean from -3.5 to -3.0 MHz, and PR is the mean
* from +3.0 to +3.5 MHz. The window spectra are Welch-averaged over 26.2 ms.
*
* The FPGA's own registers cannot be used any more: its 20-bit |DFT|^2 output
* quantises the shoulder bins to 0 once the shoulder is better than ~45 dB, and
* lcd_task.c's `if (imd_l == 0) imd_l = 1` then reports a constant.
*
* limd_db/uimd_db are 10*log10(P_centre / P_shoulder), i.e. positive dB, no +11 dB
* limd_db/uimd_db are P0-PL and P0-PR respectively, i.e. positive dB, with no +11 dB
* display offset applied.
*
* Returns 1 and fills both pointers when a window newer than IQ_IMD_MAX_AGE seconds
Executable → Regular
+18
View File
@@ -105,6 +105,16 @@
#define WE_POWER_ZERO 300 //we屏 0db对应值
#define WE_POWER_MIN 0
#define WE_POWER_MAX 500
#define WE_POWER_MAX_V1_1 720 //V1.1物理功率余量允许到+21dB
typedef enum
{
HARDWARE_VERSION_V1_0 = 0,
HARDWARE_VERSION_V1_1,
HARDWARE_VERSION_END
} HARDWARE_VERSION;
#define DEFAULT_HARDWARE_VERSION HARDWARE_VERSION_V1_1
#define OCXO_MAX 10 //单位0.1hz system菜单
#define OCXO_UNIT 106 //保存时的放大倍数
@@ -144,6 +154,12 @@
#define FSSI_GI_MIN 195
#define FSSI_GI_MAX 255
/* PR16: FAGC 功率设定点, 用作 FSSI 细档(死区内 rms 残差)的零点。
* PR11 §2: rms_target = fagcpwrlevel*4; 当前 fagcpwrlevel=256 -> 1024。
* rms 恰好等于它时, 新算法与旧的纯 gain index 版读数完全相同(见 orx_power.h)。
* 若日后改了 fagcpwrlevel, 这里要同步; 更准的做法是按 PR11 §3 用标定点实测中位数。 */
#define FSSI_RMS_REF 1024
#define EEPROM_BACKUP_PARA_TYPE_NONE 0
#define EEPROM_BACKUP_PARA_TYPE_PREF 1 //工厂默认参数备份
#define EEPROM_BACKUP_PARA_TYPE_CUST 2 //客户手动备份
@@ -372,6 +388,7 @@ typedef struct
uint8 agc_att;
uint16 adrv9009_att;
uint8 orx_manu_gain;
uint8 hardware_version;
}S_SYSTEM_PARA,*PS_SYSTEM_PARA;
typedef struct
@@ -1359,6 +1376,7 @@ void e2rom_read_psts(S_PSTS_PARA *ppara);
extern void e2rom_read_leak(uint32 chn,S_WE_PARA *ppara);
extern void e2rom_save_leak(uint32 UI_ch, S_WE_PARA *ppara);
extern uint16 get_we_power_max(void);
extern void e2rom_save_ad(S_ADVANCE_PARA *ppara);
extern void e2rom_save_ad2(S_ADVANCE2_PARA *ppara);
extern void e2rom_save_ad3(S_ADVANCE3_PARA *ppara);
Executable → Regular
+20
View File
@@ -20,6 +20,26 @@ uint8 orx_fssi_hysteresis(uint8 prev, uint8 now);
* 线性映射,四舍五入,越界钳位。gi_min/gi_max 由调用方传入(标定后可收窄)。 */
uint8 orx_fssi_from_gain_index(uint8 gi, uint8 gi_min, uint8 gi_max);
/* PR16: 用「FAGC 增益字 + 死区内 rms 残差」合成的 FSSI%,替代只看 gain index 的版本。
*
* 为什么必须这样: dpd_rx_rfagc_1x.vhd 的 dz=232 是一个 2.00 dB 死区(4.6 个 gain
* step 宽), 注释原文 "the AGC only re-acquires when the ORx level genuinely moves
* more than +-1 dB"。所以只看 gain index 时, ±1 dB 以内的输入变化 gain index 一步
* 不动 —— A/B 两路差异小于 1 dB 时 b_fssi 会恒等于 a_fssi, 看着在刷新, 其实是在
* 复读 A。死区内增益冻结, 功率变化恰好完整落在 ob_fb_rms_pwr 上, 所以把 rms 残差
* 加回来就能看进死区。PR11 §4.2 早有明确要求(rms 残差必须保留浮点, 否则
* "丢失 FAGC 死区内的真实功率变化"), PR13 改用纯 gain index 时丢掉了这一半。
*
* fagc_cw : seg1 0x36/0x37 agcctrlword
* rms : seg1 0x03/0x04 ob_fb_rms_pwr
* rms_ref : FAGC 功率设定点(fagcpwrlevel*4; fagcpwrlevel=256 -> 1024)
* gi_min/gi_max: 与 orx_fssi_from_gain_index 同义, 仍决定量程
*
* 后向兼容: rms == rms_ref 时本函数与 orx_fssi_from_gain_index(255-cw_hi,...)
* 完全相等 —— 即 AGC 恰好收敛在设定点时读数不变, 只有死区内才产生新的分辨力。 */
uint8 orx_fssi_from_power(uint16 fagc_cw, uint16 rms, uint16 rms_ref,
uint8 gi_min, uint8 gi_max);
/* PR13: ORx 数字功率 rms -> 0.5dB 步数: round(10*log10(rms)/0.5) = round(20*log10(rms))。
* rms==0 按 1 处理(防 -inf), 返回 0。 */
int16 orx_pwr_halfdb(uint16 rms);
+1
View File
@@ -225,6 +225,7 @@ typedef struct
uint8 agc_att;
uint16 adrv9009_att;
uint8 orx_manu_gain;
uint8 hardware_version;
}S_CGI_SET,*PS_CGI_SET;
typedef struct
+8 -3
View File
@@ -755,13 +755,18 @@ int main(int argc, char *argv[])
break;
case 1: // restart
log_printf(LOG_DEBUG,I_NOUSED,"\n\nrestart by daemon.\n");
g_restart_app_flg = 1;
/* Keep argument 1 compatible with the deployed daemon, but use the same
* no-power-off RF recovery path as a normal application start. A fresh
* process must rebuild the TALISE handle and the FPGA-side JESD link;
* g_restart_app_flg=1 skips adrv9009_init() and can leave TX unavailable. */
log_printf(LOG_DEBUG,I_NOUSED,
"\n\nrestart by daemon, use normal RF recovery path.\n");
g_restart_app_flg = 0;
break;
}
}
log_printf(LOG_INFO, I_SYS_STATUS, "====System start===k=%d= 260428\n", USE_KOREA_VERSION);
log_printf(LOG_INFO, I_SYS_STATUS, "====System start===k=%d= 2607281650\n", USE_KOREA_VERSION);
pr_soft_start_ver();
+88
View File
@@ -0,0 +1,88 @@
#!/bin/sh
# The upgrade shell is forked by the running app and inherits every app socket.
# Re-exec after closing inherited non-stdio descriptors so the old process can
# release its ports cleanly. Keep upgrade diagnostics on the debug serial port.
if [ "$SMOD_FD_CLEAN" != "1" ]; then
export SMOD_FD_CLEAN=1
exec /bin/sh -c '
i=3
while [ $i -lt 256 ]; do
eval "exec ${i}>&-" 2>/dev/null
i=$((i + 1))
done
exec /bin/sh "$1" </dev/null >/dev/ttymxc0 2>&1
' smod_fd_clean "$0"
fi
# Kernel and device-tree updates only take effect after an IMX reboot. Pure
# userspace packages keep the clock/RF hardware powered and restart only the
# application so RF can recover without a power cycle.
need_reboot=0
reboot_reason=""
if [ -f ./src/zImage ]; then
need_reboot=1
reboot_reason="kernel"
echo "kernel image found: reboot required"
fi
if [ -f ./src/imx6ull-S-gpmi-1024x600c7.dtb ]; then
need_reboot=1
if [ -n "$reboot_reason" ]; then
reboot_reason="$reboot_reason+device-tree"
else
reboot_reason="device-tree"
fi
echo "device-tree image found: reboot required"
fi
killall logrotateStart daemon_system 2>/dev/null
sh remove.sh
sh cp.sh
sync
sleep 2
if [ "$need_reboot" = "1" ]; then
echo "boot component updated ($reboot_reason), reboot IMX now"
reboot
exit 0
fi
# pthread_setname_np changes the app's /proc comm, so identify it by the exact
# executable path. Also match "(deleted)" after cp.sh replaces the running ELF.
find_app_pids()
{
for exe in /proc/[0-9]*/exe; do
target=`readlink "$exe" 2>/dev/null` || continue
case "$target" in
/aw/anywave/app/imx_atsc_app|/aw/anywave/app/imx_atsc_app\ \(deleted\))
echo "$exe" | sed 's#/proc/##;s#/exe##'
;;
esac
done
}
old_pids=`find_app_pids`
[ -n "$old_pids" ] && kill $old_pids 2>/dev/null
i=0
while [ -n "`find_app_pids`" ] && [ $i -lt 15 ]; do
sleep 1
i=$((i + 1))
done
old_pids=`find_app_pids`
[ -n "$old_pids" ] && kill -9 $old_pids 2>/dev/null
while [ -n "`find_app_pids`" ]; do sleep 1; done
# Remove stale logging pipelines only after the old application has exited.
killall loglog 2>/dev/null
# Start without the legacy argument 1 so the normal no-power-off RF recovery
# path rebuilds the TALISE handle and FPGA-side JESD link.
sleep 2
cd /aw/anywave/app && nohup sh -c './imx_atsc_app 2>&1 | /usr/sbin/loglog /var/module_log/ imx_atsc_app.log 1' </dev/null >/dev/ttymxc0 2>&1 &
# Restore the system watchdog/housekeeper and log rotation. BusyBox on this
# target has no setsid, and /usr/sbin is not in the default PATH.
sleep 2
nohup sh -c '/usr/sbin/daemon_system 2>&1 | /usr/sbin/loglog /var/module_log/ daemon_system.log 1' >/dev/null 2>&1 &
nohup sh -c '/usr/sbin/logrotateStart 2>&1 | /usr/sbin/loglog /var/module_log/ logrotateStart.log 0' >/dev/null 2>&1 &
@@ -0,0 +1,341 @@
# 不掉电恢复 ADRV9009 / JESD / 射频链路流程
## 1. 目的与适用范围
本文整理设备在不切断整机电源的情况下,完成以下操作后恢复射频输出的处理链路:
- IMX 应用程序升级后,结束旧进程并启动新进程;
- IMX 应用程序异常退出后,由脚本或守护进程重新启动;
- FPGA 在线升级并通过 IPROG 重新加载 bitstream;
- FPGA 已重新加载,但 ADRV9009 和 AD9528 仍保持上电运行。
这里的核心问题是:ADRV9009 已经处于运行态时,再次执行 `TALISE_resetDevice()`、`TALISE_initialize()` 和初始化校准,可能返回 `err10 / TALACT_ERR_RESET_FULL`,从而造成 JESD 或 TX 通道恢复失败。因此,不掉电恢复不能简单复用完整冷启动流程,必须区分“9009 仍存活”和“9009 确实处于冷启动状态”。
## 2. 硬件状态与基本原则
当前板卡上,FPGA、ADRV9009 和 AD9528 在 IMX 应用进程退出后仍保持供电。FPGA 在线重载时,9009 的器件时钟仍由 OCXO/AD9528 提供,因此 9009 CLKPLL 通常不会因为 FPGA 重载而失锁。
不掉电恢复遵循以下原则:
1. 9009 已经正常运行时,只重新建立软件句柄和 FPGA 侧 JESD 链路,不重新初始化 TALISE。
2. FPGA 重载后,重新写入 FPGA 静态寄存器、配置 JESD、复位 FPGA GT,并重新完成 SYNC/ILAS 握手。
3. 射频输出在链路恢复完成前保持关闭或隔离,完成 PLL、JESD、功率参数恢复后再打开。
4. FPGA 状态、9009 状态应分别判断,不能长期只依赖一个 FPGA `cfg_valid` 标志推断所有硬件状态。
## 3. 当前使用的状态标志
### 3.1 FPGA `cfg_valid`
FPGA segment 5、地址 `0x60` 的 bit0 被用作配置有效标志:
- `1`:应用已经完成过 FPGA/RF 配置,FPGA 自上次配置后没有重新加载;
- `0`:冷启动或 FPGA bitstream 已重新加载,需要恢复 FPGA 配置和 JESD 链路。
完成 `config_fpga_para()` 后,应用执行:
```c
fpga_byte_write(0x05, 0x60, 0x01);
g_rf_boot_done = 1;
```
实现位置:`bsp/fpga.c` 中的 `fpga_read_cfg_valid()` 及 `fpga_save_all_para()` 尾部。
### 3.2 `g_skip_talise_cfg`
`g_skip_talise_cfg` 控制是否跳过 ADRV9009 的完整 TALISE 初始化:
- `0`:冷启动,执行 reset、initialize、ARM 加载和初始化校准;
- `1`:不掉电恢复,仅执行 `TALISE_openHw()` 和状态读取,跳过完整初始化。
实现位置:`adrv9009/adrv9009_config.c`。
### 3.3 `g_rf_boot_done`
该标志表示本进程已经完成 RF 启动配置。运行时 FPGA `cfg_valid` 轮询只有在该标志置位后才启用,避免启动阶段误触发热恢复。
### 3.4 `g_hot_relink_busy`
热恢复重入保护。FPGA 升级完成路径和后台 `cfg_valid` 检测路径都可能触发恢复,使用该标志保证同一时间只有一个 `hot_reinit_rf()` 执行。
## 4. 统一恢复链路
不掉电恢复的逻辑链路如下:
```text
触发恢复
|
+-- IMX 应用升级/重启 ---------- FPGA、9009、AD9528保持上电
|
+-- FPGA 在线升级/IPROG -------- FPGA重载,9009、AD9528保持上电
|
v
停止 DPD 和可能访问 SPI/RF 的后台状态机
|
v
判断 FPGA 是否重载、9009 是否仍存活
|
+-- 9009存活:TALISE_openHw,跳过reset/initialize/cals
|
+-- 9009未运行:执行完整TALISE冷启动初始化
|
v
恢复 FPGA 静态配置和业务参数
|
v
配置 JESD TX/RX/FB + FPGA GT reset + SYNC/ILAS重握手
|
v
恢复频率、衰减、ORx增益、FB route、SPI控制权
|
v
恢复 FAGC、ORx测量、DPD曲线/自适应状态
|
v
检查 PLL、JESD、TX状态
|
+-- 成功:按保存的RF状态打开TX通道和物理RF开关
|
+-- 失败:保持RF关闭,记录错误并进入有限次数重试/告警
```
## 5. IMX 应用升级或 kill/restart 流程
### 5.1 推荐的进程重启顺序
应用升级脚本应按以下顺序执行:
1. 停止 `daemon_system` 和 `logrotateStart`,避免守护进程抢先拉起旧程序。
2. 执行升级包的 `remove.sh` 和 `cp.sh`,复制新应用及配套文件。
3. `sync`,确保文件写入完成。
4. 根据 `/proc/<pid>/exe` 找到所有旧 `imx_atsc_app` 进程,而不是仅依赖进程名。
5. 先发送普通终止信号,等待旧进程退出;超时后再发送强制终止信号。
6. 确认旧进程全部退出后,清理遗留的 `loglog` 管道进程。
7. 无参数启动新应用:
```sh
./imx_atsc_app
```
8. 重新启动 `daemon_system` 和 `logrotateStart`。
ATSC 守护进程已经改为无参数启动新应用。App 端同时保留对历史参数 `1` 的兼容,但会将其按普通启动处理,即保持 `g_restart_app_flg=0`,确保新进程重新建立 TALISE 句柄并重建 FPGA/JESD 链路。
### 5.2 新进程中的 RF 启动流程
新进程进入 `fpga_para_init()` 后:
1. 在恢复参数前启用 RF hold,确保初始化期间物理输出保持关闭。
2. 读取 FPGA `cfg_valid`。
3. 若 `cfg_valid=1`,认为 FPGA 和 9009 未掉电:
- 设置 `g_skip_talise_cfg=1`;
- 调用 `config_fpga_para()`;
- `adrv9009_init()` 仍负责恢复 FPGA 侧 JESD,但 `adrv9009_cfg()` 只轻挂接 9009;
- 完成后清除 `g_skip_talise_cfg`。
4. 恢复 AGC、功率、DPD 和 FAGC 参数。
5. 若保存的 `atsc_para.rf_para.rf=1`,调用 `adrv9009_set_tx_path_enable(1)`。
6. 等待射频稳定延时后,释放 RF hold,并恢复物理 RF 开关和衰减值。
相关代码:
- `bsp/isys_config.c`:`fpga_para_init()`;
- `bsp/fpga.c`:`config_fpga_para()`、`fpga_save_all_para()`;
- `adrv9009/adrv9009_config.c`:`adrv9009_init()`、`adrv9009_cfg()`;
- `bsp/fpga.c`:`fpga_boot_rf_hold_begin()`、`fpga_boot_rf_hold_release()`。
## 6. FPGA 在线升级后的恢复流程
### 6.1 FPGA 升级完成
Web 或 TCP 升级路径将 `.mcs` 写入 FPGA flash。升级完成后:
1. `check_fpga_upgrade_done()` 检测 DONE 状态;
2. `reset_fpga_system()` 触发 FPGA IPROG,加载新 bitstream;
3. FPGA 重载后 `cfg_valid` 自动清零;
4. 延时约 3 秒等待 FPGA 配置稳定;
5. 调用 `hot_reinit_rf(0)`,不重启 IMX,不重新初始化 9009。
除升级完成回调外,`imx_up_thr()` 每秒检查一次 `cfg_valid`。在本进程已经完成启动、且曾经读到过 `cfg_valid=1` 的前提下,连续两次读到 `0`,也会触发 `hot_reinit_rf(0)`。这是 FPGA 异常重载时的补偿路径。
### 6.2 `hot_reinit_rf()` 处理步骤
`task/upgrade.c` 中的 `hot_reinit_rf()` 执行:
1. 使用 `g_hot_relink_busy` 防止重复进入;
2. 记录升级前 DPD/ADPC 模式;
3. 设置 `atsc_adpc_force_stop_flag=TRUE`,停止 DPD 状态机访问 SPI/RF;
4. 等待约 1.5 秒,使后台处理退出临界区;
5. 如果参数 `reload_fpga=1`,先执行 IPROG 并等待约 3 秒;
6. 设置 `g_skip_talise_cfg=1`;
7. 调用 `config_fpga_para()`,恢复 FPGA、JESD 和射频参数;
8. 清除 `g_skip_talise_cfg`;
9. 重置 DPD 软件状态,恢复 FAGC/ORx 测量;
10. 触发 TSNR/ORx phase 7~10 后台流程;
11. 如果升级前 DPD 处于 HOLD,重新加载保存曲线;如果处于 UPDATE,重新启动自适应;
12. 清除热恢复 busy 标志。
## 7. ADRV9009 轻挂接流程
当 `g_skip_talise_cfg=1` 时,`adrv9009_cfg()` 执行:
1. 根据通道选择对应的 `taliseDeviceInfo`;
2. 重建 `p_myTaliseDevice_shm` 指针;
3. 复制 TALISE 初始化结构到本进程内存;
4. 调用 `TALISE_openHw()`,重新打开 HAL/SPI 访问;
5. 调用 `TALISE_getPllsLockStatus()` 读取 PLL 状态;
6. 直接返回,不执行以下冷启动操作:
- `TALISE_resetDevice()`;
- `TALISE_initialize()`;
- ARM firmware 重新加载;
- `TALISE_runInitCals()`;
- 其他只应在冷启动执行的校准。
该流程解决了“9009 仍在运行时重复 TALISE 初始化导致 err10”的问题。
## 8. JESD204B 重连流程
轻挂接只跳过 TALISE 侧完整初始化,`adrv9009_init()` 中 FPGA 侧链路配置仍继续执行:
1. 初始化 IMX 到 ADRV9009 的 SPI 设备;
2. 暂停或隔离射频输出;
3. 配置 FPGA JESD TX0;
4. 轻挂接 ADRV9009;
5. 配置 FPGA JESD FB0 和 RX0;
6. 写 FPGA AXI `0x78 = 0x111` 后清零,复位 TX/RX/FB GT;
7. 使能 JESD TX、RX 链路;
8. 恢复 RF route、FB route 和相关 FPGA AXI 寄存器;
9. 在 9009 framer 已开始发送后,再执行一次 RX/FB GT 晚复位:
```text
AXI 0x78 = 0x110
delay 2 ms
AXI 0x78 = 0x000
```
10. 读取 FPGA `0xBB` 检查同步状态;当前代码把 `0x07` 或 `0x03` 作为 FB 初始化正常的日志判据;
11. 恢复 ORx/RF monitor、DPD 和 FB route。
本链路使用 JESD204B Subclass 0 的 SYNC/ILAS 重握手,不依赖重新初始化 9009 CLKPLL。
## 9. 射频参数和输出恢复
FPGA/JESD 恢复过程中需要恢复以下运行参数:
- FPGA 调制和系统静态寄存器;
- 发射频率、功率和频偏;
- ADRV9009 TX attenuation;
- ORx manual gain;
- HMC472/FPGA AGC attenuation;
- ADRV9009 SPI 控制权,从 MCU 切回 FPGA;
- TX/RX/ORx RF route;
- DPD bypass/HOLD/UPDATE 状态;
- 保存的 DPD 曲线;
- FAGC、ORx RMS 和 TSNR 后台测量状态。
外部射频打开应放在恢复流程最后:
1. `adrv9009_set_tx_path_enable(1)` 调用 `TALISE_setRxTxEnable()` 打开 TX1;
2. 回读 `TALISE_getRxTxEnable()` 确认 TX 状态;
3. 打开 FPGA/物理 RF 开关;
4. 等待衰减稳定;
5. 恢复保存的输出功率或衰减设置。
如果用户保存的 RF 状态为 OFF,则恢复完成后仍必须保持 TX 和物理 RF 输出关闭。
## 10. 恢复成功判据
不能只以函数执行结束作为成功,建议至少检查以下项目:
| 检查项 | 建议判据 |
|---|---|
| 9009 SPI | scratch-pad 写入/回读一致 |
| 9009 PLL | CLKPLL、RFPLL 必须锁定;使用 AUXPLL 时同时检查 AUXPLL |
| 9009 ARM | ARM 状态正常,命令接口可响应 |
| 9009 TX | `TALISE_getRxTxEnable()` 回读包含期望的 TX 通道 |
| FPGA JESD | TX/RX/FB SYNC 状态符合当前设计 |
| FPGA `0xBB` | 当前代码期望恢复后为 `0x03` 或 `0x07` |
| FPGA config | `cfg_valid` 最终重新写为 `1` |
| ORx | `ob_fb`/FWD-FSSI 不应长期为 0、1 或饱和值 `0x7fff` |
| DPD | HOLD 曲线已重新加载,或 UPDATE 状态机已重新启动 |
| 射频输出 | 频谱仪测得中心频率、功率和肩部符合保存配置 |
建议日志中保留以下关键字,方便现场定位:
```text
[PR10] boot: FPGA cfg_valid=...
[JESD-RELINK] light-attach 9009...
[HOT-RESTART] begin...
[HOT-RESTART] FAGC/ORx measurement restarted...
[HOT-RESTART] done
```
## 11. 当前实现的限制和待补强项
### 11.1 IMX `reboot` 与 AD9528 保持
当前板卡已确认 Linux `reboot` 可以重新进入 U-Boot 和内核。实测表明,异常不是 `reboot` 本身造成,而是启动脚本无条件重新配置 AD9528,打断了正在运行的参考时钟/SYSREF/JESD 关系。
启动脚本现在先调用 `imx_cfgad9528 --check-ready` 进行纯读检查:芯片型号、版本、PLL 配置、SYSREF 控制和 PLL2 锁定状态均正确时,完全不触碰 RESET 和 AD9528 寄存器;状态无效时才进入断电启动的复位+完整配置流程。因此无需依赖可能残留的文件标志。
### 11.2 升级包脚本同步保护
源码 `src/atsc_app/runme.sh` 和待打包目录 `out/upgrade_imx_fs/SMOD/runme.sh` 已统一为自动分流版本:包含 `zImage` 或指定 DTB 时执行 warm reboot;纯用户态升级则执行“kill 旧 App -> 启动新 App”。`make install` 会自动同步 `runme.sh` 和 `anywave_exc_start.sh`,避免后续构建重新引入旧版。生成 tar 包后仍应使用以下方式复查:
```sh
tar -tzf SMOD*.tar.gz | grep runme.sh
tar -xOzf SMOD*.tar.gz SMOD/runme.sh
```
### 11.3 守护进程启动入口已统一
ATSC 守护进程使用 `/proc/<pid>/exe` 精确检测 `/aw/anywave/app/imx_atsc_app`,不再使用可能受线程改名影响的 `ps | grep`。守护拉起命令不再传入参数 `1`;App 仍兼容旧版守护程序传入的 `1`,但会进入与无参数启动相同的 RF 恢复流程。
### 11.4 `cfg_valid` 不能完全代表 9009 状态
应把 FPGA 和 9009 状态拆开判断:
| FPGA状态 | 9009状态 | 应执行流程 |
|---|---|---|
| 未重载 | 存活且PLL锁定 | 轻挂接,按需刷新FPGA/JESD和射频参数 |
| 已重载 | 存活且PLL锁定 | 跳过TALISE初始化,完整重建FPGA/JESD |
| 已重载 | 未初始化/无SPI响应 | 完整冷启动TALISE,再配置FPGA/JESD |
| 未重载 | 无SPI响应或PLL异常 | 保持RF关闭,尝试9009硬复位和完整初始化 |
当 warm reboot 保留 AD9528 时,App 仍需结合 FPGA/9009 状态选择 light-attach 或完整恢复;不能单独把 `cfg_valid` 当作整条 RF 链路有效的证明。
### 11.5 当前错误处理偏弱
现有 light-attach 会打印 PLL 状态,但未严格检查 API 返回值和锁定 bit;部分 JESD 状态异常也只是打印日志,恢复流程仍会继续。发布版本建议:
- 严格检查 `TALISE_openHw()`、PLL、TX enable 和 JESD 状态;
- 任一关键检查失败时保持物理 RF 关闭;
- 增加有限次数的 FPGA/JESD 重连重试;
- 重试失败后产生明确告警,禁止无效链路下打开射频。
### 11.6 热恢复后的 RF 最终状态需要统一收口
热恢复中存在 `set_pa_output(0)`,而对应的 `set_pa_output(1)` 已被注释。需要结合 FPGA `AXI 0x100` 的实际定义确认 `0` 是否代表正常调制路径。如果它确实是 PA 输出使能,则必须在所有检查成功后统一恢复,避免 JESD 已同步但内部 RF 数据仍被关闭。
## 12. 推荐的最终启动决策
推荐在应用启动时使用以下决策顺序:
```text
1. RF物理开关置OFF,启动RF hold
2. 检查FPGA cfg_valid
3. 检查9009 SPI、scratch-pad、PLL和ARM状态
4. 9009存活 -> light-attach
5. 9009未运行 -> full TALISE init
6. FPGA重载或新进程启动 -> 恢复FPGA参数并重建JESD
7. 恢复频率/衰减/ORx/DPD/FAGC
8. 校验PLL + JESD + TX状态
9. 成功 -> 按保存状态释放RF hold
10. 失败 -> RF保持OFF,重试或告警
```
这样可以让以下入口最终汇聚到同一套 RF 恢复状态机:
- IMX 应用升级后的 kill/restart;
- 守护进程异常重启应用;
- 命令行/Web/IMX 内核或 DTB 升级触发的 warm reboot;
- FPGA 在线升级/IPROG;
- FPGA 运行时异常重载。
+57
View File
@@ -0,0 +1,57 @@
# ATSC ADRV9009 射频信号主链路
本文档按当前工程源码整理 FPGA 到激励器 RF 接口的实际信号路径。
## 射频信号主路径
```mermaid
flowchart TD
INPUT["TS 流 / 测试数据"] --> MOD["FPGA ATSC 8VSB 调制"]
MOD --> DPD["FPGA DPD 与 IQ 处理"]
DPD --> ROUTE["打开 FPGA IQ 路由<br/>FBROUTE = 0x10"]
ROUTE --> JESD_TX["FPGA JESD204B TX0<br/>2 Lane · Subclass 0 · 无 SYSREF"]
JESD_TX --> DEF["ADRV9009 JESD Deframer"]
DEF --> DAC["ADRV9009 TX1 DAC 与插值滤波"]
DAC --> MIX["正交调制 + RF PLL 上变频"]
MIX --> TX_PATH{"ADRV9009 TX1 通道使能?"}
TX_PATH -->|"TALISE_setRxTxEnable(..., TAL_TX1)"| ADRV_ATT["ADRV9009 TX 衰减<br/>0~40000 mdB"]
TX_PATH -->|"TAL_TXOFF"| STOP1["芯片内部 TX 停止"]
ADRV_ATT --> HMC["HMC472 外部衰减<br/>0~63,每步 0.5 dB"]
HMC --> RF_SW{"板级 RF 开关?"}
RF_SW -->|"FPGA seg1:0x26 = 1"| RF_OUT["激励器 RF 输出接口"]
RF_SW -->|"FPGA seg1:0x26 = 0"| STOP2["外部 RF 接口隔离"]
WE["当前频点 WE"] --> POWER["总衰减计算"]
LCD["LCD / 网页输出功率"] --> POWER
HW["HardwareVersion<br/>V1.0:0 dB<br/>V1.1:当前代码 +11 dB 补偿"] --> POWER
POWER --> SPLIT["衰减分配<br/>ADRV9009 优选约 8 dB<br/>其余优先分配给 HMC472"]
SPLIT --> ADRV_ATT
SPLIT --> HMC
MIX -. "TX1 → ORx1 内部映射" .-> ORX["ADRV9009 ORx1"]
ORX --> FRAMER["ADRV9009 JESD Framer B"]
FRAMER --> FPGA_FB["FPGA 反馈 IQ"]
FPGA_FB --> FAGC["ORx FAGC"]
FPGA_FB --> DPD_FB["DPD 反馈计算"]
FPGA_FB --> QUALITY["SNR / 左右带肩计算"]
FAGC -. "调节 ORx 增益" .-> ORX
DPD_FB -. "更新预失真参数" .-> DPD
```
## 三层射频开关
1. `TALISE_radioOn()`:ADRV9009 ARM 进入 Radio On 状态。
2. `TALISE_setRxTxEnable(..., TAL_TX1)`:开启 ADRV9009 TX1 信号链。
3. FPGA `seg1:0x26 = 1`:接通板级物理 RF 输出开关。
三层状态以及功率衰减配置均正确后,外部 RF 接口才会稳定输出射频。
## 关键源码入口
- `bsp/fpga.c::fpga_save_all_para()`:恢复 FPGA、RF、WE、DPD 等参数。
- `adrv9009/adrv9009_config.c::adrv9009_init()`:初始化 ADRV9009 与 JESD 链路。
- `adrv9009/jesd204b_config.c::jesd204b_tx0_cfg()`:配置 FPGA JESD204B TX0。
- `adrv9009/adrv9009_config.c::adrv9009_set_tx_path_enable()`:控制 ADRV9009 TX1。
- `bsp/fpga.c::fpga_save_power()`:计算并分配 ADRV9009/HMC472 衰减。
- `bsp/fpga.c::fpga_set_rf_output_with_para()`:控制最终板级 RF 输出。
Executable → Regular
+50 -19
View File
@@ -41,6 +41,7 @@
#include "../inc/fpga.h"
#include "../inc/http_get.h"
#include "../inc/adrv9009_ext.h"
#include "../inc/iq_rx.h"
extern uint32 adpc_source_udp;
extern uint32 adpc_source_web;
@@ -113,8 +114,16 @@ static void http_load_psip(S_PSTS_PARA *ppara)
void http_load_status(S_CGI_STATUS *ppara)
{
int tmp_imd;
uint16 measured_tsnr;
uint8 measured_l_imd;
uint8 measured_u_imd;
PS_LOG_PARA appara=&atsc_para.log_para;
/* PR19 values are produced asynchronously by iq_rx. Read that source here
* instead of relying on log_para's once-per-second refresh, which is skipped
* while update_flag is set during a DPD update. */
get_tsnr_limd_uimd(&measured_tsnr, &measured_l_imd, &measured_u_imd);
ppara->input = appara->asi_input;
ppara->status= appara->input_ok_flag?0:1;
@@ -145,32 +154,32 @@ void http_load_status(S_CGI_STATUS *ppara)
//ppara->tsnr = atsc_para.log_para.tsnr;
if(atsc_para.wd_para.sa >= 0)
ppara->tsnr = atsc_para.log_para.tsnr+atsc_para.wd_para.sa;
ppara->tsnr = measured_tsnr+atsc_para.wd_para.sa;
else
ppara->tsnr = atsc_para.log_para.tsnr-(u8)(atsc_para.wd_para.sa*-1);
ppara->tsnr = measured_tsnr-(u8)(atsc_para.wd_para.sa*-1);
//ppara->l_imd = atsc_para.log_para.l_imd;
if(atsc_para.wd_para.sb >= 0)
ppara->l_imd = atsc_para.log_para.l_imd+atsc_para.wd_para.sb;
ppara->l_imd = measured_l_imd+atsc_para.wd_para.sb;
else{
tmp_imd = atsc_para.log_para.l_imd - (int)(-1*atsc_para.wd_para.sb);
tmp_imd = measured_l_imd - (int)(-1*atsc_para.wd_para.sb);
if(tmp_imd<0)
ppara->l_imd = 0;
else{
int8 tmpdata = -1*atsc_para.wd_para.sb;
ppara->l_imd = atsc_para.log_para.l_imd-(u8)(tmpdata);
ppara->l_imd = measured_l_imd-(u8)(tmpdata);
}
}
//ppara->u_imd = atsc_para.log_para.u_imd;
if(atsc_para.wd_para.sc >= 0)
ppara->u_imd = atsc_para.log_para.u_imd+atsc_para.wd_para.sc;
ppara->u_imd = measured_u_imd+atsc_para.wd_para.sc;
else{
tmp_imd = atsc_para.log_para.u_imd - (int)(-1*atsc_para.wd_para.sc);
tmp_imd = measured_u_imd - (int)(-1*atsc_para.wd_para.sc);
if(tmp_imd<0)
ppara->u_imd = 0;
else{
ppara->u_imd = atsc_para.log_para.u_imd-(u8)(-1*atsc_para.wd_para.sc);
ppara->u_imd = measured_u_imd-(u8)(-1*atsc_para.wd_para.sc);
}
}
ppara->syserr = atsc_para.log_para.syserr;
@@ -272,6 +281,7 @@ void http_load_sys(S_CGI_SET *ppara)
ppara->agc_att = atsc_para.system_para.agc_att;
ppara->adrv9009_att = atsc_para.system_para.adrv9009_att;
ppara->orx_manu_gain = atsc_para.system_para.orx_manu_gain;
ppara->hardware_version = atsc_para.system_para.hardware_version;
}
#if ERR_TO_LCM
@@ -881,7 +891,12 @@ void http_write_sys(S_CGI_SET *ppara)
if(atsc_para.system_para.adpc<=ADPC_HOLD&&g_trigger_up_tsnr_flg == 0)// 无大小均衡
{
atsc_para.system_para.ab_mode = ppara->ab_mode;
/* FSSI Refresh alternates A/B feedback paths. Pause IQ collection until
* the unit returns to the stable single-A path. */
iq_spec_set_enable(ppara->ab_mode == 0);
}
//-----------------------------------------
uint8 adpc_bak;
@@ -975,6 +990,25 @@ void http_write_sys(S_CGI_SET *ppara)
c256_bytewrite(EEPROM_ADRV9009_ATT_L, ppara->adrv9009_att&0xff);
}
#endif
if(ppara->hardware_version < HARDWARE_VERSION_END &&
ppara->hardware_version != atsc_para.system_para.hardware_version)
{
/*
* V1.1 -> V1.0 removes 11 dB of compensation. Always switch
* RF off before applying a hardware profile change.
*/
atsc_para.rf_para.rf = 0;
c256_bytewrite(EEPROM_RF_ADDR, 0);
fpga_set_rf_output(0);
atsc_para.system_para.hardware_version = ppara->hardware_version;
c256_bytewrite(EEPROM_HARDWARE_VERSION, ppara->hardware_version);
atsc_para.we_para.ch = atsc_para.rf_para.txfreq;
e2rom_read_leak(atsc_para.we_para.ch, &atsc_para.we_para);
fpga_save_we(&atsc_para.we_para);
log_printf(LOG_INFO, I_USER_CFG,
"hardware version changed to V1.%d; RF forced off; WE=%u reloaded\n",
ppara->hardware_version, atsc_para.we_para.power);
}
if(ppara->orx_manu_gain!=atsc_para.system_para.orx_manu_gain){
cfg_adrv_orx_manu_gain(ppara->orx_manu_gain);
atsc_para.system_para.orx_manu_gain = ppara->orx_manu_gain;
@@ -1055,15 +1089,14 @@ void http_write_sys(S_CGI_SET *ppara)
}
if(ppara->sys_reset) {
log_printf(LOG_DEBUG, I_USER_CFG, "SYSTEM RESET NOW (IMX+FPGA)!!!!!!!!!!!!!\n");
//复位 FPGA本身复位
fpga_byte_write(0x01,0xfe,0x5a);
fpga_byte_write(0x01,0xfe,0xa5);
// [one@20220419 add imx reset]
system("sync");
system("reboot");
}
/* A Web System Reset is an IMX warm reboot. Keep FPGA, AD9528 and
* ADRV9009 powered so the normal boot path can preserve the RF link. */
log_printf(LOG_DEBUG, I_USER_CFG,
"WEB SYSTEM RESET NOW (IMX warm reboot, keep RF hardware)\n");
system("sync");
system("reboot");
return;
}
}
@@ -1623,5 +1656,3 @@ void http_save_change_para(void *p)
/*****************************END**************************************/
Executable → Regular
+66 -77
View File
@@ -46,24 +46,23 @@
#define IQ_WBYTES (IQ_NPKT * IQ_PAY) /* 1 MiB per window */
/* Welch over the whole window. The FFT is 4096 points because at 10 MSPS that is
* a 2441.40625 Hz bin -- bit for bit the same bin the FPGA's imd_imd_fft_meas uses
* at 40 MHz/16384. So bins 10 / +1290 / -1290 land exactly on its cent/L/R taps and
* UIMD/LIMD can be recomputed here without redefining anything. The published trace
* is folded down to 1024 points (4 bins each) to keep the JSON small. */
* a 2441.40625 Hz bin. The published trace is folded down to 1024 points (4 bins
* each) to keep the JSON small. */
#define IQ_FFT 4096
#define IQ_SEGPKT (IQ_FFT / IQ_NF) /* 16 packets per segment */
#define IQ_NSEG (IQ_NPKT / IQ_SEGPKT)
#define IQ_OUT 1024 /* published trace points */
#define IQ_FOLD (IQ_FFT / IQ_OUT)
/* imd_imd_fft_meas taps: cent = +24.414 kHz, L = +3.1494 MHz, R = -3.1494 MHz.
* HALF widens each tap to 11 bins (+-12.2 kHz): a single 2.4 kHz bin of a data
* signal is chi-square with 2 dof, i.e. +-8 dB of scatter, and the -3.1494 MHz bin
* additionally sits on a narrow feature ~5 dB above its neighbours. */
#define IMD_CENT 10
#define IMD_L 1290
#define IMD_R (IQ_FFT - IMD_L)
#define IMD_HALF 5
/* PR19 shoulder measurement windows, in MHz relative to the channel centre.
* Average linear power in each 500 kHz window before converting the ratios to dB. */
#define IMD_CENT_LO_MHZ (-0.25f)
#define IMD_CENT_HI_MHZ ( 0.25f)
#define IMD_LEFT_LO_MHZ (-3.50f)
#define IMD_LEFT_HI_MHZ (-3.00f)
#define IMD_RIGHT_LO_MHZ ( 3.00f)
#define IMD_RIGHT_HI_MHZ ( 3.50f)
#define IMD_BW_KHZ 500.0f
#define IQ_JSON "/tmp/iq_spectrum.json"
#define IQ_JSON_TMP "/tmp/iq_spectrum.json.tmp"
@@ -128,12 +127,6 @@ static int longest_run(const unsigned char *seen)
return best;
}
static int cmp_float(const void *a, const void *b)
{
float x = *(const float *)a, y = *(const float *)b;
return (x < y) ? -1 : (x > y);
}
/* ---- newest UIMD/LIMD, published to lcd_task.c's get_tsnr_limd_uimd() ---- */
static pthread_mutex_t imd_mtx = PTHREAD_MUTEX_INITIALIZER;
static float imd_l_db, imd_u_db;
@@ -142,7 +135,6 @@ static time_t imd_stamp; /* 0 = nothing measured yet */
int iq_get_imd(float *limd_db, float *uimd_db)
{
int fresh;
if (!iq_spec_get_enable()) return 0;
pthread_mutex_lock(&imd_mtx);
fresh = imd_stamp && (time(NULL) - imd_stamp) <= IQ_IMD_MAX_AGE;
if (fresh) { *limd_db = imd_l_db; *uimd_db = imd_u_db; }
@@ -169,22 +161,22 @@ void iq_spec_set_enable(int enable)
iq_spec_enable = enable;
if (!enable) {
/* FSSI A/B switching mixes two feedback paths, so no IQ window collected
* in this mode is valid as a stable single-path spectrum or eye. */
pthread_mutex_lock(&iq_mtx);
iq_ready = -1;
pthread_mutex_unlock(&iq_mtx);
pthread_mutex_lock(&vsb_mtx);
vsb_pending = 0;
pthread_mutex_unlock(&vsb_mtx);
pthread_mutex_lock(&imd_mtx);
imd_stamp = 0;
pthread_mutex_unlock(&imd_mtx);
iq_vsb_clear_results();
unlink(IQ_JSON);
unlink(IQ_JSON_TMP);
unlink("/tmp/iq_eye.json");
unlink("/tmp/iq_raw.bin");
}
printf("[iq_rx] spectrum %s\n", enable ? "enabled" : "disabled");
}
@@ -203,10 +195,9 @@ static void *iq_vsb_thr(void *arg)
while (!vsb_pending) pthread_cond_wait(&vsb_cond, &vsb_mtx);
pthread_mutex_unlock(&vsb_mtx);
if (!iq_spec_get_enable()) continue;
iq_vsb_analyse(vsb_win, vsb_wid);
if (!iq_spec_get_enable()) {
if (iq_spec_get_enable()) {
iq_vsb_analyse(vsb_win, vsb_wid);
} else {
iq_vsb_clear_results();
unlink("/tmp/iq_eye.json");
}
@@ -218,13 +209,21 @@ static void *iq_vsb_thr(void *arg)
return 0;
}
/* mean power of the 2*half+1 bins centred on k, wrapping the FFT index */
static float band_power(const float *psd, int k, int half)
/* Mean linear power of all FFT bins inside the inclusive signed-frequency range. */
static float band_power_range(const float *psd, float f_lo_mhz, float f_hi_mhz)
{
float acc = 0.0f;
int d;
for (d = -half; d <= half; d++) acc += psd[(k + d) & (IQ_FFT - 1)];
return acc / (2 * half + 1);
const float df = IQ_FS_MHZ / IQ_FFT;
int i, count = 0;
for (i = 0; i < IQ_FFT; i++) {
float f = (i < IQ_FFT / 2 ? i : i - IQ_FFT) * df;
if (f >= f_lo_mhz && f <= f_hi_mhz) {
acc += psd[i];
count++;
}
}
return count ? acc / count : 0.0f;
}
/*
@@ -290,19 +289,12 @@ static void *iq_spec_thr(void *arg)
}
if (nseg == 0) { pthread_mutex_lock(&iq_mtx); iq_ready = -1;
pthread_mutex_unlock(&iq_mtx); continue; }
if (!iq_spec_get_enable()) {
pthread_mutex_lock(&iq_mtx);
iq_ready = -1;
pthread_mutex_unlock(&iq_mtx);
continue;
}
{
const float df = IQ_FS_MHZ / IQ_FFT; /* 2.44140625 kHz, == FPGA's 40MHz/16384 */
const float dfo = df * IQ_FOLD; /* published bin */
float flat = 0.0f, pk = 0.0f, pilot = 0.0f, shoulder = 0.0f;
float p_cent, p_hi, p_lo, limd, uimd;
int nflat = 0, nsh = 0, pkbin = 0, pos = 0, j;
float flat = 0.0f, pk = 0.0f, pilot = 0.0f;
float p_cent, p_hi, p_lo, p0_db, pl_db, pr_db, limd, uimd;
int nflat = 0, pkbin = 0, pos = 0, j;
for (i = 0; i < IQ_FFT; i++) {
float f = (i < IQ_FFT / 2 ? i : i - IQ_FFT) * df;
@@ -313,16 +305,17 @@ static void *iq_spec_thr(void *arg)
flat = (nflat ? flat / nflat : 1e-12f) + 1e-12f;
pilot = (pkbin < IQ_FFT / 2 ? pkbin : pkbin - IQ_FFT) * df;
/* UIMD/LIMD on imd_imd_fft_meas's own taps, band-averaged.
* LIMD is by definition the shoulder on the pilot's side of the carrier,
* so key it off the measured pilot rather than off a hard-coded sign: an
* inverted ORx spectrum then swaps the taps instead of the labels. */
p_cent = band_power(psd, IMD_CENT, IMD_HALF);
p_hi = band_power(psd, IMD_L, IMD_HALF); /* +3.1494 MHz */
p_lo = band_power(psd, IMD_R, IMD_HALF); /* -3.1494 MHz */
if (pilot > 0) { float t = p_hi; p_hi = p_lo; p_lo = t; }
/* PR19: P0 is the mean power in the centre 500 kHz; PL and PR are
* the mean powers in -3.5..-3.0 MHz and +3.0..+3.5 MHz. The reported
* left/right shoulders are P0-PL and P0-PR respectively. */
p_cent = band_power_range(psd, IMD_CENT_LO_MHZ, IMD_CENT_HI_MHZ);
p_lo = band_power_range(psd, IMD_LEFT_LO_MHZ, IMD_LEFT_HI_MHZ);
p_hi = band_power_range(psd, IMD_RIGHT_LO_MHZ, IMD_RIGHT_HI_MHZ);
limd = 10.0f * log10f(p_cent / (p_lo + 1e-12f) + 1e-12f);
uimd = 10.0f * log10f(p_cent / (p_hi + 1e-12f) + 1e-12f);
p0_db = 10.0f * log10f(p_cent / flat + 1e-12f);
pl_db = 10.0f * log10f(p_lo / flat + 1e-12f);
pr_db = 10.0f * log10f(p_hi / flat + 1e-12f);
pthread_mutex_lock(&imd_mtx);
imd_l_db = limd; imd_u_db = uimd; imd_stamp = time(NULL);
@@ -337,25 +330,18 @@ static void *iq_spec_thr(void *arg)
}
for (j = 0; j < IQ_OUT; j++) psd[j] = sh[j];
for (j = 0; j < IQ_OUT; j++) {
float f = (j < IQ_OUT / 2 ? j : j - IQ_OUT) * dfo;
float a = f < 0 ? -f : f;
if (a >= 3.5f && a <= 4.8f) sh[nsh++] = psd[j]; /* shoulder, clear of Nyquist */
}
if (nsh) {
qsort(sh, nsh, sizeof(float), cmp_float);
shoulder = sh[nsh / 2];
}
pos = snprintf(json, sizeof(json),
"{\"wid\":%d,\"pkts\":%d,\"npkt\":%d,\"run\":%d,\"ms\":%.2f,"
"\"nseg\":%d,\"fs\":%.3f,\"n\":%d,\"f0\":%.6f,\"df\":%.6f,"
"\"rbw_khz\":%.2f,\"pilot\":%.4f,\"peak\":%.2f,\"shoulder\":%.2f,"
"\"limd\":%.2f,\"uimd\":%.2f,\"imd_rbw_khz\":%.3f,\"psd\":[",
"\"rbw_khz\":%.2f,\"pilot\":%.4f,\"peak\":%.2f,"
"\"shoulder\":%.2f,\"lshoulder\":%.2f,\"rshoulder\":%.2f,"
"\"limd\":%.2f,\"uimd\":%.2f,\"p0_db\":%.2f,\"pl_db\":%.2f,\"pr_db\":%.2f,"
"\"imd_rbw_khz\":%.3f,\"psd\":[",
wid, npkt, IQ_NPKT, run, run * IQ_NF / (IQ_FS_MHZ * 1e3f),
nseg, IQ_FS_MHZ, IQ_OUT, -IQ_FS_MHZ / 2.0f + 0.5f * (IQ_FOLD - 1) * df, dfo,
dfo * 1000.0f, pilot, 10.0f * log10f(pk / flat + 1e-12f), shoulder,
limd, uimd, df * 1000.0f * (2 * IMD_HALF + 1));
dfo * 1000.0f, pilot, 10.0f * log10f(pk / flat + 1e-12f),
limd < uimd ? limd : uimd, limd, uimd, limd, uimd,
p0_db, pl_db, pr_db, IMD_BW_KHZ);
/* emit fftshift-ed, i.e. ascending frequency from f0 */
for (j = 0; j < IQ_OUT && pos < (int)sizeof(json) - 16; j++) {
int k = (j + IQ_OUT / 2) & (IQ_OUT - 1);
@@ -364,12 +350,14 @@ static void *iq_spec_thr(void *arg)
}
pos += snprintf(json + pos, sizeof(json) - pos, "]}\n");
{ FILE *f = fopen(IQ_JSON_TMP, "w"); /* write+rename: the CGI never sees a partial file */
if (iq_spec_get_enable()) {
FILE *f = fopen(IQ_JSON_TMP, "w"); /* write+rename: the CGI never sees a partial file */
if (f) { fwrite(json, 1, pos, f); fclose(f); rename(IQ_JSON_TMP, IQ_JSON); }
}
}
/* a full window is 26.21 ms = 1.08 ATSC fields; print only a short burst every 30s */
#if IQ_ANALYSIS_UART_DEBUG
{
static time_t log_burst_start = 0;
static int log_burst_count = 0;
@@ -386,7 +374,8 @@ static void *iq_spec_thr(void *arg)
log_burst_count++;
}
}
if (npkt == IQ_NPKT && ++nraw >= IQ_RAW_EVERY) {
#endif
if (iq_spec_get_enable() && npkt == IQ_NPKT && ++nraw >= IQ_RAW_EVERY) {
FILE *r = fopen("/tmp/iq_raw.bin", "wb"); /* whole contiguous window, for PC-side analysis */
if (r) { fwrite(iq_win[buf], 1, IQ_WBYTES, r); fclose(r); }
nraw = 0;
@@ -414,7 +403,7 @@ static void *iq_spec_thr(void *arg)
void *iq_rx_thr(void *arg)
{
int fd, n, cur = -1, buf = 0, npkt = 0, active = 0;
int fd, n, cur = -1, buf = 0, npkt = 0;
struct sockaddr_in a;
unsigned char pkt[2048];
pthread_t spec_id;
@@ -425,7 +414,7 @@ void *iq_rx_thr(void *arg)
printf("[sched] iq_rx_thr nice 5 failed: %s\n", strerror(errno));
#endif
/* PR06 step6: the IQ frames are now injected by the FPGA into eth0 (management port, RMII2)
* and addressed to 192.168.1.143 -- eth0's OWN IP -- so the kernel hands them straight to
* and addressed to limited broadcast on eth0, so the kernel hands them straight to
* this UDP socket. This removes the old dependency on the external eth1 cable: eth0 is always
* connected while you observe the spectrum via the web, so the internal FPGA->imx6 IQ transfer
* no longer needs eth1's PHY link to be up. (busybox has no bare `ip`; absolute path required.) */
@@ -466,25 +455,25 @@ void *iq_rx_thr(void *arg)
memset(iq_seen[0], 0, IQ_NPKT);
for (;;) {
int wid, idx;
n = recvfrom(fd, pkt, sizeof(pkt), 0, NULL, NULL);
if (!iq_spec_get_enable()) {
active = 0;
cur = -1; npkt = 0;
memset(iq_seen[buf], 0, IQ_NPKT);
sleep(1);
cur = -1;
npkt = 0;
continue;
}
if (!active) {
while (recvfrom(fd, pkt, sizeof(pkt), MSG_DONTWAIT, NULL, NULL) > 0) {}
active = 1;
}
n = recvfrom(fd, pkt, sizeof(pkt), 0, NULL, NULL);
if (n < 8 + IQ_PAY) continue;
if (pkt[0] != 0x49 || pkt[1] != 0x51 || pkt[6] != 0x02) continue; /* 'IQ', fmt=2 */
wid = pkt[2];
idx = ((pkt[4] & 0x03) << 8) | pkt[5];
if (idx >= IQ_NPKT) continue;
if (cur < 0) cur = wid;
/* After FSSI Refresh is turned off, wait for packet zero so the first
* analysed window cannot contain a stale or partial A/B interval. */
if (cur < 0) {
if (idx != 0) continue;
memset(iq_seen[buf], 0, IQ_NPKT);
cur = wid;
}
if (wid != cur) { /* window boundary: hand this one off */
int other = buf ^ 1;
pthread_mutex_lock(&iq_mtx);
Executable → Regular
+16 -8
View File
@@ -41,6 +41,7 @@
#include <pthread.h>
#include "iq_rx.h"
#include "../inc/config.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -139,7 +140,6 @@ static int iq_vsb_log_allowed(void)
int iq_get_snr(float *snr_db)
{
int fresh;
if (!iq_spec_get_enable()) return 0;
pthread_mutex_lock(&snr_mtx);
fresh = snr_stamp && (time(NULL) - snr_stamp) <= IQ_SNR_MAX_AGE;
if (fresh) *snr_db = snr_db_pub;
@@ -1022,17 +1022,23 @@ static float line_cancel(float *r, float *q, int n, int k0)
}
/*
* T-spaced 11-tap complex LS equalizer, decision-directed, retrained per
* window (22 real unknowns; normal equations + Gaussian elimination). After
* T-spaced 41-tap complex LS equalizer, decision-directed, retrained per
* window (82 real unknowns; normal equations + Gaussian elimination). After
* the line canceller the residual floor (~-39 dB) is even-lag LINEAR ISI
* (observation-path ripple); this recovers it to ~43 dB (PC, half/half
* held-out). Real ATSC receivers/analyzers measure MER post-equalizer.
* Trains on every 2nd symbol; overfit bias with 22 dof over 70k samples is
* < 0.001 dB. Output written to out[] (edges copied through), returns SNR.
* PR16 offline held-out sweep on four eth0 IQ windows:
* EQ11=42.30..43.36 dB, EQ21=42.45..43.53 dB, EQ41=42.84..44.02 dB.
* PR16 stride sweep showed that using every 32nd symbol costs only
* 0.09..0.17 dB on four held-out windows while reducing the Cortex-A7 normal
* matrix work by 16x. The resulting ~4374 training rows for 82 unknowns are
* still safely overdetermined. Output written to out[] (edges copied through),
* returns SNR.
*/
#define EQ_M 5
#define EQ_M 20
#define EQ_NT (2 * EQ_M + 1)
#define EQ_NU (2 * EQ_NT)
#define EQ_TRAIN_STRIDE 32
static float eq_snr(const float *cr, const float *cq, int n, float *out)
{
static double xtx[EQ_NU][EQ_NU], xty[EQ_NU];
@@ -1042,7 +1048,7 @@ static float eq_snr(const float *cr, const float *cq, int n, float *out)
if (n < 100 * EQ_NT) return -99.0f;
memset(xtx, 0, sizeof(xtx));
memset(xty, 0, sizeof(xty));
for (k = EQ_M; k < n - EQ_M; k += 2) { /* stride-2 training */
for (k = EQ_M; k < n - EQ_M; k += EQ_TRAIN_STRIDE) {
double v[EQ_NU], d = slice1(cr[k]);
for (j = 0; j < EQ_NT; j++) {
v[2 * j] = cr[k - (j - EQ_M)];
@@ -1144,7 +1150,7 @@ static void pr12_run(struct pr12_result *R, float tau_old)
}
gain_track(ci, nsym);
R->line_db = line_cancel(ci, cq, nsym, k_lo); /* narrowband +Rs/4 canceller */
{ float s_eq = eq_snr(ci, cq, nsym, lc_er); /* 11-tap DD equalizer -> lc_er */
{ float s_eq = eq_snr(ci, cq, nsym, lc_er); /* 41-tap DD equalizer -> lc_er */
const float *eqstat;
if (s_eq > -90.0f) {
snr = s_eq;
@@ -1395,6 +1401,7 @@ void iq_vsb_analyse(const unsigned char *win, int wid)
minh = eyeh[0];
for (j = 1; j < 7; j++) if (eyeh[j] < minh) minh = eyeh[j];
#if IQ_ANALYSIS_UART_DEBUG
printf("[iq_vsb] win %d: SNR %.2f dB (raw %.2f, ORx AGC jitter %.2f%%) tau %.4f theta %+.1f deg coff %+.3f Hz "
"segsync %.1f@%d eye_w %.3f UI min_eye_h %.2f %s "
"[ms: unpack %d tau %d demod %d stats %d eye %d total %d]\n",
@@ -1412,4 +1419,5 @@ void iq_vsb_analyse(const unsigned char *win, int wid)
else
printf("[pr12] win %d: no field sync (ratio %.1f < %.1f), LEGACY fallback\n",
wid, pr.ratio, PR12_RATIO_THR);
#endif
}
Executable → Regular
+21 -5
View File
@@ -770,7 +770,7 @@ int get_sys_cfg(char *pbuf,short bufMaxLen)
//printf("adpc=%d\n", atsc_para.system_para.adpc);
sprintf(pbuf, "#%u#%u#%u#%u#%u#%s#%u#%u#%u#%u#%u#0x%04X#%u#%u#%u#%u",
sprintf(pbuf, "#%u#%u#%u#%u#%u#%s#%u#%u#%u#%u#%u#0x%04X#%u#%u#%u#%u#%u",
input, // 通道选择
atsc_para.system_para.gps, // 时钟源
atsc_para.system_para.adpc>=ADPC_UPDATE3?ADPC_UPDATE3:atsc_para.system_para.adpc, //预校正
@@ -786,8 +786,14 @@ int get_sys_cfg(char *pbuf,short bufMaxLen)
atsc_para.sn_para.eas_key_valid,
atsc_para.system_para.agc_att,
atsc_para.system_para.adrv9009_att,
atsc_para.system_para.orx_manu_gain
atsc_para.system_para.orx_manu_gain,
atsc_para.dpd_para.dpd_ctl
);
{
size_t used = strlen(pbuf);
if(used < (size_t)bufMaxLen)
snprintf(pbuf + used, (size_t)bufMaxLen - used, "#%u", atsc_para.system_para.hardware_version);
}
#if CGI_PRINT
//printf("[%s,%d] %s\n", __FUNCTION__,__LINE__,pbuf);
@@ -888,6 +894,12 @@ int set_sys_cfg(char *pbuf, short bufMaxLen)
p_cgi_sys_para->adrv9009_att = (uint16)atoi(paraBuf[13]);
#endif
p_cgi_sys_para->orx_manu_gain = (uint16)atoi(paraBuf[14]);
if(paraNum > 15)
{
uint8 hardware_version = (uint8)atoi(paraBuf[15]);
if(hardware_version < HARDWARE_VERSION_END)
p_cgi_sys_para->hardware_version = hardware_version;
}
}
#if 1
@@ -1303,8 +1315,9 @@ int set_spec_cfg(char *pbuf,short bufMaxLen)
if (!pbuf || bufMaxLen == 0) return -1;
atsc_para.system_para.spec_enable = 1;
c256_bytewrite(EEPROM_SPEC_ENABLE, atsc_para.system_para.spec_enable);
iq_spec_set_enable(1);
log_printf(LOG_INFO, I_USER_CFG, "spectrum enable fixed on\n");
iq_spec_set_enable(atsc_para.system_para.ab_mode == 0);
log_printf(LOG_INFO, I_USER_CFG, "spectrum page enabled, IQ collection %s\n",
atsc_para.system_para.ab_mode == 0 ? "running" : "paused by FSSI Refresh");
return 0;
}
@@ -1576,8 +1589,8 @@ int get_tsoip_systs1(char *pbuf, short bufMaxLen)
((pnet->udp_rtp&0xff)<<16)|((pnet->packet_len&0xff)<<8)|(pnet->receive_type&0xff),
pnet->fec,
pnet->delay,
pnet->col,
pnet->row,
pnet->col,
pnet->dest_ip[0],
(pnet->dest_ip[1]<<16)|(pnet->dest_ip[2]<<8)|pnet->dest_ip[3],
pnet->dest_port,
@@ -1777,6 +1790,9 @@ int start_fpga_upgrade(char *value,short maxlen)
return 0;
}
log_printf(LOG_INFO, I_USER_CFG, "get FPGA .mcs upgrade cmd from web.\n");
/* Clear the previous request's terminal status before the asynchronous
* imx_up_thr starts. Otherwise the web poll can consume a stale DONE. */
fpga_up_write_status("PENDING", 0, 0);
set_fpga_up_flg(1);
return 0;
}
Executable → Regular
+53 -33
View File
@@ -829,6 +829,27 @@ static uint8 orx_gain_index_now(void)
return (uint8)(255 - ((orx_median5(cw) >> 8) & 0xff));
}
/* PR16: 取一次 FSSI% —— 增益字(粗) + ob_fb_rms_pwr(细), 两者各 5 点中位数。
*
* 只看 gain index 时 FAGC 的 2 dB 死区会把 ±1 dB 以内的输入变化整个吃掉
* (dpd_rx_rfagc_1x.vhd dz=232), A/B 差异小于 1 dB 时 b_fssi 会恒等于 a_fssi。
* 死区内增益冻结, 功率变化完整落在 rms 上, 所以把 rms 残差加回来。详见 orx_power.h。
*
* 读法照抄 eeprom.c 里已验证的那段: 两对寄存器交替读、各取中位数 —— 0x03/0x04
* 共用 temp_reg 高字节锁存器, 交替读才能拿到配对的一组。 */
static uint8 orx_fssi_now(void)
{
uint16 cw[5], rms[5];
int i;
for (i = 0; i < 5; i++) {
rms[i] = fpga_word_read_lh(0x01, 0x03, 0x04);
cw[i] = fpga_word_read_hl(0x01, 0x36, 0x37);
}
return orx_fssi_from_power(orx_median5(cw), orx_median5(rms),
FSSI_RMS_REF, FSSI_GI_MIN, FSSI_GI_MAX);
}
static void calc_fwd(void)
{
#if 0
@@ -925,11 +946,14 @@ void get_tsnr_limd_uimd(uint16 *ptsnr, uint8 *plmid, uint8 *pumid)
uint16 u16_tsnr;
#if 1
if(atsc_para.system_para.adpc_err==DPD_SAVE_DATA || atsc_para.system_para.adpc_err==DPD_LOAD_DATA)
return;
imd_l = last_l; imd_r = last_r; u16_tsnr = last_snr;
/* Always initialise outputs. IQ results are protected independently and
* remain valid while DPD save/load suppresses the normal log refresh. */
*plmid = (uint8)imd_l;
*pumid = (uint8)imd_r;
*ptsnr = u16_tsnr;
// The FPGA's imd_imd_fft_meas registers (0x05:0x17/0x18/0x19) used to be read here.
// They are gone: its 20-bit |DFT|^2 output quantises both shoulder bins to 0 once
// the shoulder is better than ~45 dB, and the old `if(imd_l==0) imd_l=1` clamp then
@@ -942,9 +966,9 @@ void get_tsnr_limd_uimd(uint16 *ptsnr, uint8 *plmid, uint8 *pumid)
// alternative either: with DPD bypassed, snr_txpwr/snr_errpwr reads 1.84 dB.
//
// UIMD/LIMD and SNR now all come from the imx6's own 10 MSPS ORx window:
// - LIMD/UIMD: iq_rx.c, same taps as the FPGA but band-averaged over 11 bins
// and Welch-averaged. LIMD is the shoulder on the pilot's side. Half-dB units,
// no +11 dB display offset (the web adds that).
// - LIMD/UIMD: iq_rx.c, fixed 500 kHz centre/left/right windows,
// Welch-averaged. LIMD is P0-PL and UIMD is P0-PR. Half-dB units,
// displayed without the historical +11 dB offset.
// - SNR: iq_vsb.c, the real 8VSB MER (I-path) of a demodulated window.
// If a source has not produced a fresh value we keep the previous one rather than
// report a fabricated number.
@@ -4215,18 +4239,17 @@ static void refresh_ad4(void *parg,void *parv)
//需添加A:FSSI和B:FSSI ; FSSI 单源: update_fssi_state() 是唯一计算点,
//本函数只读 st_extend_state.a_fssi/b_fssi 显示, 不再自己取数/换算。
//ab_flag=TRUE -> 当前选中 B 路(set_ab_switch(1)); FALSE -> A 路(set_ab_switch(0))。
//ab_flag=TRUE -> 逻辑/丝印 B;FALSE -> 逻辑/丝印 A,硬件位由统一映射转换。
if(atsc_para.system_para.ab_mode==1)
{
if(check_delay_over(&tickbak,50)==TRUE)
{ // 0x23 IQB_UPD: 1=B, 0=A (E_SELECT_A=0/E_SELECT_B=1)
{
if(++ppara->ab_sec>16)
{
ppara->ab_flag = ppara->ab_flag?FALSE:TRUE;
ppara->ab_sec = 0;
}
set_ab_switch(ppara->ab_flag);
}
}
if(ppara->ab_flag)
{
ppara->fssi = st_extend_state.b_fssi;
@@ -6513,7 +6536,7 @@ static void refresh_we1(void *parg,void *parv)
sprintf(out_str,"%3d",txfreq_to_hz(ppara->ch));
lcd_write_string(LINE_2,prompt_pos.pos[0],out_str);
if(ppara->power>=WE_POWER_ZERO) //-15dB~0dB~+10dB 0~300~500
if(ppara->power>=WE_POWER_ZERO) //V1.0: -15~+10dB; V1.1: -15~+21dB
{
positive = TRUE;
temp = ppara->power-WE_POWER_ZERO;
@@ -10578,10 +10601,9 @@ static BOOL proc_key_wc1(void *parg,uint8 key)
add_4_hex(&ppara->gl, p->item_index);
break;
case 4:
ppara->dpd_ctl+=0x0010;
break;
case 5:
ppara->dpd_ctl+=0x0001;
// PR16: production supports only disabled (0x00) or enabled (0x07).
ppara->dpd_ctl = (ppara->dpd_ctl == 0x07) ? 0x00 : 0x07;
break;
default:
break;
@@ -10601,10 +10623,8 @@ static BOOL proc_key_wc1(void *parg,uint8 key)
dec_4_hex(&ppara->gl, p->item_index);
break;
case 4:
ppara->dpd_ctl-=0x0010;
break;
case 5:
ppara->dpd_ctl-=0x0001;
ppara->dpd_ctl = (ppara->dpd_ctl == 0x07) ? 0x00 : 0x07;
break;
default:
break;
@@ -11062,7 +11082,7 @@ static BOOL proc_key_we1(void *parg,uint8 key)
case 1:
if(atsc_para.rf_para.agc==0)
{
if(ppara->power<WE_POWER_MAX)
if(ppara->power<get_we_power_max())
{
ppara->power++;
}
@@ -14533,13 +14553,10 @@ static void sec_proc_func(void)
static void update_fssi_state(void)
{
// 0x23 IQB_UPD: 1=B, 0=A (E_SELECT_A=0/E_SELECT_B=1)
uint8 a_fssi;
uint8 b_fssi;
uint8 data;
// 0x23 IQB_UPD: 逻辑A/B由 DPD_RF_SEL_VALUE() 映射到反向硬件位
static uint16 sec_cnt=0;
static uint8 ab_flg =0;
uint8 just_switched = 0; /* PR16: 本秒刚拨过开关 -> 只拨不读, 留 1s 给 AGC 建立 */
/*
@@ -14574,39 +14591,42 @@ static void update_fssi_state(void)
ab_flg = 0;
else
ab_flg = 1;
just_switched = 1;
}
if(atsc_para.system_para.ab_mode==1){
if(ab_flg){
//B
set_ab_switch(E_SELECT_B);
{ /* 显示滞回: FAGC 一步 0.5dB = FSSI 2.5%, 无滞回会来回闪 */
/* PR16: 拨完开关这一秒不读。AGC 一个 gain step = 256 个积分周期
* x 51.2us ~= 13ms, 1s 足够建立; 不跳过的话这一次读到的还是 A 的增益字
* (原代码 A 分支那句被注释掉的 delay_ms(9) 就是想解决这个)。
* 每路驻留 10s, 只丢首采, 剩 9 次照常刷新。 */
if(!just_switched){
/* 显示滞回: 相邻档位间来回闪时保持前值 */
static uint8 b_prev = 0;
b_prev = orx_fssi_hysteresis(b_prev,
orx_fssi_from_gain_index(orx_gain_index_now(), FSSI_GI_MIN, FSSI_GI_MAX));
b_prev = orx_fssi_hysteresis(b_prev, orx_fssi_now());
st_extend_state.b_fssi = b_prev;
}
}
else{
//delay_ms(9);
//A
set_ab_switch(E_SELECT_A);
{
if(!just_switched){
static uint8 a_prev = 0;
a_prev = orx_fssi_hysteresis(a_prev,
orx_fssi_from_gain_index(orx_gain_index_now(), FSSI_GI_MIN, FSSI_GI_MAX));
a_prev = orx_fssi_hysteresis(a_prev, orx_fssi_now());
st_extend_state.a_fssi = a_prev;
}
}
//printf("[%s,%d] A_FSSI=%d B_FSSI=%d\n",__FUNCTION__,__LINE__,st_extend_state.a_fssi,st_extend_state.b_fssi);
}
else{
/* ab_mode!=1: 开关常驻 A, 不轮换, 也就没有建立时间问题 */
set_ab_switch(0);
//printf("AB switch A\n");
{
static uint8 a_prev2 = 0;
a_prev2 = orx_fssi_hysteresis(a_prev2,
orx_fssi_from_gain_index(orx_gain_index_now(), FSSI_GI_MIN, FSSI_GI_MAX));
a_prev2 = orx_fssi_hysteresis(a_prev2, orx_fssi_now());
st_extend_state.a_fssi = a_prev2;
}
//printf("[%s,%d] only A_FSSI=%d\n",__FUNCTION__,__LINE__,st_extend_state.a_fssi);
@@ -14800,7 +14820,7 @@ int sys_rf_agc_switch_proc(uint8 trigger)
//1 射频开关切换到A
u_dpdctl_r23.u8val = 0;
u_dpdctl_r23.dpd_bt.iqb_upd = 0;
u_dpdctl_r23.dpd_bt.rf_sel = 0; //A
u_dpdctl_r23.dpd_bt.rf_sel = DPD_RF_SEL_VALUE(E_SELECT_A);
u_dpdctl_r23.dpd_bt.sync_mode = 0;
fpga_byte_write(0x05, FPGA_ADDR_DPD_IQB_UPD, u_dpdctl_r23.u8val);
+4 -4
View File
@@ -863,7 +863,7 @@ static uint32 fpga_upgrade_process(void)
#define FPGA_UP_STATUS_PATH "/tmp/fpga_up_status"
/* Publish flash progress for the web page: one line "<PHASE> <PCT> <RET>".
* PHASE: PENDING|ERASE|WRITE|DONE|ERROR ; fpga_up_status.cgi serves this file. */
* PHASE: PENDING|ERASE|WRITE|RELINK|READY|ERROR ; fpga_up_status.cgi serves this file. */
void fpga_up_write_status(const char *phase, int pct, int ret)
{
FILE *sf = fopen(FPGA_UP_STATUS_PATH, "w");
@@ -990,7 +990,7 @@ int fpga_upgrade_from_file(const char *path)
delay_ms(200);
}
g_now_only_upgrade_fpga = 0;
if(ret == 0) fpga_up_write_status("DONE", 100, 0);
if(ret == 0) fpga_up_write_status("RELINK", 100, 0);
else fpga_up_write_status("ERROR", last_pct < 0 ? 0 : last_pct, ret);
log_printf(LOG_DEBUG, I_NOUSED, "fpga upgrade: finished ret=%d lines=%d\n", ret, upState.lineCnt);
return ret;
@@ -2174,8 +2174,8 @@ void check_fpga_up_finished_reboot_sys(void)
fpga_up_sys_reboot=0;
sec_cnt=0;
/* PR10: the FPGA update image was already IPROG-loaded by reset_fpga_system() in
* check_fpga_upgrade_done(). The old reset_imx_system() reboot is a no-op on this
* board (Task10: no HW soft-reboot path). Instead re-establish RF in-place: reconfig
* check_fpga_upgrade_done(). An IMX reboot is unnecessary for an FPGA-only update.
* Re-establish RF in-place: reconfig
* the FPGA + re-handshake the JESD link (skip 9009, which stayed powered) + reload the
* DPD HOLD curve. This makes a web FPGA .mcs upgrade self-recover WITHOUT a power cycle. */
extern void hot_reinit_rf(int reload_fpga);
+14 -5
View File
@@ -59,6 +59,7 @@ extern int g_trigger_up_start_flg;
static volatile int g_hot_relink_busy = 0; /* re-entrancy guard: only one relink at a time
* (the upgrade-completion path and the runtime
* cfg_valid poll can both request one). */
static volatile int g_fpga_upgrade_relink_pending = 0; /* web upgrade waits for RF relink */
void hot_reinit_rf(int reload_fpga)
{
extern uint8 hot_get_adpc_mode(void);
@@ -70,6 +71,8 @@ void hot_reinit_rf(int reload_fpga)
return;
}
g_hot_relink_busy = 1;
if(g_fpga_upgrade_relink_pending)
fpga_up_write_status("RELINK", 100, 0);
prev_adpc = hot_get_adpc_mode(); /* capture DPD mode BEFORE force-stop wipes it */
log_printf(LOG_INFO, I_USER_CFG, "[HOT-RESTART] begin (reload_fpga=%d, jesd-relink, skip TALISE, prev_adpc=%d)\n", reload_fpga, prev_adpc);
@@ -105,6 +108,10 @@ void hot_reinit_rf(int reload_fpga)
hot_dpd_rearm(prev_adpc);
}
log_printf(LOG_INFO, I_USER_CFG, "[HOT-RESTART] done\n");
if(g_fpga_upgrade_relink_pending){
fpga_up_write_status("READY", 100, 0);
g_fpga_upgrade_relink_pending = 0;
}
g_hot_relink_busy = 0;
}
@@ -125,10 +132,9 @@ void sys_upgrade(void)
result = atoi(msg);
//usleep(1000000);
if(result == 0){
log_printf(LOG_DEBUG,I_NOUSED,"run upgrade sh, and will reboot\n");
log_printf(LOG_DEBUG,I_NOUSED,"run upgrade sh (kernel/DTB -> reboot, userspace-only -> app restart)\n");
system(DO_UPGRADE_RUN);
log_printf(LOG_DEBUG,I_NOUSED,"system rebooting....\n");
// reboot in runme.sh
log_printf(LOG_DEBUG,I_NOUSED,"upgrade script started restart/reboot flow\n");
}
}
}
@@ -165,8 +171,11 @@ void* imx_up_thr(void * args)
atsc_adpc_force_stop_flag = TRUE;
usleep(2000000);
int fret = fpga_upgrade_from_file(FPGA_UP_MCS_PATH);
log_printf(LOG_DEBUG,I_NOUSED,"FPGA upgrade result=%d (0=ok, app will reset+reboot)\n", fret);
/* success path: check_fpga_upgrade_done() set fpga_up_sys_reboot=1 -> imx6 reboots */
if(fret == 0)
g_fpga_upgrade_relink_pending = 1;
log_printf(LOG_DEBUG,I_NOUSED,"FPGA upgrade result=%d (0=ok, app will hot-relink RF)\n", fret);
/* Success path uses fpga_up_sys_reboot as a legacy delayed-work flag;
* check_fpga_up_finished_reboot_sys() now performs an in-place RF relink. */
}
/* test hook: `touch /tmp/hot_restart_now` (or `echo 1 > ...` for reload_fpga) triggers
+14
View File
@@ -0,0 +1,14 @@
# 宿主机(x86)单元测试。不依赖交叉工具链、不依赖板子。
CC = gcc
CFLAGS = -Wall -Wextra -O0 -g -I../inc
LDLIBS = -lm
test_orx_power: test_orx_power.c ../bsp/orx_power.c
$(CC) $(CFLAGS) -o $@ test_orx_power.c ../bsp/orx_power.c $(LDLIBS)
.PHONY: check clean
check: test_orx_power
./test_orx_power
clean:
rm -f test_orx_power
+172
View File
@@ -0,0 +1,172 @@
/* 宿主机单元测试: orx_power 纯算法模块。
* 注意: inc/type.h 自带 uint8_t/uint16_t typedef, 本文件禁止 include <stdint.h>。 */
#include <stdio.h>
#include <math.h>
#include "type.h"
#include "orx_power.h"
static int g_fail = 0;
static void chk_f(const char *name, float got, float want, float tol)
{
if (fabsf(got - want) > tol) {
printf("FAIL %s: got %.4f want %.4f (tol %.4f)\n", name, got, want, tol);
g_fail++;
} else {
printf("ok %s: %.4f\n", name, got);
}
}
static void chk_u(const char *name, unsigned got, unsigned want)
{
if (got != want) {
printf("FAIL %s: got %u want %u\n", name, got, want);
g_fail++;
} else {
printf("ok %s: %u\n", name, got);
}
}
int main(void)
{
/* --- 合成: 真机实测工作点 cw=0x0f00, rms=992 --- */
/* 0.5*15 + 10*log10(992) = 7.5 + 29.9651 = 37.4651 */
chk_f("combine_operating_point",
orx_power_combine_db(0x0f00, 992), 37.4651f, 0.001f);
/* --- 拼接连续性: AGC 跨一步时 p_db 必须连续 ---
cw_hi 15->16 使粗档 +0.5dB, 同时增益降 0.5dB 使 rms *10^-0.05 = 992*0.89125 = 884 */
chk_f("splice_continuity",
orx_power_combine_db(0x1000, 884),
orx_power_combine_db(0x0f00, 992), 0.02f);
/* --- 单调性: 输入升高 -> p_db 升高 --- */
if (!(orx_power_combine_db(0x0f00, 1100) > orx_power_combine_db(0x0f00, 992))) {
printf("FAIL monotonic_rms\n"); g_fail++;
} else printf("ok monotonic_rms\n");
if (!(orx_power_combine_db(0x1000, 992) > orx_power_combine_db(0x0f00, 992))) {
printf("FAIL monotonic_cw\n"); g_fail++;
} else printf("ok monotonic_cw\n");
/* --- rms=0 不得产生 -inf --- */
if (!isfinite(orx_power_combine_db(0x0f00, 0))) {
printf("FAIL rms_zero_guard\n"); g_fail++;
} else printf("ok rms_zero_guard\n");
/* --- 中位数: 真机观测到的污染模式(2/5 为野值) --- */
{
uint16 s[5] = {992, 8417, 990, 991, 8414};
chk_u("median5_rejects_outliers", orx_median5(s), 992);
}
{
uint16 s2[5] = {990, 991, 992, 993, 994};
chk_u("median5_clean", orx_median5(s2), 992);
}
/* --- 滞回: 1 个百分点的抖动被吃掉, 2 个以上放行 --- */
chk_u("hyst_holds_1", orx_fssi_hysteresis(75, 76), 75);
chk_u("hyst_passes_2", orx_fssi_hysteresis(75, 77), 77);
chk_u("hyst_down_2", orx_fssi_hysteresis(75, 73), 73);
chk_u("hyst_same", orx_fssi_hysteresis(75, 75), 75);
/* --- PR13: FSSI 由 ORx gain index 线性映射 --- */
chk_u("fssi_gi_full_weak", orx_fssi_from_gain_index(255, 195, 255), 0);
chk_u("fssi_gi_full_strong", orx_fssi_from_gain_index(195, 195, 255), 100);
chk_u("fssi_gi_mid", orx_fssi_from_gain_index(225, 195, 255), 50);
chk_u("fssi_gi_quarter", orx_fssi_from_gain_index(240, 195, 255), 25);
chk_u("fssi_gi_round", orx_fssi_from_gain_index(224, 195, 255), 52);
chk_u("fssi_gi_clamp_below", orx_fssi_from_gain_index(190, 195, 255), 100);
chk_u("fssi_gi_narrow_range",orx_fssi_from_gain_index(245, 240, 250), 50);
chk_u("fssi_gi_degenerate", orx_fssi_from_gain_index(200, 220, 220), 0);
/* --- PR13: ORx 数字功率 rms -> 0.5dB 步数 --- */
chk_u("halfdb_992", (unsigned)orx_pwr_halfdb(992), 60);
chk_u("halfdb_1000",(unsigned)orx_pwr_halfdb(1000), 60);
chk_u("halfdb_884", (unsigned)orx_pwr_halfdb(884), 59);
chk_u("halfdb_1", (unsigned)orx_pwr_halfdb(1), 0);
chk_u("halfdb_0_guard", (unsigned)orx_pwr_halfdb(0), 0);
/* --- PR13: Δ(0.5dB 步数) -> FWD% --- */
chk_u("fwd_hdb_zero", orx_fwd_percent_hdb(0), 100);
chk_u("fwd_hdb_clamp_p6", orx_fwd_percent_hdb(6), 200);
chk_u("fwd_hdb_clamp_p10", orx_fwd_percent_hdb(10), 200);
chk_u("fwd_hdb_minus6", orx_fwd_percent_hdb(-6), 50);
chk_u("fwd_hdb_minus20", orx_fwd_percent_hdb(-20), 10);
chk_u("fwd_hdb_minus40", orx_fwd_percent_hdb(-40), 0);
chk_u("fwd_hdb_minus39", orx_fwd_percent_hdb(-39), 1);
/* --- PR13: 拼接连续性 ---
FAGC 涨一步(0.5dB)与 rms 相应降 10^(-0.05) 时, cw_hi+pwr_hdb 之和不变 */
chk_u("splice_continuity_hdb",
(unsigned)(1 + orx_pwr_halfdb(884)),
(unsigned)(0 + orx_pwr_halfdb(992)));
/* ================= PR16: 死区内可分辨的 FSSI ================= */
#define REF 1024u
#define GIMIN 195u
#define GIMAX 255u
/* 1) 后向兼容: rms 恰在设定点时, 必须与纯 gain index 版逐档相等。
* cw_hi -> gi = 255-cw_hi。量程 60 档, 每档 100/60 = 1.667%。 */
{
unsigned cw_hi;
int mismatch = 0;
for (cw_hi = 0; cw_hi <= 60; cw_hi++) {
unsigned a = orx_fssi_from_power((uint16)(cw_hi << 8), REF, REF, GIMIN, GIMAX);
unsigned b = orx_fssi_from_gain_index((uint8)(255 - cw_hi), GIMIN, GIMAX);
if (a != b) {
printf("FAIL compat cw_hi=%u: power=%u gain_index=%u\n", cw_hi, a, b);
mismatch++;
}
}
chk_u("pr16_compat_at_setpoint_mismatches", (unsigned)mismatch, 0);
}
/* 2) 核心诉求: gain index 冻结(cw 不变)时, rms 变化必须改变读数。
* 这正是旧算法做不到的 —— 2dB 死区内 A/B 差异会被整个吃掉。 */
{
unsigned lo = orx_fssi_from_power(0x1E00, 812, REF, GIMIN, GIMAX); /* -1.0dB */
unsigned mid= orx_fssi_from_power(0x1E00, REF, REF, GIMIN, GIMAX); /* 0.0dB */
unsigned hi = orx_fssi_from_power(0x1E00, 1290, REF, GIMIN, GIMAX); /* +1.0dB */
unsigned same = orx_fssi_from_gain_index((uint8)(255-0x1E), GIMIN, GIMAX);
printf(" pr16 deadzone sweep: lo=%u mid=%u hi=%u (old algo would be %u for all)\n",
lo, mid, hi, same);
if (!(lo < mid && mid < hi)) {
printf("FAIL pr16_deadzone_resolves: not strictly increasing\n");
g_fail++;
} else {
printf("ok pr16_deadzone_resolves\n");
}
/* 死区两端(±1dB)之间至少要拉开 1 个百分点, 否则滞回(阈值2)会把它抹平 */
if ((int)hi - (int)lo < 3) {
printf("FAIL pr16_deadzone_span: hi-lo=%d, too small vs hysteresis\n",
(int)hi - (int)lo);
g_fail++;
} else {
printf("ok pr16_deadzone_span: %d pts across the 2dB dead zone\n",
(int)hi - (int)lo);
}
}
/* 3) 单调性与钳位 */
{
unsigned c0 = orx_fssi_from_power(0x0000, REF, REF, GIMIN, GIMAX);
unsigned c1 = orx_fssi_from_power(0x3C00, REF, REF, GIMIN, GIMAX); /* cw_hi=60 */
chk_u("pr16_clamp_low", c0, 0);
chk_u("pr16_clamp_high", c1, 100);
chk_u("pr16_clamp_over", orx_fssi_from_power(0xFF00, REF, REF, GIMIN, GIMAX), 100);
chk_u("pr16_clamp_under_rms", orx_fssi_from_power(0x0000, 1, REF, GIMIN, GIMAX), 0);
}
/* 4) 退化入参不得崩 / 不得除零 */
{
chk_u("pr16_guard_gi_equal", orx_fssi_from_power(0x1E00, REF, REF, 200, 200), 0);
chk_u("pr16_guard_rms_zero",
orx_fssi_from_power(0x1E00, 0, REF, GIMIN, GIMAX) <= 100 ? 1u : 0u, 1u);
chk_u("pr16_guard_ref_zero",
orx_fssi_from_power(0x1E00, REF, 0, GIMIN, GIMAX) <= 100 ? 1u : 0u, 1u);
}
printf(g_fail ? "\n%d FAILED\n" : "\nALL PASSED\n", g_fail);
return g_fail ? 1 : 0;
}
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import csv
import datetime as dt
import subprocess
import time
from pathlib import Path
import vxi11
DOC_DIR = Path(r"\\192.168.10.100\goodman\fox_work\emv7x_pltform\atsc_smod1.0\src\atsc_app\说明_ssh访问方式")
DOC_PATH = DOC_DIR / "全频段功率归一化差值操作说明.txt"
BOARD_HOST = "192.168.1.143"
BOARD_PASS = "anywavecom"
BOARD_HOSTKEY = "ssh-rsa 2048 SHA256:qjLNx9e5kBgids1ULw3jYDq8U3ka3MfQPMAUJDCMHYY"
SA_IP = "192.168.1.147"
WAIT_STABLE_S = 5
LOW_POINTS = [58, 63, 69, 79, 85, 177, 183, 189, 195, 201, 207, 213]
U_POINTS = list(range(473, 888, 6))
POINTS = LOW_POINTS + U_POINTS
def set_tx_freq(mhz):
remote = "echo 'freq {}' > /tmp/atsc_ctrl".format(mhz)
cmd = [
"plink.exe",
"-ssh",
"root@{}".format(BOARD_HOST),
"-pw",
BOARD_PASS,
"-batch",
"-hostkey",
BOARD_HOSTKEY,
remote,
]
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
return proc.returncode, (proc.stdout or "").strip(), (proc.stderr or "").strip()
def set_sa_center(inst, mhz):
commands = [
":SENS:FREQ:CENT {}E6".format(mhz),
":SENS:FREQ:CENT {} MHz".format(mhz),
":FREQ:CENT {}MHz".format(mhz),
]
last = None
for _attempt in range(3):
for cmd in commands:
inst.write(cmd)
time.sleep(0.6)
center = float(inst.ask(":SENS:FREQ:CENT?").strip()) / 1e6
err = inst.ask(":SYST:ERR?").strip()
last = center, err
if abs(center - mhz) <= 0.001:
return center, err, True
time.sleep(0.8)
center, err = last
return center, err, False
def read_chpower(inst):
raw = inst.ask(":READ:CHPower?").strip()
return float(raw.split(",")[0].strip())
def write_outputs(stamp, rows):
csv_path = DOC_DIR / "fullband_chpower_once_{}.csv".format(stamp)
txt_path = DOC_DIR / "fullband_chpower_once_{}.txt".format(stamp)
with csv_path.open("w", encoding="utf-8-sig", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=["freq_mhz", "chpower_dbm"])
writer.writeheader()
writer.writerows(
{"freq_mhz": row["freq_mhz"], "chpower_dbm": row["chpower_dbm"]}
for row in rows
)
lines = [
"# 全频段 Channel Power 单次读取结果 ({})".format(stamp),
"",
"- 测量条件:发端设频后设置频谱仪中心频率,等待 {}s,读取一次 CHPower。".format(WAIT_STABLE_S),
"- 数据列:freq_mhz, chpower_dbm。",
"- CSV:{}".format(csv_path.name),
"",
"| freq_mhz | chpower_dbm |",
"|---:|---:|",
]
for row in rows:
lines.append("| {} | {} |".format(row["freq_mhz"], row["chpower_dbm"]))
text = "\n".join(lines) + "\n"
txt_path.write_text(text, encoding="utf-8")
with DOC_PATH.open("a", encoding="utf-8") as handle:
handle.write("\n\n")
handle.write(text)
return csv_path, txt_path
def main():
stamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S")
rows = []
csv_path = DOC_DIR / "fullband_chpower_once_{}.csv".format(stamp)
print("CSV: {}".format(csv_path), flush=True)
print("Total points: {}".format(len(POINTS)), flush=True)
inst = vxi11.Instrument(SA_IP)
inst.timeout = 15
print("SA IDN: {}".format(inst.ask("*IDN?").strip()), flush=True)
inst.write("*CLS")
for idx, mhz in enumerate(POINTS, 1):
rc, out, err = set_tx_freq(mhz)
center, sa_err, ok = set_sa_center(inst, mhz)
if rc != 0 or not ok:
print(
"[{:02d}/{}] {} MHz setup warning tx_rc={} center={:.6f} err={} out={} stderr={}".format(
idx, len(POINTS), mhz, rc, center, sa_err, out, err
),
flush=True,
)
time.sleep(WAIT_STABLE_S)
chpower = read_chpower(inst)
rows.append({"freq_mhz": mhz, "chpower_dbm": "{:.6f}".format(chpower)})
with csv_path.open("w", encoding="utf-8-sig", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=["freq_mhz", "chpower_dbm"])
writer.writeheader()
writer.writerows(rows)
print("[{:02d}/{}] {} MHz chpower={:.6f} dBm".format(idx, len(POINTS), mhz, chpower), flush=True)
csv_path, txt_path = write_outputs(stamp, rows)
print("DONE", flush=True)
print("CSV={}".format(csv_path), flush=True)
print("TXT={}".format(txt_path), flush=True)
print("DOC={}".format(DOC_PATH), flush=True)
if __name__ == "__main__":
main()
+30
View File
@@ -293,6 +293,36 @@ s8 ad9528_reg_write(u16 addr, u32 data, u8 width)
}
}
int ad9528_is_ready(void)
{
u32 chip_type = 0;
u32 part_version = 0;
u32 pll_cfg = 0;
u32 sysref_ctrl = 0;
u32 pll_status = 0;
/* Read-only probe used during Linux boot. Do not issue IO_UPDATE, reset the
* device, or touch SYSREF: a warm reboot must preserve the live RF clocks. */
if (ad9528_reg_read(0x0003, &chip_type, 1) != RET_SUCC ||
ad9528_reg_read(0x0006, &part_version, 1) != RET_SUCC ||
ad9528_reg_read(0x0200, &pll_cfg, 1) != RET_SUCC ||
ad9528_reg_read(0x0403, &sysref_ctrl, 1) != RET_SUCC ||
ad9528_reg_read(0x0508, &pll_status, 1) != RET_SUCC) {
PRINTF_ERROR("AD9528 ready check: SPI read failed\r\n");
return 0;
}
PRINTF_INFO("AD9528 ready check: type=0x%02x version=0x%02x "
"pll_cfg=0x%02x sysref=0x%02x status=0x%02x\r\n",
chip_type, part_version, pll_cfg, sysref_ctrl, pll_status);
return chip_type == 0x05 &&
part_version == 0x03 &&
pll_cfg == 0xE6 &&
sysref_ctrl == 0x91 &&
(pll_status & 0x02) != 0;
}
s8 ad9528_config(void)
{
+1
View File
@@ -16,6 +16,7 @@ typedef struct {
s8 ad9528_config(void);
int ad9528_is_ready(void);
#endif
+8 -4
View File
@@ -21,21 +21,25 @@
#include "ad9528_config.h"
int main(void)
int main(int argc, char *argv[])
{
int ret;
openSpiDev(SPIDEV_AD9528_NODE_NAME);
ret = openSpiDev(SPIDEV_AD9528_NODE_NAME);
if(ret < 0 ) {
return -1;
}
ret = initSpiDev();
if(ret < 0 ) {
return -1;
return -1;
}
if(argc == 2 && strcmp(argv[1], "--check-ready") == 0) {
return ad9528_is_ready() ? 0 : 2;
}
ret = ad9528_config();
if(ret == 0) {
PRINTF_INFO("ad9528 cfg ok.\r\n");
@@ -43,7 +47,7 @@ int main(void)
PRINTF_INFO("ERROR:ad9528 cfg fail.\r\n");
}
return 0;
return ret == 0 ? 0 : 1;
}
+78 -40
View File
@@ -5,68 +5,107 @@
***************************************/
#include "common.h"
#include <dirent.h>
#include <limits.h>
//程序名字
#define PROGRAM_NAME "imx_atsc_app"
#define NODE_NAME "http"
//查找进程中程序名字
#define PROGRAM_NAME_FIND "imx_atsc_app"
#define PROGRAM_NODE_NAME_FIND "http"
//输出目录
#define PROGRAM_DIR_OUT_FILE "/tmp/atsc_app_status"
#define NODE_PROGRAM_DIR_OUT_FILE "/tmp/web_p_status"
//要运行的程序
//#define RUN_NAME_PROGRAM "/aw/anywave/app/imx_atsc_app 1 &"
#define RUN_NAME_PROGRAM "/aw/anywave/app/imx_atsc_app 1 2>&1 | loglog /var/module_log/ imx_atsc_app.log 1 &"
#define PROGRAM_EXE_PATH "/aw/anywave/app/imx_atsc_app"
#define RUN_NAME_PROGRAM PROGRAM_EXE_PATH " 2>&1 | /usr/sbin/loglog /var/module_log/ imx_atsc_app.log 1 &"
#define RUN_NODE_NAME_PROGRAM "cd /aw/anywave/web/sh && ./start_web.sh &"
/* The application renames its main task with pthread_setname_np(), therefore
* BusyBox `ps | grep imx_atsc_app` can report it as missing and the daemon may
* start a second instance. /proc/<pid>/exe remains stable across task renames
* and also lets us match the exact executable instead of a substring. */
static int is_app_running(void)
{
DIR *proc_dir;
struct dirent *entry;
char exe_link[64];
char exe_path[PATH_MAX];
ssize_t exe_len;
size_t expected_len = strlen(PROGRAM_EXE_PATH);
int main(int argc,char *argv[])
proc_dir = opendir("/proc");
if(proc_dir == NULL)
{
/* Fail safe: an unreadable /proc must not cause duplicate app starts. */
return -1;
}
while((entry = readdir(proc_dir)) != NULL)
{
const char *p = entry->d_name;
if(*p == '\0')
{
continue;
}
while(*p >= '0' && *p <= '9')
{
p++;
}
if(*p != '\0')
{
continue;
}
snprintf(exe_link, sizeof(exe_link), "/proc/%s/exe", entry->d_name);
exe_len = readlink(exe_link, exe_path, sizeof(exe_path) - 1);
if(exe_len < 0)
{
continue;
}
exe_path[exe_len] = '\0';
if(strcmp(exe_path, PROGRAM_EXE_PATH) == 0 ||
(strncmp(exe_path, PROGRAM_EXE_PATH, expected_len) == 0 &&
strcmp(exe_path + expected_len, " (deleted)") == 0))
{
closedir(proc_dir);
return 1;
}
}
closedir(proc_dir);
return 0;
}
int main(void)
{
int fd = 0;
int ret = 0;
int remux_ok = 0;
int node_ok = 0;
char buf[100];
char cmd[100] = {0};
char cmd[256] = {0};
//开启守护进程
daemon(0,0);
while (1)
{
//新建输出文件
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "echo 0 > %s", PROGRAM_DIR_OUT_FILE);
ret = os_system(cmd);
//获得程序ID
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "ps | grep %s | grep -v grep >> %s", PROGRAM_NAME_FIND, PROGRAM_DIR_OUT_FILE);
ret = os_system(cmd);
//打开输出文件
fd = open(PROGRAM_DIR_OUT_FILE, O_CREAT|O_RDONLY, 0777);
memset(buf, 0, 100);
read(fd, buf, 100);
close(fd);
remux_ok = match(buf, PROGRAM_NAME, 90);
remux_ok = is_app_running();
// web
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "echo 0 > %s", NODE_PROGRAM_DIR_OUT_FILE);
ret = os_system(cmd);
(void)os_system(cmd);
//获得程序ID
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "ps | grep %s | grep -v grep >> %s", PROGRAM_NODE_NAME_FIND, NODE_PROGRAM_DIR_OUT_FILE);
ret = os_system(cmd);
(void)os_system(cmd);
//打开输出文件
fd = open(NODE_PROGRAM_DIR_OUT_FILE, O_CREAT|O_RDONLY, 0777);
@@ -80,14 +119,17 @@ int main(int argc,char *argv[])
//判断是否有程序文件运行
if(1 != remux_ok)
{
//运行程序
/* Debounce once before starting. This prevents a duplicate launch
* when the app is between exec/startup states during an upgrade. */
usleep(1000000);
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "%s", RUN_NAME_PROGRAM);
printf("imx_atsc_app stop , daemon work.\n%s\n\n", cmd);
system(cmd);
usleep(100000);
if(!is_app_running())
{
memset(cmd, 0 ,sizeof(cmd));
snprintf(cmd, sizeof(cmd), "%s", RUN_NAME_PROGRAM);
printf("imx_atsc_app stop, daemon restart without legacy argument.\n%s\n\n", cmd);
system(cmd);
usleep(100000);
}
}
if(1 != node_ok)
{
@@ -102,13 +144,9 @@ int main(int argc,char *argv[])
//删除输出文件
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "rm %s", PROGRAM_DIR_OUT_FILE);
ret = os_system(cmd);
memset(cmd, 0 ,sizeof(cmd));
sprintf(cmd, "rm %s", NODE_PROGRAM_DIR_OUT_FILE);
ret = os_system(cmd);
(void)os_system(cmd);
}
@@ -0,0 +1,37 @@
# 2026-07-29 Web Dashboard / Spectrum 修正记录
涉及目录:`www/`(主 web 站点)。`www_korea/`、`www_nlogo/` 未包含 Spectrum 页面,本次未修改。
## 修改内容
1. Dashboard 的 IMD 卡片已直接在各页面静态 HTML 中调整为 **LIMD 在前、UIMD 在后**;不再依赖运行时 DOM 重排,兼容旧浏览器。
2. Dashboard 的 SNR 去除了错误的固定 `+10 dB` 显示偏移;状态阈值同步改为真实 SNR/MER 的 `27 / 20 dB`。
3. Spectrum 页面复用 Dashboard 已获取的状态接口字段 `a_fssi` / `b_fssi`,在频谱标签中显示 FSSI;不新增轮询请求。
4. 恢复 Spectrum 的 Shoulder、LIMD、UIMD、Pilot、Peak、RBW、Packets、Contiguous、`> 1 field` 信息,以及 Eye Diagram 的 SNR/MER、眼高、眼宽、τ0、段同步、载波偏移、SNR raw、ORx AGC jitter;LIMD/UIMD 以不可拆分的组合卡片同排显示。缺失或非数值字段显示 `--`,不影响其他指标刷新。
5. Spectrum 与 Eye Diagram 改为标签页:默认选择 Spectrum;切换标签时仅显示并轮询当前标签对应的图形。
6. 修正标题读取:Spectrum 从 `sys_cfg` 返回的标题字段(索引 6)读取,避免显示 `0`;Login 页面改为设置 `document.title`,并保留 `Digital Exciter` 作为空标题回退。
7. Spectrum / Eye Diagram 标签页改用完整名称 `ORx Receive Spectrum`、`8VSB Eye Diagram`,并删除内容区重复标题。
8. 恢复 Spectrum 工具栏:刷新周期(0.5 / 1 / 2 / 5 秒)、Pause、Peak hold;暂停和刷新周期对当前选中的 Spectrum 或 Eye Diagram 标签页生效。
9. 压缩 Spectrum 指标卡片尺寸并禁用换行,使 FSSI、频谱指标及 LIMD/UIMD 组合保持同一行;卡片最小内容宽度调整为 120 px,以在常用页面宽度下接近充满整行;窄窗口通过横向滚动完整查看。
10. 移除 Dashboard 与 LCD 中 LIMD/UIMD 的历史固定 `+11 dB` 显示偏移,使其与 Spectrum 原始测量值一致;保留 `sb/sc` 校准量。Dashboard 的 LIMD/UIMD 正常状态阈值由 47 dB 同步调整为 36 dB。
11. FPGA `.mcs` 上传和写 Flash 进度轮询期间暂停 Settings 页的前端 20 分钟自动登出;在上传异常、升级完成、升级失败或确认重载后恢复原超时与常规状态刷新。
## 后端核对
`atsc_app/task/iweb_task.c` 已在 `State_SysState` 返回中输出 FSSI A/B 字段,`atsc_app/task/http_get.c` 已从 `st_extend_state` 赋值。本次无需修改 `atsc_app` 接口或字段顺序。
## 验证建议
1. 登录后确认所有 Dashboard 页面均为 `SNR, LIMD, UIMD` 顺序,且 SNR 与 Status 页一致。
2. 打开 Spectrum,确认默认显示频谱和 FSSI;点击 Eye Diagram 后只显示眼图和 SNR/MER。
3. 将网页标题配置为空与非空各验证一次,确认 Spectrum/Login 不再显示 `0` 或空标题。
12. FPGA consecutive-upgrade status race fixed: publish PENDING 0 0 when the web command is accepted, and start polling only after the upload CGI returns. Completion text now states FPGA reload/RF reconnect instead of IMX reboot.
13. FPGA upgrade publishes RELINK after Flash/IPROG and READY only after hot_reinit_rf() completes. The page redirects to login.html with a five-second countdown only on READY; transient CGI errors keep polling.
14. Login title fix: atsc_status_cfg.cgi is session-protected and returned unauthorized before login. Added login.cgi?action=get_title as a public read-only title lookup and updated login.html to use it.
15. Restored the historical fixed +11 dB display offset for LIMD/UIMD in the shared Dashboard header and LCD, and restored the matching 47 dB Dashboard normal threshold. Status already used this same display conversion. Spectrum now labels its unshifted values LIMD RAW / UIMD RAW.
16. When FSSI Refresh (A/B switching) is ON, Spectrum and Eye Diagram polling and drawing are suspended and both panels show an explicit unavailable notice. Polling resumes automatically when FSSI Refresh is OFF.
17. FSSI Refresh now controls backend IQ collection as well: update_fssi_state() detects only OFF/ON edges, calls iq_spec_set_enable(0) while A/B Refresh is ON, and resumes clean collection with iq_spec_set_enable(1) when it is OFF. The edge handling precedes FSSI's DPD-busy early returns, so web and LCD changes are covered.
18. Corrected login.cgi?action=get_title HTTP headers: C source now emits real CRLF separators instead of literal backslash text. Rebuild and deploy login.cgi for the configured login title to reach the browser.
19. 新增 overview.html 作为登录后的默认落地页;登录 CGI 成功重定向、认证代理默认页及已有会话访问登录页时,统一进入 Overview,并将 Overview 加入受保护页面列表。
+4 -3
View File
@@ -18,6 +18,7 @@
/* 受保护的 HTML 页面列表 */
static const char *protected_pages[] = {
"overview.html",
"status_info.html",
"sys_set.html",
"sys_rf.html",
@@ -43,7 +44,7 @@ static const char* get_requested_page(const char *query_string) {
static char page_name[256] = {0};
if (!query_string || strlen(query_string) == 0) {
return "status_info.html"; /* 默认页面 */
return "overview.html"; /* 默认页面 */
}
/* 解析 query string 中的 page=xxx 参数 */
@@ -57,7 +58,7 @@ static const char* get_requested_page(const char *query_string) {
return page_name;
}
return "status_info.html";
return "overview.html";
}
/* 发送重定向响应 */
@@ -130,7 +131,7 @@ int main(int argc, char **argv) {
if (cookie && extract_cookie(cookie, SESSION_COOKIE_NAME,
session_id, sizeof(session_id))) {
if (validate_session(session_id, NULL, 0)) {
send_redirect("status_info.html");
send_redirect("overview.html");
}
}
/* 否则返回登录页 */
+34 -1
View File
@@ -51,6 +51,29 @@ int simplified_create_client_socket(int port)
return sockfd;
}
/* Read an application value for a public login-page endpoint. */
static int simplified_get_data_from_process(int modulePort, int moduleKey, char *buf, size_t bufLen)
{
cgi_process_elem_t elem;
int fd;
if(!buf || bufLen == 0 || modulePort <= START_PROCESS_MODULE || modulePort >= END_PROCESS_MODULE)
return -1;
memset(&elem, 0, sizeof(elem));
elem.method = GET_METHOD;
elem.keyType = moduleKey;
fd = simplified_create_client_socket(modulePort);
if(fd < 0)
return -1;
if(write(fd, &elem, sizeof(elem)) != sizeof(elem) || read(fd, &elem, sizeof(elem)) != sizeof(elem)){
close(fd);
return -1;
}
close(fd);
snprintf(buf, bufLen, "%s", elem.data);
return 0;
}
/* Define simplified version of set_data_from_process to avoid linking issues */
void simplified_set_data_from_process(int modulePort, int moduleKey, char *buf) {
if(modulePort <= START_PROCESS_MODULE || modulePort >= END_PROCESS_MODULE)
@@ -398,7 +421,7 @@ int main(int argc, char **argv) {
sizeof(cookie_header), 0);
printf("Content-type: text/html\r\n");
printf("%s\r\n", cookie_header);
printf("Location: status_info.html\r\n\r\n");
printf("Location: overview.html\r\n\r\n");
exit(0);
}
}
@@ -412,6 +435,16 @@ int main(int argc, char **argv) {
/* === Handle GET request - get current username === */
if (method && strcasecmp(method, "GET") == 0 && query) {
/* Public read-only title used before a session exists on login.html. */
if (strstr(query, "action=get_title")) {
char title[MSG_DATA_LEN] = {0};
printf("Content-type: text/plain\r\nCache-Control: no-store\r\n\r\n");
if(simplified_get_data_from_process(WEB_SOCKET_PORT, CGIKEY_WEB_TITLE,
title, sizeof(title)) == 0)
printf("%s", title);
exit(0);
}
/* Check for get_user action */
if (strstr(query, "action=get_user")) {
char *cookie = getenv("HTTP_COOKIE");
+1 -1
View File
@@ -19,7 +19,7 @@
#include <fcntl.h>
#include <stdarg.h>
#define USER_WEB_INDEX_HTML "status_info.html"
#define USER_WEB_INDEX_HTML "overview.html"
#endif
+631 -77
View File
@@ -7,7 +7,7 @@
--accent-green-bright: #4ade80;
--accent-green-dim: #16a34a;
/* Error / alert */
--status-err: #ef4444;
--status-err: #ff5252;
--status-err-dim: #dc2626;
/* Warning / amber */
--status-warn: #f59e0b;
@@ -23,15 +23,54 @@
/* Text */
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-muted: #9ca3af;
--text-muted: #b0b8c4;
--plp-pill-bg: #3a4452;
--chart-inset: #2a313c;
/* Informational text/labels (status notes, progress labels). Light value set in .light-theme. */
--info-text: var(--status-neutral);
/* Mode identity colours (overview Signal Chain page only). Light values set in .light-theme.
Gateway intentionally reuses --accent-green / --accent-green-bright (brand). */
--mode-exciter: #60a5fa; --mode-exciter-hover: #93bbfd;
--mode-translator: #fb923c; --mode-translator-hover: #fdba74;
--mode-standalone: #a78bfa; --mode-standalone-hover: #c4b5fd;
--mode-exciter-tint: rgba(96,165,250,0.10);
--mode-translator-tint: rgba(251,146,60,0.10);
--mode-standalone-tint: rgba(167,139,250,0.10);
--mode-gateway-tint: rgba(34,197,94,0.10);
/* Stronger (hover) variants of the mode tints. */
--mode-exciter-tint-strong: rgba(96,165,250,0.22);
--mode-translator-tint-strong: rgba(251,146,60,0.22);
--mode-standalone-tint-strong: rgba(167,139,250,0.22);
--mode-gateway-tint-strong: rgba(34,197,94,0.22);
/* Borders */
--border-subtle: #4b5563;
--border-accent: var(--accent-green);
/* Sidebar */
/* Sidebar (DARK ISLAND — sidebar stays dark navy in BOTH themes, so these
tokens are NOT overridden in .light-theme; see the .light-theme block). */
--sidebar-bg: #0f172a;
--sidebar-text: #94a3b8;
--sidebar-text-active: #ffffff;
--sidebar-hover: #1e293b;
/* Active-item accent. Lives as its own token (not --accent-green) because the
sidebar is a dark island: this MUST stay bright green in light theme, whereas
--accent-green darkens. Same value as --accent-green in dark theme. */
--sidebar-accent: #22c55e;
/* Primary-action button family (theme-aware). Dark values = today's look,
so dark-only products are visually unchanged. Light values softened
(entry 011) so buttons sit in harmony on the light page, not as heavy
dark blocks. Status dots keep --accent-green and stay vivid. */
--btn-green-bg: var(--accent-green-dim);
--btn-green-bg-hover: var(--accent-green);
--btn-green-text: #06230f;
--btn-green-text-hover: #06230f;
--btn-neutral-bg: #4b5563;
--btn-neutral-text: #ffffff;
/* Sidebar footer/labels: PINNED readable colours (sidebar is a dark island,
so these must NOT follow the theme tokens, which flip dark in light mode). */
--sidebar-footer-h: #cbd5e1;
--sidebar-footer-p: #94a3b8;
--sidebar-footer-link: #7fb3d5;
--sidebar-label: #7c8595;
/* Legacy / UI */
--primary: var(--accent-green);
--primary-hover: var(--accent-green-dim);
@@ -41,6 +80,31 @@
--sidebar-width: 220px;
--header-height: 64px;
--dashboard-height: auto;
/* ===== PLOT PALETTE (family, added 2026-08-11) ==============================
Colours for the spectrum + eye canvases on the Measurements page. Present as
DORMANT family tokens: only smod renders a Measurements page today, but the
page is planned for other products, so the tokens live in the shared sheet
rather than being hand-copied later (the exact mistake that motivated this
whole stylesheet consolidation). Values are the instrument-plot palette; four
of them already equal existing family tokens (trace=--status-neutral,
hold=--status-warn, band=neutral@10%, eye-level near neutral) and are kept as
separate names so a family colour change cannot silently restyle a scope plot.
The plot field stays dark in BOTH themes on purpose: the eye is a density map
whose floor must read as "no signal", which a dark field gives and a light one
does not. Not overridden in .light-theme, deliberately. =================== */
--plot-bg: #0b1220;
--plot-grid: #1e293b;
--plot-grid-zero: #334155;
--plot-axis-label: #94a3b8;
--plot-axis-title: #64748b;
--plot-trace: #38bdf8;
--plot-trace-hold: #f59e0b;
--plot-trace-error: #eab308;
--plot-marker: #f87171;
--plot-band: rgba(56, 189, 248, 0.10);
--plot-eye-level: rgba(57, 208, 255, 0.45);
--plot-eye-decision: #a3e635;
}
* {
@@ -165,20 +229,41 @@ body {
.app-sidebar li.active a {
background: transparent;
color: var(--sidebar-text-active);
border-left: 3px solid var(--accent-green);
border-left: 3px solid var(--sidebar-accent);
font-weight: 600;
}
/* --- Sidebar group labels (Operations / Configuration / Admin) ---
Added for ATSC 3.0 pages. Scoped by the .app-sidebar ancestor plus
the .ov-group-label class, so these rules only affect <li> elements
that opt in with class="ov-group-label". No existing pages in other
products (7x, 2D Marble, ATSC 1.0) use this class, so adding these
rules has zero visual or behavioral impact on them.
The existing .app-sidebar li a and .app-sidebar li.active a rules
require an <a> child; these label items have no <a>, so no collision. */
.app-sidebar li.ov-group-label {
padding: 16px 16px 4px 16px;
font-size: 9px;
font-weight: 700;
letter-spacing: 1.6px;
text-transform: uppercase;
color: var(--sidebar-label);
border-left: 3px solid transparent;
pointer-events: none;
user-select: none;
}
.app-sidebar li.ov-group-label:first-child { padding-top: 6px; }
.sidebar-footer {
padding: 16px;
color: var(--text-muted);
color: var(--sidebar-footer-p);
font-size: 12px;
line-height: 1.5;
border-top: 1px solid var(--border-subtle);
}
.sidebar-footer h4 {
color: var(--text-secondary);
color: var(--sidebar-footer-h);
margin-bottom: 8px;
font-size: 13px;
font-weight: 600;
@@ -192,11 +277,11 @@ body {
.sidebar-footer .redbox-label {
font-weight: 600;
color: var(--text-secondary);
color: var(--sidebar-footer-h);
}
.sidebar-footer a {
color: var(--status-neutral);
color: var(--sidebar-footer-link);
font-weight: 500;
}
@@ -537,8 +622,8 @@ body {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
background-color: var(--accent-green);
color: #fff;
background-color: var(--btn-green-bg);
color: var(--btn-green-text-hover);
padding: 4px 10px;
border-radius: 6px;
margin-left: auto;
@@ -547,7 +632,9 @@ body {
}
.box-header.collapsed .toggle-text {
background-color: var(--text-muted);
background-color: var(--btn-neutral-bg);
color: var(--btn-neutral-text);
border: 1px solid var(--border-subtle);
}
/* Accordion Specific Settings */
@@ -773,8 +860,8 @@ select.display-only-select:disabled.state-ok {
/* Buttons */
input[type="button"].btn-modern,
input[type="submit"].btn-modern {
background-color: var(--accent-green-dim);
color: var(--text-primary);
background-color: var(--btn-green-bg);
color: var(--btn-green-text);
font-size: 13px;
font-weight: 600;
padding: 8px 18px;
@@ -788,14 +875,14 @@ input[type="submit"].btn-modern {
input[type="button"].btn-modern:hover,
input[type="submit"].btn-modern:hover {
background-color: var(--accent-green);
border-color: var(--accent-green);
color: #fff;
background-color: var(--btn-green-bg-hover);
border-color: var(--btn-green-bg-hover);
color: var(--btn-green-text-hover);
}
input[type="button"].btn-modern:active,
input[type="submit"].btn-modern:active {
background-color: var(--accent-green-dim);
background-color: var(--btn-green-bg);
}
input[type="button"].btn-modern:disabled,
@@ -809,12 +896,26 @@ input[type="submit"].btn-modern:disabled {
/* Unsaved reminder */
input[type="button"].btn-modern.is-unsaved:not(:disabled),
input[type="submit"].btn-modern.is-unsaved:not(:disabled) {
background-color: var(--accent-green) !important;
border-color: var(--accent-green) !important;
color: #fff !important;
background-color: var(--btn-green-bg) !important;
border-color: var(--btn-green-bg-hover) !important;
color: var(--btn-green-text-hover) !important;
box-shadow: 0 0 10px rgba(34, 197, 94, 0.35);
}
/* Cancel / secondary modal button — neutral, theme-aware (replaces hardcoded #64748b). */
input[type="button"].btn-modern.btn-cancel,
input[type="submit"].btn-modern.btn-cancel {
background-color: var(--btn-neutral-bg);
color: var(--btn-neutral-text);
border-color: var(--border-subtle);
}
input[type="button"].btn-modern.btn-cancel:hover,
input[type="submit"].btn-modern.btn-cancel:hover {
background-color: var(--btn-neutral-bg);
border-color: var(--text-muted);
color: var(--btn-neutral-text);
}
fieldset {
border: none;
display: flex;
@@ -1073,9 +1174,9 @@ BUTTON VIEW DETAILS
}
.btn-view-details:hover {
background-color: var(--accent-green);
border-color: var(--accent-green);
color: #fff;
background-color: var(--btn-green-bg-hover);
border-color: var(--btn-green-bg-hover);
color: var(--btn-green-text-hover);
}
/* ==========================================================================
@@ -1372,12 +1473,12 @@ BUTTON VIEW DETAILS
/* PLP row layout — same style as subframe-header: label | bar | bitrate | pct */
.plp-row {
display: grid;
grid-template-columns: 42px 1fr auto auto;
gap: 2px 10px;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid rgba(75, 85, 99, 0.4);
display: grid;
grid-template-columns: 56px 1fr auto auto auto;
gap: 2px 10px;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid rgba(75, 85, 99, 0.4);
}
.plp-row:last-of-type {
@@ -1395,7 +1496,7 @@ BUTTON VIEW DETAILS
text-align: center;
padding: 4px 0;
border-radius: 4px;
background: rgba(75, 85, 99, 0.3);
background: var(--plp-pill-bg);
}
/* Bar track (background) */
@@ -1465,20 +1566,13 @@ BUTTON VIEW DETAILS
.plp-pct-text.pct-red { color: var(--status-err); }
.plp-pct-text.pct-muted { color: var(--text-muted); }
/* Detail line (services + FULL indicator, below bar) */
.plp-detail {
grid-column: 2 / -1;
grid-row: 2;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
/* FULL indicator — beside pct text in the same row */
.plp-full-indicator {
font-size: 11px;
font-weight: 700;
color: var(--status-err);
grid-column: 5;
grid-row: 1;
font-size: 11px;
font-weight: 700;
color: var(--status-err);
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.35);
padding: 1px 7px;
@@ -1488,32 +1582,53 @@ BUTTON VIEW DETAILS
}
.plp-full-indicator.visible {
display: inline-block;
animation: alarmPulse 1.5s ease-in-out infinite;
display: inline-block;
animation: alarmPulse 1.5s ease-in-out infinite;
}
.plp-services {
font-size: 13px;
font-weight: 600;
color: var(--status-neutral);
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* PLP trend sparkline container — inline chart below each utilization bar.
JS renderer writes SVG into this container on each 2-sec poll.
Placed in grid row 2 (row 1 = bar + pct + FULL indicator) spanning the bar columns.
Services displayed as left sidebar panel inside the SVG when present. */
.plp-trend-spark {
grid-column: 2 / -1;
grid-row: 2;
margin-top: 4px;
overflow: visible;
}
.plp-services:empty {
display: none;
/* Hide trend container when PLP row is hidden */
.plp-row[style*="display: none"] .plp-trend-spark,
.plp-row[style*="display:none"] .plp-trend-spark {
display: none;
}
.plp-services::before {
content: '[';
color: var(--border-subtle);
/* Sparkline SVG element — fills container width.
ViewBox 700×120 with preserveAspectRatio="none" — stretches to match bar width.
When services exist, left margin widens (ML += svcBandW) to accommodate sidebar panel. */
.plp-trend-svg {
display: block;
width: 100%;
/* height set dynamically by JS via inline style — matches viewBox ratio */
}
.plp-services::after {
content: ']';
color: var(--border-subtle);
/* Max/min envelope band — semi-transparent green fill (set inline by JS). */
.plp-trend-svg polygon {
stroke: none;
}
/* Subframe header trend — matches bar height */
.subframe-header .plp-trend-spark {
grid-column: 2 / -1;
grid-row: 2;
margin-top: 4px;
}
/* Global header trend — extra tall for emphasis */
.global-header .plp-trend-spark {
grid-column: 2 / -1;
grid-row: 2;
margin-top: 4px;
}
/* Subframe header bar (total payload at top) */
@@ -1589,22 +1704,16 @@ BUTTON VIEW DETAILS
/* Responsive adjustments for utilization bars */
@media (max-width: 767px) {
.plp-row {
grid-template-columns: 36px 1fr auto;
}
.plp-row {
grid-template-columns: 48px 1fr auto auto;
}
.plp-row .plp-bitrate-text {
font-size: 11px;
}
.plp-pct-text {
display: none;
}
.plp-detail {
flex-wrap: wrap;
}
.plp-services {
max-width: none;
}
.subframe-header {
.subframe-header {
grid-template-columns: 48px 1fr;
}
.subframe-header .plp-bitrate-text {
@@ -1615,12 +1724,12 @@ BUTTON VIEW DETAILS
.global-header {
grid-template-columns: 48px 1fr;
}
.global-header .plp-bitrate-text {
grid-column: 1 / -1;
text-align: center;
margin-top: 4px;
}
}
.global-header .plp-bitrate-text {
grid-column: 1 / -1;
text-align: center;
margin-top: 4px;
}
}
/* ==========================================================================
SESSION TIMEOUT WARNING
@@ -1694,3 +1803,448 @@ SESSION TIMEOUT WARNING
.session-timeout-actions .btn-modern {
min-width: 120px;
}
/* =============================================================================
Sprint 1 — dashboard bar mode-adaptive visibility + group separators
Additive only. No existing rule, selector, or variable is modified.
Shared with 2D and exciter UIs — these classes are new and will only apply
where the new data-tile attribute is present on summary-card elements.
============================================================================= */
/* Marker class for tile-hidden state (reserved; current implementation uses
inline style="display:none" set by applyBarTileVisibility for idempotency.
Class kept for future use: allow CSS-only hiding via toggled class if we
move to a transition/animation approach later.) */
.summary-card[data-tile].tile-hidden { display: none; }
/* Group-separator spacing for the bar.
The bar is a flex row; group separation is a slightly larger left margin
on the first tile of each group (applied via a data-group-first attribute
that pages set on select tiles).
Purely additive. Tiles without data-group-first are unaffected. */
.summary-card[data-group-first="input"] { margin-left: 8px; }
.summary-card[data-group-first="output"] { margin-left: 8px; }
.summary-card[data-group-first="housekeeping"] { margin-left: 8px; }
/* =============================================================================
Sprint 1 Increment 6 — Live Trends panel (Overview page only)
Additive only. No existing rule, selector, or variable is modified.
These classes are new (ov-trend-*); they only apply to the new panel
container on Overview and do not affect the shared 2D / exciter UI.
============================================================================= */
/* Panel container — visual weight matches the existing ov-chain-card. */
.ov-trend-panel {
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
border-radius: 8px;
padding: 16px 20px;
margin: 16px 0;
}
.ov-trend-panel:empty { display: none; }
/* Header row — small caps, dot, title, buffer-age label. */
.ov-trend-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
letter-spacing: 0.08em;
color: var(--text-muted);
margin-bottom: 12px;
text-transform: uppercase;
}
.ov-trend-header .ov-trend-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-ok, #22c55e);
box-shadow: 0 0 4px rgba(34, 197, 94, 0.5);
}
.ov-trend-header .ov-trend-title {
color: var(--text-primary);
font-weight: 600;
}
.ov-trend-header .ov-trend-sep {
opacity: 0.5;
}
.ov-trend-header .ov-trend-age {
color: var(--text-muted);
}
/* Grid of per-metric cards. Uses flex so cards auto-size to available
width, wrapping to a new row only when needed. */
.ov-trend-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ov-trend-card {
flex: 1 1 200px;
min-width: 180px;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: 6px;
padding: 10px 12px;
}
.ov-trend-label {
font-size: 10px;
letter-spacing: 0.08em;
color: var(--text-muted);
margin-bottom: 4px;
}
.ov-trend-value {
display: flex;
align-items: baseline;
gap: 4px;
margin-bottom: 8px;
white-space: nowrap;
}
.ov-trend-num {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
}
.ov-trend-unit {
font-size: 10px;
color: var(--text-muted);
/* No text-transform: units carry meaningful case (Mbps / dB / dBm /
dBc / MHz / %). Matches the signal chain's .ov-blk-primary .ov-u
convention; avoids collapsing dB / dBm / dBc distinctions into the
same upper-cased suffix. */
letter-spacing: 0.04em;
}
/* The sparkline itself. Height fixed; width fills the card. */
.ov-trend-spark {
display: block;
width: 100%;
height: 40px;
stroke: var(--status-ok, #22c55e);
stroke-width: 1.5;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
/* N/A overlay wrapper -- anchors the overlay text above the sparkline.
Positioned so overlay sits centered on the sparkline area. */
.ov-trend-spark-wrap {
position: relative;
}
/* Shown on FSSI-affected charts (SNR, FWD%, UIMD, LIMD) when FSSI is
active. Per v0.4 B4: overlay reads "N/A -- FSSI active" and line
drawing is suppressed (handled by extractors returning null).
pointer-events: none so the overlay never intercepts clicks. */
.ov-trend-na-overlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
letter-spacing: 0.04em;
color: var(--text-muted, #8a8a8a);
background: rgba(0, 0, 0, 0.04);
pointer-events: none;
text-transform: none;
}
/* When FSSI-active, tone down the card's current-value number. */
.ov-trend-card-na .ov-trend-num-na {
color: var(--text-muted, #8a8a8a);
font-weight: 500;
}
/* Min/max labels overlaid on the sparkline's right edge. Show the raw
data range (not the minSpan-floored display range) so operators can
always read the actual values — important when minSpan stretches a
tiny real variation into a taller chart. pointer-events: none so the
labels never intercept interaction with the svg. */
.ov-trend-spark-labels {
position: absolute;
top: 0;
right: 2px;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
pointer-events: none;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 9px;
line-height: 1;
color: var(--text-secondary);
}
.ov-trend-label-max,
.ov-trend-label-min {
/* Units are not repeated on these labels — the card's main value
already shows the unit, and repeating it here adds clutter. */
padding: 1px 2px;
background: var(--bg-main);
border-radius: 2px;
}
/* ==========================================================================
CONTEXTUAL WARNING NOTES (added 2026-05-31)
Shared classes for the amber "this feature is gated / mode-restricted"
messages that were previously hand-written inline on each page.
Two variants:
.warning-banner — block callout (tinted box) above a section
.field-note — small inline note appended after a field/label
IMPORTANT: these classes intentionally do NOT set `display`. Visibility is
toggled by page JS via `element.style.display = ''` (show) / 'none' (hide),
which relies on the element's own inline `display:none` default. Adding
`display` here would break that toggling (a cleared inline value would fall
through to this rule). Keep `style="display:none"` on the element.
========================================================================== */
.warning-banner {
padding: 8px 14px;
margin-bottom: 8px;
font-size: 12px;
color: var(--status-warn);
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.2);
border-radius: 4px;
}
.field-note {
font-size: 11px;
color: var(--status-warn);
margin-left: 8px;
}
/* Error state for .warning-banner — red instead of amber (e.g. expired cert).
Toggle `is-error` in JS instead of setting inline colors. */
.warning-banner.is-error {
color: var(--status-err);
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.2);
}
/* ==========================================================================
SECONDARY (CANCEL) BUTTON (added 2026-05-31)
Neutral-grey companion to .btn-modern, for Cancel / dismiss actions (e.g. the
account-settings modal). Use: class="btn-modern btn-secondary"
It only overrides the colour of .btn-modern; size/shape/hover come from
.btn-modern. Theme-aware via --bg-card-hover.
========================================================================== */
input[type="button"].btn-secondary,
input[type="submit"].btn-secondary {
background-color: var(--bg-card-hover);
}
input[type="button"].btn-secondary:hover,
input[type="submit"].btn-secondary:hover {
background-color: var(--border-subtle);
border-color: var(--border-subtle);
}
/* ==========================================================================
PROGRESS BARS (added 2026-05-31)
Restart / reset / firmware-update progress. The bar fill is styled via the
browser pseudo-elements (the plain `color` property does NOT colour a
<progress> fill in Chromium/WebKit). Fill colour matches the info-text notes
shown beside each bar. Theme-aware via --info-text.
========================================================================== */
progress.aw-progress {
width: 100%;
height: 24px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: 4px;
overflow: hidden;
background: var(--bg-card);
}
progress.aw-progress::-webkit-progress-bar { background: var(--bg-card); border-radius: 4px; }
progress.aw-progress::-webkit-progress-value { background: var(--info-text); border-radius: 4px; }
progress.aw-progress::-moz-progress-bar { background: var(--info-text); }
/* ============================================================================
LIGHT THEME (entry 010 — additive, owner-approved palette "Concept C balanced")
----------------------------------------------------------------------------
Activated by adding class "light-theme" to the root <html> element. Default
(no class) = dark theme above. This block ONLY overrides token VALUES; it adds
no new selectors that affect dark mode and changes nothing in :root.
Design intent (owner-approved, comfort-contrast — AA floor, NOT maximum):
- Page is soft grey (#e8ebf0), NOT white — reduces glare/eye-fatigue.
- Text is dark slate (#2a3340), NOT pure black — softer on the eyes.
- Status colours are muted, not neon. All measured AA on their real surface.
DARK ISLAND: the sidebar stays dark navy in BOTH themes, so --sidebar-bg/-text/
-text-active/-hover and --sidebar-accent are deliberately NOT overridden here —
they inherit their :root (dark) values. --sidebar-accent stays bright green.
CSS owns colour; JS owns identity (entry 008) — so the mode-* overrides below
recolour the overview wizard/chips automatically with ZERO JS change.
============================================================================ */
:root.light-theme {
/* Green - primary accent / status good / active */
--accent-green: #0f7034;
--accent-green-bright: #126e30;
--accent-green-dim: #0c5a2a;
/* Error / alert */
--status-err: #b5302a;
--status-err-dim: #8f231e; /* darker companion to --status-err */
/* Warning / amber */
--status-warn: #8f4a08;
--status-warn-dim: #7c4106; /* darker companion to --status-warn */
/* Neutral / info */
--status-neutral: #0a5f8f;
--status-neutral-dim: #084b70; /* darker companion to --status-neutral */
/* Light palette surfaces */
--bg-main: #dde2e9; /* soft grey page, NOT white */
--bg-panel: #eaeef3; /* card surface, gentle off-white */
--bg-card: #d8dee6; /* inset/chip surface */
--bg-card-hover: #cbd2dc;
/* Text */
--text-primary: #2a3340; /* dark slate, NOT pure black */
--text-secondary: #3a4452;
--text-muted: #525b6a;
--plp-pill-bg: #cdd4de;
--chart-inset: #ccd3dd;
/* --info-text inherits the :root link to --status-neutral → resolves to #0a5f8f.
Not re-declared here on purpose (the link is theme-agnostic). */
/* Mode identity colours (overview Signal Chain page). Gateway reuses --accent-green. */
--mode-exciter: #15598f; --mode-exciter-hover: #1f6fb0;
--mode-translator: #9a420a; --mode-translator-hover: #c8590f;
--mode-standalone: #5b3da8; --mode-standalone-hover: #6f4dc6;
--mode-exciter-tint: rgba(21,89,143,0.10);
--mode-translator-tint: rgba(154,66,10,0.10);
--mode-standalone-tint: rgba(91,61,168,0.10);
--mode-gateway-tint: rgba(15,112,52,0.10);
--mode-exciter-tint-strong: rgba(21,89,143,0.22);
--mode-translator-tint-strong: rgba(154,66,10,0.22);
--mode-standalone-tint-strong: rgba(91,61,168,0.22);
--mode-gateway-tint-strong: rgba(15,112,52,0.22);
/* Light button family: soft green + dark-green text (Strategy 2); soft grey
neutral. These keep white-on-dark NOT used here — light uses dark text. */
--btn-green-bg: #8fcfa8;
--btn-green-bg-hover: #7bc098;
--btn-green-text: #06230f;
--btn-green-text-hover: #06230f;
--btn-neutral-bg: #d4d9e1;
--btn-neutral-text: #3a4452;
/* NOTE: --sidebar-footer-*, --sidebar-label are intentionally NOT redefined
here — the sidebar is a dark island, so they keep their pinned :root values. */
/* Borders */
--border-subtle: #cdd3dc;
/* --border-accent inherits :root link to --accent-green → resolves to #0f7034. */
/* Sidebar tokens: NOT overridden (dark island). --sidebar-accent stays #22c55e. */
/* --primary / --primary-hover inherit :root links to --accent-green(-dim). */
}
/* ============================================================================
THEME TOGGLE + UTILITY CLUSTER (entry 010 — additive, light-theme feature)
----------------------------------------------------------------------------
A header control to switch dark/light. Grouped with the Admin dropdown in a
"utility cluster" so the two read as one controls zone on the right edge.
Styling: faint green fill + green text/icon + green border (owner-approved
"Option 1+2"). Wording names the DESTINATION ("Light Mode"/"Dark Mode") with
a matching sun/moon icon. Collapses to icon-only at narrow widths.
NOTE: the cluster must NOT set overflow:hidden — .dropdown-content is
position:absolute and would be clipped. Cluster is transparent/no-clip.
============================================================================ */
/* Cluster wraps the toggle + the existing Admin dropdown <h3> contents. */
/* The cluster is a visible "controls" panel: subtle card background, border,
rounded, slight lift — so the toggle + Admin read as ONE unit (owner-approved
"3a"). It does NOT clip (dropdown-content is absolute and must escape). */
.utility-cluster {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: 9px;
padding: 4px 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
/* Thin divider between the toggle and the Admin dropdown inside the cluster. */
.utility-divider {
width: 1px;
height: 22px;
background: var(--border-subtle);
flex-shrink: 0;
}
/* Inside the cluster, flatten the toggle + Admin so they read as cluster items,
not standalone buttons. They keep their colour but drop their own box. */
.utility-cluster .theme-toggle {
background: transparent;
border: 1px solid transparent;
height: 30px;
padding: 0 10px;
}
.utility-cluster .theme-toggle:hover {
background: var(--mode-gateway-tint);
border-color: var(--accent-green);
}
.utility-cluster .dropbtn-modern {
background: transparent;
border: 1px solid transparent;
}
.utility-cluster .dropdown:hover .dropbtn-modern {
border-color: var(--accent-green);
}
/* The header <h3> already has margin-left:auto (pushes it to the right edge).
We keep that and just lay out the cluster inside it, right-aligned. */
.app-header h3.has-toggle { display: flex; align-items: center; justify-content: flex-end; }
.theme-toggle {
display: inline-flex;
align-items: center;
gap: 7px;
height: 34px;
padding: 0 12px;
border-radius: 6px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
font-weight: 600;
white-space: nowrap;
flex-shrink: 0;
background: var(--mode-gateway-tint);
border: 1px solid var(--accent-green);
color: var(--accent-green);
transition: background 0.15s ease, border-color 0.15s ease;
}
.theme-toggle:hover { background: var(--bg-card-hover); }
.theme-toggle svg { width: 16px; height: 16px; color: var(--accent-green); }
/* Icon + label swap by theme. Default (dark) shows "Light Mode" + sun
(the destination); .light-theme shows "Dark Mode" + moon. */
.theme-toggle .tt-sun { display: inline-flex; }
.theme-toggle .tt-moon { display: none; }
.theme-toggle .tt-to-light { display: inline; }
.theme-toggle .tt-to-dark { display: none; }
:root.light-theme .theme-toggle .tt-sun { display: none; }
:root.light-theme .theme-toggle .tt-moon { display: inline-flex; }
:root.light-theme .theme-toggle .tt-to-light { display: none; }
:root.light-theme .theme-toggle .tt-to-dark { display: inline; }
/* Smooth theme transition on the big surfaces. */
body, .app-header, .app-sidebar, .scard, .panel, .card {
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
/* Narrow widths: collapse the toggle to icon-only (drop the text label). */
@media (max-width: 600px) {
.theme-toggle .tt-label { display: none; }
.theme-toggle { padding: 0 9px; }
}
Executable → Regular
+78 -22
View File
@@ -14,8 +14,32 @@
// Power decimal lookup table
var POWER_DECIMALS = ["00","05","10","15","20","25","30","35","40","45","50","55","60","65","70","75","80","85","90","95"];
// Input source labels
var INPUT_SOURCES = ["ASI1","ASI2","EAS","Tuner","TSoIP","None"];
// Input source labels — C1 (2026-08-05): smod's OWN status map. Index 5 = SMPTE per
// smod firmware (matches the status page's proven parse). Copied by LABEL from the
// family, values re-read from this product (register W9 — never copy values).
// SMPTE slot PROVISIONAL pending the board-path hardware answer (team question).
// "None" is NOT a source: it renders only for absent/out-of-range data via
// getInputSource(). Frozen so no page can patch it into a parallel list again.
var INPUT_SOURCES = Object.freeze(["ASI1","ASI2","EAS","Tuner","TSoIP","SMPTE"]);
/**
* Map a firmware input-source index to its display name.
* The ONLY translator from form[1] to a label — status_info.html calls this too
* (C1 killed its private ternary). Out-of-range/absent -> "None" (genuine no-input).
*/
function getInputSource(index) {
var i = Number(index);
return (isFinite(i) && i >= 0 && i < INPUT_SOURCES.length) ? INPUT_SOURCES[i] : "None";
}
/**
* R-2 (2026-08-05): NaN guard for numeric card population (in-tree pattern from
* sys_spectrum.html). Finite -> formatted via fn; else "--" (readback convention).
*/
function fmtNum(v, fn) {
var n = Number(v);
return isFinite(n) ? fn(n) : "--";
}
/**
* Calculate frequency from channel number
@@ -42,6 +66,7 @@
* @returns {number|string} Temperature in Fahrenheit
*/
function convertTemperature(rawValue, unit) {
if (!isFinite(Number(rawValue))) return "--"; // R-2: absent data -> "--", never NaN
if (unit === 0) {
return rawValue; // Already Fahrenheit
} else {
@@ -74,21 +99,32 @@
* @returns {string} Status class
*/
function getSnrStatus(snr) {
if (snr === "N/A") return 'off';
var val = parseFloat(snr); // already carries the +10 dB display offset
if (val >= 37) return 'ok';
if (val >= 30) return 'warn';
// "N/A" and "N/A-FSSI" both mean no meaningful reading -> grey, never red.
// (FSSI parity round; the isNaN belt matches the 1.0 core's fall-through.)
if (typeof snr === "string" && snr.indexOf("N/A") === 0) return 'off';
var val = parseFloat(snr); // true measured SNR/MER in dB
if (isNaN(val)) return 'off';
if (val >= 27) return 'ok';
if (val >= 20) return 'warn';
return 'err';
}
function updateSpectrumFssi(aFssi, bFssi, abMode) {
var fssi = document.getElementById('rd_fssi');
if (!fssi) return;
fssi.innerText = (abMode === 1) ? (aFssi + ' / ' + bFssi) : aFssi;
}
/**
* Get IMD status (UIMD/LIMD)
* @param {string} imd - IMD value or "N/A"
* @returns {string} Status class
*/
function getImdStatus(imd) {
if (imd === "N/A") return 'off';
return parseFloat(imd) >= 47 ? 'ok' : 'err';
if (typeof imd === "string" && imd.indexOf("N/A") === 0) return 'off';
var v = parseFloat(imd);
if (isNaN(v)) return 'off';
return v >= 47 ? 'ok' : 'err';
}
/**
@@ -126,7 +162,10 @@
(data.ctrl.toUpperCase() === "REMOTE") ? 'neutral' : 'warn');
setCard('sum_rf', 'dot_rf', (data.rfStat === 1) ? "On" : "Off",
(data.rfStat === 1) ? 'ok' : 'off');
setCard('sum_outpow', 'dot_outpow', data.finalPower, 'ok');
// REG-5 (2026-08-05, 1.0 C.4 parity): OUTPUT POWER must not stay green with a
// stale figure while RF is off/muted — grey it whenever RF is not On.
setCard('sum_outpow', 'dot_outpow', data.finalPower,
(data.rfStat === 1) ? 'ok' : 'off');
};
/**
@@ -186,19 +225,34 @@
var chan = Number(form[7]);
var freqVal = calcFrequency(chan);
// SNR: half-dB from the app plus the fixed +10 dB display offset.
var snr = (ab_modenum === 1) ? "N/A" : (Number(form[11]) / 2 + 10).toFixed(1);
// SNR: half-dB value from the app, displayed as the true measured SNR/MER.
var rawSnr = Number(form[11]);
var snr = (ab_modenum === 1) ? "N/A-FSSI" :
(rawSnr <= 0 ? "N/A" : fmtNum(form[11], function(n){ return (n / 2).toFixed(1); }));
// LIMD / UIMD: half-dB from the app plus the fixed +11 dB display offset.
// Rounding the sum and then appending the half misreports odd half-dB
// values by 1 dB, so format the sum directly.
var limd = (ab_modenum === 1) ? "N/A" : (Number(form[12]) / 2 + 11).toFixed(1);
var uimd = (ab_modenum === 1) ? "N/A" : (Number(form[13]) / 2 + 11).toFixed(1);
// LIMD / UIMD are half-dB values from the app. Apply the fixed +11 dB
// display offset used by Status and the LCD.
var rawLimd = Number(form[12]);
var rawUimd = Number(form[13]);
var limd = (ab_modenum === 1) ? "N/A-FSSI" :
(rawLimd <= 0 ? "N/A" : fmtNum(form[12], function(n){ return (n / 2 + 11).toFixed(1); }));
var uimd = (ab_modenum === 1) ? "N/A-FSSI" :
(rawUimd <= 0 ? "N/A" : fmtNum(form[13], function(n){ return (n / 2 + 11).toFixed(1); }));
// Input source
var inputIdx = Number(form[1]);
if (inputIdx < 0 || inputIdx > 5) inputIdx = 5;
var inputSrc = INPUT_SOURCES[inputIdx];
// The status interface already carries the shared A/B FSSI values.
// Spectrum consumes the same values instead of issuing another request.
updateSpectrumFssi(Number(form[16]), Number(form[17]), ab_modenum);
var refreshActive = (ab_modenum === 1);
if (typeof window.setSpectrumRefreshActive === 'function') {
// Let the spectrum page compare with and update its previous state.
window.setSpectrumRefreshActive(refreshActive);
} else {
window.fssiRefreshActive = refreshActive;
}
// Input source — C1: via the single accessor; the old >5 clamp is gone
// (it is what rendered firmware idx 5 = SMPTE as "None").
var inputSrc = getInputSource(form[1]);
// Input status
var inputStatus = (Number(form[2]) === 0) ? "OK" : "Err";
@@ -222,7 +276,8 @@
var p_sign = (Number(rf_form[1]) === 0) ? "+" : "-";
var p_int = Number(rf_form[2]);
var p_dec = POWER_DECIMALS[Number(rf_form[3])] || "00";
var finalPower = p_sign + p_int + "." + p_dec;
// R-2: absent power data -> "--", never "+NaN.00"
var finalPower = isFinite(p_int) ? (p_sign + p_int + "." + p_dec) : "--";
// Update all cards using the shared function
window.updateDashboardCards({
@@ -254,7 +309,8 @@
getSnrStatus: getSnrStatus,
getImdStatus: getImdStatus,
POWER_DECIMALS: POWER_DECIMALS,
INPUT_SOURCES: INPUT_SOURCES
INPUT_SOURCES: INPUT_SOURCES,
getInputSource: getInputSource
};
})();
+49
View File
@@ -0,0 +1,49 @@
/* smod js/theme.js — ported verbatim from the 1.0 dashboard_core.js:605-651 (entry 010)
* as its own file because smod's login.html loads no dashboard.js and the module must run
* in <head> on EVERY page (prevents a wrong-theme first paint). Theme round 2026-08-06. */
/* ============================================================================
* THEME TOGGLE (light/dark) — entry 010, light-theme feature
* ----------------------------------------------------------------------------
* Adds/removes the "light-theme" class on the root <html> element. CSS owns all
* colour (see styles.css :root.light-theme block); this only flips the class
* and remembers the choice. Default = dark (no class). Exposed as a global so
* the inline header onclick can reach it, matching this codebase's convention
* of page-local inline handlers (e.g. toggleSidebar).
*
* localStorage is used for persistence. This is real on-device firmware, where
* localStorage is available; the embedded browser retains it across reloads.
* ==========================================================================*/
(function () {
'use strict';
var KEY = 'aw-theme'; // stored value: 'light' or 'dark'
var CLASS = 'light-theme';
var root = document.documentElement;
function apply(theme) {
if (theme === 'light') {
root.classList.add(CLASS);
} else {
root.classList.remove(CLASS);
}
}
// Apply the saved choice as early as possible to avoid a flash of the
// wrong theme. dashboard_core.js is loaded in <head>, so this runs before
// the body paints on most pages.
try {
var saved = window.localStorage ? localStorage.getItem(KEY) : null;
apply(saved === 'light' ? 'light' : 'dark'); // default dark
} catch (e) {
apply('dark');
}
// Global handler for the header toggle button's onclick.
window.toggleTheme = function () {
var nowLight = root.classList.toggle(CLASS);
try {
if (window.localStorage) {
localStorage.setItem(KEY, nowLight ? 'light' : 'dark');
}
} catch (e) { /* storage unavailable; theme still applies for this session */ }
};
})();
+25 -15
View File
@@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
<script src="js/dashboard.js"></script>
@@ -299,12 +300,13 @@
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li class="active"><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -324,8 +326,15 @@
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -345,7 +354,8 @@
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -371,16 +381,6 @@
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -391,6 +391,16 @@
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
+16 -9
View File
@@ -3,7 +3,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<title id="webtitle">Digital Exciter</title>
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<style>
#content .box table td:first-child {
@@ -40,13 +41,13 @@ function loginsubmit() {
}
function refresh_show(){
var queryData = "web_title=&"
$.get("atsc_status_cfg.cgi?"+queryData, function(data, status) {
var table = data.split("|");
var title = table[0].split("#");
document.getElementById('webtitle').textContent = title[1];
document.getElementById('webtitle_header').textContent = title[1];
});
/* atsc_status_cfg.cgi requires a session, but the login page has none yet. */
$.get("login.cgi?action=get_title", function(data) {
var titleText = String(data || "").replace(/^#/, "").trim() || "Digital Exciter";
document.title = titleText;
document.getElementById('webtitle').textContent = titleText;
document.getElementById('webtitle_header').textContent = titleText;
}, "text");
}
</script>
@@ -73,7 +74,13 @@ function refresh_show(){
</svg>
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h2 id="webtitle_header"><span>Digital Exciter</span></h2>
<h3 class="has-toggle" style="margin-left:auto"><div class="utility-cluster"><button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button></div></h3>
</header>
<main class="app-main">
File diff suppressed because it is too large Load Diff
Executable → Regular
+146 -62
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
@@ -41,7 +42,19 @@ function get_remote_local_ctrl() {
var table = data.split("|");
// Logic for remote/local control if needed
// g_remot_local_ctrl = Number(table[0].split("#")[5]);
document.title = table[0].split("#")[11];
/* P-4 ROOT CAUSE + the better fix (2026-08-06).
What was here: document.title = table[0].split("#")[11]; — copied from the
1.0 BY VALUE, index and all (the same copy-by-value class as C1's
INPUT_SOURCES). The 1.0's sys_cfg carries five NTP fields after ctl so its
title sits at index 11; smod's sys_cfg has no NTP fields there, so index 11
is ab_mode — the FSSI Refresh switch. The tab was displaying the FSSI switch
state: "0" or "1", flipping as FSSI was toggled (owner, 2026-08-06).
Why the line is DELETED rather than re-indexed: this page already has a
correct, authoritative title source — the dedicated web_title section polled
in SetFormDefaults below (table[7]). Two writers for one value is the defect,
not the index; re-indexing would have left a second writer racing the first
(and is exactly what C1 removed on the input-source axis). One source of
truth: the web_title section. This function now only does what its name says. */
});
}
@@ -51,6 +64,11 @@ function get_remote_local_ctrl() {
}
function SetFormDefaults() {
// R-2 (2026-08-05): NaN guards — in-tree pattern from sys_spectrum.html. Readbacks
// show "--" for absent data; finite values format as before.
function fmtScaled(v, scale, dp){ var n = Number(v); return isFinite(n) ? (n*scale).toFixed(dp) : "--"; }
function fmtRaw(v){ var n = Number(v); return isFinite(n) ? n : "--"; }
var queryData = "State_SysState=&tuner_status=&pac_status=&gps_status=&State_SysVer=&mac_sn_status=&sys_rf=&web_title=&"
$.get("atsc_status_cfg.cgi?"+queryData, function(data, status) {
var temp_unit=1;
@@ -63,10 +81,23 @@ function SetFormDefaults() {
var ab_modenum = Number(form[25]);
temp_unit = Number(form[26]);
// Input Source Map
var i = Number(form[1]);
var s_str = (i==0)?"ASI1":(i==1)?"ASI2":(i==2)?"EAS":(i==3)?"Tuner":(i==4)?"TSoIP":(i==5)?"SMPTE":"None";
cf.asi_input.value = s_str;
// Input Source — C1 (2026-08-05): the page-private ternary is DELETED; this and the
// dashboard cards read the ONE map via DashboardHelpers.getInputSource (the two lists
// disagreed at index 5 — SMPTE vs None — which is your "None for a selected input").
cf.asi_input.value = window.DashboardHelpers.getInputSource(form[1]);
/* REG-2 / ST.F11 port (2026-08-05, from the 1.0): auto-expand Tuner Status on
ENTERING tuner/translator mode — smod IS a translator, so this is its primary
mode. Same "changed" semantics as the 1.0's adaptation of 3.0's publishMode:
expand only on the transition INTO Tuner input (form[1]===3, same index here,
proven by this page's own map), tracked by _stTunerMode — NEVER on every poll,
so an operator's manual collapse is not fought. Box-headers exist by now
(initCollapsibleTables runs at DOMContentLoaded, before this callback). */
(function(){
var inTuner = (Number(form[1]) === 3);
if (inTuner && window._stTunerMode !== true) { autoExpandTunerStatus(); }
window._stTunerMode = inTuner;
})();
// Input Status
cf.asi_status.value = (Number(form[2])==0) ? "OK" : "Err";
@@ -76,12 +107,15 @@ function SetFormDefaults() {
cf.ts_rate.value = form[4]; // TS Rate
// Temperature
if(temp_unit==0){
// Temperature — R-2: absent data -> "--", never NaN cascades
var t_raw = Number(form[5]);
if(!isFinite(t_raw)){
cf.temp1.value = "--"; cf.temp1_c.value = "--";
} else if(temp_unit==0){
cf.temp1.value = form[5];
cf.temp1_c.value = ((cf.temp1.value - 32)/1.8).toFixed(2);
cf.temp1_c.value = ((t_raw - 32)/1.8).toFixed(2);
} else {
cf.temp1.value = (form[5]*1.8+32).toFixed(2);
cf.temp1.value = (t_raw*1.8+32).toFixed(2);
cf.temp1_c.value = form[5];
}
@@ -102,27 +136,27 @@ function SetFormDefaults() {
// Offset
i = Number(form[8]);
s_str = (i==0) ? "+" : "-";
var s_str = (i==0) ? "+" : "-"; // scratch var — its declaration moved here when C1 deleted the input-source ternary that used to declare it
cf.hz1.value = s_str + form[9];
// FWD Power & SNR
var rawFwd = Number(form[10]);
cf.fwd.value = (ab_modenum==1) ? "N/A" : (rawFwd + " %");
cf.fwd.value = (ab_modenum==1) ? "N/A-FSSI" : (rawFwd + " %");
// SNR. Half-dB from the app, shown as-is (PR12 removed the old fixed
// +10 dB display offset; this is the true measured MER).
var tmpdata = Number(form[11]);
var snrStr = (tmpdata/2).toFixed(1);
cf.tsnr.value = (ab_modenum==1) ? "N/A" : snrStr;
cf.tsnr.value = (ab_modenum==1) ? "N/A-FSSI" : (tmpdata<=0 ? "N/A" : snrStr);
// IMD. Half-dB from the app, plus the fixed +11 dB display offset.
// (Do not Math.round() the sum and then paste on the half: for an odd
// half-dB value that rounds x.5 up and reports 1 dB too much.)
tmpdata = Number(form[12]);
cf.l_imd.value = (ab_modenum==1) ? "N/A" : (tmpdata/2 + 11).toFixed(1);
cf.l_imd.value = (ab_modenum==1) ? "N/A-FSSI" : (tmpdata<=0 ? "N/A" : (tmpdata/2 + 11).toFixed(1));
tmpdata = Number(form[13]);
cf.u_imd.value = (ab_modenum==1) ? "N/A" : (tmpdata/2 + 11).toFixed(1);
tmpdata = Number(form[13]);
cf.u_imd.value = (ab_modenum==1) ? "N/A-FSSI" : (tmpdata<=0 ? "N/A" : (tmpdata/2 + 11).toFixed(1));
// Control & Amp
cf.f1ctrl.value = (Number(form[14])==0) ? "Remote" : "Local";
@@ -143,6 +177,13 @@ function SetFormDefaults() {
cf.b_fssi.value = (ab_modenum==1) ? (Number(form[17]) + " %") : "N/A";
// AGC State
/* FSSI active alternates the RFinA feedback path AGC realizes on,
so AGC state (and its error codes) are not meaningful — show the
reason instead of a misleading adjustment value. Takes priority.
(Ported from the 1.0, status_info.html:239-243 — FSSI parity round.) */
if(ab_modenum==1){
s_str = "N/A-FSSI";
} else {
agc_err_code = Number(form[27]);
var agc_adj = Number(form[28]);
if(agc_err_code==1) s_str="Down Limit!";
@@ -154,6 +195,7 @@ function SetFormDefaults() {
s_str="OK";
}
}
}
cf.agc_state.value = s_str;
// --- 2. PARSE SYS_RF FOR DASHBOARD ---
@@ -191,37 +233,40 @@ function SetFormDefaults() {
// Form 1: Tuner
var form1 = table[1].split("#");
var cf1 = document.getElementById("tuner");
cf1.rx_freq.value = Number(form1[1]);
cf1.rssi.value = Number(form1[2]);
cf1.rsnr.value = Number(form1[3]);
cf1.rx_freq.value = fmtRaw(form1[1]);
cf1.rssi.value = fmtRaw(form1[2]);
cf1.rsnr.value = fmtRaw(form1[3]);
cf1.lock.selectedIndex = Number(form1[4]);
// Form 2: PAC Status
var form2 = table[2].split("#");
var cf2 = document.getElementById("pac_status");
cf2.v9v.value = (Number(form2[1])*0.1).toFixed(2);
cf2.v12v.value = (Number(form2[2])*0.1).toFixed(2);
cf2.v50v.value = (Number(form2[3])*0.1).toFixed(2);
cf2.pac_fwd.value = (Number(form2[4])*0.1).toFixed(2);
cf2.ref.value = (Number(form2[5])*0.1).toFixed(2);
cf2.vswr.value = (Number(form2[6])*0.1).toFixed(2);
cf2.v9v.value = fmtScaled(form2[1], 0.1, 2);
cf2.v12v.value = fmtScaled(form2[2], 0.1, 2);
cf2.v50v.value = fmtScaled(form2[3], 0.1, 2);
cf2.pac_fwd.value = fmtScaled(form2[4], 0.1, 2);
cf2.ref.value = fmtScaled(form2[5], 0.1, 2);
cf2.vswr.value = fmtScaled(form2[6], 0.1, 2);
// PAC Temp
if(temp_unit==0){
cf2.temp.value = (Number(form2[7])*0.1).toFixed(2);
// PAC Temp — R-2 guarded
var pac_t = Number(form2[7]);
if(!isFinite(pac_t)){
cf2.temp.value = "--"; cf2.temp_c.value = "--";
} else if(temp_unit==0){
cf2.temp.value = (pac_t*0.1).toFixed(2);
cf2.temp_c.value = (cf2.temp.value < 32) ? 0 : ((cf2.temp.value - 32)/1.8).toFixed(2);
} else {
cf2.temp.value = (Number(form2[7])*0.1*1.8+32).toFixed(2);
cf2.temp_c.value = (Number(form2[7])*0.1).toFixed(2);
}
cf2.temp.value = (pac_t*0.1*1.8+32).toFixed(2);
cf2.temp_c.value = (pac_t*0.1).toFixed(2);
}
cf2.pain.value = Number(form2[8]); // This is PA_IN
cf2.c50v1.value = (Number(form2[9])*0.1).toFixed(2);
cf2.c50v2.value = (Number(form2[10])*0.1).toFixed(2);
cf2.c50v3.value = (Number(form2[11])*0.1).toFixed(2);
cf2.c50v4.value = (Number(form2[12])*0.1).toFixed(2);
cf2.gv.value = (Number(form2[13])*0.1).toFixed(2);
cf2.c50v1.value = fmtScaled(form2[9], 0.1, 2);
cf2.c50v2.value = fmtScaled(form2[10], 0.1, 2);
cf2.c50v3.value = fmtScaled(form2[11], 0.1, 2);
cf2.c50v4.value = fmtScaled(form2[12], 0.1, 2);
cf2.gv.value = fmtScaled(form2[13], 0.1, 2);
cf2.lvl.selectedIndex = Number(form2[14]);
// Form 3: GPS
@@ -252,8 +297,11 @@ function SetFormDefaults() {
if (table[7]) {
var title = table[7].split("#");
var titleStr = (title[1] != null) ? title[1] : "";
document.getElementById('webtitle').textContent = titleStr;
document.getElementById('webtitle_header').textContent = titleStr;
/* P-4 guard: never paint a short/absent read over a good title (R-2 class). */
if (titleStr !== "") {
document.getElementById('webtitle').textContent = titleStr;
document.getElementById('webtitle_header').textContent = titleStr;
}
}
// Update Status Colors and UI helpers
@@ -330,14 +378,15 @@ function highlightProblemTableRows() {
}
// SNR, LIMD, UIMD: same error thresholds as summary cards / updateStatusColors
var id = inp.id || '';
if (id === 'tsnr' && v !== '' && v !== 'N/A') {
/* v.indexOf('N/A') !== 0 excludes both "N/A" and "N/A-FSSI" (FSSI-active) — 1.0 :413 pattern */
if (id === 'tsnr' && v !== '' && v.indexOf('N/A') !== 0) {
var snrNum = parseFloat(v); // true measured dB (PR12: no display offset)
if (snrNum < 15) { tr.classList.add('row-has-error'); break; }
}
if (id === 'l_imd' && v !== '' && v !== 'N/A') {
if (id === 'l_imd' && v !== '' && v.indexOf('N/A') !== 0) {
if (parseFloat(v) < 47) { tr.classList.add('row-has-error'); break; }
}
if (id === 'u_imd' && v !== '' && v !== 'N/A') {
if (id === 'u_imd' && v !== '' && v.indexOf('N/A') !== 0) {
if (parseFloat(v) < 47) { tr.classList.add('row-has-error'); break; }
}
// License fields: INACTIVE is an error
@@ -381,7 +430,7 @@ function updateStatusColors() {
// Main Status details: LIMD, UIMD, SNR – same rules as summary cards (green/red/warn by range)
var tsnrEl = document.getElementById("tsnr");
if(tsnrEl && tsnrEl.value !== "" && tsnrEl.value !== "N/A") {
if(tsnrEl && tsnrEl.value !== "" && tsnrEl.value.indexOf("N/A") !== 0) {
var snrVal = parseFloat(tsnrEl.value); // true measured dB (PR12: no display offset)
if(snrVal >= 27) tsnrEl.style.color = color_ok;
else if(snrVal >= 20) tsnrEl.style.color = color_warn;
@@ -389,12 +438,12 @@ function updateStatusColors() {
} else if(tsnrEl) tsnrEl.style.color = "";
var limdEl = document.getElementById("l_imd");
if(limdEl && limdEl.value !== "" && limdEl.value !== "N/A") {
if(limdEl && limdEl.value !== "" && limdEl.value.indexOf("N/A") !== 0) {
limdEl.style.color = parseFloat(limdEl.value) >= 47 ? color_ok : color_err;
} else if(limdEl) limdEl.style.color = "";
var uimdEl = document.getElementById("u_imd");
if(uimdEl && uimdEl.value !== "" && uimdEl.value !== "N/A") {
if(uimdEl && uimdEl.value !== "" && uimdEl.value.indexOf("N/A") !== 0) {
uimdEl.style.color = parseFloat(uimdEl.value) >= 47 ? color_ok : color_err;
} else if(uimdEl) uimdEl.style.color = "";
@@ -599,7 +648,28 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// Collapsible Tables - MAIN STATUS, PAC STATUS, GPS STATUS
/* ST.F11 support (2026-08-05): expand the Tuner Status box if collapsed. Called only on
the transition into tuner/translator mode by the data callback — see the port note there. */
function autoExpandTunerStatus() {
var headers = document.querySelectorAll('.box-header');
for (var i = 0; i < headers.length; i++) {
var titleSpan = headers[i].querySelector('span');
if (titleSpan && titleSpan.innerText.indexOf('Tuner Status') !== -1) {
if (headers[i].classList.contains('collapsed')) {
headers[i].classList.remove('collapsed');
var content = headers[i].nextElementSibling;
if (content && content.className.indexOf('box-content') !== -1) {
content.style.display = 'block';
}
var badge = headers[i].querySelector('.toggle-text');
if (badge) badge.innerText = 'Hide Details';
}
break;
}
}
}
// Collapsible Tables - MAIN STATUS, TUNER STATUS, PAC STATUS, GPS STATUS
function initCollapsibleTables() {
var headers = document.querySelectorAll('.app-main h2');
for (var i = 0; i < headers.length; i++) {
@@ -621,20 +691,26 @@ function initCollapsibleTables() {
}
if (box) {
/* REG-1 (2026-08-05, 1.0 C.2 port — "3.0 parity"): the primary status block
is OPEN on load so the operator sees it without a click; every other
section starts collapsed. Tuner Status is additionally auto-expanded on
entering translator mode by the data callback (ST.F11) — it cannot be
decided here because the mode is not yet read at init. */
var startOpen = (headerText === "Main Status");
var boxHeader = document.createElement('div');
boxHeader.className = "box-header collapsed";
boxHeader.className = startOpen ? "box-header" : "box-header collapsed";
var titleSpan = document.createElement('span');
titleSpan.innerText = headerText + " Details";
var toggleSpan = document.createElement('span');
toggleSpan.className = "toggle-text";
toggleSpan.innerText = "View Details";
toggleSpan.innerText = startOpen ? "Hide Details" : "View Details";
boxHeader.appendChild(titleSpan);
boxHeader.appendChild(toggleSpan);
var contentDiv = document.createElement('div');
contentDiv.className = "box-content";
contentDiv.style.display = "none";
contentDiv.style.display = startOpen ? "block" : "none";
while (box.firstChild) {
contentDiv.appendChild(box.firstChild);
@@ -683,17 +759,17 @@ document.addEventListener('DOMContentLoaded', function() {
</script>
</head>
<body onload="refresh_show()">
<body onload="refresh_show()">
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li class="active"><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -713,8 +789,15 @@ document.addEventListener('DOMContentLoaded', function() {
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -734,7 +817,8 @@ document.addEventListener('DOMContentLoaded', function() {
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -760,16 +844,6 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -780,6 +854,16 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
+120 -18
View File
@@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
<script src="js/dashboard.js"></script>
@@ -57,10 +58,99 @@
var table = data.split("|");
g_remot_local_ctrl = Number(table[0].split("#")[5]);
document.title = table[0].split("#")[11];
/* P-4b (2026-08-06): the title write here — document.title =
table[0].split("#")[11] — is DELETED. On smod index 11 is ab_mode
(the FSSI Refresh switch), not the web title. This page already sets
the title from its web_title section below. Round 3 fixed only
status_info; this is the rest of that class. */
});
}
/* REG-4 (2026-08-05) — CTRL gate, ported from the 1.0 (its C.5, 3.0 SLT.7 parity; the
owner-found DESTRUCTIVE class: saving TSID/program rows while CTRL != Enable writes
non-effective values the operator believes took). When CTRL (ts_edit) != Enable(1),
the TSID save and every program-row save are disabled. The CTRL row's own Set
(#ts_ctrl_set) stays live so CTRL can be changed (CTRL+TSID share one post here, as
on the 1.0 — changing CTRL via that button is the intended path). No-op in Local
mode, where SetFormDefaults has already disabled everything. Safe on load and on
every ts_edit change. */
/* P-6 (2026-08-06, CORRECTED after owner review). What the licence gate must do —
matched to what the 1.0 (sys_psip.html:157-168) and the 3.0 actually do, and to the
owner's ruling "no licence should STILL ALLOW Clear":
no PSIP licence -> remove ONLY the "Enable" choice from ts_edit.
Disable(0) and Clear(2) stay fully usable; every Set stays live; NO grey-out,
NO banner. The operator can still disable or clear PSIP without a licence; they
simply cannot ENABLE it.
The prior version of this function was the reverse of that — it forced the value off
Enable, disabled both Sets and the whole program table, and showed a banner. That
locked out Clear, which is exactly backwards. Corrected here.
R4 (2026-08-11) — THE PREVIOUS COMMENT HERE WAS WRONG AND IS CORRECTED.
It claimed the 1.0's $('option').hide() "browsers do not honour reliably", promoted
from an ambiguous field report. Retrieval settled it: the 1.0's TSoIP page has NO
licence handling at all (zero references in sys_tsoip.html), so the option the owner
saw selectable cannot have been a 1.0 TSoIP gate; and smod had no gate whatsoever
before round 3, which explains "selectable on both" on its own. The owner then
confirmed the 1.0's Enable option DISAPPEARS when unlicensed — i.e. hiding works.
Owner ruling 2026-08-11: the option must be HIDDEN, not shown-but-unselectable.
So the gate now hides AND disables: hidden removes it from the list (1.0 parity, and
what the owner sees), disabled is the belt for any engine that renders a hidden
option anyway. Disable(0) and Clear(2) and every Set remain untouched. */
var g_psip_licensed = null; // null = not yet known; never gate on an unknown
function applyLicenceGate(){
var edit = document.getElementById("ts_edit");
if (!edit) return;
var enableOpt = edit.querySelector('option[value="1"]');
if (!enableOpt) return;
if (g_psip_licensed === false) {
enableOpt.hidden = true; // out of the list entirely (owner ruling)
enableOpt.style.display = "none"; // same effect for engines ignoring [hidden]
enableOpt.disabled = true; // belt: unselectable even if rendered
if (Number(edit.value) === 1) { edit.value = "0"; } // if it was on Enable, fall back to Disable
} else {
enableOpt.hidden = false; // licensed or unknown -> Enable available
enableOpt.style.display = "";
enableOpt.disabled = false;
}
// Disable/Clear and every Set are deliberately left ALONE — Clear must work unlicensed.
}
function get_licence(){
$.get("atsc_status_cfg.cgi?sn_license=&", function(data, status){
if (status !== "success" || !data) return; // unknown -> no gate
var licence_para = data.split("|")[0].split("#");
var raw = licence_para[2];
var key_psip = Number(raw);
/* Fail-open on a garbled read (round-1 bench caught the fail-closed version): only a
clean 0/1 is a licence answer; anything else stays unknown and ungated. */
if (raw === undefined || raw === "" || isNaN(key_psip)) return;
g_psip_licensed = (key_psip === 1);
applyLicenceGate();
});
}
function applyCtrlGate() {
if (g_remot_local_ctrl == 1) return; // Local: all already disabled
var edit = document.getElementById("ts_edit");
var enabled = edit && Number(edit.value) === 1; // 1 = Enable
if (enabled) return; // Enable: normal Set state stands
// Not Enable -> block dependent saves; leave #ts_ctrl_set live.
var tsidBtn = document.getElementById("ts_tsid_set");
if (tsidBtn) { tsidBtn.disabled = true; tsidBtn.value = "Set"; }
$('#psip_table_form input[type="button"]').prop("disabled", true).val("Set");
}
// REG-4: re-gate the moment the operator changes CTRL in the form (registered once)
document.addEventListener('DOMContentLoaded', function () {
var tsEditEl = document.getElementById('ts_edit');
if (tsEditEl) tsEditEl.addEventListener('change', function () {
applyCtrlGate();
applyLicenceGate(); // P-6: licence outranks CTRL — re-assert on every change
});
get_licence(); // P-6: read the licence once, gate when it answers
});
// Updated SetFormDefaults to populate the Single Form
function SetFormDefaults() {
// 1. DISABLE BUTTONS START (only in Remote mode)
@@ -133,6 +223,9 @@ function SetFormDefaults() {
}
if (typeof setTsidFormSnapshotUpdate === 'function') setTsidFormSnapshotUpdate();
if (typeof psipFormSnapshotUpdate === 'function') psipFormSnapshotUpdate();
// REG-4: gate dependent saves whenever fresh CTRL state lands (after the re-enable above)
applyCtrlGate();
applyLicenceGate(); // P-6: licence gate outranks CTRL gate; re-assert after every repaint
document.getElementById('webtitle').textContent = title[1];
document.getElementById('webtitle_header').textContent = title[1];
@@ -474,12 +567,13 @@ function cmd_confirm() {
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li class="active"><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -499,8 +593,15 @@ function cmd_confirm() {
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -520,7 +621,8 @@ function cmd_confirm() {
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -546,16 +648,6 @@ function cmd_confirm() {
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -566,6 +658,16 @@ function cmd_confirm() {
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
@@ -659,7 +761,7 @@ function cmd_confirm() {
</select>
</td>
<td>
<input type="button" class="btn-modern" alt="Set" value="Set" disabled onclick="return tsid_psip_top_set();">
<input type="button" id="ts_ctrl_set" class="btn-modern" alt="Set" value="Set" disabled onclick="return tsid_psip_top_set();">
</td>
</tr>
<tr>
@@ -668,7 +770,7 @@ function cmd_confirm() {
<input type="text" maxlength="5" size="8" name="tsid" id="tsid" value="">
</td>
<td>
<input type="button" class="btn-modern" alt="Set" value="Set" disabled onclick="return tsid_psip_top_set();">
<input type="button" id="ts_tsid_set" class="btn-modern" alt="Set" value="Set" disabled onclick="return tsid_psip_top_set();">
</td>
</tr>
</table>
+88 -22
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
@@ -66,7 +67,11 @@ function get_remote_local_ctrl() {
var table = data.split("|");
g_remot_local_ctrl = Number(table[0].split("#")[5]);
document.title = table[0].split("#")[11];
/* P-4b (2026-08-06): the title write here — document.title =
table[0].split("#")[11] — is DELETED. On smod index 11 is ab_mode
(the FSSI Refresh switch), not the web title. This page already sets
the title from its web_title section below. Round 3 fixed only
status_info; this is the rest of that class. */
});
}
@@ -466,12 +471,13 @@ if (document.readyState === 'loading') {
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li class="active"><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -491,8 +497,15 @@ if (document.readyState === 'loading') {
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -512,7 +525,8 @@ if (document.readyState === 'loading') {
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -538,16 +552,6 @@ if (document.readyState === 'loading') {
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -558,6 +562,16 @@ if (document.readyState === 'loading') {
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
@@ -696,6 +710,21 @@ if (document.readyState === 'loading') {
</td>
</tr>
<!-- AGC / FSSI interaction (Session 4, 2026-08-11 — owner decision: LEAVE UNGATED).
DECISION: AGC stays freely settable on this page, including while FSSI Refresh is On.
ANALYSIS behind the decision (recorded so a future session does not "fix" this blindly):
- Under FSSI, AGC state is not physically meaningful: "FSSI active alternates the
RFinA feedback path AGC realizes on" (status_info.html physics comment, identical in
the 1.0 and 3.0). The status page already shows AGC as "N/A-FSSI" when FSSI is On.
- The family's hard interlock is FSSI<->ADPC, not FSSI<->AGC. smod HAS that ADPC gate
(sys_set.html: blocks ADPC transitions while FSSI Refresh On; 3.0 sys_set.html:858
"do not relax"). ADPC and AGC share the same RFinA feedback loop, so gating ADPC was
the intended guard; AGC is covered indirectly through it.
- NONE of the three products (smod, 1.0, 3.0) gates AGC on FSSI at the control layer —
so this is consistent family behaviour, not a smod-unique omission.
Owner ruled 2026-08-11 to keep AGC ungated (the ADPC interlock is the guard). If this is
ever revisited, the RF page would need to poll FSSI state (it does not today) — see the
S4 ledger append and S5 handoff for the full reasoning. -->
<tr>
<td>AGC</td>
<td>
@@ -882,7 +911,30 @@ if (document.readyState === 'loading') {
</table>
</form>
<!-- This warning is ABOUT the TX Frequency control above it.
The PR07 Reference Output section had been inserted between them, pushing it
to the bottom of the page, away from the control it describes (on the 1.0 it
sits adjacent because nothing intervenes). Moved back to sit under its control. -->
<!-- R8 (2026-08-11): STANDING informational warning -> plain amber TEXT, matching the 3.0
exactly (3.0 sys_rf.html:974). The family rule, read off the 3.0's own pages:
- plain amber text (color:var(--status-warn), no box) = a STANDING FACT about how a
control behaves (this note, and the 10 MHz note below). Always visible.
- .warning-banner (tinted box) = a CONDITIONAL mode/section advisory that appears and
disappears (e.g. the 3.0's hidden "Gateway RF-In mode" note).
- .warning-banner.is-error (red box) = a genuine fault.
R3/R4 wrongly put this standing note in the tinted box; the 3.0 never did. Corrected. -->
<div style="padding: 10px; color: var(--status-warn); font-size: 12px;">
Warning: Exciter RF Output Power will be reset to -25 dBm when the channel (TX Frequency) is changed.
</div>
</div><!-- /.box — P-7 (2026-08-06): THE STRUCTURAL DEFECT. The RF Control card
was never closed here, so the PR07 "Reference Output" section (h2, table and
note) rendered INSIDE the RF Control card — an h2 with a full-width rule
sitting in another card's padding, which is the "doesn't fit / out of the
picture" the owner reported. Closing it makes Reference Output a sibling
section: h2 then its own .box, exactly like every other section on sys_set. -->
<h2>Reference Output</h2>
<div class="box">
<table>
<tr>
<td>10 MHz Output</td>
@@ -897,21 +949,35 @@ if (document.readyState === 'loading') {
</td>
</tr>
</table>
<div style="padding: 4px 10px; color: #64748b; font-size: 12px;">
Enables the AB5 10 MHz reference clock output. Default Off; resets to Off on every power-up.
<!-- R8: standing informational note -> plain amber text, same rule as the -25 dBm note above. -->
<!-- R11 (2026-08-17, owner-ruled). SUPERSEDES the R9/D-27.3 and D-28.1 rulings above, which
decided WHERE the word "Warning:" sat before either of us had read the sentence for sense.
History worth keeping: this note was chased for several rounds as a "missing Warning:"
bug. It was never missing — the word was present in source AND in the unit's own served
bytes (owner F12 Network->Response capture, 2026-08-17). Both of us mis-read it, because
both of us expected "Warning:" at the START of the sentence, as it is on the -25 dBm note
above; here it sat mid-sentence and the eye slid past it.
Reading it for SENSE surfaced the real defect: the sentence opened like a command
("Enables...") and then hung "Warning:" in front of a NON-hazard.
THE GOVERNING RULE, which applies to any future note: a standing note earns "Warning:"
only when it flags a REAL CONSEQUENCE; reserve the word so the label keeps discriminating.
This matters here specifically because the tree's only two prose notes (this one and the
-25 dBm note above) share ONE identical amber style — so COLOUR cannot distinguish a
hazard from a heads-up, and the LABEL is the only thing that can.
Hence: the -25 dBm note keeps "Warning:" (real consequence — silent power loss on a
channel change); this one becomes "Note:" (behavioural heads-up only). Styling unchanged. -->
<div style="padding: 6px 10px 2px; color: var(--status-warn); font-size: 12px;">
Note: The 10 MHz reference clock output is Off by default, and resets to Off on every power-up.
</div>
</div><!-- /.box Reference Output -->
<div style="padding: 10px; color: #b91c1c; font-weight: 500;">
Warning: Exciter RF Output Power will be reset to -25dBm when the channel(TX Frequency) is changed.
</div>
</div>
</main>
</div>
<div id="accountModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>ACCOUNT SETTINGS</h3>
<h3>Account Settings</h3>
<span class="close" onclick="closeAccountModal()">&times;</span>
</div>
<div class="modal-body">
Executable → Regular
+151 -71
View File
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
@@ -16,9 +17,17 @@ var lastTime = new Date().getTime();
var currentTime = new Date().getTime();
var timeOut = 20 * 60 * 1000; // 20 min
var upgrade_imx_flg = 0;
/* Keep the settings page authenticated while a long FPGA upload/flash is active. */
var fpga_upgrade_in_progress = 0;
function finishFpgaUpgrade(){
fpga_upgrade_in_progress = 0;
upgrade_imx_flg = 0; // resume normal status refresh
lastTime = new Date().getTime();
}
var remot_local_ctrl = 0; // 0 remote 1 local
var g_old_adpc_status = 0;
var g_old_adpc = 0;
var ldpd_ctl = 0;
var g_key_tsoip_valid = 0;
var g_key_eas_valid = 0;
var g_old_ntp_ip1 = 0;
@@ -37,7 +46,7 @@ $(function(){
function testTime(){
currentTime = new Date().getTime();
if(currentTime - lastTime > timeOut){
if(!fpga_upgrade_in_progress && currentTime - lastTime > timeOut){
window.location.href="login.cgi?logout=1"
}
@@ -145,6 +154,17 @@ $(document).ready(function(){
update_dpd_state();
});
/* REG-3 (2026-08-05) — ONE canonical control-lock list. The grey/green mismatch the owner
reported came from hand-typed parallel lists (the load-lock had 9 IDs; the IMX-upgrade
lock was missing #upgrade_fpga_id/#key_set_id/#ntp_set_id; the FPGA flash locked only
its own button). Every full-page lock/unlock now goes through lockAllControls();
NEVER re-type the ID list anywhere. Text (Loading.../Local Mode/Set) stays owned by
each call site — this helper owns only the disabled state. */
var ALL_CONTROL_IDS = '#para_set_id, #net_set_id, #sys_rtc_id, #sysreset_bt_id, #upgrade_imx_id, #upgrade_fpga_id, #key_set_id, #ntp_set_id, #timezone_set_id';
function lockAllControls(lock) {
$(ALL_CONTROL_IDS).attr("disabled", !!lock);
}
function SetFormDefaults(){
if(upgrade_imx_flg == 1) return;
@@ -154,7 +174,7 @@ function SetFormDefaults(){
var queryData = "sys_cfg=&sysnet=&imxsystime=&web_title=&system_a1_key=&ntp_server=&imx_timezone=&";
} else {
// Remote mode: show LOADING indicator
$('#para_set_id, #net_set_id, #sys_rtc_id, #sysreset_bt_id, #upgrade_imx_id, #upgrade_fpga_id, #key_set_id, #ntp_set_id, #timezone_set_id')
$(ALL_CONTROL_IDS)
.attr("disabled", true)
.val("Loading...");
var queryData = "sys_cfg=&sysnet=&imxsystime=&web_title=&system_a1_key=&ntp_server=&imx_timezone=&";
@@ -211,6 +231,10 @@ function SetFormDefaults(){
cf0.agc_att.value = Number(syspara[i++])*(-0.5);
cf0.adrv9009_att.value = syspara[i++];
cf0.orx_manu_gain.value = syspara[i++];
ldpd_ctl = Number(syspara[i++]);
var adpc_label = document.getElementById('adpc_label');
if (adpc_label) adpc_label.style.color = (ldpd_ctl === 0x07) ? '' : '#d6a700';
cf0.hardware_version.value = (syspara[i] === undefined || Number(syspara[i]) !== 0) ? 1 : 0;
// sysnet form1
i = 1;
@@ -279,12 +303,12 @@ function SetFormDefaults(){
var currentCtlValue = Number(cf0.ctl.value);
if(currentCtlValue == 1) {
// Local mode: keep buttons disabled
$('#para_set_id, #net_set_id, #sys_rtc_id, #sysreset_bt_id, #upgrade_imx_id, #upgrade_fpga_id, #key_set_id, #ntp_set_id, #timezone_set_id')
$(ALL_CONTROL_IDS)
.attr("disabled", true)
.val("Local Mode");
} else {
// Remote mode: enable buttons
$('#para_set_id, #net_set_id, #sys_rtc_id, #sysreset_bt_id, #upgrade_imx_id, #upgrade_fpga_id, #key_set_id, #ntp_set_id, #timezone_set_id')
$(ALL_CONTROL_IDS)
.attr("disabled", false)
.val("Set");
$('#upgrade_imx_id').val("Upgrade");
@@ -459,9 +483,10 @@ function set_syspara() {
postData += "#"+cf.agc_att.value;
postData += "#"+cf.adrv9009_att.value;
postData += "#"+cf.orx_manu_gain.value;
postData += "#"+cf.hardware_version.value;
if(cf.ab_mode.value == "1" && Number(cf.adp.value) > 0) {
alert("The ADPC can't be set to Hold or Update or UpdateLIN/UpdateNLIN mode when 'FSSI Refresh' switch 'On' !!! Please change 'FSSI Refresh' to 'Off'.");
alert("The ADPC cannot be set to Hold or Update or UpdateLIN/UpdateNLIN mode when 'FSSI Refresh' switch is 'On'. Please change 'FSSI Refresh' to 'Off'.");
return;
} else {
var bak_adpc = Number(g_old_adpc);
@@ -510,6 +535,7 @@ function setsysnet() {
if(cmd_confirm()){
$.post("atsc_status_cfg.cgi?",postData,function(data, status){
if(status!="success"){ upgrade_imx_flg = 0; lockAllControls(false); return; } // P-5: release on failure
if(status=="success"){
SetFormDefaults();
} else{
@@ -522,6 +548,20 @@ function setsysnet() {
}
function sysresetff() {
/* P-5 (2026-08-06). Two gaps closed here, both proven against the 1.0:
(a) LOCAL-LOCKOUT: the 1.0 refuses a restart in Local mode and re-syncs the
form (its sys_set.html:926); smod posted regardless. A restart is
destructive, so it must honour the same lockout as every other write.
(b) CONTROL LOCK: the restart path locked nothing, so every Set/Upgrade
button stayed live (and green) while the unit rebooted — the owner's
hardware finding. REG-3 already built ONE lockAllControls(); the restart
simply never called it. Same single-lock-list class as the upgrades. */
if(remot_local_ctrl == 1){
cmd_confirm();
SetFormDefaults();
return;
}
var postData = "sysreset=";
var cf = document.getElementById("form_reset");
var prog_val = 1;
@@ -534,13 +574,14 @@ function sysresetff() {
if(cmd_confirm()){
upgrade_imx_flg = 1;
lockAllControls(true);
$.post("atsc_status_cfg.cgi?",postData,function(data, status){
if(status=="success"){
var t = setInterval(function() {
$('#rst_tb').hide();
$('#rst_progid').show();
$('#rst_note').text("System is now reseting. Please don't power off during the reseting. Wait for a minute and the page will automatically refreshed.");
$('#rst_note').text("The unit is now restarting. Please do not power off during the restart. Wait for a minute and the page will automatically refresh.");
var rst_prog_num = document.getElementById("rst_progid");
rst_prog_num.value = prog_val;
@@ -599,18 +640,15 @@ function uploadfile(){
}
var result=document.getElementById('result');
$('#upgrade_imx_id').attr("disabled",true);
// REG-3: ALL controls lock during an upgrade — including the FPGA/key/NTP buttons the
// old hand-typed list missed (the grey/green mismatch).
lockAllControls(true);
$('#formupgrade').attr("disabled",true);
$('#net_set_id').attr("disabled",true);
$('#para_set_id').attr("disabled",true);
$('#sys_rtc_id').attr("disabled",true);
$('#sysreset_bt_id').attr("disabled",true);
$('#timezone_set_id').attr("disabled",true);
var file = document.getElementById('Upgrade').files[0];
if (!file) {
alert('Please select a file');
$('#upgrade_imx_id').attr("disabled",false); // undo the disables above
lockAllControls(false); // undo the disables above (all of them)
$('#formupgrade').attr("disabled",false);
return
}
@@ -643,7 +681,10 @@ function uploadfile(){
// 1. Update the Text (You already have this)
result.innerHTML = "Uploading package ... " + percent + '%';
// 2. ADD THIS LINE to update the Green Bar visual
// 2. Show + update the Green Bar. Starts display:none so it is not an empty
// grey bar at rest (matches the sibling #prog_id / #rst_progid pattern);
// revealed here when upload begins, hidden again on completion below.
$("#progress").show();
document.getElementById('progress').value = percent;
if(e.loaded == e.total){
@@ -651,7 +692,7 @@ function uploadfile(){
$('#formupgrade').hide();
$('#prog_id').show();
$("#progress").hide();
$('#retnote').text("Code package has been uploaded successfully. System is now upgrading and reboot. Please don't power off the system during the upgrade. Wait for a minute and the page will automatically refreshed.");
$('#retnote').text("Code package uploaded successfully. System is now upgrading and rebooting. Please do not power off during the upgrade. Wait for a minute and the page will automatically refresh.");
SetFormDefaults();
var prog_num = document.getElementById("prog_id");
prog_num.value = widthddd;
@@ -689,7 +730,8 @@ function uploadFpgaFile(){
}
var result = document.getElementById('fpga_result');
document.getElementById('upgrade_fpga_id').disabled = true;
// REG-3: a flash locks the whole page's controls, not only its own button
lockAllControls(true);
// The .mcs is ~23 MB and takes ~10 min to land on the board's flash. The board runs a
// small forking httpd; if testTime() keeps firing update_dpd_state/get_ntp_server_state/
@@ -699,6 +741,8 @@ function uploadFpgaFile(){
// the imx6 package; the FPGA path was missing it, which is why large .mcs uploads never
// finished from the browser while a scripted POST (no background polling) did.
upgrade_imx_flg = 1;
fpga_upgrade_in_progress = 1;
lastTime = new Date().getTime();
var params = new FormData();
params.append('file', file);
@@ -706,22 +750,33 @@ function uploadFpgaFile(){
var xhr = new XMLHttpRequest();
xhr.onerror = function(){
alert('FPGA upload request failed');
document.getElementById('upgrade_fpga_id').disabled = false;
upgrade_imx_flg = 0; // let the dashboard resume
lockAllControls(false);
finishFpgaUpgrade();
};
xhr.onabort = xhr.ontimeout = function(){
document.getElementById('upgrade_fpga_id').disabled = false;
upgrade_imx_flg = 0;
lockAllControls(false);
finishFpgaUpgrade();
};
xhr.upload.onprogress = function(e){
var percent = Math.round(e.loaded / e.total * 100);
result.innerHTML = "Uploading .mcs ... " + percent + '%';
$("#fpga_progress").show(); // reveal on upload start (starts display:none, sibling pattern)
document.getElementById('fpga_progress').value = percent;
if(e.loaded == e.total){
// upload done -> the board now erases+writes the flash; track its progress
result.innerHTML = "Upload complete. Flashing FPGA... DO NOT power off.";
document.getElementById('fpga_progress').value = 0;
}
};
xhr.onload = function(){
if(xhr.status >= 200 && xhr.status < 300){
/* The CGI has accepted this request and reset the server-side status.
* Start polling now so a previous DONE result cannot be consumed. */
startFpgaFlashPoll();
}else{
alert('FPGA upload request failed');
lockAllControls(false);
finishFpgaUpgrade();
}
};
xhr.open('POST', 'fpga_upload.cgi', true);
@@ -747,26 +802,35 @@ function startFpgaFlashPoll(){
if(phase==='PENDING'){ result.innerHTML = "Preparing (stopping DPD)..."; bar.value = 0; }
else if(phase==='ERASE'){ result.innerHTML = "Erasing flash (~30s)..."; bar.value = 0; }
else if(phase==='WRITE'){ sawProgress = true; result.innerHTML = "Writing FPGA image ... " + pct + "%"; bar.value = pct; }
else if(phase==='DONE'){
else if(phase==='RELINK' || phase==='DONE'){
bar.value = 100;
result.innerHTML = "FPGA flash complete (100%). System is rebooting.";
note.innerText = "Done. FPGA reloaded + RF re-established automatically (no power cycle needed).";
clearInterval(poll); if(btn) btn.disabled = false;
result.innerHTML = "FPGA flash complete (100%). Re-establishing RF...";
note.innerText = "FPGA reloaded. Waiting for RF/JESD relink...";
}
else if(phase==='READY'){
var redirectLeft = 5;
bar.value = 100;
note.innerText = "RF re-established. Redirecting to login page...";
clearInterval(poll); lockAllControls(false); finishFpgaUpgrade();
result.innerHTML = "FPGA and RF are ready. Opening login page in " + redirectLeft + " s...";
var redirectTimer = setInterval(function(){
redirectLeft--;
if(redirectLeft <= 0){ clearInterval(redirectTimer); window.location.href = 'login.html'; }
else result.innerHTML = "FPGA and RF are ready. Opening login page in " + redirectLeft + " s...";
}, 1000);
}
else if(phase==='ERROR'){
result.innerHTML = "<span style='color:#d00;font-weight:bold'>FPGA flash FAILED (code " + ret + "). Aborted — check the device log. The board falls back to golden on cold boot.</span>";
clearInterval(poll); if(btn) btn.disabled = false;
clearInterval(poll); lockAllControls(false); finishFpgaUpgrade();
}
},
error:function(){
// the board reboots on success and drops the connection; if we had
// already seen write progress, treat a sustained drop as done-then-reboot
// FPGA reload may temporarily interrupt CGI access. Do not redirect
// until the backend explicitly publishes READY.
errAfterProgress++;
if(sawProgress && errAfterProgress >= 4){
bar.value = 100;
result.innerHTML = "FPGA flashed; board is rebooting (connection lost). Cold-restart it when it returns.";
note.innerText = "Done. FPGA reloaded + RF re-established automatically (no power cycle needed).";
clearInterval(poll); if(btn) btn.disabled = false;
result.innerHTML = "FPGA flashed. Waiting for RF/JESD relink...";
note.innerText = "Connection temporarily unavailable; retrying status check.";
}
}
});
@@ -1249,12 +1313,13 @@ function set_timezone() {
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li><a href="status_info.html" title="Status">Status</a></li>
<li class="active"><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -1274,8 +1339,15 @@ function set_timezone() {
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -1295,7 +1367,8 @@ function set_timezone() {
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -1321,16 +1394,6 @@ function set_timezone() {
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -1341,6 +1404,16 @@ function set_timezone() {
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
@@ -1445,7 +1518,7 @@ function set_timezone() {
</td>
</tr>
<tr>
<td>ADPC</td>
<td id="adpc_label">ADPC</td>
<td>
<select name="adp" id="adp">
<option value="0">Off
@@ -1548,6 +1621,13 @@ function set_timezone() {
<td>
<input maxlength="3" size="5" name="orx_manu_gain" value="">(195~255)
</td>
<td>HardwareVersion</td>
<td>
<select name="hardware_version" id="hardware_version" style="width:100px">
<option value="0">V1.0</option>
<option value="1">V1.1</option>
</select>
</td>
</tr>
<tr>
<td><input type="button" class="btn-modern" disabled id="para_set_id" alt="Set" value="Set" onclick="return set_syspara();"></td>
@@ -1596,7 +1676,7 @@ function set_timezone() {
</div>
<br>
<h2>RTC Configuration</h2>
<h2>Time &amp; Clock Configuration</h2>
<div class="box">
<form id="form_rtc">
<table>
@@ -1660,11 +1740,11 @@ function set_timezone() {
</tr>
</table>
</form>
</div>
<br>
<h2>NTP Server Configuration</h2>
<div class="box">
<!-- R4 (2026-08-11): RTC and NTP merged into ONE "Time & Clock Configuration"
section, matching the 1.0 (sys_set.html:1556-1642), which holds form_rtc and
form_ntp in a single .box separated by this hairline rule. Both forms, both
Set buttons and every field id are unchanged - only the section wrapper is. -->
<hr style="border:none; border-top:1px solid var(--border-subtle); margin:10px 0;">
<form id="form_ntp">
<table>
<tr>
@@ -1696,16 +1776,16 @@ function set_timezone() {
</div>
<br>
<h2>SYSTEM KEY</h2>
<h2>System Key</h2>
<div class="box">
<form id="form_key">
<table>
<tr>
<td>PSIP KEY</td>
<td>PSIP Key</td>
<td style="width:500px"><fieldset>
<input style="width:100px" maxlength="8" size="9" name="psip_key" id="psip_key" value="" oninput="validateHex(this)">
</fieldset></td>
<td>EAS KEY</td>
<td>EAS Key</td>
<td style="width:500px"><fieldset>
<input style="width:100px" maxlength="8" size="9" name="eas_key" id="eas_key" value="" oninput="validateHex(this)">
</fieldset></td>
@@ -1718,51 +1798,51 @@ function set_timezone() {
</div>
<br>
<h2>System Reset Configuration</h2>
<h2>Remote Restart</h2>
<div class="box">
<form id="form_reset">
<table id="rst_tb">
<tr>
<td>System Reset</td>
<td>Remote Restart</td>
<td>
<select name="sysreset" id="sysreset">
<option value="0">No
<option value="1">Yes
</select>
</td>
<td><input type="button" class="btn-modern" disabled id="sysreset_bt_id" alt="Set" value="Set" onclick="return sysresetff();"> </td>
<td><input type="button" class="btn-modern" disabled id="sysreset_bt_id" alt="Restart" value="Restart" onclick="return sysresetff();"> </td>
</tr>
</table>
</form>
</div>
<div><span id="rst_note" style="color:#0B77B7; font-size:14px" ></span></div>
<div><span id="rst_note" style="color:var(--info-text); font-size:14px" ></span></div>
<br><br>
<div>
<progress id="rst_progid" style="width:100%; margin-left:1px; display:none; color:#0bb710;" value="0" max="100"></progress>
<span id='rst_progsval' style="color:#0B77B7; font-size:18px;"></span>
<progress id="rst_progid" class="aw-progress" style="width:100%; margin-left:1px; display:none" value="0" max="100"></progress>
<span id='rst_progsval' style="color:var(--info-text); font-size:18px;"></span>
</div>
<br>
<h2>Remote Update</h2>
<h2>MCU Update (.tar.gz)</h2>
<div class="box">
<form id="formupgrade" target="myIframe" name="formupgrade" method="post" action="upload.cgi" enctype="multipart/form-data">
<div id="divupgrade" style="padding: 20px;">
<span id="LUpgrade" ></span>
<input name="Upgrade" type="file" id="Upgrade"/>
<input type="button" class="btn-modern" alt="UPGRADE" value="Upgrade" disabled id="upgrade_imx_id" onclick='uploadfile("imx")'>
<input name="Upgrade" type="file" id="Upgrade" accept=".tar.gz,.tgz"/>
<input type="button" class="btn-modern" alt="MCU UPGRADE" value="Upgrade MCU" disabled id="upgrade_imx_id" onclick='uploadfile("imx")'>
</div>
</form>
<iframe name="myIframe" id="myIframe" style="display: none;"></iframe>
</div>
<div><span id="retnote" style="color:#0B77B7; font-size:14px" ></span></div>
<div><span id="retnote" style="color:var(--info-text); font-size:14px" ></span></div>
<br><br>
<div>
<progress id="prog_id" style="width:100%; margin-left:1px; display:none; color:#0bb710;" value="0" max="100"></progress>
<progress id="prog_id" class="aw-progress" style="width:100%; margin-left:1px; display:none" value="0" max="100"></progress>
<br><br>
<span id='result' style="color: #0B77B7 ;font-size:18px"></span>
<progress id='progress' style="width:100%; margin-left:1px; color:#0B77B7; font-size:18px" value="0" max="100"></progress>
<span id='result' style="color:var(--info-text);font-size:18px"></span>
<progress id='progress' class="aw-progress" style="width:100%; margin-left:1px; display:none" value="0" max="100"></progress>
</div>
<br>
@@ -1777,11 +1857,11 @@ function set_timezone() {
</form>
<iframe name="myIframeFpga" id="myIframeFpga" style="display: none;"></iframe>
</div>
<div><span id="fpga_retnote" style="color:#0B77B7; font-size:14px"></span></div>
<div><span id="fpga_retnote" style="color:var(--info-text); font-size:14px"></span></div>
<br>
<div>
<span id='fpga_result' style="color: #0B77B7 ;font-size:18px"></span>
<progress id='fpga_progress' style="width:100%; margin-left:1px; color:#0B77B7; font-size:18px" value="0" max="100"></progress>
<span id='fpga_result' style="color:var(--info-text);font-size:18px"></span>
<progress id='fpga_progress' class="aw-progress" style="width:100%; margin-left:1px; display:none" value="0" max="100"></progress>
</div>
</main>
Executable → Regular
+352 -77
View File
@@ -3,8 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<link href="css/styles.css" rel="stylesheet" media="screen">
<title id="webtitle">Digital Exciter</title>
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
<script src="js/dashboard.js"></script>
@@ -13,13 +14,26 @@
.spec-toolbar { display:flex; flex-wrap:wrap; gap:18px; align-items:center; margin-bottom:10px; }
.spec-toolbar label { font-size:13px; color:inherit; }
.spec-readouts { display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
.spec-readout { min-width:130px; padding:8px 12px; border:1px solid rgba(148,163,184,0.35); border-radius:6px; }
.spec-readout .k { display:block; font-size:11px; letter-spacing:.04em; text-transform:uppercase; opacity:.7; }
.spec-readout .v { display:block; font-size:18px; font-weight:600; }
#spec_canvas { width:100%; height:420px; display:block; background:#0b1220; border-radius:6px; }
#eye_canvas { width:100%; height:380px; display:block; background:#0b1220; border-radius:6px; }
.spec-readout { min-width:110px; padding:6px 8px; border:1px solid rgba(148,163,184,0.35); border-radius:6px; }
.spec-readout-pair { display:flex; gap:10px; flex:0 0 auto; }
.spec-readout .k { display:block; font-size:10px; letter-spacing:.04em; text-transform:uppercase; opacity:.7; }
.spec-readout .v { display:block; font-size:16px; font-weight:600; }
#spectrum_panel .spec-readouts { gap:8px; overflow-x:auto; padding-bottom:2px; }
#spectrum_panel .spec-readout-pair { gap:8px; }
#spec_canvas { width:100%; height:420px; display:block; background:var(--plot-bg); border-radius:6px; }
#eye_canvas { width:100%; height:380px; display:block; background:var(--plot-bg); border-radius:6px; }
#eye_state { font-size:13px; opacity:.7; margin-left:auto; }
#spec_state { font-size:13px; opacity:.7; margin-left:auto; }
.spec-tabs { display:flex; gap:0; border-bottom:1px solid rgba(148,163,184,.35); margin-bottom:16px; }
.spec-tab { appearance:none; border:0; border-bottom:3px solid transparent; background:transparent; color:inherit; cursor:pointer; font:inherit; font-weight:600; padding:10px 18px; }
.spec-tab:hover { background:rgba(148,163,184,.10); }
.spec-tab.active { border-bottom-color:var(--status-neutral); color:var(--status-neutral); }
.spectrum-panel[hidden] { display:none; }
.fssi-refresh-notice { display:none; padding:44px 18px; text-align:center; color:#fbbf24; font-weight:600; }
.spectrum-panel.fssi-refresh-active canvas,
.spectrum-panel.fssi-refresh-active .spec-readouts,
.spectrum-panel.fssi-refresh-active .spec-toolbar { display:none; }
.spectrum-panel.fssi-refresh-active .fssi-refresh-notice { display:block; }
</style>
<script>
var lastTime = new Date().getTime();
@@ -46,7 +60,11 @@
$.get("atsc_status_cfg.cgi?sys_cfg=&", function(data, status){
if (typeof data === 'object' && data !== null) return;
var table = String(data).split("|");
document.title = table[0].split("#")[11];
var sysCfg = table[0].split("#");
var title = sysCfg[6] || "Digital Exciter";
document.title = title;
document.getElementById('webtitle').textContent = title;
document.getElementById('webtitle_header').textContent = title;
});
}
@@ -69,23 +87,74 @@
var specFails = 0;
var Y_TOP = 30, Y_BOT = -80; // dB rel in-band flat density
var SH_LO = 3.5, SH_HI = 4.8; // shoulder band the app takes the median over
/* PR19 measurement windows: P0=-0.25..+0.25 MHz,
* PL=-3.5..-3.0 MHz and PR=+3.0..+3.5 MHz. */
var SH_LO = 3.0, SH_HI = 3.5;
var activeView = 'spectrum';
var fssiRefreshActive = false;
function specInit(){
window.addEventListener('resize', function(){ if (specLast) drawSpectrum(specLast); else drawGrid(); });
drawGrid();
specRestart();
eyeRestart();
window.addEventListener('resize', function(){
if (activeView === 'spectrum') {
if (specLast) drawSpectrum(specLast); else drawGrid();
} else if (eyeLastData) {
drawEye(eyeLastData);
}
});
selectSpectrumView('spectrum');
}
/* PR12: the Refresh/Pause/Peak-hold toolbar and the status lines were
* removed on request, so these helpers tolerate the elements being absent
* (fixed 1 s refresh, never paused, no peak-hold). */
function selectSpectrumView(view) {
activeView = (view === 'eye') ? 'eye' : 'spectrum';
var spectrumActive = activeView === 'spectrum';
document.getElementById('spectrum_panel').hidden = !spectrumActive;
document.getElementById('eye_panel').hidden = spectrumActive;
document.getElementById('spectrum_tab').classList.toggle('active', spectrumActive);
document.getElementById('eye_tab').classList.toggle('active', !spectrumActive);
document.getElementById('spectrum_tab').setAttribute('aria-selected', spectrumActive);
document.getElementById('eye_tab').setAttribute('aria-selected', !spectrumActive);
if (fssiRefreshActive) {
setState('FSSI Refresh ON');
setEyeState('FSSI Refresh ON');
return;
}
if (spectrumActive) {
if (EYE_TIMER) clearInterval(EYE_TIMER);
EYE_TIMER = null;
if (specLast) drawSpectrum(specLast); else drawGrid();
specRestart();
} else {
if (SPEC_TIMER) clearInterval(SPEC_TIMER);
SPEC_TIMER = null;
if (eyeLastData) drawEye(eyeLastData);
eyeRestart();
}
}
/* Spectrum controls are shared by both tabs. */
function elChecked(id){ var e = document.getElementById(id); return !!(e && e.checked); }
function specRateMs(){ var e = document.getElementById('spec_rate'); return e ? Number(e.value) : 1000; }
function spectrumControlsChanged(){
if (activeView === 'spectrum') {
specRestart();
} else {
eyeRestart();
if (elChecked('spec_pause')) setEyeState('paused');
}
}
function peakHoldChanged(){
specPeak = null;
if (activeView === 'spectrum' && specLast) drawSpectrum(specLast);
}
function specRestart(){
if (SPEC_TIMER) clearInterval(SPEC_TIMER);
if (activeView !== 'spectrum') return;
if (fssiRefreshActive) { setState('FSSI Refresh ON'); return; }
if (elChecked('spec_pause')) { setState('paused'); return; }
specFetch();
SPEC_TIMER = setInterval(specFetch, specRateMs());
@@ -94,6 +163,26 @@
function setState(s){ var e = document.getElementById('spec_state'); if (e) e.innerText = s; }
function setEyeState(s){ var e = document.getElementById('eye_state'); if (e) e.innerText = s; }
window.setSpectrumRefreshActive = function(active){
active = !!active;
if (fssiRefreshActive === active) return;
fssiRefreshActive = active;
document.getElementById('spectrum_panel').classList.toggle('fssi-refresh-active', active);
document.getElementById('eye_panel').classList.toggle('fssi-refresh-active', active);
if (active) {
if (SPEC_TIMER) clearInterval(SPEC_TIMER);
if (EYE_TIMER) clearInterval(EYE_TIMER);
SPEC_TIMER = null;
EYE_TIMER = null;
setState('FSSI Refresh ON');
setEyeState('FSSI Refresh ON');
} else if (activeView === 'spectrum') {
specRestart();
} else {
eyeRestart();
}
};
function specFetch(){
$.ajax({url: 'iq_spectrum.cgi?t=' + Date.now(), dataType: 'json', cache: false})
.done(function(d){
@@ -102,18 +191,73 @@
setState(d && d.error === 'nodata' ? 'no window captured yet' : 'error');
return;
}
if (fssiRefreshActive) return;
specFails = 0;
specLast = d;
updateReadouts(d);
drawSpectrum(d);
setState('window ' + d.wid + ' | updated ' + new Date().toLocaleTimeString());
if (activeView === 'spectrum') {
updateReadouts(d);
drawSpectrum(d);
setState('window ' + d.wid + ' | updated ' + new Date().toLocaleTimeString());
}
})
.fail(function(){ specFails++; setState('fetch failed (' + specFails + ')'); });
}
function updateReadouts(d){
/* PR12: the readout cards under the spectrum were removed; d.shoulder
* and d.pilot are still drawn on the plot itself in drawSpectrum(). */
/* The PR19 values are positive P0-P{L,R} attenuation magnitudes. Apply the
established +11 dB display correction so these cards match Status/LCD. */
var IMD_PILOT_OFFSET = 11; // dB; documented pilot offset (1.0 status_info_new.html)
/* Threshold colouring for the three shoulder-suppression readouts, to match
the dashboard's scheme (owner, 2026-08-17). The dashboard colours SNR/LIMD/
UIMD via getSnrStatus/getImdStatus; SNR here is already coloured below
(line ~303). LIMD and UIMD use the dashboard's IMD rule directly: >= 47 dBc
green, else red (they already carry the +11 offset so they match the
dashboard values). Shoulder has NO pre-existing threshold anywhere; owner
ruled it uses the SAME 47 dBc line as the IMDs on its ABSOLUTE value (it is
the same kind of dBc shoulder-suppression measure, only signed negative). All
OTHER spectrum readouts (FSSI, Pilot, Peak, RBW, ...) stay plain — no invented
thresholds. A '--'/NaN reading clears the colour (stays default), never red. */
function imdColour(v){ // dBc magnitude vs the 47 dBc pass line
return (v === null) ? '' : (Math.abs(v) >= 47 ? 'var(--accent-green)' : 'var(--status-err)');
}
var shoulderVal = setReadout('rd_shoulder', d.shoulder + IMD_PILOT_OFFSET, 1);
var limdVal = setReadout('rd_limd', d.limd + IMD_PILOT_OFFSET, 1);
var uimdVal = setReadout('rd_uimd', d.uimd + IMD_PILOT_OFFSET, 1);
var shoulderEl = document.getElementById('rd_shoulder');
var limdEl = document.getElementById('rd_limd');
var uimdEl = document.getElementById('rd_uimd');
if (shoulderEl) shoulderEl.style.color = imdColour(shoulderVal);
if (limdEl) limdEl.style.color = imdColour(limdVal);
if (uimdEl) uimdEl.style.color = imdColour(uimdVal);
setReadout('rd_pilot', d.pilot, 4);
setReadout('rd_peak', d.peak, 1);
setReadout('rd_rbw', d.rbw_khz, 2);
setReadout('rd_ms', d.ms, 2);
var packets = document.getElementById('rd_pkts');
if (packets) {
packets.innerText = (d.pkts === undefined || d.npkt === undefined) ? '--' : d.pkts + '/' + d.npkt;
packets.style.color = (d.pkts === d.npkt) ? '' : 'var(--status-err)';
}
var field = document.getElementById('rd_field');
var contiguous = readNumber(d.ms);
if (field) {
field.innerText = (contiguous === null) ? '--' : (contiguous > 24.2 ? 'yes' : 'no');
field.style.color = (contiguous === null) ? '' : (contiguous > 24.2 ? 'var(--accent-green)' : 'var(--status-err)');
}
}
function readNumber(value){
var number = Number(value);
return isFinite(number) ? number : null;
}
function setReadout(id, value, decimals){
var element = document.getElementById(id);
var number = readNumber(value);
if (element) element.innerText = (number === null) ? '--' : number.toFixed(decimals);
return number;
}
@@ -128,6 +272,8 @@
function eyeRestart(){
if (EYE_TIMER) clearInterval(EYE_TIMER);
if (activeView !== 'eye') return;
if (fssiRefreshActive) { setEyeState('FSSI Refresh ON'); return; }
if (elChecked('spec_pause')) return;
eyeFetch();
EYE_TIMER = setInterval(eyeFetch, specRateMs());
@@ -142,24 +288,47 @@
return;
}
if (!d.sync){ setEyeState('no symbol lock'); return; }
if (fssiRefreshActive) return;
eyeFails = 0;
eyeLastData = d;
eyeReadouts(d);
if (d.wid !== eyeWid){ eyeWid = d.wid; drawEye(d); }
setEyeState('window ' + d.wid + ' | ' + d.nsym + ' symbols');
if (activeView === 'eye') {
eyeReadouts(d);
if (d.wid !== eyeWid){ eyeWid = d.wid; drawEye(d); }
setEyeState('window ' + d.wid + ' | ' + d.nsym + ' symbols');
}
})
.fail(function(){ eyeFails++; setEyeState('fetch failed (' + eyeFails + ')'); });
}
function eyeReadouts(d){
/* PR12: only SNR/MER and SNR raw remain on request. */
document.getElementById('rd_snr').innerText = d.snr_db.toFixed(2);
document.getElementById('rd_snrraw').innerText = (d.snr_raw_db === undefined) ? '--' : d.snr_raw_db.toFixed(2);
var eyeHeights = Array.isArray(d.eye_h) ? d.eye_h.map(readNumber).filter(function(value){ return value !== null; }) : [];
var sigma = Array.isArray(d.sigma) ? d.sigma.map(readNumber).filter(function(value){ return value !== null; }) : [];
var minh = eyeHeights.length ? Math.min.apply(null, eyeHeights) : null;
var sAvg = sigma.length ? sigma.reduce(function(a, b){ return a + b; }, 0) / sigma.length : null;
var snr = setReadout('rd_snr', d.snr_db, 2);
setReadout('rd_eyeh', minh, 2);
setReadout('rd_eyew', d.eye_w_ui, 3);
setReadout('rd_tau', d.tau, 4);
setReadout('rd_segsync', d.segsync_ratio, 1);
setReadout('rd_coff', d.carrier_off_hz, 3);
setReadout('rd_snrraw', d.snr_raw_db, 2);
var jitter = setReadout('rd_gjit', d.gain_jitter_pct, 2);
var sn = document.getElementById('rd_snr');
sn.style.color = (d.snr_db >= 27) ? '#22c55e' : (d.snr_db >= 20 ? '#f59e0b' : '#ef4444');
if (sn) sn.style.color = (snr === null) ? '' : (snr >= 27 ? 'var(--accent-green)' : (snr >= 20 ? 'var(--status-warn)' : 'var(--status-err)'));
var height = document.getElementById('rd_eyeh');
if (height) height.style.color = (minh === null || sAvg === null) ? '' :
((minh > 0.6 && minh > 2 - 6 * sAvg - 0.35) ? 'var(--accent-green)' : (minh > 0.3 ? 'var(--status-warn)' : 'var(--status-err)'));
var jitterElement = document.getElementById('rd_gjit');
if (jitterElement) jitterElement.style.color = (jitter === null) ? '' :
(jitter < 1.0 ? 'var(--accent-green)' : (jitter < 4 ? 'var(--status-warn)' : 'var(--status-err)'));
}
function drawEye(d){
var P = PLOT();
var c = document.getElementById('eye_canvas');
var dpr = window.devicePixelRatio || 1;
var W = c.clientWidth, H = c.clientHeight;
@@ -169,7 +338,7 @@
var g = c.getContext('2d');
g.setTransform(dpr,0,0,dpr,0,0);
g.clearRect(0,0,W,H);
g.fillStyle = '#0b1220'; g.fillRect(0,0,W,H);
g.fillStyle = P.bg; g.fillRect(0,0,W,H);
var cols = d.eye.cols, rows = d.eye.rows, amp = d.eye.amp;
var bin = atob(d.eye.data), x, y;
@@ -196,30 +365,74 @@
g.drawImage(off, L, T, W - L - R, H - T - B);
function yOfLvl(v){ return T + (amp - v) / (2 * amp) * (H - T - B); }
g.strokeStyle = 'rgba(57,208,255,0.45)'; g.lineWidth = 0.7;
g.strokeStyle = P.eyeLevel; g.lineWidth = 0.7;
[-7,-5,-3,-1,1,3,5,7].forEach(function(v){
var yy = yOfLvl(v);
g.beginPath(); g.moveTo(L,yy); g.lineTo(W-R,yy); g.stroke();
});
var xc = L + (W - L - R) / 2; /* decision instant = centre column */
g.strokeStyle = '#a3e635'; g.setLineDash([4,3]);
g.strokeStyle = P.decision; g.setLineDash([4,3]);
g.beginPath(); g.moveTo(xc,T); g.lineTo(xc,H-B); g.stroke();
g.setLineDash([]);
g.fillStyle = '#94a3b8'; g.font = '11px sans-serif';
g.fillStyle = P.axisLbl; g.font = '11px sans-serif';
g.textAlign = 'right'; g.textBaseline = 'middle';
[-7,-5,-3,-1,1,3,5,7].forEach(function(v){ g.fillText(v.toFixed(0), L-6, yOfLvl(v)); });
g.textAlign = 'center'; g.textBaseline = 'top';
[-1,-0.5,0,0.5,1].forEach(function(u){
g.fillText(u.toFixed(1), L + (u+1)/2*(W-L-R), H-B+5);
});
g.fillStyle = '#64748b'; g.textBaseline = 'bottom';
g.fillStyle = P.axisTtl; g.textBaseline = 'bottom';
g.fillText('symbol periods (UI) - decision instant at 0', (L+W-R)/2, H-2);
g.save(); g.translate(12,(T+H-B)/2); g.rotate(-Math.PI/2);
g.textBaseline = 'top'; g.fillText('normalised I level', 0, 0); g.restore();
}
/* ---- canvas ---- */
/* -----------------------------------------------------------------------
R5 (2026-08-11): colour lives in the stylesheet, never in this file.
Read once per draw and cached until the theme class changes -- at a 1 Hz
redraw the cost is nil, and it means a family colour ruling reaches these
plots without anyone editing JavaScript again.
Each read carries the pre-R5 literal as its fallback ON PURPOSE: this
product has already shipped a unit running a STALE styles.css against a
correct package (S3, the white-banner round). If that happens again the
plots degrade to exactly today's appearance instead of to black.
----------------------------------------------------------------------- */
var PLOT = (function(){
var cache = null, cacheKey = null;
return function(){
var key = document.documentElement.className;
if (cache && cacheKey === key) return cache;
var cs = getComputedStyle(document.documentElement);
function v(name, fallback){
var got = cs.getPropertyValue(name);
got = got ? got.trim() : '';
return got || fallback;
}
cache = {
bg: v('--plot-bg', '#0b1220'),
grid: v('--plot-grid', '#1e293b'),
zero: v('--plot-grid-zero', '#334155'),
axisLbl: v('--plot-axis-label', '#94a3b8'),
axisTtl: v('--plot-axis-title', '#64748b'),
trace: v('--plot-trace', '#38bdf8'),
hold: v('--plot-trace-hold', '#f59e0b'),
marker: v('--plot-marker', '#f87171'),
band: v('--plot-band', 'rgba(56,189,248,0.10)'),
eyeLevel: v('--plot-eye-level', 'rgba(57,208,255,0.45)'),
decision: v('--plot-eye-decision', '#a3e635'),
traceError: v('--plot-trace-error', '#eab308')
};
cacheKey = key;
return cache;
};
})();
/* NOTE: the eye's density colormap (40+255v^0.75 / 20+200v^1.6 / 60+120v^2.6)
is an ALGORITHM, not a palette entry, and is left exactly as shipped. It is
the reason the plot field stays dark in both themes -- see styles.css. */
function specGeom(){
var c = document.getElementById('spec_canvas');
var dpr = window.devicePixelRatio || 1;
@@ -235,34 +448,39 @@
function yOf(m, db){ return m.t + (Y_TOP - db)/(Y_TOP - Y_BOT) * (m.h - m.t - m.b); }
function drawGrid(fmin, fmax){
var P = PLOT();
if (fmin === undefined){ fmin = -5; fmax = 5; }
var m = specGeom(), g = m.g, f, db;
g.clearRect(0,0,m.w,m.h);
g.fillStyle = '#0b1220'; g.fillRect(0,0,m.w,m.h);
g.fillStyle = P.bg; g.fillRect(0,0,m.w,m.h);
g.fillStyle = 'rgba(56,189,248,0.10)'; // the band the shoulder is measured in
g.fillStyle = P.band; // the band the shoulder is measured in
[[SH_LO,SH_HI],[-SH_HI,-SH_LO]].forEach(function(b){
var x0 = xOf(m,Math.max(b[0],fmin),fmin,fmax), x1 = xOf(m,Math.min(b[1],fmax),fmin,fmax);
g.fillRect(x0, m.t, x1-x0, m.h-m.t-m.b);
});
g.fillStyle = 'rgba(163,230,53,0.08)'; // P0 window
g.fillRect(xOf(m,Math.max(-0.25,fmin),fmin,fmax), m.t,
xOf(m,Math.min(0.25,fmax),fmin,fmax)-xOf(m,Math.max(-0.25,fmin),fmin,fmax),
m.h-m.t-m.b);
g.lineWidth = 1;
g.font = '11px sans-serif';
g.textAlign = 'center'; g.textBaseline = 'top';
for (f = Math.ceil(fmin); f <= fmax; f++){
var x = Math.round(xOf(m,f,fmin,fmax)) + 0.5;
g.strokeStyle = '#1e293b';
g.strokeStyle = P.grid;
g.beginPath(); g.moveTo(x,m.t); g.lineTo(x,m.h-m.b); g.stroke();
g.fillStyle = '#94a3b8'; g.fillText(f.toFixed(0), x, m.h-m.b+5);
g.fillStyle = P.axisLbl; g.fillText(f.toFixed(0), x, m.h-m.b+5);
}
g.textAlign = 'right'; g.textBaseline = 'middle';
for (db = Y_TOP; db >= Y_BOT; db -= 10){
var y = Math.round(yOf(m,db)) + 0.5;
g.strokeStyle = (db === 0) ? '#334155' : '#1e293b';
g.strokeStyle = (db === 0) ? P.zero : P.grid;
g.beginPath(); g.moveTo(m.l,y); g.lineTo(m.w-m.r,y); g.stroke();
g.fillStyle = '#94a3b8'; g.fillText(db.toFixed(0), m.l-6, y);
g.fillStyle = P.axisLbl; g.fillText(db.toFixed(0), m.l-6, y);
}
g.fillStyle = '#64748b'; g.textAlign = 'center'; g.textBaseline = 'bottom';
g.fillStyle = P.axisTtl; g.textAlign = 'center'; g.textBaseline = 'bottom';
g.fillText('MHz from carrier', (m.l+m.w-m.r)/2, m.h-2);
g.save(); g.translate(12,(m.t+m.h-m.b)/2); g.rotate(-Math.PI/2);
g.textBaseline = 'top'; g.fillText('dB rel. in-band flat density', 0, 0); g.restore();
@@ -282,23 +500,36 @@
}
function drawSpectrum(d){
var P = PLOT();
var fmin = d.f0, fmax = d.f0 + (d.n-1)*d.df;
var m = drawGrid(fmin, fmax), i;
if (elChecked('spec_hold')){
if (!specPeak || specPeak.length !== d.psd.length) specPeak = d.psd.slice();
else for (i = 0; i < d.psd.length; i++) if (d.psd[i] > specPeak[i]) specPeak[i] = d.psd[i];
trace(m, specPeak, d.f0, d.df, fmin, fmax, '#f59e0b', 1);
trace(m, specPeak, d.f0, d.df, fmin, fmax, P.hold, 1);
} else specPeak = null;
trace(m, d.psd, d.f0, d.df, fmin, fmax, '#38bdf8', 1.2);
trace(m, d.psd, d.f0, d.df, fmin, fmax, P.trace, 1.2);
var y = yOf(m, d.shoulder); // measured shoulder level
m.g.strokeStyle = '#f87171'; m.g.setLineDash([5,4]); m.g.lineWidth = 1;
m.g.beginPath(); m.g.moveTo(m.l,y); m.g.lineTo(m.w-m.r,y); m.g.stroke();
m.g.setLineDash([]);
m.g.fillStyle = '#f87171'; m.g.textAlign = 'left'; m.g.textBaseline = 'bottom';
m.g.fillText('shoulder ' + d.shoulder.toFixed(1) + ' dB', m.l+6, y-3);
/* PR19: show the two uncorrected P0-PL/P0-PR shoulder results. The
* dashboard's LIMD/UIMD adds the established +11 dB correction, so its
* value is exactly 11 dB above the matching label here. Quantise to the
* same 0.5 dB resolution used by the status CGI. */
function drawShoulder(fa, fb, level, value, label, align){
var y = yOf(m, Math.max(Math.min(level,Y_TOP),Y_BOT));
var x0 = xOf(m,fa,fmin,fmax), x1 = xOf(m,fb,fmin,fmax);
m.g.strokeStyle = P.marker; m.g.setLineDash([5,4]); m.g.lineWidth = 1;
m.g.beginPath(); m.g.moveTo(x0,y); m.g.lineTo(x1,y); m.g.stroke();
m.g.setLineDash([]);
m.g.fillStyle = P.marker; m.g.textAlign = align; m.g.textBaseline = 'bottom';
m.g.fillText(label + ' ' + (Math.round(value*2)/2).toFixed(1) + ' dB',
align === 'left' ? x0+3 : x1-3, y-3);
}
if (d.pl_db !== undefined && d.limd !== undefined)
drawShoulder(-3.5,-3.0,d.pl_db,d.limd,'L shoulder','left');
if (d.pr_db !== undefined && d.uimd !== undefined)
drawShoulder(3.0,3.5,d.pr_db,d.uimd,'R shoulder','right');
/* PR12: pilot vertical marker removed on request (it covered the pilot
* peak). The pilot frequency is still available in d.pilot. */
@@ -311,7 +542,7 @@
/* DISABLED on request (2026-07-25): drop the leading `false &&` to restore */
if (false && eyeLastData && eyeLastData.err && eyeLastData.err.db){
var er = eyeLastData.err, g2 = m.g, started = false, ei;
g2.strokeStyle = '#eab308'; g2.lineWidth = 1.4; g2.beginPath();
g2.strokeStyle = P.traceError; g2.lineWidth = 1.4; g2.beginPath();
for (ei = 0; ei < er.db.length; ei++){
var ef = er.f0 + ei*er.df;
if (ef < fmin || ef > fmax){ started = false; continue; }
@@ -498,12 +729,13 @@
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li class="active"><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li class="active"><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -523,8 +755,15 @@
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -544,7 +783,8 @@
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -570,16 +810,6 @@
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -590,6 +820,16 @@
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
@@ -670,27 +910,62 @@
<main class="app-main">
<h2>ORx Receive Spectrum</h2>
<div class="box">
<!-- PR12: Refresh/Pause/Peak-hold toolbar + status line removed on
request (fixed 1 s refresh). Readout cards were removed earlier;
the on-plot shoulder line and yellow error trace remain. -->
<canvas id="spec_canvas"></canvas>
<div class="spec-tabs" role="tablist" aria-label="Spectrum display">
<button type="button" class="spec-tab active" id="spectrum_tab" role="tab" aria-selected="true" onclick="selectSpectrumView('spectrum')">Output Spectrum</button>
<button type="button" class="spec-tab" id="eye_tab" role="tab" aria-selected="false" onclick="selectSpectrumView('eye')">8VSB Eye Diagram</button>
</div>
<h2>8VSB Eye Diagram</h2>
<div class="box">
<section class="spectrum-panel" id="spectrum_panel" role="tabpanel" aria-labelledby="spectrum_tab">
<div class="box">
<div class="fssi-refresh-notice">FSSI Refresh is ON. Spectrum and Eye Diagram are suspended.</div>
<div class="spec-toolbar">
<label>Refresh
<select id="spec_rate" onchange="spectrumControlsChanged()">
<option value="500">0.5 s</option>
<option value="1000" selected>1 s</option>
<option value="2000">2 s</option>
<option value="5000">5 s</option>
</select>
</label>
<label><input type="checkbox" id="spec_pause" onchange="spectrumControlsChanged()"> Pause</label>
<label><input type="checkbox" id="spec_hold" onchange="peakHoldChanged()"> Peak hold</label>
<span id="spec_state">starting&hellip;</span>
</div>
<canvas id="spec_canvas"></canvas>
<div class="spec-readouts">
<div class="spec-readout" title="ORx front-end signal strength from the FAGC gain index. A path; A / B when the FSSI refresh switch is on."><span class="k">FSSI</span><span class="v"><span id="rd_fssi">--</span> %</span></div>
<div class="spec-readout" title="Shoulder level, dBc (signed; includes the −11 dB pilot offset)."><span class="k">Shoulder</span><span class="v"><span id="rd_shoulder">--</span> dBc</span></div>
<div class="spec-readout-pair">
<div class="spec-readout" title="Lower shoulder IMD, dBc (includes the +11 dB pilot offset; matches the dashboard LIMD)."><span class="k">LIMD</span><span class="v"><span id="rd_limd">--</span> dBc</span></div>
<div class="spec-readout" title="Upper shoulder IMD, dBc (includes the +11 dB pilot offset; matches the dashboard UIMD)."><span class="k">UIMD</span><span class="v"><span id="rd_uimd">--</span> dBc</span></div>
</div>
<div class="spec-readout"><span class="k">Pilot</span><span class="v"><span id="rd_pilot">--</span> MHz</span></div>
<div class="spec-readout"><span class="k">Peak</span><span class="v"><span id="rd_peak">--</span> dB</span></div>
<div class="spec-readout"><span class="k">RBW</span><span class="v"><span id="rd_rbw">--</span> kHz</span></div>
<div class="spec-readout"><span class="k">Packets</span><span class="v" id="rd_pkts">--</span></div>
<div class="spec-readout"><span class="k">Contiguous</span><span class="v"><span id="rd_ms">--</span> ms</span></div>
<div class="spec-readout"><span class="k">&gt; 1 field</span><span class="v" id="rd_field">--</span></div>
</div>
</div>
</section>
<section class="spectrum-panel" id="eye_panel" role="tabpanel" aria-labelledby="eye_tab" hidden>
<div class="box">
<div class="fssi-refresh-notice">FSSI Refresh is ON. Spectrum and Eye Diagram are suspended.</div>
<!-- PR12: eye caption + status line removed on request. -->
<canvas id="eye_canvas"></canvas>
<div class="spec-readouts">
<div class="spec-readout" title="MER, I-path definition: 10log10(sum d^2 / sum (r-d)^2)"><span class="k">SNR / MER</span><span class="v"><span id="rd_snr">--</span> dB</span></div>
<div class="spec-readout" title="MER before the ORx AGC gain steps are tracked out. The gap to SNR/MER is what the observation receiver costs you, not the transmitter."><span class="k">SNR raw</span><span class="v"><span id="rd_snrraw">--</span> dB</span></div>
<!-- PR13: FSSI from the ORx FAGC gain index (255->0%, 195->100%).
Value comes from the shared 3 s dashboard poll (dashboard.js),
not from iq_eye.cgi. Shows "A / B" when FSSI refresh is on. -->
<div class="spec-readout" title="ORx front-end signal strength from the FAGC gain index (gain index 255 -> 0%, 195 -> 100%). A path; A / B when the FSSI refresh switch is on."><span class="k">FSSI</span><span class="v"><span id="rd_fssi">--</span> %</span></div>
<div class="spec-readout" title="Smallest of the 7 eye openings at the decision instant."><span class="k">Min eye height</span><span class="v" id="rd_eyeh">--</span></div>
<div class="spec-readout" title="Time span over which the central eye stays open."><span class="k">Eye width</span><span class="v"><span id="rd_eyew">--</span> UI</span></div>
<div class="spec-readout" title="Sampling phase offset."><span class="k">&tau;0</span><span class="v"><span id="rd_tau">--</span> sa</span></div>
<div class="spec-readout" title="Segment synchronization correlation ratio."><span class="k">Seg sync</span><span class="v" id="rd_segsync">--</span></div>
<div class="spec-readout" title="Pilot phase drift across the window."><span class="k">Carrier offset</span><span class="v"><span id="rd_coff">--</span> Hz</span></div>
<div class="spec-readout" title="MER before ORx AGC gain steps are tracked out."><span class="k">SNR raw</span><span class="v"><span id="rd_snrraw">--</span> dB</span></div>
<div class="spec-readout" title="RMS of ORx RF AGC gain steps over the window."><span class="k">ORx AGC jitter</span><span class="v"><span id="rd_gjit">--</span> %</span></div>
</div>
</div>
</div>
</section>
</main>
</div>
+97 -31
View File
@@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
<script src="js/dashboard.js"></script>
@@ -58,10 +59,66 @@
var table = data.split("|");
g_remot_local_ctrl = Number(table[0].split("#")[5]);
document.title = table[0].split("#")[11];
/* P-4b (2026-08-06): the title write here — document.title =
table[0].split("#")[11] — is DELETED. On smod index 11 is ab_mode
(the FSSI Refresh switch), not the web title. This page already sets
the title from its web_title section below. Round 3 fixed only
status_info; this is the rest of that class. */
});
}
/* ==========================================================================
R4 (2026-08-11, owner ruling) — NO UNNAMED VALUE MAY BE POSTED
==========================================================================
Protocol / Length / Type each carried a third option, "Invalid", and the page
clamped any out-of-range firmware value into it (if(i >= 2) i = 2). The owner
challenged the premise and was right: these are SETTINGS. The write path posts
the raw select value (tsoip_setcfg, "#"+forms.pktp.value), so an operator who
pressed Set while a control sat on "Invalid" would send 2 back to the unit — a
value this page cannot name. Disabling the option would NOT have closed that:
a disabled option can still be the selected one and .value still reads 2.
So the option is deleted, and the read path is made honest instead: a value we
recognise selects its option; anything else leaves the control UNSET, states the
reason, and holds this section's Set until the operator picks a real value. The
guard only ever blocks; it releases as soon as all three resolve (and never in
Local mode, where the button is locked for a different reason). */
var TS_ENUM_IDS = ['pktp', 'pklen', 'pkrec'];
function setEnumFromUnit(sel, i) {
if (!sel) return false;
if (i >= 0 && i < sel.options.length) { sel.selectedIndex = i; return true; }
sel.selectedIndex = -1; // no phantom slot to land on
return false;
}
function tsEnumsResolved() {
for (var k = 0; k < TS_ENUM_IDS.length; k++) {
var sel = document.getElementById(TS_ENUM_IDS[k]);
if (!sel || sel.selectedIndex < 0) return false;
}
return true;
}
function applyTsEnumGuard() {
var ok = tsEnumsResolved();
var note = document.getElementById('ts_enum_note');
var btn = document.getElementById('setcfg_id1');
if (note) note.style.display = ok ? 'none' : 'block';
if (!btn) return;
if (!ok) { btn.disabled = true; }
else if (g_remot_local_ctrl != 1) { btn.disabled = false; }
}
function attachTsEnumGuard() {
for (var k = 0; k < TS_ENUM_IDS.length; k++) {
var sel = document.getElementById(TS_ENUM_IDS[k]);
if (sel) sel.addEventListener('change', applyTsEnumGuard);
}
applyTsEnumGuard();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', attachTsEnumGuard);
} else {
attachTsEnumGuard();
}
function SetFormDefaults() {
// 1. DISABLE BUTTONS START (only in Remote mode)
if(g_remot_local_ctrl == 1) {
@@ -150,17 +207,11 @@
var lpk_para = Number(tb_systs[ops]);
ops++;
i = (lpk_para&0xff0000)>>16;
if(i >= 2) i = 2;
fcfg1.pktp.selectedIndex = i;
i = (lpk_para&0x00ff00)>>8;
if(i >= 2) i = 2;
fcfg1.pklen.selectedIndex = i;
i = (lpk_para&0x0000ff);
if(i >= 2) i = 2;
fcfg1.pkrec.selectedIndex = i;
/* R4 (2026-08-11): the three clamps that parked an unrecognised firmware value on
a phantom "Invalid" option are gone, and so is the option. See setEnumFromUnit. */
setEnumFromUnit(fcfg1.pktp, (lpk_para&0xff0000)>>16);
setEnumFromUnit(fcfg1.pklen, (lpk_para&0x00ff00)>>8);
setEnumFromUnit(fcfg1.pkrec, (lpk_para&0x0000ff));
fcfg1.fec_swh.selectedIndex=Number(tb_systs[ops]);
ops++;
@@ -186,6 +237,7 @@
// Remote mode: enable button
$('#setnet_id_1, #setcfg_id1').attr("disabled", false).val("Set");
}
applyTsEnumGuard(); // R4: re-assert after every readback — only ever blocks
if (typeof setFormSnapshotUpdate === 'function') setFormSnapshotUpdate();
@@ -514,12 +566,13 @@ function cmd_confirm() {
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li class="active"><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -539,8 +592,15 @@ function cmd_confirm() {
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -560,7 +620,8 @@ function cmd_confirm() {
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -586,16 +647,6 @@ function cmd_confirm() {
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -606,6 +657,16 @@ function cmd_confirm() {
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>
@@ -720,7 +781,7 @@ function cmd_confirm() {
</tr>
<tr style="display: none;">
<td>NET SPEED</td>
<td>Net Speed</td>
<td>
<select name="eth_spd" id="eth_spd">
<option value="0">100Mbps
@@ -762,7 +823,6 @@ function cmd_confirm() {
<select name="pktp" id="pktp">
<option value="0">UDP
<option value="1">RTP
<option value="2">Invalid
</select>
</td>
</tr>
@@ -772,7 +832,6 @@ function cmd_confirm() {
<select name="pklen" id="pklen">
<option value="0">188
<option value="1">204
<option value="2">Invalid
</select>
</td>
<td>Type</td>
@@ -780,7 +839,6 @@ function cmd_confirm() {
<select name="pkrec" id="pkrec">
<option value="0">Multicast
<option value="1">Unicast
<option value="2">Invalid
</select>
</td>
</tr>
@@ -813,6 +871,14 @@ function cmd_confirm() {
<td> <input type="button" class="btn-modern" alt="Set" value="Set" disabled id="setcfg_id1" onclick="return tsoip_setcfg(0);"> </td>
</tr>
</table>
<!-- R4 (2026-08-11): shown only when the unit reports a Protocol/Length/Type
value this page has no name for. Replaces the old "Invalid" option. -->
<!-- R6: invented .field-note.is-error dropped. Unrecognised value is a genuine
section-level fault -> .warning-banner.is-error (red), per the 3.0 scope rule. -->
<div class="warning-banner is-error" id="ts_enum_note" style="display:none">
The unit reported a Protocol, Length or Type value this page does not recognise.
That control has been left unset and Set is held until a valid value is chosen.
</div>
</form>
</div>
</main>
+18
View File
@@ -0,0 +1,18 @@
ATSC SMOD 工程修改记录
2026-08-28
- 完成 V1.1 硬件功率配置相关调整:默认硬件版本、功率上限及 V1.0/V1.1 切换支持。
- 开启切换频点时先降至 -25 dB 的保护流程。
- 更新网页系统设置布局,将 HardwareVersion 与 ORX_Manu_Gain 同行,并移除旧提示。
- 新增 ADRV9009 射频主链路说明文档 `src/atsc_app/tar_sum_atsc_rf_chain.md`。
- 本次修改按用户要求未执行编译验证。
2026-08-21
- 当前工作版本更新为 V136R,尚未作为稳定版本提交。
- 已知问题:设备开机后以及 DPD 处理完成后,SNR 和左右带肩数据不能及时更新。
- 当前修改已增加 DPD 完成后恢复 ORx FAGC 跟踪的处理,但开机刷新场景及整体效果仍需验证。
- 在冷启动和 DPD 两种场景验证通过前,不作为正式 V136R 发布。
2026-08-20
- 初次完整提交,提交基线版本为 V135R。
- Git 提交:daf8c673(chore: 初始化 ATSC SMOD 工程仓库)。