py-sfp-eeprom 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-sfp-eeprom
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Python library for creating and manipulating SFP EEPROM data
|
|
5
|
+
Home-page: https://github.com/Better-Internet-Ltd/py-sfp-eeprom
|
|
6
|
+
Author: py-sfp-eeprom contributors
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Better-Internet-Ltd/py-sfp-eeprom
|
|
9
|
+
Project-URL: Repository, https://github.com/Better-Internet-Ltd/py-sfp-eeprom
|
|
10
|
+
Keywords: sfp,eeprom,fiber,optics,transceiver,sff-8472
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Telecommunications Industry
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Hardware
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
|
|
31
|
+
# Python SFP EEPROM
|
|
32
|
+
|
|
33
|
+
A Python library to manage SFP module EEPROM data based on SFF-8472 specifications.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Create A0h EEPROM from scratch
|
|
38
|
+
- Load A0h EEPROM from binary bytes
|
|
39
|
+
- Get and set individual EEPROM fields
|
|
40
|
+
- Automatic checksum calculation and validation
|
|
41
|
+
- Export EEPROM as 256-byte binary data
|
|
42
|
+
- Human-readable field access and information display
|
|
43
|
+
- Support for all SFF-8472 standard fields
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
Install from PyPI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install py-sfp-eeprom
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or for development, you can install in editable mode:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
### Creating an EEPROM from Scratch
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from sfp_eeprom import SFPA0h
|
|
65
|
+
|
|
66
|
+
# Create a new empty EEPROM
|
|
67
|
+
eeprom = SFPA0h()
|
|
68
|
+
|
|
69
|
+
# Set vendor information
|
|
70
|
+
eeprom.set('vendor_name', 'ACME FIBER')
|
|
71
|
+
eeprom.set('vendor_pn', 'SFP-10G-LR')
|
|
72
|
+
eeprom.set('vendor_sn', 'ABC123456789')
|
|
73
|
+
eeprom.set('vendor_oui', bytes.fromhex('001122'))
|
|
74
|
+
|
|
75
|
+
# Set transceiver type using named constants
|
|
76
|
+
eeprom.set('identifier', SFPA0h.IDENTIFIER_SFP) # SFP/SFP+/SFP28
|
|
77
|
+
eeprom.set('connector', SFPA0h.CONNECTOR_LC) # LC connector
|
|
78
|
+
eeprom.set('encoding', SFPA0h.ENCODING_64B66B) # 64B/66B encoding
|
|
79
|
+
|
|
80
|
+
# Set specifications
|
|
81
|
+
eeprom.set('wavelength', 1310) # 1310nm
|
|
82
|
+
eeprom.set('br_nominal', 103) # 10.3 Gbps
|
|
83
|
+
|
|
84
|
+
# Update checksums
|
|
85
|
+
eeprom.update_checksums()
|
|
86
|
+
|
|
87
|
+
# Export to binary file
|
|
88
|
+
with open('eeprom.bin', 'wb') as f:
|
|
89
|
+
f.write(eeprom.to_bytes())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Loading an Existing EEPROM
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from sfp_eeprom import SFPA0h
|
|
96
|
+
|
|
97
|
+
# Read from binary file
|
|
98
|
+
with open('eeprom.bin', 'rb') as f:
|
|
99
|
+
data = f.read()
|
|
100
|
+
|
|
101
|
+
# Create EEPROM object
|
|
102
|
+
eeprom = SFPA0h.from_bytes(data)
|
|
103
|
+
|
|
104
|
+
# Access fields
|
|
105
|
+
print(f"Vendor: {eeprom.get('vendor_name')}")
|
|
106
|
+
print(f"Part Number: {eeprom.get('vendor_pn')}")
|
|
107
|
+
print(f"Serial: {eeprom.get('vendor_sn')}")
|
|
108
|
+
|
|
109
|
+
# Get comprehensive info
|
|
110
|
+
info = eeprom.get_info()
|
|
111
|
+
print(info)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Modifying EEPROM Values
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from sfp_eeprom import SFPA0h
|
|
118
|
+
|
|
119
|
+
# Load existing EEPROM
|
|
120
|
+
eeprom = SFPA0h.from_bytes(data)
|
|
121
|
+
|
|
122
|
+
# Change serial number
|
|
123
|
+
eeprom.set('vendor_sn', 'NEW-SN-12345')
|
|
124
|
+
|
|
125
|
+
# Change wavelength
|
|
126
|
+
eeprom.set('wavelength', 1550) # 1550nm
|
|
127
|
+
|
|
128
|
+
# Checksums are automatically recalculated
|
|
129
|
+
# when you use the set() method
|
|
130
|
+
|
|
131
|
+
# Export modified EEPROM
|
|
132
|
+
modified_data = eeprom.to_bytes()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Named Constants
|
|
136
|
+
|
|
137
|
+
The library provides named constants for common field values, making code more readable:
|
|
138
|
+
|
|
139
|
+
### Identifier Types
|
|
140
|
+
- `IDENTIFIER_UNKNOWN` - Unknown/unspecified (0x00)
|
|
141
|
+
- `IDENTIFIER_GBIC` - GBIC (0x01)
|
|
142
|
+
- `IDENTIFIER_SOLDERED` - Soldered to motherboard (0x02)
|
|
143
|
+
- `IDENTIFIER_SFP` - SFP/SFP+/SFP28 (0x03)
|
|
144
|
+
- `IDENTIFIER_XFP` - XFP (0x06)
|
|
145
|
+
- `IDENTIFIER_QSFP` - QSFP (0x0C)
|
|
146
|
+
- `IDENTIFIER_QSFP_PLUS` - QSFP+ or later (0x0D)
|
|
147
|
+
- `IDENTIFIER_QSFP28` - QSFP28 or later (0x11)
|
|
148
|
+
- And more...
|
|
149
|
+
|
|
150
|
+
### Connector Types
|
|
151
|
+
- `CONNECTOR_UNKNOWN` - Unknown/unspecified (0x00)
|
|
152
|
+
- `CONNECTOR_SC` - SC connector (0x01)
|
|
153
|
+
- `CONNECTOR_LC` - LC connector (0x07)
|
|
154
|
+
- `CONNECTOR_MT_RJ` - MT-RJ connector (0x08)
|
|
155
|
+
- `CONNECTOR_MU` - MU connector (0x09)
|
|
156
|
+
- `CONNECTOR_RJ45` - RJ45 connector (0x22)
|
|
157
|
+
- `CONNECTOR_MPO_1X12` - MPO 1x12 connector (0x0C)
|
|
158
|
+
- And more...
|
|
159
|
+
|
|
160
|
+
### Encoding Types
|
|
161
|
+
- `ENCODING_UNSPECIFIED` - Unspecified (0x00)
|
|
162
|
+
- `ENCODING_8B10B` - 8B/10B (0x01)
|
|
163
|
+
- `ENCODING_4B5B` - 4B/5B (0x02)
|
|
164
|
+
- `ENCODING_NRZ` - NRZ (0x03)
|
|
165
|
+
- `ENCODING_64B66B` - 64B/66B (0x06)
|
|
166
|
+
|
|
167
|
+
### Extended Identifier
|
|
168
|
+
- `EXT_IDENTIFIER_GBIC` - GBIC definition (0x00)
|
|
169
|
+
- `EXT_IDENTIFIER_SFF` - SFF-8472 compliant (0x04)
|
|
170
|
+
|
|
171
|
+
Example using constants:
|
|
172
|
+
```python
|
|
173
|
+
eeprom.set('identifier', SFPA0h.IDENTIFIER_SFP)
|
|
174
|
+
eeprom.set('connector', SFPA0h.CONNECTOR_LC)
|
|
175
|
+
eeprom.set('encoding', SFPA0h.ENCODING_64B66B)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Supported Fields
|
|
179
|
+
|
|
180
|
+
The library supports all standard SFF-8472 A0h fields:
|
|
181
|
+
|
|
182
|
+
### Identification Fields
|
|
183
|
+
- `identifier` - Transceiver type (0x03 = SFP)
|
|
184
|
+
- `ext_identifier` - Extended identifier
|
|
185
|
+
- `connector` - Connector type (0x07 = LC)
|
|
186
|
+
|
|
187
|
+
### Vendor Information
|
|
188
|
+
- `vendor_name` - 16-character vendor name
|
|
189
|
+
- `vendor_oui` - IEEE company ID (3 bytes)
|
|
190
|
+
- `vendor_pn` - Part number (16 characters)
|
|
191
|
+
- `vendor_rev` - Revision (4 characters)
|
|
192
|
+
- `vendor_sn` - Serial number (16 characters)
|
|
193
|
+
- `date_code` - Manufacturing date (8 characters: YYMMDD + lot code)
|
|
194
|
+
|
|
195
|
+
### Transceiver Specifications
|
|
196
|
+
- `transceiver` - Compliance codes (8 bytes)
|
|
197
|
+
- `encoding` - Encoding type
|
|
198
|
+
- `br_nominal` - Nominal bit rate (units of 100 Mbps)
|
|
199
|
+
- `br_max` - Upper bit rate margin
|
|
200
|
+
- `br_min` - Lower bit rate margin
|
|
201
|
+
- `wavelength` - Wavelength in nm (or copper attenuation)
|
|
202
|
+
|
|
203
|
+
### Link Lengths
|
|
204
|
+
- `length_smf_km` - Single-mode fiber, km
|
|
205
|
+
- `length_smf` - Single-mode fiber, 100m units
|
|
206
|
+
- `length_50um` - 50µm OM2 fiber, 10m units
|
|
207
|
+
- `length_62_5um` - 62.5µm OM1 fiber, 10m units
|
|
208
|
+
- `length_copper` - Copper cable, 1m units
|
|
209
|
+
- `length_om3` - 50µm OM3 fiber, 10m units
|
|
210
|
+
|
|
211
|
+
### Options and Diagnostics
|
|
212
|
+
- `options` - Implemented options (2 bytes)
|
|
213
|
+
- `diagnostic_monitoring` - Diagnostic monitoring type
|
|
214
|
+
- `enhanced_options` - Enhanced options
|
|
215
|
+
- `sff8472_compliance` - SFF-8472 compliance level
|
|
216
|
+
|
|
217
|
+
### Checksums
|
|
218
|
+
- `cc_base` - Checksum for base ID (bytes 0-62)
|
|
219
|
+
- `cc_ext` - Checksum for extended ID (bytes 64-94)
|
|
220
|
+
|
|
221
|
+
## API Reference
|
|
222
|
+
|
|
223
|
+
### SFPA0h Class
|
|
224
|
+
|
|
225
|
+
#### `__init__(data=None)`
|
|
226
|
+
Create a new EEPROM instance. If `data` is provided, it must be exactly 256 bytes.
|
|
227
|
+
|
|
228
|
+
#### `get(field_name)`
|
|
229
|
+
Get a field value in human-readable format (str, int, or bytes).
|
|
230
|
+
|
|
231
|
+
#### `set(field_name, value)`
|
|
232
|
+
Set a field value. Automatically updates checksums for affected regions.
|
|
233
|
+
|
|
234
|
+
#### `to_bytes()`
|
|
235
|
+
Export the complete EEPROM as 256 bytes.
|
|
236
|
+
|
|
237
|
+
#### `from_bytes(data)` (class method)
|
|
238
|
+
Create an EEPROM instance from 256 bytes of data.
|
|
239
|
+
|
|
240
|
+
#### `update_checksums()`
|
|
241
|
+
Manually recalculate and update both CC_BASE and CC_EXT checksums.
|
|
242
|
+
|
|
243
|
+
#### `validate_checksums()`
|
|
244
|
+
Validate both checksums. Returns dict with `cc_base` and `cc_ext` boolean values.
|
|
245
|
+
|
|
246
|
+
#### `get_info()`
|
|
247
|
+
Get a dictionary of all human-readable EEPROM information.
|
|
248
|
+
|
|
249
|
+
## Examples
|
|
250
|
+
|
|
251
|
+
### Creating an EEPROM (examples/example.py)
|
|
252
|
+
|
|
253
|
+
See `examples/example.py` for a complete demonstration of creating an EEPROM from scratch, setting all fields, validating checksums, and exporting to a binary file.
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
python examples/example.py
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
This will create an `eeprom_a0h.bin` file with a complete, valid SFP EEPROM image.
|
|
260
|
+
|
|
261
|
+
### Reading an EEPROM (examples/read_eeprom.py)
|
|
262
|
+
|
|
263
|
+
Use the `examples/read_eeprom.py` CLI tool to read and display information from an existing EEPROM binary file:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Display EEPROM information
|
|
267
|
+
python examples/read_eeprom.py eeprom_a0h.bin
|
|
268
|
+
|
|
269
|
+
# Display with hex dump
|
|
270
|
+
python examples/read_eeprom.py eeprom_a0h.bin --hex
|
|
271
|
+
|
|
272
|
+
# Get help
|
|
273
|
+
python examples/read_eeprom.py --help
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The tool displays:
|
|
277
|
+
- Identification information (transceiver type, connector type)
|
|
278
|
+
- Vendor information (name, OUI, part number, serial number, date code)
|
|
279
|
+
- Transceiver specifications (encoding, bit rate, wavelength)
|
|
280
|
+
- Supported link lengths
|
|
281
|
+
- Options and diagnostic monitoring information
|
|
282
|
+
- Checksum validation status
|
|
283
|
+
- Optional hex dump of the entire EEPROM
|
|
284
|
+
|
|
285
|
+
Exit codes:
|
|
286
|
+
- `0` - Success (checksums valid)
|
|
287
|
+
- `1` - File error or invalid EEPROM
|
|
288
|
+
- `2` - EEPROM loaded but checksums invalid
|
|
289
|
+
|
|
290
|
+
## Specifications
|
|
291
|
+
|
|
292
|
+
Based on SFF-8472 (SFP MSA) specification for the A0h lower page (256 bytes).
|
|
293
|
+
|
|
294
|
+
## License
|
|
295
|
+
|
|
296
|
+
This is free and unencumbered software released into the public domain.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
py_sfp_eeprom-0.1.3.dist-info/licenses/LICENSE,sha256=L5-gh_Rs35Qf_yIycmhpaS9OvY5Tck8N-rNeapRPBEU,1083
|
|
2
|
+
sfp_eeprom/__init__.py,sha256=XdWVbp4_Qc7BhZYhpdlbHe1ECdgM-jlztLQAKHcrFik,204
|
|
3
|
+
sfp_eeprom/eeprom.py,sha256=XnH2m5WqrXgBNS5doxIx-hL4NJ4uEmci8CTs3D61mWY,20132
|
|
4
|
+
py_sfp_eeprom-0.1.3.dist-info/METADATA,sha256=JAMXcAmouFMIi4sLHB1cN6PcZibFlqc_iCJUoG4stwo,8465
|
|
5
|
+
py_sfp_eeprom-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
py_sfp_eeprom-0.1.3.dist-info/top_level.txt,sha256=RinKWo9oLttb4nb5vlLjHiVOAmxmERNeFYnWD_tJlv8,11
|
|
7
|
+
py_sfp_eeprom-0.1.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 py-sfp-eeprom contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sfp_eeprom
|
sfp_eeprom/__init__.py
ADDED
sfp_eeprom/eeprom.py
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SFP EEPROM A0h implementation based on SFF-8472 specification.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import struct
|
|
6
|
+
from typing import Dict, Any, Optional, Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SFPA0h:
|
|
10
|
+
"""
|
|
11
|
+
SFP EEPROM A0h (256 bytes) based on SFF-8472 specification.
|
|
12
|
+
|
|
13
|
+
This class manages the lower page (A0h) of the SFP EEPROM which contains
|
|
14
|
+
identification and vendor information.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# EEPROM size is 256 bytes
|
|
18
|
+
EEPROM_SIZE = 256
|
|
19
|
+
|
|
20
|
+
# Field offsets and sizes based on SFF-8472 Table 3-1
|
|
21
|
+
FIELDS = {
|
|
22
|
+
'identifier': (0, 1), # Type of serial transceiver
|
|
23
|
+
'ext_identifier': (1, 1), # Extended identifier
|
|
24
|
+
'connector': (2, 1), # Code for connector type
|
|
25
|
+
'transceiver': (3, 8), # Transceiver compliance codes
|
|
26
|
+
'encoding': (11, 1), # Code for serial encoding algorithm
|
|
27
|
+
'br_nominal': (12, 1), # Nominal signaling rate
|
|
28
|
+
'rate_identifier': (13, 1), # Type of rate select functionality
|
|
29
|
+
'length_smf_km': (14, 1), # Link length supported for SMF, km
|
|
30
|
+
'length_smf': (15, 1), # Link length supported for SMF, 100m
|
|
31
|
+
'length_50um': (16, 1), # Link length supported for 50um OM2, 10m
|
|
32
|
+
'length_62_5um': (17, 1), # Link length supported for 62.5um OM1, 10m
|
|
33
|
+
'length_copper': (18, 1), # Link length supported for copper, 1m
|
|
34
|
+
'length_om3': (19, 1), # Link length supported for 50um OM3, 10m
|
|
35
|
+
'vendor_name': (20, 16), # SFP vendor name (ASCII)
|
|
36
|
+
'transceiver_ext': (36, 1), # Extended transceiver codes
|
|
37
|
+
'vendor_oui': (37, 3), # SFP vendor IEEE company ID
|
|
38
|
+
'vendor_pn': (40, 16), # Part number provided by SFP vendor (ASCII)
|
|
39
|
+
'vendor_rev': (56, 4), # Revision level for part number (ASCII)
|
|
40
|
+
'wavelength': (60, 2), # Laser wavelength (or copper cable attenuation)
|
|
41
|
+
'unallocated_1': (62, 1), # Unallocated
|
|
42
|
+
'cc_base': (63, 1), # Check code for base ID fields (0-62)
|
|
43
|
+
'options': (64, 2), # Indicates implemented options
|
|
44
|
+
'br_max': (66, 1), # Upper bit rate margin
|
|
45
|
+
'br_min': (67, 1), # Lower bit rate margin
|
|
46
|
+
'vendor_sn': (68, 16), # Serial number (ASCII)
|
|
47
|
+
'date_code': (84, 8), # Vendor's mfg date code
|
|
48
|
+
'diagnostic_monitoring': (92, 1), # Diagnostic monitoring type
|
|
49
|
+
'enhanced_options': (93, 1), # Enhanced options
|
|
50
|
+
'sff8472_compliance': (94, 1), # SFF-8472 compliance
|
|
51
|
+
'cc_ext': (95, 1), # Check code for extended ID fields (64-94)
|
|
52
|
+
'vendor_specific': (96, 32), # Vendor specific data
|
|
53
|
+
'reserved': (128, 128), # Reserved for SFF-8079
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Identifier type constants
|
|
57
|
+
IDENTIFIER_UNKNOWN = 0x00
|
|
58
|
+
IDENTIFIER_GBIC = 0x01
|
|
59
|
+
IDENTIFIER_SOLDERED = 0x02
|
|
60
|
+
IDENTIFIER_SFP = 0x03
|
|
61
|
+
IDENTIFIER_XBI = 0x04
|
|
62
|
+
IDENTIFIER_XENPAK = 0x05
|
|
63
|
+
IDENTIFIER_XFP = 0x06
|
|
64
|
+
IDENTIFIER_XFF = 0x07
|
|
65
|
+
IDENTIFIER_XFP_E = 0x08
|
|
66
|
+
IDENTIFIER_XPAK = 0x09
|
|
67
|
+
IDENTIFIER_X2 = 0x0A
|
|
68
|
+
IDENTIFIER_DWDM_SFP = 0x0B
|
|
69
|
+
IDENTIFIER_QSFP = 0x0C
|
|
70
|
+
IDENTIFIER_QSFP_PLUS = 0x0D
|
|
71
|
+
IDENTIFIER_CXP = 0x0E
|
|
72
|
+
IDENTIFIER_HD_4X = 0x0F
|
|
73
|
+
IDENTIFIER_HD_8X = 0x10
|
|
74
|
+
IDENTIFIER_QSFP28 = 0x11
|
|
75
|
+
IDENTIFIER_CXP2 = 0x12
|
|
76
|
+
IDENTIFIER_CDFP = 0x13
|
|
77
|
+
IDENTIFIER_HD_4X_FANOUT = 0x14
|
|
78
|
+
IDENTIFIER_HD_8X_FANOUT = 0x15
|
|
79
|
+
IDENTIFIER_CDFP_STYLE3 = 0x16
|
|
80
|
+
IDENTIFIER_MICRO_QSFP = 0x17
|
|
81
|
+
|
|
82
|
+
# Identifier types (byte 0)
|
|
83
|
+
IDENTIFIER_TYPES = {
|
|
84
|
+
0x00: 'Unknown or unspecified',
|
|
85
|
+
0x01: 'GBIC',
|
|
86
|
+
0x02: 'Module/connector soldered to motherboard',
|
|
87
|
+
0x03: 'SFP/SFP+/SFP28',
|
|
88
|
+
0x04: '300 pin XBI',
|
|
89
|
+
0x05: 'XENPAK',
|
|
90
|
+
0x06: 'XFP',
|
|
91
|
+
0x07: 'XFF',
|
|
92
|
+
0x08: 'XFP-E',
|
|
93
|
+
0x09: 'XPAK',
|
|
94
|
+
0x0A: 'X2',
|
|
95
|
+
0x0B: 'DWDM-SFP/SFP+',
|
|
96
|
+
0x0C: 'QSFP',
|
|
97
|
+
0x0D: 'QSFP+ or later',
|
|
98
|
+
0x0E: 'CXP or later',
|
|
99
|
+
0x0F: 'Shielded Mini Multilane HD 4X',
|
|
100
|
+
0x10: 'Shielded Mini Multilane HD 8X',
|
|
101
|
+
0x11: 'QSFP28 or later',
|
|
102
|
+
0x12: 'CXP2 (aka CXP28) or later',
|
|
103
|
+
0x13: 'CDFP (Style 1/Style2)',
|
|
104
|
+
0x14: 'Shielded Mini Multilane HD 4X Fanout Cable',
|
|
105
|
+
0x15: 'Shielded Mini Multilane HD 8X Fanout Cable',
|
|
106
|
+
0x16: 'CDFP (Style 3)',
|
|
107
|
+
0x17: 'microQSFP',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Connector type constants
|
|
111
|
+
CONNECTOR_UNKNOWN = 0x00
|
|
112
|
+
CONNECTOR_SC = 0x01
|
|
113
|
+
CONNECTOR_FC_STYLE1 = 0x02
|
|
114
|
+
CONNECTOR_FC_STYLE2 = 0x03
|
|
115
|
+
CONNECTOR_BNC_TNC = 0x04
|
|
116
|
+
CONNECTOR_FC_COAX = 0x05
|
|
117
|
+
CONNECTOR_FIBER_JACK = 0x06
|
|
118
|
+
CONNECTOR_LC = 0x07
|
|
119
|
+
CONNECTOR_MT_RJ = 0x08
|
|
120
|
+
CONNECTOR_MU = 0x09
|
|
121
|
+
CONNECTOR_SG = 0x0A
|
|
122
|
+
CONNECTOR_OPTICAL_PIGTAIL = 0x0B
|
|
123
|
+
CONNECTOR_MPO_1X12 = 0x0C
|
|
124
|
+
CONNECTOR_MPO_2X16 = 0x0D
|
|
125
|
+
CONNECTOR_HSSDC_II = 0x20
|
|
126
|
+
CONNECTOR_COPPER_PIGTAIL = 0x21
|
|
127
|
+
CONNECTOR_RJ45 = 0x22
|
|
128
|
+
CONNECTOR_NO_SEPARABLE = 0x23
|
|
129
|
+
CONNECTOR_MXC_2X16 = 0x24
|
|
130
|
+
|
|
131
|
+
# Connector types (byte 2)
|
|
132
|
+
CONNECTOR_TYPES = {
|
|
133
|
+
0x00: 'Unknown or unspecified',
|
|
134
|
+
0x01: 'SC (Subscriber Connector)',
|
|
135
|
+
0x02: 'Fibre Channel Style 1 copper connector',
|
|
136
|
+
0x03: 'Fibre Channel Style 2 copper connector',
|
|
137
|
+
0x04: 'BNC/TNC (Bayonet/Threaded Neill-Concelman)',
|
|
138
|
+
0x05: 'Fibre Channel coax headers',
|
|
139
|
+
0x06: 'Fiber Jack',
|
|
140
|
+
0x07: 'LC (Lucent Connector)',
|
|
141
|
+
0x08: 'MT-RJ (Mechanical Transfer - Registered Jack)',
|
|
142
|
+
0x09: 'MU (Multiple Optical)',
|
|
143
|
+
0x0A: 'SG',
|
|
144
|
+
0x0B: 'Optical Pigtail',
|
|
145
|
+
0x0C: 'MPO 1x12 (Multifiber Parallel Optic)',
|
|
146
|
+
0x0D: 'MPO 2x16',
|
|
147
|
+
0x20: 'HSSDC II (High Speed Serial Data Connector)',
|
|
148
|
+
0x21: 'Copper pigtail',
|
|
149
|
+
0x22: 'RJ45 (Registered Jack)',
|
|
150
|
+
0x23: 'No separable connector',
|
|
151
|
+
0x24: 'MXC 2x16',
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Encoding type constants
|
|
155
|
+
ENCODING_UNSPECIFIED = 0x00
|
|
156
|
+
ENCODING_8B10B = 0x01
|
|
157
|
+
ENCODING_4B5B = 0x02
|
|
158
|
+
ENCODING_NRZ = 0x03
|
|
159
|
+
ENCODING_MANCHESTER = 0x04
|
|
160
|
+
ENCODING_SONET = 0x05
|
|
161
|
+
ENCODING_64B66B = 0x06
|
|
162
|
+
ENCODING_256B257B = 0x07
|
|
163
|
+
|
|
164
|
+
# Extended identifier constants
|
|
165
|
+
EXT_IDENTIFIER_GBIC = 0x00
|
|
166
|
+
EXT_IDENTIFIER_SFF = 0x04
|
|
167
|
+
|
|
168
|
+
# SFF-8472 Compliance (Byte 94) constants
|
|
169
|
+
SFF8472_UNSPECIFIED = 0x00
|
|
170
|
+
SFF8472_REV_9_3 = 0x01
|
|
171
|
+
SFF8472_REV_9_5 = 0x02
|
|
172
|
+
SFF8472_REV_10_2 = 0x03
|
|
173
|
+
SFF8472_REV_10_4 = 0x04
|
|
174
|
+
SFF8472_REV_11_0 = 0x05
|
|
175
|
+
SFF8472_REV_11_3 = 0x06
|
|
176
|
+
SFF8472_REV_11_4 = 0x07
|
|
177
|
+
SFF8472_REV_12_0 = 0x08
|
|
178
|
+
|
|
179
|
+
# Diagnostic Monitoring Type (Byte 92) bit flags
|
|
180
|
+
DIAG_MONITORING_REQUIRED = 0x40 # Bit 6: Digital diagnostic monitoring implemented
|
|
181
|
+
DIAG_MONITORING_INTERNALLY_CALIBRATED = 0x20 # Bit 5: Internally calibrated
|
|
182
|
+
DIAG_MONITORING_EXTERNALLY_CALIBRATED = 0x10 # Bit 4: Externally calibrated
|
|
183
|
+
DIAG_MONITORING_RX_POWER_AVG = 0x08 # Bit 3: Received power measurement type (0=OMA, 1=average)
|
|
184
|
+
DIAG_MONITORING_ADDR_CHANGE = 0x04 # Bit 2: Address change required
|
|
185
|
+
|
|
186
|
+
# Options (Bytes 64-65) bit flags
|
|
187
|
+
# Byte 64 (high byte of options)
|
|
188
|
+
OPTIONS_RATE_SELECT = 0x2000 # Bit 13: RATE_SELECT implemented
|
|
189
|
+
OPTIONS_TX_DISABLE = 0x1000 # Bit 12: TX_DISABLE implemented
|
|
190
|
+
OPTIONS_TX_FAULT = 0x0800 # Bit 11: TX_FAULT signal implemented
|
|
191
|
+
OPTIONS_RX_LOS_INVERTED = 0x0400 # Bit 10: Loss of Signal inverted
|
|
192
|
+
OPTIONS_RX_LOS = 0x0200 # Bit 9: Loss of Signal implemented
|
|
193
|
+
# Byte 65 (low byte of options)
|
|
194
|
+
OPTIONS_TUNABLE = 0x0004 # Bit 2: Tunable transmitter technology
|
|
195
|
+
OPTIONS_COOLED_TRANSMITTER = 0x0002 # Bit 1: Cooled transmitter implemented
|
|
196
|
+
OPTIONS_POWER_LEVEL_2 = 0x0001 # Bit 0: Power level 2 requirement
|
|
197
|
+
|
|
198
|
+
# Enhanced Options (Byte 93) bit flags
|
|
199
|
+
ENHANCED_ALARM_WARNING = 0x80 # Bit 7: Optional alarm/warning flags implemented
|
|
200
|
+
ENHANCED_SOFT_TX_DISABLE = 0x40 # Bit 6: Soft TX_DISABLE implemented
|
|
201
|
+
ENHANCED_SOFT_TX_FAULT = 0x20 # Bit 5: Soft TX_FAULT implemented
|
|
202
|
+
ENHANCED_SOFT_RX_LOS = 0x10 # Bit 4: Soft RX_LOS implemented
|
|
203
|
+
ENHANCED_SOFT_RATE_SELECT = 0x08 # Bit 3: Soft RATE_SELECT implemented
|
|
204
|
+
|
|
205
|
+
# Transceiver compliance code constants (bit positions in bytes 3-10)
|
|
206
|
+
# Byte 3 (index 0): 10G Ethernet Compliance Codes
|
|
207
|
+
TRANSCEIVER_10GBASE_ER = (0, 0x80) # 10GBASE-ER
|
|
208
|
+
TRANSCEIVER_10GBASE_LRM = (0, 0x40) # 10GBASE-LRM
|
|
209
|
+
TRANSCEIVER_10GBASE_LR = (0, 0x20) # 10GBASE-LR
|
|
210
|
+
TRANSCEIVER_10GBASE_SR = (0, 0x10) # 10GBASE-SR
|
|
211
|
+
|
|
212
|
+
# Byte 6 (index 3): Ethernet Compliance Codes
|
|
213
|
+
TRANSCEIVER_BASE_PX = (3, 0x80) # BASE-PX
|
|
214
|
+
TRANSCEIVER_BASE_BX10 = (3, 0x40) # BASE-BX10
|
|
215
|
+
TRANSCEIVER_100BASE_FX = (3, 0x20) # 100BASE-FX
|
|
216
|
+
TRANSCEIVER_100BASE_LX = (3, 0x10) # 100BASE-LX/LX10
|
|
217
|
+
TRANSCEIVER_1000BASE_T = (3, 0x08) # 1000BASE-T
|
|
218
|
+
TRANSCEIVER_1000BASE_CX = (3, 0x04) # 1000BASE-CX
|
|
219
|
+
TRANSCEIVER_1000BASE_LX = (3, 0x02) # 1000BASE-LX
|
|
220
|
+
TRANSCEIVER_1000BASE_SX = (3, 0x01) # 1000BASE-SX
|
|
221
|
+
|
|
222
|
+
# Byte 4 (index 1): Fibre Channel Speed
|
|
223
|
+
TRANSCEIVER_FC_1200_MBYTES = (1, 0x80) # 1200 MBytes/sec
|
|
224
|
+
TRANSCEIVER_FC_800_MBYTES = (1, 0x40) # 800 MBytes/sec
|
|
225
|
+
TRANSCEIVER_FC_1600_MBYTES = (1, 0x20) # 1600 MBytes/sec
|
|
226
|
+
TRANSCEIVER_FC_400_MBYTES = (1, 0x10) # 400 MBytes/sec
|
|
227
|
+
TRANSCEIVER_FC_3200_MBYTES = (1, 0x08) # 3200 MBytes/sec (modern)
|
|
228
|
+
TRANSCEIVER_FC_200_MBYTES = (1, 0x04) # 200 MBytes/sec
|
|
229
|
+
TRANSCEIVER_FC_EXTENDED = (1, 0x02) # See byte 36 for extended speeds
|
|
230
|
+
TRANSCEIVER_FC_100_MBYTES = (1, 0x01) # 100 MBytes/sec
|
|
231
|
+
|
|
232
|
+
# Byte 7 (index 4): Fibre Channel Link Length
|
|
233
|
+
TRANSCEIVER_FC_VERY_LONG = (4, 0x80) # Very long distance (V)
|
|
234
|
+
TRANSCEIVER_FC_SHORT = (4, 0x40) # Short distance (S)
|
|
235
|
+
TRANSCEIVER_FC_INTERMEDIATE = (4, 0x20) # Intermediate distance (I)
|
|
236
|
+
TRANSCEIVER_FC_LONG = (4, 0x10) # Long distance (L)
|
|
237
|
+
TRANSCEIVER_FC_MEDIUM = (4, 0x08) # Medium distance (M)
|
|
238
|
+
|
|
239
|
+
# Byte 8 (index 5): Fibre Channel Technology
|
|
240
|
+
TRANSCEIVER_FC_SHORTWAVE = (5, 0x04) # Shortwave laser (SN)
|
|
241
|
+
TRANSCEIVER_FC_LONGWAVE = (5, 0x02) # Longwave laser (LC)
|
|
242
|
+
TRANSCEIVER_FC_ELECTRICAL = (5, 0x01) # Electrical inter-enclosure (EL)
|
|
243
|
+
|
|
244
|
+
# Byte 9 (index 6): SFP+ Cable Technology
|
|
245
|
+
TRANSCEIVER_LIMITING = (6, 0x40) # Limiting (SFP specific bit)
|
|
246
|
+
TRANSCEIVER_ACTIVE_CABLE = (6, 0x08) # Active Cable
|
|
247
|
+
TRANSCEIVER_PASSIVE_CABLE = (6, 0x04) # Passive Cable
|
|
248
|
+
|
|
249
|
+
# Byte 10 (index 7): Fibre Channel Transmission Media
|
|
250
|
+
TRANSCEIVER_TWIN_AXIAL = (7, 0x80) # Twin Axial Pair (TW)
|
|
251
|
+
TRANSCEIVER_TWISTED_PAIR = (7, 0x40) # Twisted Pair (TP)
|
|
252
|
+
TRANSCEIVER_MINIATURE_COAX = (7, 0x20) # Miniature Coax (MI)
|
|
253
|
+
TRANSCEIVER_VIDEO_COAX = (7, 0x10) # Video Coax (TV)
|
|
254
|
+
TRANSCEIVER_MULTIMODE_62_5 = (7, 0x08) # Multi-mode 62.5µm (M6)
|
|
255
|
+
TRANSCEIVER_MULTIMODE_50 = (7, 0x04) # Multi-mode 50µm (M5)
|
|
256
|
+
TRANSCEIVER_MULTIMODE_OM3 = (7, 0x02) # Multi-mode 50µm (OM3)
|
|
257
|
+
TRANSCEIVER_SINGLE_MODE = (7, 0x01) # Single Mode (SM)
|
|
258
|
+
|
|
259
|
+
def __init__(self, data: Optional[bytes] = None):
|
|
260
|
+
"""
|
|
261
|
+
Initialize SFP A0h EEPROM.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
data: Optional 256-byte EEPROM data to load. If None, creates empty EEPROM.
|
|
265
|
+
"""
|
|
266
|
+
if data is None:
|
|
267
|
+
# Initialize with zeros
|
|
268
|
+
self._data = bytearray(self.EEPROM_SIZE)
|
|
269
|
+
else:
|
|
270
|
+
if len(data) != self.EEPROM_SIZE:
|
|
271
|
+
raise ValueError(f"EEPROM data must be exactly {self.EEPROM_SIZE} bytes")
|
|
272
|
+
self._data = bytearray(data)
|
|
273
|
+
|
|
274
|
+
def _get_field(self, field_name: str) -> bytes:
|
|
275
|
+
"""Get raw bytes for a field."""
|
|
276
|
+
if field_name not in self.FIELDS:
|
|
277
|
+
raise ValueError(f"Unknown field: {field_name}")
|
|
278
|
+
offset, size = self.FIELDS[field_name]
|
|
279
|
+
return bytes(self._data[offset:offset + size])
|
|
280
|
+
|
|
281
|
+
def _set_field(self, field_name: str, value: Union[bytes, int, str]):
|
|
282
|
+
"""Set raw bytes for a field."""
|
|
283
|
+
if field_name not in self.FIELDS:
|
|
284
|
+
raise ValueError(f"Unknown field: {field_name}")
|
|
285
|
+
offset, size = self.FIELDS[field_name]
|
|
286
|
+
|
|
287
|
+
if isinstance(value, int):
|
|
288
|
+
if size == 1:
|
|
289
|
+
self._data[offset] = value & 0xFF
|
|
290
|
+
elif size == 2:
|
|
291
|
+
self._data[offset:offset + 2] = struct.pack('>H', value)
|
|
292
|
+
elif size == 3:
|
|
293
|
+
self._data[offset:offset + 3] = value.to_bytes(3, 'big')
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Cannot set integer for field with size {size}")
|
|
296
|
+
elif isinstance(value, str):
|
|
297
|
+
# Convert string to ASCII bytes, padding with spaces
|
|
298
|
+
encoded = value.encode('ascii')[:size]
|
|
299
|
+
padded = encoded.ljust(size, b' ')
|
|
300
|
+
self._data[offset:offset + size] = padded
|
|
301
|
+
elif isinstance(value, bytes):
|
|
302
|
+
if len(value) > size:
|
|
303
|
+
raise ValueError(f"Value too large for field {field_name} (max {size} bytes)")
|
|
304
|
+
# Pad with zeros if needed
|
|
305
|
+
padded = value.ljust(size, b'\x00')
|
|
306
|
+
self._data[offset:offset + size] = padded
|
|
307
|
+
else:
|
|
308
|
+
raise ValueError(f"Unsupported value type: {type(value)}")
|
|
309
|
+
|
|
310
|
+
def get(self, field_name: str) -> Any:
|
|
311
|
+
"""
|
|
312
|
+
Get a field value in a human-readable format.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
field_name: Name of the field to retrieve
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Field value (int, str, or bytes depending on field type)
|
|
319
|
+
"""
|
|
320
|
+
raw = self._get_field(field_name)
|
|
321
|
+
|
|
322
|
+
# String fields
|
|
323
|
+
if field_name in ['vendor_name', 'vendor_pn', 'vendor_rev', 'vendor_sn']:
|
|
324
|
+
return raw.rstrip(b' \x00').decode('ascii', errors='ignore')
|
|
325
|
+
|
|
326
|
+
# Date code (YYMMDD + lot code)
|
|
327
|
+
if field_name == 'date_code':
|
|
328
|
+
return raw.decode('ascii', errors='ignore')
|
|
329
|
+
|
|
330
|
+
# Single byte integer fields
|
|
331
|
+
if self.FIELDS[field_name][1] == 1:
|
|
332
|
+
return raw[0]
|
|
333
|
+
|
|
334
|
+
# Two byte integer fields
|
|
335
|
+
if field_name == 'wavelength':
|
|
336
|
+
return struct.unpack('>H', raw)[0]
|
|
337
|
+
|
|
338
|
+
# Multi-byte fields
|
|
339
|
+
if field_name == 'vendor_oui':
|
|
340
|
+
return raw.hex().upper()
|
|
341
|
+
|
|
342
|
+
if field_name == 'options':
|
|
343
|
+
return struct.unpack('>H', raw)[0]
|
|
344
|
+
|
|
345
|
+
# Default: return raw bytes
|
|
346
|
+
return raw
|
|
347
|
+
|
|
348
|
+
def set(self, field_name: str, value: Union[int, str, bytes]):
|
|
349
|
+
"""
|
|
350
|
+
Set a field value.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
field_name: Name of the field to set
|
|
354
|
+
value: Value to set (int, str, or bytes)
|
|
355
|
+
"""
|
|
356
|
+
self._set_field(field_name, value)
|
|
357
|
+
|
|
358
|
+
# Auto-calculate related SMF length fields
|
|
359
|
+
# length_smf_km (byte 14) is in km, length_smf (byte 15) is in 100m units
|
|
360
|
+
if field_name == 'length_smf_km' and isinstance(value, int):
|
|
361
|
+
# Set length_smf to match: 1 km = 10 units of 100m
|
|
362
|
+
self._set_field('length_smf', value * 10)
|
|
363
|
+
elif field_name == 'length_smf' and isinstance(value, int):
|
|
364
|
+
# Set length_smf_km to match: 10 units of 100m = 1 km
|
|
365
|
+
self._set_field('length_smf_km', value // 10)
|
|
366
|
+
|
|
367
|
+
# Recalculate checksums if we modified a checksummed field
|
|
368
|
+
if field_name != 'cc_base' and self.FIELDS[field_name][0] < 63:
|
|
369
|
+
self._update_cc_base()
|
|
370
|
+
if field_name != 'cc_ext' and 64 <= self.FIELDS[field_name][0] < 95:
|
|
371
|
+
self._update_cc_ext()
|
|
372
|
+
|
|
373
|
+
def set_transceiver_codes(self, *codes):
|
|
374
|
+
"""
|
|
375
|
+
Set transceiver compliance codes using named constants.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
*codes: Variable number of transceiver code constants (tuples of byte_index, bitmask)
|
|
379
|
+
e.g., SFPA0h.TRANSCEIVER_1000BASE_LX, SFPA0h.TRANSCEIVER_SINGLE_MODE
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
eeprom.set_transceiver_codes(
|
|
383
|
+
SFPA0h.TRANSCEIVER_10GBASE_LR,
|
|
384
|
+
SFPA0h.TRANSCEIVER_SINGLE_MODE
|
|
385
|
+
)
|
|
386
|
+
"""
|
|
387
|
+
# Start with zeros
|
|
388
|
+
transceiver_bytes = bytearray(8)
|
|
389
|
+
|
|
390
|
+
# Set bits for each code
|
|
391
|
+
for code in codes:
|
|
392
|
+
if isinstance(code, tuple) and len(code) == 2:
|
|
393
|
+
byte_index, bitmask = code
|
|
394
|
+
if 0 <= byte_index < 8:
|
|
395
|
+
transceiver_bytes[byte_index] |= bitmask
|
|
396
|
+
else:
|
|
397
|
+
raise ValueError(f"Invalid transceiver code byte index: {byte_index}")
|
|
398
|
+
else:
|
|
399
|
+
raise ValueError(f"Invalid transceiver code format: {code}")
|
|
400
|
+
|
|
401
|
+
# Set the transceiver field
|
|
402
|
+
self.set('transceiver', bytes(transceiver_bytes))
|
|
403
|
+
|
|
404
|
+
def _calculate_checksum(self, start: int, end: int) -> int:
|
|
405
|
+
"""Calculate checksum for a range of bytes."""
|
|
406
|
+
total = sum(self._data[start:end])
|
|
407
|
+
return total & 0xFF
|
|
408
|
+
|
|
409
|
+
def _update_cc_base(self):
|
|
410
|
+
"""Update the base ID checksum (CC_BASE at byte 63)."""
|
|
411
|
+
checksum = self._calculate_checksum(0, 63)
|
|
412
|
+
self._data[63] = checksum
|
|
413
|
+
|
|
414
|
+
def _update_cc_ext(self):
|
|
415
|
+
"""Update the extended ID checksum (CC_EXT at byte 95)."""
|
|
416
|
+
checksum = self._calculate_checksum(64, 95)
|
|
417
|
+
self._data[95] = checksum
|
|
418
|
+
|
|
419
|
+
def validate_checksums(self) -> Dict[str, bool]:
|
|
420
|
+
"""
|
|
421
|
+
Validate both checksums.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Dictionary with 'cc_base' and 'cc_ext' validation results
|
|
425
|
+
"""
|
|
426
|
+
cc_base_calculated = self._calculate_checksum(0, 63)
|
|
427
|
+
cc_ext_calculated = self._calculate_checksum(64, 95)
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
'cc_base': self._data[63] == cc_base_calculated,
|
|
431
|
+
'cc_ext': self._data[95] == cc_ext_calculated,
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
def update_checksums(self):
|
|
435
|
+
"""Update both CC_BASE and CC_EXT checksums."""
|
|
436
|
+
self._update_cc_base()
|
|
437
|
+
self._update_cc_ext()
|
|
438
|
+
|
|
439
|
+
def to_bytes(self) -> bytes:
|
|
440
|
+
"""
|
|
441
|
+
Export EEPROM as 256 bytes.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
Complete EEPROM data as bytes
|
|
445
|
+
"""
|
|
446
|
+
return bytes(self._data)
|
|
447
|
+
|
|
448
|
+
@classmethod
|
|
449
|
+
def from_bytes(cls, data: bytes) -> 'SFPA0h':
|
|
450
|
+
"""
|
|
451
|
+
Create EEPROM object from binary data.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
data: 256 bytes of EEPROM data
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
New SFPA0h instance
|
|
458
|
+
"""
|
|
459
|
+
return cls(data)
|
|
460
|
+
|
|
461
|
+
def get_info(self) -> Dict[str, Any]:
|
|
462
|
+
"""
|
|
463
|
+
Get a dictionary of human-readable EEPROM information.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
Dictionary containing decoded EEPROM fields
|
|
467
|
+
"""
|
|
468
|
+
info = {}
|
|
469
|
+
|
|
470
|
+
# Basic identification
|
|
471
|
+
identifier = self.get('identifier')
|
|
472
|
+
info['identifier'] = {
|
|
473
|
+
'value': identifier,
|
|
474
|
+
'description': self.IDENTIFIER_TYPES.get(identifier, 'Unknown')
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
connector = self.get('connector')
|
|
478
|
+
info['connector'] = {
|
|
479
|
+
'value': connector,
|
|
480
|
+
'description': self.CONNECTOR_TYPES.get(connector, 'Unknown')
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
# Vendor information
|
|
484
|
+
info['vendor_name'] = self.get('vendor_name')
|
|
485
|
+
info['vendor_oui'] = self.get('vendor_oui')
|
|
486
|
+
info['vendor_pn'] = self.get('vendor_pn')
|
|
487
|
+
info['vendor_rev'] = self.get('vendor_rev')
|
|
488
|
+
info['vendor_sn'] = self.get('vendor_sn')
|
|
489
|
+
info['date_code'] = self.get('date_code')
|
|
490
|
+
|
|
491
|
+
# Transceiver specifications
|
|
492
|
+
info['encoding'] = self.get('encoding')
|
|
493
|
+
info['br_nominal'] = self.get('br_nominal')
|
|
494
|
+
info['wavelength'] = self.get('wavelength')
|
|
495
|
+
|
|
496
|
+
# Options and diagnostics
|
|
497
|
+
info['options'] = self.get('options')
|
|
498
|
+
|
|
499
|
+
# Link lengths
|
|
500
|
+
info['length_smf_km'] = self.get('length_smf_km')
|
|
501
|
+
info['length_smf'] = self.get('length_smf')
|
|
502
|
+
info['length_50um'] = self.get('length_50um')
|
|
503
|
+
info['length_62_5um'] = self.get('length_62_5um')
|
|
504
|
+
info['length_copper'] = self.get('length_copper')
|
|
505
|
+
info['length_om3'] = self.get('length_om3')
|
|
506
|
+
|
|
507
|
+
# Checksums
|
|
508
|
+
checksums = self.validate_checksums()
|
|
509
|
+
info['checksums'] = checksums
|
|
510
|
+
|
|
511
|
+
return info
|
|
512
|
+
|
|
513
|
+
def __repr__(self) -> str:
|
|
514
|
+
"""String representation of EEPROM."""
|
|
515
|
+
vendor = self.get('vendor_name')
|
|
516
|
+
pn = self.get('vendor_pn')
|
|
517
|
+
sn = self.get('vendor_sn')
|
|
518
|
+
return f"SFPA0h(vendor='{vendor}', pn='{pn}', sn='{sn}')"
|