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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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
@@ -0,0 +1,11 @@
1
+ """
2
+ SFP EEPROM Library
3
+
4
+ A Python library for creating and manipulating SFP module EEPROM data
5
+ based on SFF-8472 specifications.
6
+ """
7
+
8
+ from .eeprom import SFPA0h
9
+
10
+ __version__ = "0.1.1"
11
+ __all__ = ["SFPA0h"]
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}')"