Skip to content

Commit eb2a179

Browse files
committed
Expand DWR docs and clarify behavior
- Clarify warning/violation threshold semantics in DWR docstring - Document DWR window check fields in ATDMA stats docs
1 parent f9a8ad8 commit eb2a179

4 files changed

Lines changed: 827 additions & 76 deletions

File tree

docs/api/fast-api/single/us/atdma/chan/stats.md

Lines changed: 96 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ TFTP parameters are not required.
1515

1616
This endpoint returns the standard envelope described in [Common → Response](../../../../common/response.md) (`mac_address`, `status`, `message`, `data`).
1717

18-
`data` is an **array** of upstream channels. Each item contains the SNMP table `index`, the upstream `channel_id`, and an `entry` with configuration, status, and (where available) raw pre-EQ data (`docsIf3CmStatusUsEqData`).
18+
`data` is an **object** with the upstream channel entries plus an optional DWR window evaluation summary. Each entry contains the SNMP table `index`, the upstream `channel_id`, and an `entry` with configuration, status, and (where available) raw pre-EQ data (`docsIf3CmStatusUsEqData`).
1919

2020
### Abbreviated Example
2121

@@ -24,71 +24,105 @@ This endpoint returns the standard envelope described in [Common → Response](.
2424
"mac_address": "aa:bb:cc:dd:ee:ff",
2525
"status": 0,
2626
"message": null,
27-
"data": [
28-
{
29-
"index": 80,
30-
"channel_id": 1,
31-
"entry": {
32-
"docsIfUpChannelId": 1,
33-
"docsIfUpChannelFrequency": 14600000,
34-
"docsIfUpChannelWidth": 6400000,
35-
"docsIfUpChannelModulationProfile": 0,
36-
"docsIfUpChannelSlotSize": 2,
37-
"docsIfUpChannelTxTimingOffset": 6436,
38-
"docsIfUpChannelRangingBackoffStart": 3,
39-
"docsIfUpChannelRangingBackoffEnd": 8,
40-
"docsIfUpChannelTxBackoffStart": 2,
41-
"docsIfUpChannelTxBackoffEnd": 6,
42-
"docsIfUpChannelType": 2,
43-
"docsIfUpChannelCloneFrom": 0,
44-
"docsIfUpChannelUpdate": false,
45-
"docsIfUpChannelStatus": 1,
46-
"docsIfUpChannelPreEqEnable": true,
47-
"docsIf3CmStatusUsTxPower": 49.0,
48-
"docsIf3CmStatusUsT3Timeouts": 0,
49-
"docsIf3CmStatusUsT4Timeouts": 0,
50-
"docsIf3CmStatusUsRangingAborteds": 0,
51-
"docsIf3CmStatusUsModulationType": 2,
52-
"docsIf3CmStatusUsEqData": "0x08011800ffff0003...00020001",
53-
"docsIf3CmStatusUsT3Exceededs": 0,
54-
"docsIf3CmStatusUsIsMuted": false,
55-
"docsIf3CmStatusUsRangingStatus": 4
56-
}
57-
},
58-
{
59-
"index": 81,
60-
"channel_id": 2,
61-
"entry": {
62-
"docsIfUpChannelId": 2,
63-
"docsIfUpChannelFrequency": 21000000,
64-
"docsIfUpChannelWidth": 6400000,
65-
"docsIfUpChannelModulationProfile": 0,
66-
"docsIfUpChannelSlotSize": 2,
67-
"docsIfUpChannelTxTimingOffset": 6436,
68-
"docsIfUpChannelRangingBackoffStart": 3,
69-
"docsIfUpChannelRangingBackoffEnd": 8,
70-
"docsIfUpChannelTxBackoffStart": 2,
71-
"docsIfUpChannelTxBackoffEnd": 6,
72-
"docsIfUpChannelType": 2,
73-
"docsIfUpChannelCloneFrom": 0,
74-
"docsIfUpChannelUpdate": false,
75-
"docsIfUpChannelStatus": 1,
76-
"docsIfUpChannelPreEqEnable": true,
77-
"docsIf3CmStatusUsTxPower": 48.5,
78-
"docsIf3CmStatusUsT3Timeouts": 0,
79-
"docsIf3CmStatusUsT4Timeouts": 0,
80-
"docsIf3CmStatusUsRangingAborteds": 0,
81-
"docsIf3CmStatusUsModulationType": 2,
82-
"docsIf3CmStatusUsEqData": "0x08011800ffff0001...0002",
83-
"docsIf3CmStatusUsT3Exceededs": 0,
84-
"docsIf3CmStatusUsIsMuted": false,
85-
"docsIf3CmStatusUsRangingStatus": 4
27+
"data": {
28+
"entries": [
29+
{
30+
"index": 80,
31+
"channel_id": 1,
32+
"entry": {
33+
"docsIfUpChannelId": 1,
34+
"docsIfUpChannelFrequency": 14600000,
35+
"docsIfUpChannelWidth": 6400000,
36+
"docsIfUpChannelModulationProfile": 0,
37+
"docsIfUpChannelSlotSize": 2,
38+
"docsIfUpChannelTxTimingOffset": 6436,
39+
"docsIfUpChannelRangingBackoffStart": 3,
40+
"docsIfUpChannelRangingBackoffEnd": 8,
41+
"docsIfUpChannelTxBackoffStart": 2,
42+
"docsIfUpChannelTxBackoffEnd": 6,
43+
"docsIfUpChannelType": 2,
44+
"docsIfUpChannelCloneFrom": 0,
45+
"docsIfUpChannelUpdate": false,
46+
"docsIfUpChannelStatus": 1,
47+
"docsIfUpChannelPreEqEnable": true,
48+
"docsIf3CmStatusUsTxPower": 49.0,
49+
"docsIf3CmStatusUsT3Timeouts": 0,
50+
"docsIf3CmStatusUsT4Timeouts": 0,
51+
"docsIf3CmStatusUsRangingAborteds": 0,
52+
"docsIf3CmStatusUsModulationType": 2,
53+
"docsIf3CmStatusUsEqData": "0x08011800ffff0003...00020001",
54+
"docsIf3CmStatusUsT3Exceededs": 0,
55+
"docsIf3CmStatusUsIsMuted": false,
56+
"docsIf3CmStatusUsRangingStatus": 4
57+
}
58+
},
59+
{
60+
"index": 81,
61+
"channel_id": 2,
62+
"entry": {
63+
"docsIfUpChannelId": 2,
64+
"docsIfUpChannelFrequency": 21000000,
65+
"docsIfUpChannelWidth": 6400000,
66+
"docsIfUpChannelModulationProfile": 0,
67+
"docsIfUpChannelSlotSize": 2,
68+
"docsIfUpChannelTxTimingOffset": 6436,
69+
"docsIfUpChannelRangingBackoffStart": 3,
70+
"docsIfUpChannelRangingBackoffEnd": 8,
71+
"docsIfUpChannelTxBackoffStart": 2,
72+
"docsIfUpChannelTxBackoffEnd": 6,
73+
"docsIfUpChannelType": 2,
74+
"docsIfUpChannelCloneFrom": 0,
75+
"docsIfUpChannelUpdate": false,
76+
"docsIfUpChannelStatus": 1,
77+
"docsIfUpChannelPreEqEnable": true,
78+
"docsIf3CmStatusUsTxPower": 48.5,
79+
"docsIf3CmStatusUsT3Timeouts": 0,
80+
"docsIf3CmStatusUsT4Timeouts": 0,
81+
"docsIf3CmStatusUsRangingAborteds": 0,
82+
"docsIf3CmStatusUsModulationType": 2,
83+
"docsIf3CmStatusUsEqData": "0x08011800ffff0001...0002",
84+
"docsIf3CmStatusUsT3Exceededs": 0,
85+
"docsIf3CmStatusUsIsMuted": false,
86+
"docsIf3CmStatusUsRangingStatus": 4
87+
}
8688
}
89+
],
90+
"dwr_window_check": {
91+
"dwr_warning_db": 6.0,
92+
"dwr_violation_db": 12.0,
93+
"channel_count": 2,
94+
"min_power_dbmv": 48.5,
95+
"max_power_dbmv": 49.0,
96+
"spread_db": 0.5,
97+
"is_warning": false,
98+
"is_violation": false,
99+
"extreme_channel_ids": [1, 2]
87100
}
88-
]
101+
}
89102
}
90103
```
91104

105+
## Data Fields
106+
107+
| Field | Type | Description |
108+
| ------------------ | ------ | ------------------------------------------------ |
109+
| `entries` | array | Upstream channel entries (same as prior format). |
110+
| `dwr_window_check` | object | DWR evaluation summary, or null when unavailable. |
111+
112+
## DWR Window Check Fields
113+
114+
| Field | Type | Units | Description |
115+
| ------------------ | ----- | ----- | ----------- |
116+
| `dwr_warning_db` | float | dB | Warning threshold for the DWR spread. |
117+
| `dwr_violation_db` | float | dB | Violation threshold for the DWR spread. |
118+
| `channel_count` | int || Number of channels included in the evaluation. |
119+
| `min_power_dbmv` | float | dBmV | Minimum transmit power across channels. |
120+
| `max_power_dbmv` | float | dBmV | Maximum transmit power across channels. |
121+
| `spread_db` | float | dB | Power spread across channels (max-min). |
122+
| `is_warning` | bool || True when warning_db < spread_db <= violation_db. |
123+
| `is_violation` | bool || True when spread_db > violation_db. |
124+
| `extreme_channel_ids` | array || Channel IDs that define the min/max spread. |
125+
92126
## Channel Fields
93127

94128
| Field | Type | Description |
@@ -131,6 +165,7 @@ This endpoint returns the standard envelope described in [Common → Response](.
131165
* The hex string preserves original SNMP octets (for example `FF` stays `FF`, not UTF-8 encoded).
132166
* Use the combination of `TxPower`, timeout counters, and ranging status to corroborate upstream health with pre-EQ shape.
133167
* Channels are discovered automatically; no channel list is required in the request.
168+
* DWR warning and violation thresholds are evaluated against the min/max power spread for all channels returned.
134169
# DOCSIS 3.0 Upstream ATDMA Pre-Equalization
135170

136171
Provides Access To DOCSIS 3.0 Upstream SC-QAM (ATDMA) Pre-Equalization Tap Data For Plant Analysis (Reflections, Group Delay, Pre-Echo).

src/pypnm/api/routes/docs/if30/us/atdma/chan/stats/service.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
from pypnm.docsis.cable_modem import CableModem
1010
from pypnm.lib.inet import Inet
1111
from pypnm.lib.mac_address import MacAddress
12-
from pypnm.lib.types import BandwidthHz, InetAddressStr, MacAddressStr
12+
from pypnm.lib.types import BandwidthHz, ChannelId, InetAddressStr, MacAddressStr, PowerdB, PowerdBmV
13+
from pypnm.pnm.analysis.us_drw import (
14+
DwrChannelPowerModel,
15+
DwrDynamicWindowRangeChecker,
16+
DwrWindowCheckModel,
17+
)
1318
from pypnm.pnm.data_type.DocsEqualizerData import DocsEqualizerData
1419

1520

@@ -36,15 +41,47 @@ def __init__(self, mac_address: MacAddressStr,
3641
inet=Inet(ip_address),
3742
write_community=snmp_config.snmp_v2c.community)
3843

39-
async def get_upstream_entries(self) -> list[dict]:
44+
async def get_upstream_entries(
45+
self,
46+
dwr_warning_db: PowerdB = PowerdB(6.0),
47+
dwr_violation_db: PowerdB = PowerdB(12.0),
48+
) -> dict[str, object]:
4049
"""
4150
Fetches DOCSIS Upstream SC-QAM channel entries.
4251
4352
Returns:
44-
List[dict]: A list of dictionaries representing upstream channel information.
53+
Dict[str, object]: Upstream channel entries with optional DWR evaluation summary.
4554
"""
4655
entries = await self.cm.getDocsIfUpstreamChannelEntry()
47-
return [entry.model_dump() for entry in entries]
56+
entry_dicts = [entry.model_dump() for entry in entries]
57+
58+
channel_powers: list[DwrChannelPowerModel] = []
59+
for entry in entries:
60+
tx_power = entry.entry.docsIf3CmStatusUsTxPower
61+
if tx_power is None:
62+
continue
63+
channel_powers.append(
64+
DwrChannelPowerModel(
65+
channel_id=ChannelId(entry.channel_id),
66+
tx_power_dbmv=PowerdBmV(tx_power),
67+
)
68+
)
69+
70+
dwr_check: DwrWindowCheckModel | None = None
71+
if len(channel_powers) >= DwrDynamicWindowRangeChecker.MIN_CHANNELS:
72+
try:
73+
checker = DwrDynamicWindowRangeChecker(
74+
dwr_violation_db=dwr_violation_db,
75+
dwr_warning_db=dwr_warning_db,
76+
)
77+
dwr_check = checker.evaluate(channel_powers)
78+
except Exception:
79+
dwr_check = None
80+
81+
return {
82+
"entries": entry_dicts,
83+
"dwr_window_check": (dwr_check.model_dump() if dwr_check is not None else None),
84+
}
4885

4986
async def get_upstream_pre_equalizations(self) -> dict[int, dict]:
5087
"""
@@ -54,9 +91,9 @@ async def get_upstream_pre_equalizations(self) -> dict[int, dict]:
5491
List[dict]: A dictionary containing per-channel equalizer data with real, imag,
5592
magnitude, and power (dB) for each tap.
5693
"""
57-
entries = await self.get_upstream_entries()
94+
entries_payload = await self.get_upstream_entries()
5895
channel_widths: dict[int, BandwidthHz] = {}
59-
for entry in entries:
96+
for entry in entries_payload.get("entries", []):
6097
index = entry.get("index")
6198
entry_data = entry.get("entry") or {}
6299
channel_width = entry_data.get("docsIfUpChannelWidth")

0 commit comments

Comments
 (0)