itla.itla12
1from time import sleep 2 3from . import logger 4from .itla import ITLABase 5from .itla_errors import ( 6 CIEError, 7 CPException, 8 EREError, 9 ExecutionError, 10 NOPException, 11 RVEError, 12) 13from .itla_status import ( 14 MCB, 15 AlarmTrigger, 16 FatalError, 17 FatalTrigger, 18 Resena, 19 SQRTrigger, 20 WarningError, 21 Waveform, 22) 23 24 25class ITLA12(ITLABase): 26 """ 27 A class that represents the ITLA12 28 and exposes a user friendly API for controlling functionality. 29 30 Things to figure out 31 32 * What should be exposed to the user? 33 * Can we abstract away the concept of registers and stuff 34 in here so you don't have to deal with it. 35 36 There are some functions that could be implemented like set_fatalstatus. 37 I think this is probably a bad idea even though it isnt write only. 38 39 Set Frequency is my platonic ideal for the higher level functions. 40 41 """ 42 43 def __init__( 44 self, serial_port, baudrate, timeout=0.5, register_files=None, sleep_time=0.1 45 ): 46 """Initializes the ITLA12 object. 47 48 :param serial_port: The serial port to connect to. 49 Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX' 50 :param baudrate: The baudrate for communication. I have primarily seen 51 lasers using 9600 as the default and then higher for firmware upgrades. 52 This may be laser specific. 53 :param timeout: How long should we wait to receive a response from the laser 54 :param register_files: Any additional register files you would like to include 55 beyond the default MSA-01.2 defined registers. These must be in a yaml format as 56 described in the project's README. 57 :param sleep_time: time in seconds. Used in wait function 58 """ 59 if register_files is None: 60 register_files = [] 61 62 register_files = ["registers_itla12.yaml", *register_files] 63 64 self.sleep_time = sleep_time 65 66 super().__init__( 67 serial_port, baudrate, timeout=timeout, register_files=register_files 68 ) 69 70 def nop(self, data=None): 71 """The No-Op operation. 72 73 This is a good test to see if your laser is communicating properly. 74 It should read 0000 data or echo whatever data you send it if you send it something. 75 The data you write to nop gets overwritten as soon as you read it back. 76 77 `nop()` also returns more informative errors following an ExecutionError. 78 This is not called by default so you must do this explicitly if you want 79 to see more informative error information. 80 81 See 9.4.1 NOP/Status (ResEna 0x00) [RW] in OIF-ITLA-MSA. 82 83 :param data: Data to write 84 85 :returns: None 86 """ 87 # pretty sure the data does nothing 88 if data is not None: 89 response = self._nop(data) 90 else: 91 response = self._nop() 92 93 error_field = int(response.hex()[-1], 16) 94 if bool(error_field): 95 raise self._nop_errors[error_field] 96 97 def enable(self): 98 """Enables laser optical output. 99 There is a time delay after execution of this function to 100 proper stable laser output. 101 102 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 103 104 :returns: None 105 """ 106 # I'm writing this out partially for transparency 107 # Maybe unnecessary or non-optimal 108 self._resena(Resena.SENA) 109 110 def disable(self): 111 """disables the laser 112 113 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 114 115 :returns: None 116 """ 117 self._resena(0) 118 119 def hard_reset(self): 120 """initiate module reset 121 122 The hardware reset is typically traffic interrupting since it will reset 123 control loops as well. The host can poll the communication’s interface 124 waiting for a response packet indicating that the interface is ready to 125 communicate. Note that a response is returned to acknowledge the reset 126 request before the reset is started 127 128 The impact to the optical signal is undefined. This bit is self clearing. 129 130 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 131 132 :returns: None 133 """ 134 self._resena(Resena.MR) 135 136 def soft_reset(self): 137 """initiate soft reset 138 139 The soft reset resets the communication’s interface and is traffic 140 non-interrupting. Extended address registers are reset. 141 142 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 143 144 :returns: None 145 """ 146 self._resena(Resena.SR) 147 148 def get_reset_enable(self): 149 """return ResEna register. 150 151 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 152 """ 153 response = self._resena() 154 return Resena(int.from_bytes(response, "big")) 155 156 def set_alarm_during_tuning(self): 157 """Set alarm during tuning 158 mcb(ADT) 159 160 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 161 """ 162 self._mcb(MCB.ADT) 163 164 def set_shutdown_on_fatal(self): 165 """Set shutdown on fatal. 166 mcb(SDF) 167 168 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 169 """ 170 self._mcb(MCB.SDF) 171 172 def get_configuration_behavior(self): 173 """ 174 Return MCB register. 175 176 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 177 """ 178 response = self._mcb() 179 return MCB(int.from_bytes(response, "big")) 180 181 def is_disabled(self): 182 """ 183 Return if disabled. 184 185 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI. 186 187 For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). 188 Consider overwriting this methods and monitoring FatalError.ALM. 189 """ 190 fatal_error = self.get_error_fatal() 191 fatal_trigger = self.get_fatal_trigger() 192 resena = self.get_reset_enable() 193 mcb = self.get_configuration_behavior() 194 195 sdf = MCB.SDF in mcb 196 sena = Resena.SENA in resena 197 dis = FatalError.DIS in fatal_error 198 199 return ((fatal_error & fatal_trigger) and sdf) or (not sena) or (not dis) 200 201 def is_enabled(self, *args): 202 """ 203 Return if enabled. See is_disabled. 204 """ 205 return not self.is_disabled(*args) 206 207 def get_device_type(self): 208 """returns a string containing the device type 209 210 DevTyp returns the module’s device type. For all tunable lasers covered by this MSA, the 211 module will return the null terminated string “CW ITLA\0” (eight bytes including the 212 terminating null character) indirectly through the AEA mechanism. The device type register 213 is provided such that a host can distinguish between different types of tunable devices.type. 214 215 See 9.4.2 Device Type (DevTyp 0x01) [R] 216 """ 217 response_bytes = self._devtyp() 218 219 return response_bytes.decode("utf-8") 220 221 def get_manufacturer(self): 222 """returns a string containing the manufacturer's name. 223 224 MFGR returns the module’s manufacturers ID null terminated string indirectly through the 225 AEA mechanism 226 227 See 9.4.3 Manufacturer (MFGR 0x02) [R] 228 """ 229 response_bytes = self._mfgr() 230 231 return response_bytes.decode("utf-8") 232 233 def get_model(self): 234 """returns the model as a string 235 236 Model returns the module’s model designation string indirectly through the AEA 237 mechanism. The null terminated string containing the module’s model designation is 238 placed into a field of not more than 80 bytes in size. The model string is defined by the 239 manufacturer 240 241 See 9.4.4 Model (Model 0x03) [R] 242 """ 243 response_bytes = self._model() 244 245 return response_bytes.decode("utf-8") 246 247 def get_serialnumber(self): 248 """returns the serial number 249 250 SerNo returns the module’s serial number string indirectly through the AEA mechanism. 251 The null terminated string containing the module’s serial number is placed into a field of not 252 more than 80 bytes in size. The serial number string is defined by the manufacturer. 253 254 See 9.4.5 Serial Number (SerNo 0x04) [R] 255 """ 256 response_bytes = self._serno() 257 258 return response_bytes.decode("utf-8") 259 260 def get_manufacturing_date(self): 261 """returns the manufacturing date 262 263 MFGDate returns the manufacturing date string of the module indirectly through the AEA 264 mechanism. The null terminated string containing the date string is contained in a field 265 size of 12 bytes. 266 267 See 9.4.5 Manufaturing Date (MFGDate 0x05) [R] 268 """ 269 response_bytes = self._mfgdate() 270 271 return response_bytes.decode("utf-8") 272 273 def get_firmware_release(self): 274 """returns a manufacturer specific firmware release 275 276 Release returns the release string of the module indirectly through the AEA mechanism. 277 The null terminated string containing the module release information is placed into a field of 278 not more than 80 bytes in size. Note that a module may have one or more firmware and/or 279 hardware revisions to track. The release field also encodes the application space 280 identifier. 281 282 See 9.4.7 Release (Release 0x06) [R] 283 """ 284 response_bytes = self._release() 285 286 return response_bytes.decode("utf-8") 287 288 def get_backwardscompatibility(self): 289 """returns a manufacturer specific firmware backwards compatibility 290 as a null terminated string 291 292 RelBack returns the release backwards compatibility string of the module indirectly through 293 the AEA mechanism. The null terminated string containing the earliest release string which 294 is fully backwards compatible with the current module. The string is contained in a field of 295 not more than 80 bytes in size. Note that a module may have one or more firmware and/or 296 hardware revisions to track as described in the Release (0x06) register 297 298 See 9.4.8 Release Backwards Compatibility (RelBack 0x07) [R] 299 """ 300 response_bytes = self._relback() 301 302 return response_bytes.decode("utf-8") 303 304 def get_general_module_configuration(self): 305 """returns the power on/reset module configuration 306 307 GenCfg defines the general module configuration for the generic tunable device. For the 308 tunable laser, the register is used to save the power on/reset module configuration 309 defaults. 310 311 See 9.4.9 General Module Configuration (GenCfg 0x08) [RW] 312 """ 313 raise NotImplementedError() 314 315 def set_general_module_configuration(self): 316 """returns the power on/reset module configuration 317 318 GenCfg defines the general module configuration for the generic tunable device. For the 319 tunable laser, the register is used to save the power on/reset module configuration 320 defaults. 321 322 See 9.4.9 General Module Configuration (GenCfg 0x08) [RW] 323 """ 324 raise NotImplementedError() 325 326 def get_iocap(self): 327 """ 328 The IOCap register returns or sets the I/O interface capabilities 329 330 See 9.4.10 IO Capabilities (IOCap 0x0D) [RW] 331 """ 332 raise NotImplementedError() 333 334 def set_iocap(self, data): 335 """ 336 The IOCap register returns or sets the I/O interface capabilities 337 338 See 9.4.10 IO Capabilities (IOCap 0x0D) [RW] 339 """ 340 raise NotImplementedError() 341 342 def read_aea(self): 343 """reads the AEA register data until an execution error is thrown. 344 345 See 9.4.11 Extended Addressing Mode Registers (0x09-0x0B, 0x0E-0x10) [RW] 346 """ 347 aea_response = b"" 348 try: 349 while True: 350 aea_response += self._aea_ear() 351 352 except ExecutionError: 353 try: 354 self.nop() 355 356 except EREError: 357 # If this throws an ERE error then we have completed reading AEA 358 pass 359 360 except NOPException as nop_e: 361 raise nop_e 362 363 return aea_response 364 365 def get_download_configuration(self): 366 """ 367 The DLConfig register configures a host to module download of code or data for 368 reconfiguration purposes or configures a module to host upload of code or data to the host. 369 A file transfer may occur at several locations such as vendor factory, customer site (on the 370 bench), customer system (circuit down), or potentially a customer system (live circuit) 371 372 See 9.4.13 Download Configuration (DLConfig 0x14) [RW] 373 """ 374 raise NotImplementedError() 375 376 def set_download_configuration(self): 377 """ 378 The DLConfig register configures a host to module download of code or data for 379 reconfiguration purposes or configures a module to host upload of code or data to the host. 380 A file transfer may occur at several locations such as vendor factory, customer site (on the 381 bench), customer system (circuit down), or potentially a customer system (live circuit) 382 383 See 9.4.13 Download Configuration (DLConfig 0x14) [RW] 384 """ 385 raise NotImplementedError() 386 387 def get_download_status(self): 388 """ 389 DLStatus provides information about the status or viability of a code segment. 390 391 See 9.4.14 Download Status (DLStatus 0x15) [R] 392 """ 393 raise NotImplementedError() 394 395 def wait(self): 396 """Wait until operation is complete. 397 It check if the operation is completed every self.sleep_time seconds. 398 """ 399 while True: 400 try: 401 self.nop() 402 except CPException: 403 if self.sleep_time is not None: 404 sleep(self.sleep_time) 405 else: 406 break 407 408 def wait_until_enabled(self): 409 """TODO: Add documentation.""" 410 while self.is_disabled(): 411 if self.sleep_time is not None: 412 sleep(self.sleep_time) 413 414 def wait_until_disabled(self): 415 """TODO: Add documentation.""" 416 while self.is_enabled(): 417 if self.sleep_time is not None: 418 sleep(self.sleep_time) 419 420 def set_power(self, pwr_dBm): 421 """Sets the power of the ITLA laser. 422 Units of dBm. 423 424 See 9.6.2 Optical Power Set Point (PWR 0x31) [RW] 425 426 :param pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. 427 :returns: None 428 """ 429 try: 430 self._pwr(round(pwr_dBm * 100)) 431 432 except ExecutionError: 433 try: 434 self.nop() 435 436 except RVEError as error: 437 logger.error( 438 "The provided power %.2f dBm is outside of the range for this device. " 439 "The power is currently set to: %.2f dBm.", 440 pwr_dBm, 441 self.get_power_setting(), 442 ) 443 raise error 444 445 def get_power_setting(self): 446 """Gets current power setting set by set_power. 447 Should be in dBm. 448 449 See 9.6.2 Optical Power Set Point (PWR 0x31) [RW] 450 451 :returns: 452 """ 453 # Gets power setting, not actual optical output power. 454 response = self._pwr() 455 return int.from_bytes(response, "big", signed=True) / 100 456 457 def get_power_output(self): 458 """returns external optical power estimate 459 460 See 9.6.8 Optical Output Power (OOP 0x42) [R] 461 462 :returns: External optical power estimate in dBm 463 """ 464 response = self._oop() 465 466 return int.from_bytes(response, "big", signed=True) / 100 467 468 def get_power_min(self): 469 """returns minimum optical power output of the module. 470 Units dBm. 471 472 See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R] 473 474 :returns: minimum optical power 475 """ 476 response = self._opsl() 477 return int.from_bytes(response, "big", signed=True) / 100 478 479 def get_power_max(self): 480 """returns maximum optical power output of the module. 481 Units dBm. 482 483 See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R] 484 485 :returns: maximum optical power 486 """ 487 response = self._opsh() 488 return int.from_bytes(response, "big", signed=True) / 100 489 490 def set_fcf(self, freq): 491 """ 492 This sets the first channel frequency. 493 It does not reset the channel so this frequency will only be equal 494 to the output frequency if channel=1. 495 496 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 497 498 :returns: None 499 """ 500 # convert frequency to MHz 501 freq = round(freq * 1e6) 502 503 freq_str = str(freq) 504 fcf1 = int(freq_str[0:3]) 505 fcf2 = int(freq_str[3:7]) 506 507 try: 508 # is it better to split these into their own try/except blocks? 509 self._fcf1(fcf1) 510 self._fcf2(fcf2) 511 512 except ExecutionError: 513 try: 514 self.nop() 515 except RVEError as error: 516 logger.error( 517 "%.2f THz is out of bounds for this laser. " 518 "The frequency must be within the range %.2f - %.2f THz.", 519 fcf1, 520 self.get_frequency_min(), 521 self.get_frequency_max(), 522 ) 523 raise error 524 525 except CIEError as error: 526 logger.error( 527 "You cannot change the first channel frequency while the laser is enabled. " 528 "The current frequency is: %.2f THz", 529 self.get_frequency(), 530 ) 531 raise error 532 533 def set_frequency(self, freq): 534 """Sets the frequency of the laser in TeraHertz. 535 536 Has MHz resolution. Will round down. 537 538 This function sets the first channel frequency and then sets the 539 channel to 1 so that the frequency output when the laser is enabled 540 will be the frequency given to this function. 541 542 If you would like to only change the first channel frequency use 543 the function `set_fcf`. 544 545 Disable the laser before calling this function. 546 547 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 548 549 :param freq: The desired frequency setting in THz. 550 :returns: None 551 """ 552 553 # This does a check so this only runs if fine tuning has been turned on. 554 if self.get_fine_tuning() != 0: 555 # Set the fine tuning off! 556 while True: 557 try: 558 self.set_fine_tuning(0) 559 except ExecutionError: 560 sleep(self.sleep_time) 561 except CPException: 562 self.wait() 563 break 564 else: 565 break 566 567 try: 568 self.set_fcf(freq) 569 except CPException: 570 self.wait() 571 572 try: 573 self.set_channel(1) 574 except CPException: 575 self.wait() 576 577 def get_fcf(self): 578 """returns first channel frequency 579 580 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 581 582 :returns: First channel frequency in THz 583 """ 584 response = self._fcf1() 585 fcf1 = int.from_bytes(response, "big") 586 587 response = self._fcf2() 588 fcf2 = int.from_bytes(response, "big") 589 590 return fcf1 + fcf2 * 1e-4 591 592 def get_frequency(self): 593 """gets the current laser operating frequency 594 595 See 9.6.7 Laser Frequency (LF1, LF2 0x40 – 0x41) [R] 596 597 :returns: Current laser frequency in THz. 598 """ 599 response = self._lf1() 600 lf1 = int.from_bytes(response, "big") 601 602 response = self._lf2() 603 lf2 = int.from_bytes(response, "big") 604 605 return lf1 + lf2 * 1e-4 606 607 def dither_enable(self, waveform: Waveform = Waveform.SIN): 608 """enables dither 609 610 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 611 612 :param waveform: specifies the dither waveform 613 """ 614 615 data = (waveform << 4) | 2 616 617 self._dithere(data) 618 619 def dither_disable(self): 620 """disables digital dither 621 622 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 623 """ 624 # TODO: This should preserve the waveform setting if possible 625 self._dithere(0) 626 627 def set_dither_rate(self, rate): 628 """set dither rate in kHz 629 utilizes DitherR register 630 631 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 632 633 :param rate: an unsigned short integer specifying dither rate in kHz 634 """ 635 self._ditherr(rate) 636 637 def get_dither_rate(self): 638 """get dither rate 639 utilizes DitherR register 640 641 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 642 643 :returns: Dither rate in kHz 644 """ 645 response = self._ditherr() 646 return int.from_bytes(response, "big") 647 648 def set_dither_frequency(self, rate): 649 """set dither modulation frequency 650 utilizes DitherF register 651 652 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 653 654 :param rate: an unsigned short integer encoded as the FM peak-to-peak frequency 655 deviation as Ghz*10 656 """ 657 self._ditherf(rate) 658 659 def get_dither_frequency(self): 660 """get dither modulation frequency 661 utilizes DitherF register 662 663 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 664 665 :returns: dither modulation frequency in GHz * 10 666 """ 667 response = self._ditherf() 668 return int.from_bytes(response, "big") 669 670 def set_dither_amplitude(self, amplitude): 671 """set dither modulation amplitude 672 utilizes DitherA register 673 674 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 675 676 :param amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude 677 deviation as 10*percentage of the optical power 678 679 :returns: None 680 """ 681 self._dithera(amplitude) 682 683 def get_dither_amplitude(self): 684 """get dither modulation amplitude 685 utilizes DitherA register 686 687 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 688 689 :returns: dither aplitude in 10ths of percents 690 """ 691 response = self._dithera() 692 return int.from_bytes(response, "big") 693 694 def get_temp(self): 695 """Returns the current primary control temperature in deg C. 696 697 See 9.6.9 Current Temperature (CTemp 0x43) [R] 698 699 :returns: current primary control temperature in deg C 700 """ 701 response = self._ctemp() 702 temp_100 = int.from_bytes(response, "big") 703 704 return temp_100 / 100 705 706 def get_frequency_min(self): 707 """returns minimum frequency supported by the module 708 709 See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R] 710 711 :returns: minimum frequency in THz 712 """ 713 response = self._lfl1() 714 lfl1 = int.from_bytes(response, "big") 715 716 response = self._lfl2() 717 lfl2 = int.from_bytes(response, "big") 718 719 return lfl1 + lfl2 * 1e-4 720 721 def get_frequency_max(self): 722 """returns maximum frequency supported by the module 723 724 See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R] 725 726 :returns: maximum frequency in THz 727 """ 728 response = self._lfh1() 729 fcf1 = int.from_bytes(response, "big") 730 731 response = self._lfh2() 732 fcf2 = int.from_bytes(response, "big") 733 734 return fcf1 + fcf2 * 1e-4 735 736 def get_grid_min(self): 737 """returns minimum grid spacing of the module 738 739 See 9.7.4 Laser’s Minimum Grid Spacing (LGrid 0x56) [R] 740 741 :returns: The minimum grid supported by the module in GHz 742 """ 743 try: 744 freq_lgrid = int.from_bytes(self._lgrid(), "big", signed=False) 745 746 except ExecutionError: 747 self.nop() 748 749 return freq_lgrid * 1e-1 750 751 def set_grid(self, grid_freq): 752 """Set the grid spacing in GHz. 753 MHz resolution. 754 755 See 9.6.5 Grid Spacing (Grid 0x34) [RW] 756 757 :param grid_freq: the grid frequency spacing in GHz 758 :returns: 759 760 """ 761 grid_freq = str(round(grid_freq * 1000)) 762 data = int(grid_freq[0:4]) 763 764 self._grid(data) 765 766 def get_grid(self): 767 """get the grid spacing in GHz 768 769 See 9.6.5 Grid Spacing (Grid 0x34) [RW] 770 771 :returns: The grid spacing in GHz. 772 """ 773 response = self._grid() 774 grid_freq = int.from_bytes(response, "big", signed=True) 775 776 return grid_freq * 1e-1 777 778 def get_age(self): 779 """Returns the percentage of aging of the laser. 780 0% indicated a brand new laser 781 100% indicates the laser should be replaces. 782 783 See 9.8.6 Laser Age (Age 0x61) [R] 784 785 :returns: percent aging of the laser 786 """ 787 response = self._age() 788 age = int.from_bytes(response, "big") 789 790 return age 791 792 def set_channel(self, channel): 793 """Sets the laser's operating channel. 794 795 MSA-01.2 lasers support a 16 bit channel value. 796 797 This defines how many spaces along the grid 798 the laser's frequency is displaced from the first channel frequency. 799 800 See 9.6.1 Channel (Channel 0x30) [RW] 801 802 :param channel: 803 :returns: None 804 805 """ 806 # check type and stuff 807 if not isinstance(channel, int): 808 raise TypeError("Channel must be an integer") 809 if channel < 0: 810 raise ValueError("Channel must be positive.") 811 if channel > 0xFFFF: 812 raise ValueError("Channel must be a 16 bit integer (<=0xFFFF).") 813 814 # Split the channel choice into two options. 815 channel_hex = f"{channel:08x}" 816 817 channell = int(channel_hex[4:], 16) 818 819 # Set the channel registers. 820 self._channel(channell) 821 822 def get_channel(self): 823 """gets the current channel setting 824 825 The channel defines how many spaces along the grid 826 the laser's frequency is displaced from the first channel frequency. 827 828 See 9.6.1 Channel (Channel 0x30) [RW] 829 830 :returns: channel as an integer. 831 832 """ 833 # This concatenates the data bytestrings 834 response = self._channel() 835 836 channel = int.from_bytes(response, "big") 837 838 return channel 839 840 def set_fine_tuning(self, ftf): 841 """ 842 This function provides off grid tuning for the laser's frequency. 843 The adjustment applies to all channels uniformly. 844 This is typically used after the laser if locked and minor adjustments 845 are required to the frequency. 846 847 The command may be pending in the event that the laser output is 848 enabled. The pending bit is cleared once the fine tune frequency 849 has been achieved. 850 851 See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW] 852 853 **???** It seems like this can be done with the laser running. 854 855 :param ftf: The fine tune frequency adjustment in GHz 856 """ 857 858 ftf = round(ftf * 1e3) 859 860 # We will leave this bare. This way the user can set and handle 861 # their own timing and check to make sure the laser has reached the 862 # desired fine tuning frequency. 863 # It WILL throw a "pending" error if the laser is on when setting. 864 self._ftf(ftf) 865 866 def get_fine_tuning(self): 867 """ 868 This function returns the setting for the 869 off grid tuning for the laser's frequency. 870 871 See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW] 872 873 :return ftf: The fine tune frequency adjustment in GHz 874 """ 875 876 response = self._ftf() 877 ftf = int.from_bytes(response, "big", signed=True) 878 879 return ftf * 1e-3 880 881 def get_ftf_range(self): 882 """return the maximum and minimum off grid tuning for the laser's frequency. 883 884 See 9.7.1 Fine Tune Frequency Range (FTF 0x4F) [R] 885 886 :return ftfr: The fine tune frequency range [-ftfr, + ftfr] in GHz 887 """ 888 response = self._ftfr() 889 890 ftfr = int.from_bytes(response, "big") 891 892 return ftfr * 1e-3 893 894 def get_temps(self): 895 """returns a list of technology specific temperatures 896 units in deg C unless otherwise specified 897 898 **Technologies** 899 Technology 1: [Diode Temp, Case Temp] 900 Technology 2: [Diode Temp, Case Temp] 901 (why are they the same?) 902 903 See 9.8.2 Module Temperatures (Temps 0x58) [R] 904 905 :returns: list of temperatures in deg C 906 """ 907 # get response this should be a long byte string 908 response = self._temps() 909 910 data = [ 911 int.from_bytes(response[i : i + 2], "big", signed=True) / 100 912 for i in range(0, len(response), 2) 913 ] 914 915 return data 916 917 def get_currents(self): 918 """returns a list of technology specific currents in mA. 919 920 **Technologies** 921 Technology 1: [TEC, Diode] 922 Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] 923 Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...] 924 925 See 9.8.1 Module Currents (Currents 0x57) [R] 926 927 :returns: list of currents in mA 928 """ 929 # get response this should be a long byte string 930 response = self._currents() 931 932 data = [ 933 int.from_bytes(response[i : i + 2], "big", signed=True) / 10 934 for i in range(0, len(response), 2) 935 ] 936 937 return data 938 939 def get_last_response(self): 940 """reads the last response sent from the laser 941 942 This function gets the most recent response sent from the laser. 943 The response is parsed for errors the way any normal response 944 would be. The variable `self._response` is set to the last response again. 945 946 See 9.4.12 Last Response (LstResp 0x13) [R] 947 948 """ 949 return self._lstresp() 950 951 def get_error_fatal(self, reset=False): 952 """reads fatal error register statusf and checks each bit against the 953 fatal error table to determine the fault 954 955 See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW] 956 957 :param reset: resets/clears latching errors 958 959 """ 960 response = self._statusf() 961 statusf = int.from_bytes(response, "big") 962 963 logger.debug("Current Status Fatal Error: %d", statusf) 964 965 if reset: 966 data_reset = 0x00FF 967 self._statusf(data_reset) 968 969 return FatalError(statusf) 970 971 def get_error_warning(self, reset=False): 972 """reads warning error register statusw and checks each bit against the 973 warning error table to determine the fault 974 975 If the laser is off then some of the latched warnings will be set on. 976 977 See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW] 978 979 :param reset: resets/clears latching errors 980 """ 981 response = self._statusw() 982 statusw = int.from_bytes(response, "big") 983 984 logger.debug("Current Status Warning Error: %d", statusw) 985 986 if reset: 987 data_reset = 0x00FF 988 self._statusw(data_reset) 989 990 return WarningError(statusw) 991 992 def get_fatal_power_thresh(self): 993 """reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted 994 995 See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW] 996 """ 997 response = self._fpowth() 998 pow_fatal = int.from_bytes(response, "big") / 100 999 # correcting for proper order of magnitude 1000 return pow_fatal 1001 1002 def get_warning_power_thresh(self): 1003 """reads maximum plus/minus power deviation in dB for which the warning alarm is asserted 1004 1005 See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW] 1006 """ 1007 response = self._wpowth() 1008 pow_warn = int.from_bytes(response, "big") / 100 1009 # correcting for proper order of magnitude 1010 return pow_warn 1011 1012 def get_fatal_freq_thresh(self): 1013 """reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted 1014 1015 See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW] 1016 """ 1017 response = self._ffreqth() 1018 freq_fatal = int.from_bytes(response, "big") / 10 1019 return freq_fatal 1020 1021 def get_warning_freq_thresh(self): 1022 """reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted 1023 1024 See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW] 1025 """ 1026 response = self._wfreqth() 1027 freq_warn = int.from_bytes(response, "big") / 10 1028 return freq_warn 1029 1030 def get_fatal_therm_thresh(self): 1031 """reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted 1032 1033 See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW] 1034 """ 1035 response = self._fthermth() 1036 therm_fatal = int.from_bytes(response, "big") / 100 1037 # correcting for proper order of magnitude 1038 return therm_fatal 1039 1040 def get_warning_therm_thresh(self): 1041 """reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted 1042 1043 See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW] 1044 """ 1045 response = self._wthermth() 1046 therm_thresh = int.from_bytes(response, "big") / 100 1047 # correcting for proper order of magnitude 1048 return therm_thresh 1049 1050 def get_srq_trigger(self): 1051 """identifies corresponding bits in status registers StatusF, StatusW 1052 1053 Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers 1054 1055 See 9.5.5 SRQ* Triggers (SRQT 0x28) [RW] 1056 """ 1057 response = self._srqt() 1058 status = int.from_bytes(response, "big") 1059 1060 logger.debug("SRQT Status: %d", status) 1061 1062 return SQRTrigger(status) 1063 1064 def get_fatal_trigger(self): 1065 """identifies which fatal condition was asserted in StatusF and StatusW registers 1066 1067 See 9.5.6 FATAL Triggers (FatalT 0x29) [RW] 1068 """ 1069 response = self._fatalt() 1070 status = int.from_bytes(response, "big") 1071 1072 logger.debug("FatalT Status: %d", status) 1073 1074 return FatalTrigger(status) 1075 1076 def get_alm_trigger(self): 1077 """identifies why ALM was asserted in StatusF and StatusW registers 1078 1079 See 9.5.7 ALM Triggers (ALMT 0x2A) [RW] 1080 """ 1081 response = self._almt() 1082 status = int.from_bytes(response, "big") 1083 1084 logger.debug("AlarmT Status: %d", status) 1085 1086 return AlarmTrigger(status)
26class ITLA12(ITLABase): 27 """ 28 A class that represents the ITLA12 29 and exposes a user friendly API for controlling functionality. 30 31 Things to figure out 32 33 * What should be exposed to the user? 34 * Can we abstract away the concept of registers and stuff 35 in here so you don't have to deal with it. 36 37 There are some functions that could be implemented like set_fatalstatus. 38 I think this is probably a bad idea even though it isnt write only. 39 40 Set Frequency is my platonic ideal for the higher level functions. 41 42 """ 43 44 def __init__( 45 self, serial_port, baudrate, timeout=0.5, register_files=None, sleep_time=0.1 46 ): 47 """Initializes the ITLA12 object. 48 49 :param serial_port: The serial port to connect to. 50 Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX' 51 :param baudrate: The baudrate for communication. I have primarily seen 52 lasers using 9600 as the default and then higher for firmware upgrades. 53 This may be laser specific. 54 :param timeout: How long should we wait to receive a response from the laser 55 :param register_files: Any additional register files you would like to include 56 beyond the default MSA-01.2 defined registers. These must be in a yaml format as 57 described in the project's README. 58 :param sleep_time: time in seconds. Used in wait function 59 """ 60 if register_files is None: 61 register_files = [] 62 63 register_files = ["registers_itla12.yaml", *register_files] 64 65 self.sleep_time = sleep_time 66 67 super().__init__( 68 serial_port, baudrate, timeout=timeout, register_files=register_files 69 ) 70 71 def nop(self, data=None): 72 """The No-Op operation. 73 74 This is a good test to see if your laser is communicating properly. 75 It should read 0000 data or echo whatever data you send it if you send it something. 76 The data you write to nop gets overwritten as soon as you read it back. 77 78 `nop()` also returns more informative errors following an ExecutionError. 79 This is not called by default so you must do this explicitly if you want 80 to see more informative error information. 81 82 See 9.4.1 NOP/Status (ResEna 0x00) [RW] in OIF-ITLA-MSA. 83 84 :param data: Data to write 85 86 :returns: None 87 """ 88 # pretty sure the data does nothing 89 if data is not None: 90 response = self._nop(data) 91 else: 92 response = self._nop() 93 94 error_field = int(response.hex()[-1], 16) 95 if bool(error_field): 96 raise self._nop_errors[error_field] 97 98 def enable(self): 99 """Enables laser optical output. 100 There is a time delay after execution of this function to 101 proper stable laser output. 102 103 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 104 105 :returns: None 106 """ 107 # I'm writing this out partially for transparency 108 # Maybe unnecessary or non-optimal 109 self._resena(Resena.SENA) 110 111 def disable(self): 112 """disables the laser 113 114 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 115 116 :returns: None 117 """ 118 self._resena(0) 119 120 def hard_reset(self): 121 """initiate module reset 122 123 The hardware reset is typically traffic interrupting since it will reset 124 control loops as well. The host can poll the communication’s interface 125 waiting for a response packet indicating that the interface is ready to 126 communicate. Note that a response is returned to acknowledge the reset 127 request before the reset is started 128 129 The impact to the optical signal is undefined. This bit is self clearing. 130 131 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 132 133 :returns: None 134 """ 135 self._resena(Resena.MR) 136 137 def soft_reset(self): 138 """initiate soft reset 139 140 The soft reset resets the communication’s interface and is traffic 141 non-interrupting. Extended address registers are reset. 142 143 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 144 145 :returns: None 146 """ 147 self._resena(Resena.SR) 148 149 def get_reset_enable(self): 150 """return ResEna register. 151 152 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 153 """ 154 response = self._resena() 155 return Resena(int.from_bytes(response, "big")) 156 157 def set_alarm_during_tuning(self): 158 """Set alarm during tuning 159 mcb(ADT) 160 161 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 162 """ 163 self._mcb(MCB.ADT) 164 165 def set_shutdown_on_fatal(self): 166 """Set shutdown on fatal. 167 mcb(SDF) 168 169 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 170 """ 171 self._mcb(MCB.SDF) 172 173 def get_configuration_behavior(self): 174 """ 175 Return MCB register. 176 177 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 178 """ 179 response = self._mcb() 180 return MCB(int.from_bytes(response, "big")) 181 182 def is_disabled(self): 183 """ 184 Return if disabled. 185 186 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI. 187 188 For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). 189 Consider overwriting this methods and monitoring FatalError.ALM. 190 """ 191 fatal_error = self.get_error_fatal() 192 fatal_trigger = self.get_fatal_trigger() 193 resena = self.get_reset_enable() 194 mcb = self.get_configuration_behavior() 195 196 sdf = MCB.SDF in mcb 197 sena = Resena.SENA in resena 198 dis = FatalError.DIS in fatal_error 199 200 return ((fatal_error & fatal_trigger) and sdf) or (not sena) or (not dis) 201 202 def is_enabled(self, *args): 203 """ 204 Return if enabled. See is_disabled. 205 """ 206 return not self.is_disabled(*args) 207 208 def get_device_type(self): 209 """returns a string containing the device type 210 211 DevTyp returns the module’s device type. For all tunable lasers covered by this MSA, the 212 module will return the null terminated string “CW ITLA\0” (eight bytes including the 213 terminating null character) indirectly through the AEA mechanism. The device type register 214 is provided such that a host can distinguish between different types of tunable devices.type. 215 216 See 9.4.2 Device Type (DevTyp 0x01) [R] 217 """ 218 response_bytes = self._devtyp() 219 220 return response_bytes.decode("utf-8") 221 222 def get_manufacturer(self): 223 """returns a string containing the manufacturer's name. 224 225 MFGR returns the module’s manufacturers ID null terminated string indirectly through the 226 AEA mechanism 227 228 See 9.4.3 Manufacturer (MFGR 0x02) [R] 229 """ 230 response_bytes = self._mfgr() 231 232 return response_bytes.decode("utf-8") 233 234 def get_model(self): 235 """returns the model as a string 236 237 Model returns the module’s model designation string indirectly through the AEA 238 mechanism. The null terminated string containing the module’s model designation is 239 placed into a field of not more than 80 bytes in size. The model string is defined by the 240 manufacturer 241 242 See 9.4.4 Model (Model 0x03) [R] 243 """ 244 response_bytes = self._model() 245 246 return response_bytes.decode("utf-8") 247 248 def get_serialnumber(self): 249 """returns the serial number 250 251 SerNo returns the module’s serial number string indirectly through the AEA mechanism. 252 The null terminated string containing the module’s serial number is placed into a field of not 253 more than 80 bytes in size. The serial number string is defined by the manufacturer. 254 255 See 9.4.5 Serial Number (SerNo 0x04) [R] 256 """ 257 response_bytes = self._serno() 258 259 return response_bytes.decode("utf-8") 260 261 def get_manufacturing_date(self): 262 """returns the manufacturing date 263 264 MFGDate returns the manufacturing date string of the module indirectly through the AEA 265 mechanism. The null terminated string containing the date string is contained in a field 266 size of 12 bytes. 267 268 See 9.4.5 Manufaturing Date (MFGDate 0x05) [R] 269 """ 270 response_bytes = self._mfgdate() 271 272 return response_bytes.decode("utf-8") 273 274 def get_firmware_release(self): 275 """returns a manufacturer specific firmware release 276 277 Release returns the release string of the module indirectly through the AEA mechanism. 278 The null terminated string containing the module release information is placed into a field of 279 not more than 80 bytes in size. Note that a module may have one or more firmware and/or 280 hardware revisions to track. The release field also encodes the application space 281 identifier. 282 283 See 9.4.7 Release (Release 0x06) [R] 284 """ 285 response_bytes = self._release() 286 287 return response_bytes.decode("utf-8") 288 289 def get_backwardscompatibility(self): 290 """returns a manufacturer specific firmware backwards compatibility 291 as a null terminated string 292 293 RelBack returns the release backwards compatibility string of the module indirectly through 294 the AEA mechanism. The null terminated string containing the earliest release string which 295 is fully backwards compatible with the current module. The string is contained in a field of 296 not more than 80 bytes in size. Note that a module may have one or more firmware and/or 297 hardware revisions to track as described in the Release (0x06) register 298 299 See 9.4.8 Release Backwards Compatibility (RelBack 0x07) [R] 300 """ 301 response_bytes = self._relback() 302 303 return response_bytes.decode("utf-8") 304 305 def get_general_module_configuration(self): 306 """returns the power on/reset module configuration 307 308 GenCfg defines the general module configuration for the generic tunable device. For the 309 tunable laser, the register is used to save the power on/reset module configuration 310 defaults. 311 312 See 9.4.9 General Module Configuration (GenCfg 0x08) [RW] 313 """ 314 raise NotImplementedError() 315 316 def set_general_module_configuration(self): 317 """returns the power on/reset module configuration 318 319 GenCfg defines the general module configuration for the generic tunable device. For the 320 tunable laser, the register is used to save the power on/reset module configuration 321 defaults. 322 323 See 9.4.9 General Module Configuration (GenCfg 0x08) [RW] 324 """ 325 raise NotImplementedError() 326 327 def get_iocap(self): 328 """ 329 The IOCap register returns or sets the I/O interface capabilities 330 331 See 9.4.10 IO Capabilities (IOCap 0x0D) [RW] 332 """ 333 raise NotImplementedError() 334 335 def set_iocap(self, data): 336 """ 337 The IOCap register returns or sets the I/O interface capabilities 338 339 See 9.4.10 IO Capabilities (IOCap 0x0D) [RW] 340 """ 341 raise NotImplementedError() 342 343 def read_aea(self): 344 """reads the AEA register data until an execution error is thrown. 345 346 See 9.4.11 Extended Addressing Mode Registers (0x09-0x0B, 0x0E-0x10) [RW] 347 """ 348 aea_response = b"" 349 try: 350 while True: 351 aea_response += self._aea_ear() 352 353 except ExecutionError: 354 try: 355 self.nop() 356 357 except EREError: 358 # If this throws an ERE error then we have completed reading AEA 359 pass 360 361 except NOPException as nop_e: 362 raise nop_e 363 364 return aea_response 365 366 def get_download_configuration(self): 367 """ 368 The DLConfig register configures a host to module download of code or data for 369 reconfiguration purposes or configures a module to host upload of code or data to the host. 370 A file transfer may occur at several locations such as vendor factory, customer site (on the 371 bench), customer system (circuit down), or potentially a customer system (live circuit) 372 373 See 9.4.13 Download Configuration (DLConfig 0x14) [RW] 374 """ 375 raise NotImplementedError() 376 377 def set_download_configuration(self): 378 """ 379 The DLConfig register configures a host to module download of code or data for 380 reconfiguration purposes or configures a module to host upload of code or data to the host. 381 A file transfer may occur at several locations such as vendor factory, customer site (on the 382 bench), customer system (circuit down), or potentially a customer system (live circuit) 383 384 See 9.4.13 Download Configuration (DLConfig 0x14) [RW] 385 """ 386 raise NotImplementedError() 387 388 def get_download_status(self): 389 """ 390 DLStatus provides information about the status or viability of a code segment. 391 392 See 9.4.14 Download Status (DLStatus 0x15) [R] 393 """ 394 raise NotImplementedError() 395 396 def wait(self): 397 """Wait until operation is complete. 398 It check if the operation is completed every self.sleep_time seconds. 399 """ 400 while True: 401 try: 402 self.nop() 403 except CPException: 404 if self.sleep_time is not None: 405 sleep(self.sleep_time) 406 else: 407 break 408 409 def wait_until_enabled(self): 410 """TODO: Add documentation.""" 411 while self.is_disabled(): 412 if self.sleep_time is not None: 413 sleep(self.sleep_time) 414 415 def wait_until_disabled(self): 416 """TODO: Add documentation.""" 417 while self.is_enabled(): 418 if self.sleep_time is not None: 419 sleep(self.sleep_time) 420 421 def set_power(self, pwr_dBm): 422 """Sets the power of the ITLA laser. 423 Units of dBm. 424 425 See 9.6.2 Optical Power Set Point (PWR 0x31) [RW] 426 427 :param pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. 428 :returns: None 429 """ 430 try: 431 self._pwr(round(pwr_dBm * 100)) 432 433 except ExecutionError: 434 try: 435 self.nop() 436 437 except RVEError as error: 438 logger.error( 439 "The provided power %.2f dBm is outside of the range for this device. " 440 "The power is currently set to: %.2f dBm.", 441 pwr_dBm, 442 self.get_power_setting(), 443 ) 444 raise error 445 446 def get_power_setting(self): 447 """Gets current power setting set by set_power. 448 Should be in dBm. 449 450 See 9.6.2 Optical Power Set Point (PWR 0x31) [RW] 451 452 :returns: 453 """ 454 # Gets power setting, not actual optical output power. 455 response = self._pwr() 456 return int.from_bytes(response, "big", signed=True) / 100 457 458 def get_power_output(self): 459 """returns external optical power estimate 460 461 See 9.6.8 Optical Output Power (OOP 0x42) [R] 462 463 :returns: External optical power estimate in dBm 464 """ 465 response = self._oop() 466 467 return int.from_bytes(response, "big", signed=True) / 100 468 469 def get_power_min(self): 470 """returns minimum optical power output of the module. 471 Units dBm. 472 473 See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R] 474 475 :returns: minimum optical power 476 """ 477 response = self._opsl() 478 return int.from_bytes(response, "big", signed=True) / 100 479 480 def get_power_max(self): 481 """returns maximum optical power output of the module. 482 Units dBm. 483 484 See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R] 485 486 :returns: maximum optical power 487 """ 488 response = self._opsh() 489 return int.from_bytes(response, "big", signed=True) / 100 490 491 def set_fcf(self, freq): 492 """ 493 This sets the first channel frequency. 494 It does not reset the channel so this frequency will only be equal 495 to the output frequency if channel=1. 496 497 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 498 499 :returns: None 500 """ 501 # convert frequency to MHz 502 freq = round(freq * 1e6) 503 504 freq_str = str(freq) 505 fcf1 = int(freq_str[0:3]) 506 fcf2 = int(freq_str[3:7]) 507 508 try: 509 # is it better to split these into their own try/except blocks? 510 self._fcf1(fcf1) 511 self._fcf2(fcf2) 512 513 except ExecutionError: 514 try: 515 self.nop() 516 except RVEError as error: 517 logger.error( 518 "%.2f THz is out of bounds for this laser. " 519 "The frequency must be within the range %.2f - %.2f THz.", 520 fcf1, 521 self.get_frequency_min(), 522 self.get_frequency_max(), 523 ) 524 raise error 525 526 except CIEError as error: 527 logger.error( 528 "You cannot change the first channel frequency while the laser is enabled. " 529 "The current frequency is: %.2f THz", 530 self.get_frequency(), 531 ) 532 raise error 533 534 def set_frequency(self, freq): 535 """Sets the frequency of the laser in TeraHertz. 536 537 Has MHz resolution. Will round down. 538 539 This function sets the first channel frequency and then sets the 540 channel to 1 so that the frequency output when the laser is enabled 541 will be the frequency given to this function. 542 543 If you would like to only change the first channel frequency use 544 the function `set_fcf`. 545 546 Disable the laser before calling this function. 547 548 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 549 550 :param freq: The desired frequency setting in THz. 551 :returns: None 552 """ 553 554 # This does a check so this only runs if fine tuning has been turned on. 555 if self.get_fine_tuning() != 0: 556 # Set the fine tuning off! 557 while True: 558 try: 559 self.set_fine_tuning(0) 560 except ExecutionError: 561 sleep(self.sleep_time) 562 except CPException: 563 self.wait() 564 break 565 else: 566 break 567 568 try: 569 self.set_fcf(freq) 570 except CPException: 571 self.wait() 572 573 try: 574 self.set_channel(1) 575 except CPException: 576 self.wait() 577 578 def get_fcf(self): 579 """returns first channel frequency 580 581 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 582 583 :returns: First channel frequency in THz 584 """ 585 response = self._fcf1() 586 fcf1 = int.from_bytes(response, "big") 587 588 response = self._fcf2() 589 fcf2 = int.from_bytes(response, "big") 590 591 return fcf1 + fcf2 * 1e-4 592 593 def get_frequency(self): 594 """gets the current laser operating frequency 595 596 See 9.6.7 Laser Frequency (LF1, LF2 0x40 – 0x41) [R] 597 598 :returns: Current laser frequency in THz. 599 """ 600 response = self._lf1() 601 lf1 = int.from_bytes(response, "big") 602 603 response = self._lf2() 604 lf2 = int.from_bytes(response, "big") 605 606 return lf1 + lf2 * 1e-4 607 608 def dither_enable(self, waveform: Waveform = Waveform.SIN): 609 """enables dither 610 611 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 612 613 :param waveform: specifies the dither waveform 614 """ 615 616 data = (waveform << 4) | 2 617 618 self._dithere(data) 619 620 def dither_disable(self): 621 """disables digital dither 622 623 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 624 """ 625 # TODO: This should preserve the waveform setting if possible 626 self._dithere(0) 627 628 def set_dither_rate(self, rate): 629 """set dither rate in kHz 630 utilizes DitherR register 631 632 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 633 634 :param rate: an unsigned short integer specifying dither rate in kHz 635 """ 636 self._ditherr(rate) 637 638 def get_dither_rate(self): 639 """get dither rate 640 utilizes DitherR register 641 642 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 643 644 :returns: Dither rate in kHz 645 """ 646 response = self._ditherr() 647 return int.from_bytes(response, "big") 648 649 def set_dither_frequency(self, rate): 650 """set dither modulation frequency 651 utilizes DitherF register 652 653 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 654 655 :param rate: an unsigned short integer encoded as the FM peak-to-peak frequency 656 deviation as Ghz*10 657 """ 658 self._ditherf(rate) 659 660 def get_dither_frequency(self): 661 """get dither modulation frequency 662 utilizes DitherF register 663 664 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 665 666 :returns: dither modulation frequency in GHz * 10 667 """ 668 response = self._ditherf() 669 return int.from_bytes(response, "big") 670 671 def set_dither_amplitude(self, amplitude): 672 """set dither modulation amplitude 673 utilizes DitherA register 674 675 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 676 677 :param amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude 678 deviation as 10*percentage of the optical power 679 680 :returns: None 681 """ 682 self._dithera(amplitude) 683 684 def get_dither_amplitude(self): 685 """get dither modulation amplitude 686 utilizes DitherA register 687 688 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 689 690 :returns: dither aplitude in 10ths of percents 691 """ 692 response = self._dithera() 693 return int.from_bytes(response, "big") 694 695 def get_temp(self): 696 """Returns the current primary control temperature in deg C. 697 698 See 9.6.9 Current Temperature (CTemp 0x43) [R] 699 700 :returns: current primary control temperature in deg C 701 """ 702 response = self._ctemp() 703 temp_100 = int.from_bytes(response, "big") 704 705 return temp_100 / 100 706 707 def get_frequency_min(self): 708 """returns minimum frequency supported by the module 709 710 See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R] 711 712 :returns: minimum frequency in THz 713 """ 714 response = self._lfl1() 715 lfl1 = int.from_bytes(response, "big") 716 717 response = self._lfl2() 718 lfl2 = int.from_bytes(response, "big") 719 720 return lfl1 + lfl2 * 1e-4 721 722 def get_frequency_max(self): 723 """returns maximum frequency supported by the module 724 725 See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R] 726 727 :returns: maximum frequency in THz 728 """ 729 response = self._lfh1() 730 fcf1 = int.from_bytes(response, "big") 731 732 response = self._lfh2() 733 fcf2 = int.from_bytes(response, "big") 734 735 return fcf1 + fcf2 * 1e-4 736 737 def get_grid_min(self): 738 """returns minimum grid spacing of the module 739 740 See 9.7.4 Laser’s Minimum Grid Spacing (LGrid 0x56) [R] 741 742 :returns: The minimum grid supported by the module in GHz 743 """ 744 try: 745 freq_lgrid = int.from_bytes(self._lgrid(), "big", signed=False) 746 747 except ExecutionError: 748 self.nop() 749 750 return freq_lgrid * 1e-1 751 752 def set_grid(self, grid_freq): 753 """Set the grid spacing in GHz. 754 MHz resolution. 755 756 See 9.6.5 Grid Spacing (Grid 0x34) [RW] 757 758 :param grid_freq: the grid frequency spacing in GHz 759 :returns: 760 761 """ 762 grid_freq = str(round(grid_freq * 1000)) 763 data = int(grid_freq[0:4]) 764 765 self._grid(data) 766 767 def get_grid(self): 768 """get the grid spacing in GHz 769 770 See 9.6.5 Grid Spacing (Grid 0x34) [RW] 771 772 :returns: The grid spacing in GHz. 773 """ 774 response = self._grid() 775 grid_freq = int.from_bytes(response, "big", signed=True) 776 777 return grid_freq * 1e-1 778 779 def get_age(self): 780 """Returns the percentage of aging of the laser. 781 0% indicated a brand new laser 782 100% indicates the laser should be replaces. 783 784 See 9.8.6 Laser Age (Age 0x61) [R] 785 786 :returns: percent aging of the laser 787 """ 788 response = self._age() 789 age = int.from_bytes(response, "big") 790 791 return age 792 793 def set_channel(self, channel): 794 """Sets the laser's operating channel. 795 796 MSA-01.2 lasers support a 16 bit channel value. 797 798 This defines how many spaces along the grid 799 the laser's frequency is displaced from the first channel frequency. 800 801 See 9.6.1 Channel (Channel 0x30) [RW] 802 803 :param channel: 804 :returns: None 805 806 """ 807 # check type and stuff 808 if not isinstance(channel, int): 809 raise TypeError("Channel must be an integer") 810 if channel < 0: 811 raise ValueError("Channel must be positive.") 812 if channel > 0xFFFF: 813 raise ValueError("Channel must be a 16 bit integer (<=0xFFFF).") 814 815 # Split the channel choice into two options. 816 channel_hex = f"{channel:08x}" 817 818 channell = int(channel_hex[4:], 16) 819 820 # Set the channel registers. 821 self._channel(channell) 822 823 def get_channel(self): 824 """gets the current channel setting 825 826 The channel defines how many spaces along the grid 827 the laser's frequency is displaced from the first channel frequency. 828 829 See 9.6.1 Channel (Channel 0x30) [RW] 830 831 :returns: channel as an integer. 832 833 """ 834 # This concatenates the data bytestrings 835 response = self._channel() 836 837 channel = int.from_bytes(response, "big") 838 839 return channel 840 841 def set_fine_tuning(self, ftf): 842 """ 843 This function provides off grid tuning for the laser's frequency. 844 The adjustment applies to all channels uniformly. 845 This is typically used after the laser if locked and minor adjustments 846 are required to the frequency. 847 848 The command may be pending in the event that the laser output is 849 enabled. The pending bit is cleared once the fine tune frequency 850 has been achieved. 851 852 See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW] 853 854 **???** It seems like this can be done with the laser running. 855 856 :param ftf: The fine tune frequency adjustment in GHz 857 """ 858 859 ftf = round(ftf * 1e3) 860 861 # We will leave this bare. This way the user can set and handle 862 # their own timing and check to make sure the laser has reached the 863 # desired fine tuning frequency. 864 # It WILL throw a "pending" error if the laser is on when setting. 865 self._ftf(ftf) 866 867 def get_fine_tuning(self): 868 """ 869 This function returns the setting for the 870 off grid tuning for the laser's frequency. 871 872 See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW] 873 874 :return ftf: The fine tune frequency adjustment in GHz 875 """ 876 877 response = self._ftf() 878 ftf = int.from_bytes(response, "big", signed=True) 879 880 return ftf * 1e-3 881 882 def get_ftf_range(self): 883 """return the maximum and minimum off grid tuning for the laser's frequency. 884 885 See 9.7.1 Fine Tune Frequency Range (FTF 0x4F) [R] 886 887 :return ftfr: The fine tune frequency range [-ftfr, + ftfr] in GHz 888 """ 889 response = self._ftfr() 890 891 ftfr = int.from_bytes(response, "big") 892 893 return ftfr * 1e-3 894 895 def get_temps(self): 896 """returns a list of technology specific temperatures 897 units in deg C unless otherwise specified 898 899 **Technologies** 900 Technology 1: [Diode Temp, Case Temp] 901 Technology 2: [Diode Temp, Case Temp] 902 (why are they the same?) 903 904 See 9.8.2 Module Temperatures (Temps 0x58) [R] 905 906 :returns: list of temperatures in deg C 907 """ 908 # get response this should be a long byte string 909 response = self._temps() 910 911 data = [ 912 int.from_bytes(response[i : i + 2], "big", signed=True) / 100 913 for i in range(0, len(response), 2) 914 ] 915 916 return data 917 918 def get_currents(self): 919 """returns a list of technology specific currents in mA. 920 921 **Technologies** 922 Technology 1: [TEC, Diode] 923 Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] 924 Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...] 925 926 See 9.8.1 Module Currents (Currents 0x57) [R] 927 928 :returns: list of currents in mA 929 """ 930 # get response this should be a long byte string 931 response = self._currents() 932 933 data = [ 934 int.from_bytes(response[i : i + 2], "big", signed=True) / 10 935 for i in range(0, len(response), 2) 936 ] 937 938 return data 939 940 def get_last_response(self): 941 """reads the last response sent from the laser 942 943 This function gets the most recent response sent from the laser. 944 The response is parsed for errors the way any normal response 945 would be. The variable `self._response` is set to the last response again. 946 947 See 9.4.12 Last Response (LstResp 0x13) [R] 948 949 """ 950 return self._lstresp() 951 952 def get_error_fatal(self, reset=False): 953 """reads fatal error register statusf and checks each bit against the 954 fatal error table to determine the fault 955 956 See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW] 957 958 :param reset: resets/clears latching errors 959 960 """ 961 response = self._statusf() 962 statusf = int.from_bytes(response, "big") 963 964 logger.debug("Current Status Fatal Error: %d", statusf) 965 966 if reset: 967 data_reset = 0x00FF 968 self._statusf(data_reset) 969 970 return FatalError(statusf) 971 972 def get_error_warning(self, reset=False): 973 """reads warning error register statusw and checks each bit against the 974 warning error table to determine the fault 975 976 If the laser is off then some of the latched warnings will be set on. 977 978 See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW] 979 980 :param reset: resets/clears latching errors 981 """ 982 response = self._statusw() 983 statusw = int.from_bytes(response, "big") 984 985 logger.debug("Current Status Warning Error: %d", statusw) 986 987 if reset: 988 data_reset = 0x00FF 989 self._statusw(data_reset) 990 991 return WarningError(statusw) 992 993 def get_fatal_power_thresh(self): 994 """reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted 995 996 See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW] 997 """ 998 response = self._fpowth() 999 pow_fatal = int.from_bytes(response, "big") / 100 1000 # correcting for proper order of magnitude 1001 return pow_fatal 1002 1003 def get_warning_power_thresh(self): 1004 """reads maximum plus/minus power deviation in dB for which the warning alarm is asserted 1005 1006 See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW] 1007 """ 1008 response = self._wpowth() 1009 pow_warn = int.from_bytes(response, "big") / 100 1010 # correcting for proper order of magnitude 1011 return pow_warn 1012 1013 def get_fatal_freq_thresh(self): 1014 """reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted 1015 1016 See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW] 1017 """ 1018 response = self._ffreqth() 1019 freq_fatal = int.from_bytes(response, "big") / 10 1020 return freq_fatal 1021 1022 def get_warning_freq_thresh(self): 1023 """reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted 1024 1025 See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW] 1026 """ 1027 response = self._wfreqth() 1028 freq_warn = int.from_bytes(response, "big") / 10 1029 return freq_warn 1030 1031 def get_fatal_therm_thresh(self): 1032 """reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted 1033 1034 See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW] 1035 """ 1036 response = self._fthermth() 1037 therm_fatal = int.from_bytes(response, "big") / 100 1038 # correcting for proper order of magnitude 1039 return therm_fatal 1040 1041 def get_warning_therm_thresh(self): 1042 """reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted 1043 1044 See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW] 1045 """ 1046 response = self._wthermth() 1047 therm_thresh = int.from_bytes(response, "big") / 100 1048 # correcting for proper order of magnitude 1049 return therm_thresh 1050 1051 def get_srq_trigger(self): 1052 """identifies corresponding bits in status registers StatusF, StatusW 1053 1054 Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers 1055 1056 See 9.5.5 SRQ* Triggers (SRQT 0x28) [RW] 1057 """ 1058 response = self._srqt() 1059 status = int.from_bytes(response, "big") 1060 1061 logger.debug("SRQT Status: %d", status) 1062 1063 return SQRTrigger(status) 1064 1065 def get_fatal_trigger(self): 1066 """identifies which fatal condition was asserted in StatusF and StatusW registers 1067 1068 See 9.5.6 FATAL Triggers (FatalT 0x29) [RW] 1069 """ 1070 response = self._fatalt() 1071 status = int.from_bytes(response, "big") 1072 1073 logger.debug("FatalT Status: %d", status) 1074 1075 return FatalTrigger(status) 1076 1077 def get_alm_trigger(self): 1078 """identifies why ALM was asserted in StatusF and StatusW registers 1079 1080 See 9.5.7 ALM Triggers (ALMT 0x2A) [RW] 1081 """ 1082 response = self._almt() 1083 status = int.from_bytes(response, "big") 1084 1085 logger.debug("AlarmT Status: %d", status) 1086 1087 return AlarmTrigger(status)
A class that represents the ITLA12 and exposes a user friendly API for controlling functionality.
Things to figure out
- What should be exposed to the user?
- Can we abstract away the concept of registers and stuff in here so you don't have to deal with it.
There are some functions that could be implemented like set_fatalstatus. I think this is probably a bad idea even though it isnt write only.
Set Frequency is my platonic ideal for the higher level functions.
44 def __init__( 45 self, serial_port, baudrate, timeout=0.5, register_files=None, sleep_time=0.1 46 ): 47 """Initializes the ITLA12 object. 48 49 :param serial_port: The serial port to connect to. 50 Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX' 51 :param baudrate: The baudrate for communication. I have primarily seen 52 lasers using 9600 as the default and then higher for firmware upgrades. 53 This may be laser specific. 54 :param timeout: How long should we wait to receive a response from the laser 55 :param register_files: Any additional register files you would like to include 56 beyond the default MSA-01.2 defined registers. These must be in a yaml format as 57 described in the project's README. 58 :param sleep_time: time in seconds. Used in wait function 59 """ 60 if register_files is None: 61 register_files = [] 62 63 register_files = ["registers_itla12.yaml", *register_files] 64 65 self.sleep_time = sleep_time 66 67 super().__init__( 68 serial_port, baudrate, timeout=timeout, register_files=register_files 69 )
Initializes the ITLA12 object.
Parameters
- serial_port: The serial port to connect to. Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX'
- baudrate: The baudrate for communication. I have primarily seen lasers using 9600 as the default and then higher for firmware upgrades. This may be laser specific.
- timeout: How long should we wait to receive a response from the laser
- register_files: Any additional register files you would like to include beyond the default MSA-01.2 defined registers. These must be in a yaml format as described in the project's README.
- sleep_time: time in seconds. Used in wait function
71 def nop(self, data=None): 72 """The No-Op operation. 73 74 This is a good test to see if your laser is communicating properly. 75 It should read 0000 data or echo whatever data you send it if you send it something. 76 The data you write to nop gets overwritten as soon as you read it back. 77 78 `nop()` also returns more informative errors following an ExecutionError. 79 This is not called by default so you must do this explicitly if you want 80 to see more informative error information. 81 82 See 9.4.1 NOP/Status (ResEna 0x00) [RW] in OIF-ITLA-MSA. 83 84 :param data: Data to write 85 86 :returns: None 87 """ 88 # pretty sure the data does nothing 89 if data is not None: 90 response = self._nop(data) 91 else: 92 response = self._nop() 93 94 error_field = int(response.hex()[-1], 16) 95 if bool(error_field): 96 raise self._nop_errors[error_field]
The No-Op operation.
This is a good test to see if your laser is communicating properly. It should read 0000 data or echo whatever data you send it if you send it something. The data you write to nop gets overwritten as soon as you read it back.
nop()
also returns more informative errors following an ExecutionError.
This is not called by default so you must do this explicitly if you want
to see more informative error information.
See 9.4.1 NOP/Status (ResEna 0x00) [RW] in OIF-ITLA-MSA.
Parameters
- data: Data to write
:returns: None
98 def enable(self): 99 """Enables laser optical output. 100 There is a time delay after execution of this function to 101 proper stable laser output. 102 103 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 104 105 :returns: None 106 """ 107 # I'm writing this out partially for transparency 108 # Maybe unnecessary or non-optimal 109 self._resena(Resena.SENA)
Enables laser optical output. There is a time delay after execution of this function to proper stable laser output.
See 9.6.3 Reset/Enable (ResEna 0x32) [RW]
:returns: None
111 def disable(self): 112 """disables the laser 113 114 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 115 116 :returns: None 117 """ 118 self._resena(0)
disables the laser
See 9.6.3 Reset/Enable (ResEna 0x32) [RW]
:returns: None
120 def hard_reset(self): 121 """initiate module reset 122 123 The hardware reset is typically traffic interrupting since it will reset 124 control loops as well. The host can poll the communication’s interface 125 waiting for a response packet indicating that the interface is ready to 126 communicate. Note that a response is returned to acknowledge the reset 127 request before the reset is started 128 129 The impact to the optical signal is undefined. This bit is self clearing. 130 131 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 132 133 :returns: None 134 """ 135 self._resena(Resena.MR)
initiate module reset
The hardware reset is typically traffic interrupting since it will reset control loops as well. The host can poll the communication’s interface waiting for a response packet indicating that the interface is ready to communicate. Note that a response is returned to acknowledge the reset request before the reset is started
The impact to the optical signal is undefined. This bit is self clearing.
See 9.6.3 Reset/Enable (ResEna 0x32) [RW]
:returns: None
137 def soft_reset(self): 138 """initiate soft reset 139 140 The soft reset resets the communication’s interface and is traffic 141 non-interrupting. Extended address registers are reset. 142 143 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 144 145 :returns: None 146 """ 147 self._resena(Resena.SR)
initiate soft reset
The soft reset resets the communication’s interface and is traffic non-interrupting. Extended address registers are reset.
See 9.6.3 Reset/Enable (ResEna 0x32) [RW]
:returns: None
149 def get_reset_enable(self): 150 """return ResEna register. 151 152 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] 153 """ 154 response = self._resena() 155 return Resena(int.from_bytes(response, "big"))
return ResEna register.
See 9.6.3 Reset/Enable (ResEna 0x32) [RW]
157 def set_alarm_during_tuning(self): 158 """Set alarm during tuning 159 mcb(ADT) 160 161 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 162 """ 163 self._mcb(MCB.ADT)
Set alarm during tuning mcb(ADT)
See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW]
165 def set_shutdown_on_fatal(self): 166 """Set shutdown on fatal. 167 mcb(SDF) 168 169 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 170 """ 171 self._mcb(MCB.SDF)
Set shutdown on fatal. mcb(SDF)
See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW]
173 def get_configuration_behavior(self): 174 """ 175 Return MCB register. 176 177 See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW] 178 """ 179 response = self._mcb() 180 return MCB(int.from_bytes(response, "big"))
Return MCB register.
See 9.6.4 Module Configuration Behavior (MCB 0x33) [RW]
182 def is_disabled(self): 183 """ 184 Return if disabled. 185 186 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI. 187 188 For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). 189 Consider overwriting this methods and monitoring FatalError.ALM. 190 """ 191 fatal_error = self.get_error_fatal() 192 fatal_trigger = self.get_fatal_trigger() 193 resena = self.get_reset_enable() 194 mcb = self.get_configuration_behavior() 195 196 sdf = MCB.SDF in mcb 197 sena = Resena.SENA in resena 198 dis = FatalError.DIS in fatal_error 199 200 return ((fatal_error & fatal_trigger) and sdf) or (not sena) or (not dis)
Return if disabled.
See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI.
For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). Consider overwriting this methods and monitoring FatalError.ALM.
202 def is_enabled(self, *args): 203 """ 204 Return if enabled. See is_disabled. 205 """ 206 return not self.is_disabled(*args)
Return if enabled. See is_disabled.
208 def get_device_type(self): 209 """returns a string containing the device type 210 211 DevTyp returns the module’s device type. For all tunable lasers covered by this MSA, the 212 module will return the null terminated string “CW ITLA\0” (eight bytes including the 213 terminating null character) indirectly through the AEA mechanism. The device type register 214 is provided such that a host can distinguish between different types of tunable devices.type. 215 216 See 9.4.2 Device Type (DevTyp 0x01) [R] 217 """ 218 response_bytes = self._devtyp() 219 220 return response_bytes.decode("utf-8")
returns a string containing the device type
DevTyp returns the module’s device type. For all tunable lasers covered by this MSA, the module will return the null terminated string “CW ITLA ” (eight bytes including the terminating null character) indirectly through the AEA mechanism. The device type register is provided such that a host can distinguish between different types of tunable devices.type.
See 9.4.2 Device Type (DevTyp 0x01) [R]
222 def get_manufacturer(self): 223 """returns a string containing the manufacturer's name. 224 225 MFGR returns the module’s manufacturers ID null terminated string indirectly through the 226 AEA mechanism 227 228 See 9.4.3 Manufacturer (MFGR 0x02) [R] 229 """ 230 response_bytes = self._mfgr() 231 232 return response_bytes.decode("utf-8")
returns a string containing the manufacturer's name.
MFGR returns the module’s manufacturers ID null terminated string indirectly through the AEA mechanism
See 9.4.3 Manufacturer (MFGR 0x02) [R]
234 def get_model(self): 235 """returns the model as a string 236 237 Model returns the module’s model designation string indirectly through the AEA 238 mechanism. The null terminated string containing the module’s model designation is 239 placed into a field of not more than 80 bytes in size. The model string is defined by the 240 manufacturer 241 242 See 9.4.4 Model (Model 0x03) [R] 243 """ 244 response_bytes = self._model() 245 246 return response_bytes.decode("utf-8")
returns the model as a string
Model returns the module’s model designation string indirectly through the AEA mechanism. The null terminated string containing the module’s model designation is placed into a field of not more than 80 bytes in size. The model string is defined by the manufacturer
See 9.4.4 Model (Model 0x03) [R]
248 def get_serialnumber(self): 249 """returns the serial number 250 251 SerNo returns the module’s serial number string indirectly through the AEA mechanism. 252 The null terminated string containing the module’s serial number is placed into a field of not 253 more than 80 bytes in size. The serial number string is defined by the manufacturer. 254 255 See 9.4.5 Serial Number (SerNo 0x04) [R] 256 """ 257 response_bytes = self._serno() 258 259 return response_bytes.decode("utf-8")
returns the serial number
SerNo returns the module’s serial number string indirectly through the AEA mechanism. The null terminated string containing the module’s serial number is placed into a field of not more than 80 bytes in size. The serial number string is defined by the manufacturer.
See 9.4.5 Serial Number (SerNo 0x04) [R]
261 def get_manufacturing_date(self): 262 """returns the manufacturing date 263 264 MFGDate returns the manufacturing date string of the module indirectly through the AEA 265 mechanism. The null terminated string containing the date string is contained in a field 266 size of 12 bytes. 267 268 See 9.4.5 Manufaturing Date (MFGDate 0x05) [R] 269 """ 270 response_bytes = self._mfgdate() 271 272 return response_bytes.decode("utf-8")
returns the manufacturing date
MFGDate returns the manufacturing date string of the module indirectly through the AEA mechanism. The null terminated string containing the date string is contained in a field size of 12 bytes.
See 9.4.5 Manufaturing Date (MFGDate 0x05) [R]
274 def get_firmware_release(self): 275 """returns a manufacturer specific firmware release 276 277 Release returns the release string of the module indirectly through the AEA mechanism. 278 The null terminated string containing the module release information is placed into a field of 279 not more than 80 bytes in size. Note that a module may have one or more firmware and/or 280 hardware revisions to track. The release field also encodes the application space 281 identifier. 282 283 See 9.4.7 Release (Release 0x06) [R] 284 """ 285 response_bytes = self._release() 286 287 return response_bytes.decode("utf-8")
returns a manufacturer specific firmware release
Release returns the release string of the module indirectly through the AEA mechanism. The null terminated string containing the module release information is placed into a field of not more than 80 bytes in size. Note that a module may have one or more firmware and/or hardware revisions to track. The release field also encodes the application space identifier.
See 9.4.7 Release (Release 0x06) [R]
289 def get_backwardscompatibility(self): 290 """returns a manufacturer specific firmware backwards compatibility 291 as a null terminated string 292 293 RelBack returns the release backwards compatibility string of the module indirectly through 294 the AEA mechanism. The null terminated string containing the earliest release string which 295 is fully backwards compatible with the current module. The string is contained in a field of 296 not more than 80 bytes in size. Note that a module may have one or more firmware and/or 297 hardware revisions to track as described in the Release (0x06) register 298 299 See 9.4.8 Release Backwards Compatibility (RelBack 0x07) [R] 300 """ 301 response_bytes = self._relback() 302 303 return response_bytes.decode("utf-8")
returns a manufacturer specific firmware backwards compatibility as a null terminated string
RelBack returns the release backwards compatibility string of the module indirectly through the AEA mechanism. The null terminated string containing the earliest release string which is fully backwards compatible with the current module. The string is contained in a field of not more than 80 bytes in size. Note that a module may have one or more firmware and/or hardware revisions to track as described in the Release (0x06) register
See 9.4.8 Release Backwards Compatibility (RelBack 0x07) [R]
305 def get_general_module_configuration(self): 306 """returns the power on/reset module configuration 307 308 GenCfg defines the general module configuration for the generic tunable device. For the 309 tunable laser, the register is used to save the power on/reset module configuration 310 defaults. 311 312 See 9.4.9 General Module Configuration (GenCfg 0x08) [RW] 313 """ 314 raise NotImplementedError()
returns the power on/reset module configuration
GenCfg defines the general module configuration for the generic tunable device. For the tunable laser, the register is used to save the power on/reset module configuration defaults.
See 9.4.9 General Module Configuration (GenCfg 0x08) [RW]
316 def set_general_module_configuration(self): 317 """returns the power on/reset module configuration 318 319 GenCfg defines the general module configuration for the generic tunable device. For the 320 tunable laser, the register is used to save the power on/reset module configuration 321 defaults. 322 323 See 9.4.9 General Module Configuration (GenCfg 0x08) [RW] 324 """ 325 raise NotImplementedError()
returns the power on/reset module configuration
GenCfg defines the general module configuration for the generic tunable device. For the tunable laser, the register is used to save the power on/reset module configuration defaults.
See 9.4.9 General Module Configuration (GenCfg 0x08) [RW]
327 def get_iocap(self): 328 """ 329 The IOCap register returns or sets the I/O interface capabilities 330 331 See 9.4.10 IO Capabilities (IOCap 0x0D) [RW] 332 """ 333 raise NotImplementedError()
The IOCap register returns or sets the I/O interface capabilities
See 9.4.10 IO Capabilities (IOCap 0x0D) [RW]
335 def set_iocap(self, data): 336 """ 337 The IOCap register returns or sets the I/O interface capabilities 338 339 See 9.4.10 IO Capabilities (IOCap 0x0D) [RW] 340 """ 341 raise NotImplementedError()
The IOCap register returns or sets the I/O interface capabilities
See 9.4.10 IO Capabilities (IOCap 0x0D) [RW]
343 def read_aea(self): 344 """reads the AEA register data until an execution error is thrown. 345 346 See 9.4.11 Extended Addressing Mode Registers (0x09-0x0B, 0x0E-0x10) [RW] 347 """ 348 aea_response = b"" 349 try: 350 while True: 351 aea_response += self._aea_ear() 352 353 except ExecutionError: 354 try: 355 self.nop() 356 357 except EREError: 358 # If this throws an ERE error then we have completed reading AEA 359 pass 360 361 except NOPException as nop_e: 362 raise nop_e 363 364 return aea_response
reads the AEA register data until an execution error is thrown.
See 9.4.11 Extended Addressing Mode Registers (0x09-0x0B, 0x0E-0x10) [RW]
366 def get_download_configuration(self): 367 """ 368 The DLConfig register configures a host to module download of code or data for 369 reconfiguration purposes or configures a module to host upload of code or data to the host. 370 A file transfer may occur at several locations such as vendor factory, customer site (on the 371 bench), customer system (circuit down), or potentially a customer system (live circuit) 372 373 See 9.4.13 Download Configuration (DLConfig 0x14) [RW] 374 """ 375 raise NotImplementedError()
The DLConfig register configures a host to module download of code or data for reconfiguration purposes or configures a module to host upload of code or data to the host. A file transfer may occur at several locations such as vendor factory, customer site (on the bench), customer system (circuit down), or potentially a customer system (live circuit)
See 9.4.13 Download Configuration (DLConfig 0x14) [RW]
377 def set_download_configuration(self): 378 """ 379 The DLConfig register configures a host to module download of code or data for 380 reconfiguration purposes or configures a module to host upload of code or data to the host. 381 A file transfer may occur at several locations such as vendor factory, customer site (on the 382 bench), customer system (circuit down), or potentially a customer system (live circuit) 383 384 See 9.4.13 Download Configuration (DLConfig 0x14) [RW] 385 """ 386 raise NotImplementedError()
The DLConfig register configures a host to module download of code or data for reconfiguration purposes or configures a module to host upload of code or data to the host. A file transfer may occur at several locations such as vendor factory, customer site (on the bench), customer system (circuit down), or potentially a customer system (live circuit)
See 9.4.13 Download Configuration (DLConfig 0x14) [RW]
388 def get_download_status(self): 389 """ 390 DLStatus provides information about the status or viability of a code segment. 391 392 See 9.4.14 Download Status (DLStatus 0x15) [R] 393 """ 394 raise NotImplementedError()
DLStatus provides information about the status or viability of a code segment.
See 9.4.14 Download Status (DLStatus 0x15) [R]
396 def wait(self): 397 """Wait until operation is complete. 398 It check if the operation is completed every self.sleep_time seconds. 399 """ 400 while True: 401 try: 402 self.nop() 403 except CPException: 404 if self.sleep_time is not None: 405 sleep(self.sleep_time) 406 else: 407 break
Wait until operation is complete. It check if the operation is completed every self.sleep_time seconds.
409 def wait_until_enabled(self): 410 """TODO: Add documentation.""" 411 while self.is_disabled(): 412 if self.sleep_time is not None: 413 sleep(self.sleep_time)
TODO: Add documentation.
415 def wait_until_disabled(self): 416 """TODO: Add documentation.""" 417 while self.is_enabled(): 418 if self.sleep_time is not None: 419 sleep(self.sleep_time)
TODO: Add documentation.
421 def set_power(self, pwr_dBm): 422 """Sets the power of the ITLA laser. 423 Units of dBm. 424 425 See 9.6.2 Optical Power Set Point (PWR 0x31) [RW] 426 427 :param pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. 428 :returns: None 429 """ 430 try: 431 self._pwr(round(pwr_dBm * 100)) 432 433 except ExecutionError: 434 try: 435 self.nop() 436 437 except RVEError as error: 438 logger.error( 439 "The provided power %.2f dBm is outside of the range for this device. " 440 "The power is currently set to: %.2f dBm.", 441 pwr_dBm, 442 self.get_power_setting(), 443 ) 444 raise error
Sets the power of the ITLA laser. Units of dBm.
See 9.6.2 Optical Power Set Point (PWR 0x31) [RW]
Parameters
- pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. :returns: None
446 def get_power_setting(self): 447 """Gets current power setting set by set_power. 448 Should be in dBm. 449 450 See 9.6.2 Optical Power Set Point (PWR 0x31) [RW] 451 452 :returns: 453 """ 454 # Gets power setting, not actual optical output power. 455 response = self._pwr() 456 return int.from_bytes(response, "big", signed=True) / 100
Gets current power setting set by set_power. Should be in dBm.
See 9.6.2 Optical Power Set Point (PWR 0x31) [RW]
:returns:
458 def get_power_output(self): 459 """returns external optical power estimate 460 461 See 9.6.8 Optical Output Power (OOP 0x42) [R] 462 463 :returns: External optical power estimate in dBm 464 """ 465 response = self._oop() 466 467 return int.from_bytes(response, "big", signed=True) / 100
returns external optical power estimate
See 9.6.8 Optical Output Power (OOP 0x42) [R]
:returns: External optical power estimate in dBm
469 def get_power_min(self): 470 """returns minimum optical power output of the module. 471 Units dBm. 472 473 See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R] 474 475 :returns: minimum optical power 476 """ 477 response = self._opsl() 478 return int.from_bytes(response, "big", signed=True) / 100
returns minimum optical power output of the module. Units dBm.
See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R]
:returns: minimum optical power
480 def get_power_max(self): 481 """returns maximum optical power output of the module. 482 Units dBm. 483 484 See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R] 485 486 :returns: maximum optical power 487 """ 488 response = self._opsh() 489 return int.from_bytes(response, "big", signed=True) / 100
returns maximum optical power output of the module. Units dBm.
See 9.7.2 Optical Power Min/Max Set Points (OPSL, OPSH 0x50 – 0x51) [R]
:returns: maximum optical power
491 def set_fcf(self, freq): 492 """ 493 This sets the first channel frequency. 494 It does not reset the channel so this frequency will only be equal 495 to the output frequency if channel=1. 496 497 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 498 499 :returns: None 500 """ 501 # convert frequency to MHz 502 freq = round(freq * 1e6) 503 504 freq_str = str(freq) 505 fcf1 = int(freq_str[0:3]) 506 fcf2 = int(freq_str[3:7]) 507 508 try: 509 # is it better to split these into their own try/except blocks? 510 self._fcf1(fcf1) 511 self._fcf2(fcf2) 512 513 except ExecutionError: 514 try: 515 self.nop() 516 except RVEError as error: 517 logger.error( 518 "%.2f THz is out of bounds for this laser. " 519 "The frequency must be within the range %.2f - %.2f THz.", 520 fcf1, 521 self.get_frequency_min(), 522 self.get_frequency_max(), 523 ) 524 raise error 525 526 except CIEError as error: 527 logger.error( 528 "You cannot change the first channel frequency while the laser is enabled. " 529 "The current frequency is: %.2f THz", 530 self.get_frequency(), 531 ) 532 raise error
This sets the first channel frequency. It does not reset the channel so this frequency will only be equal to the output frequency if channel=1.
See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW]
:returns: None
534 def set_frequency(self, freq): 535 """Sets the frequency of the laser in TeraHertz. 536 537 Has MHz resolution. Will round down. 538 539 This function sets the first channel frequency and then sets the 540 channel to 1 so that the frequency output when the laser is enabled 541 will be the frequency given to this function. 542 543 If you would like to only change the first channel frequency use 544 the function `set_fcf`. 545 546 Disable the laser before calling this function. 547 548 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 549 550 :param freq: The desired frequency setting in THz. 551 :returns: None 552 """ 553 554 # This does a check so this only runs if fine tuning has been turned on. 555 if self.get_fine_tuning() != 0: 556 # Set the fine tuning off! 557 while True: 558 try: 559 self.set_fine_tuning(0) 560 except ExecutionError: 561 sleep(self.sleep_time) 562 except CPException: 563 self.wait() 564 break 565 else: 566 break 567 568 try: 569 self.set_fcf(freq) 570 except CPException: 571 self.wait() 572 573 try: 574 self.set_channel(1) 575 except CPException: 576 self.wait()
Sets the frequency of the laser in TeraHertz.
Has MHz resolution. Will round down.
This function sets the first channel frequency and then sets the channel to 1 so that the frequency output when the laser is enabled will be the frequency given to this function.
If you would like to only change the first channel frequency use
the function set_fcf
.
Disable the laser before calling this function.
See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW]
Parameters
- freq: The desired frequency setting in THz. :returns: None
578 def get_fcf(self): 579 """returns first channel frequency 580 581 See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW] 582 583 :returns: First channel frequency in THz 584 """ 585 response = self._fcf1() 586 fcf1 = int.from_bytes(response, "big") 587 588 response = self._fcf2() 589 fcf2 = int.from_bytes(response, "big") 590 591 return fcf1 + fcf2 * 1e-4
returns first channel frequency
See 9.6.6 First Channel’s Frequency (FCF1, FCF2 0x35 – 0x36) [RW]
:returns: First channel frequency in THz
593 def get_frequency(self): 594 """gets the current laser operating frequency 595 596 See 9.6.7 Laser Frequency (LF1, LF2 0x40 – 0x41) [R] 597 598 :returns: Current laser frequency in THz. 599 """ 600 response = self._lf1() 601 lf1 = int.from_bytes(response, "big") 602 603 response = self._lf2() 604 lf2 = int.from_bytes(response, "big") 605 606 return lf1 + lf2 * 1e-4
gets the current laser operating frequency
See 9.6.7 Laser Frequency (LF1, LF2 0x40 – 0x41) [R]
:returns: Current laser frequency in THz.
608 def dither_enable(self, waveform: Waveform = Waveform.SIN): 609 """enables dither 610 611 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 612 613 :param waveform: specifies the dither waveform 614 """ 615 616 data = (waveform << 4) | 2 617 618 self._dithere(data)
enables dither
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
Parameters
- waveform: specifies the dither waveform
620 def dither_disable(self): 621 """disables digital dither 622 623 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 624 """ 625 # TODO: This should preserve the waveform setting if possible 626 self._dithere(0)
disables digital dither
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
628 def set_dither_rate(self, rate): 629 """set dither rate in kHz 630 utilizes DitherR register 631 632 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 633 634 :param rate: an unsigned short integer specifying dither rate in kHz 635 """ 636 self._ditherr(rate)
set dither rate in kHz utilizes DitherR register
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
Parameters
- rate: an unsigned short integer specifying dither rate in kHz
638 def get_dither_rate(self): 639 """get dither rate 640 utilizes DitherR register 641 642 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 643 644 :returns: Dither rate in kHz 645 """ 646 response = self._ditherr() 647 return int.from_bytes(response, "big")
get dither rate utilizes DitherR register
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
:returns: Dither rate in kHz
649 def set_dither_frequency(self, rate): 650 """set dither modulation frequency 651 utilizes DitherF register 652 653 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 654 655 :param rate: an unsigned short integer encoded as the FM peak-to-peak frequency 656 deviation as Ghz*10 657 """ 658 self._ditherf(rate)
set dither modulation frequency utilizes DitherF register
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
Parameters
- rate: an unsigned short integer encoded as the FM peak-to-peak frequency deviation as Ghz*10
660 def get_dither_frequency(self): 661 """get dither modulation frequency 662 utilizes DitherF register 663 664 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 665 666 :returns: dither modulation frequency in GHz * 10 667 """ 668 response = self._ditherf() 669 return int.from_bytes(response, "big")
get dither modulation frequency utilizes DitherF register
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
:returns: dither modulation frequency in GHz * 10
671 def set_dither_amplitude(self, amplitude): 672 """set dither modulation amplitude 673 utilizes DitherA register 674 675 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 676 677 :param amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude 678 deviation as 10*percentage of the optical power 679 680 :returns: None 681 """ 682 self._dithera(amplitude)
set dither modulation amplitude utilizes DitherA register
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
Parameters
- amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude deviation as 10*percentage of the optical power
:returns: None
684 def get_dither_amplitude(self): 685 """get dither modulation amplitude 686 utilizes DitherA register 687 688 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 689 690 :returns: dither aplitude in 10ths of percents 691 """ 692 response = self._dithera() 693 return int.from_bytes(response, "big")
get dither modulation amplitude utilizes DitherA register
See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional]
:returns: dither aplitude in 10ths of percents
695 def get_temp(self): 696 """Returns the current primary control temperature in deg C. 697 698 See 9.6.9 Current Temperature (CTemp 0x43) [R] 699 700 :returns: current primary control temperature in deg C 701 """ 702 response = self._ctemp() 703 temp_100 = int.from_bytes(response, "big") 704 705 return temp_100 / 100
Returns the current primary control temperature in deg C.
See 9.6.9 Current Temperature (CTemp 0x43) [R]
:returns: current primary control temperature in deg C
707 def get_frequency_min(self): 708 """returns minimum frequency supported by the module 709 710 See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R] 711 712 :returns: minimum frequency in THz 713 """ 714 response = self._lfl1() 715 lfl1 = int.from_bytes(response, "big") 716 717 response = self._lfl2() 718 lfl2 = int.from_bytes(response, "big") 719 720 return lfl1 + lfl2 * 1e-4
returns minimum frequency supported by the module
See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R]
:returns: minimum frequency in THz
722 def get_frequency_max(self): 723 """returns maximum frequency supported by the module 724 725 See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R] 726 727 :returns: maximum frequency in THz 728 """ 729 response = self._lfh1() 730 fcf1 = int.from_bytes(response, "big") 731 732 response = self._lfh2() 733 fcf2 = int.from_bytes(response, "big") 734 735 return fcf1 + fcf2 * 1e-4
returns maximum frequency supported by the module
See 9.7.3 Laser’s First/Last Frequency (LFL1/2, LFH1/2 0x52-0x55) [R]
:returns: maximum frequency in THz
737 def get_grid_min(self): 738 """returns minimum grid spacing of the module 739 740 See 9.7.4 Laser’s Minimum Grid Spacing (LGrid 0x56) [R] 741 742 :returns: The minimum grid supported by the module in GHz 743 """ 744 try: 745 freq_lgrid = int.from_bytes(self._lgrid(), "big", signed=False) 746 747 except ExecutionError: 748 self.nop() 749 750 return freq_lgrid * 1e-1
returns minimum grid spacing of the module
See 9.7.4 Laser’s Minimum Grid Spacing (LGrid 0x56) [R]
:returns: The minimum grid supported by the module in GHz
752 def set_grid(self, grid_freq): 753 """Set the grid spacing in GHz. 754 MHz resolution. 755 756 See 9.6.5 Grid Spacing (Grid 0x34) [RW] 757 758 :param grid_freq: the grid frequency spacing in GHz 759 :returns: 760 761 """ 762 grid_freq = str(round(grid_freq * 1000)) 763 data = int(grid_freq[0:4]) 764 765 self._grid(data)
Set the grid spacing in GHz. MHz resolution.
See 9.6.5 Grid Spacing (Grid 0x34) [RW]
Parameters
- grid_freq: the grid frequency spacing in GHz :returns:
767 def get_grid(self): 768 """get the grid spacing in GHz 769 770 See 9.6.5 Grid Spacing (Grid 0x34) [RW] 771 772 :returns: The grid spacing in GHz. 773 """ 774 response = self._grid() 775 grid_freq = int.from_bytes(response, "big", signed=True) 776 777 return grid_freq * 1e-1
get the grid spacing in GHz
See 9.6.5 Grid Spacing (Grid 0x34) [RW]
:returns: The grid spacing in GHz.
779 def get_age(self): 780 """Returns the percentage of aging of the laser. 781 0% indicated a brand new laser 782 100% indicates the laser should be replaces. 783 784 See 9.8.6 Laser Age (Age 0x61) [R] 785 786 :returns: percent aging of the laser 787 """ 788 response = self._age() 789 age = int.from_bytes(response, "big") 790 791 return age
Returns the percentage of aging of the laser. 0% indicated a brand new laser 100% indicates the laser should be replaces.
See 9.8.6 Laser Age (Age 0x61) [R]
:returns: percent aging of the laser
793 def set_channel(self, channel): 794 """Sets the laser's operating channel. 795 796 MSA-01.2 lasers support a 16 bit channel value. 797 798 This defines how many spaces along the grid 799 the laser's frequency is displaced from the first channel frequency. 800 801 See 9.6.1 Channel (Channel 0x30) [RW] 802 803 :param channel: 804 :returns: None 805 806 """ 807 # check type and stuff 808 if not isinstance(channel, int): 809 raise TypeError("Channel must be an integer") 810 if channel < 0: 811 raise ValueError("Channel must be positive.") 812 if channel > 0xFFFF: 813 raise ValueError("Channel must be a 16 bit integer (<=0xFFFF).") 814 815 # Split the channel choice into two options. 816 channel_hex = f"{channel:08x}" 817 818 channell = int(channel_hex[4:], 16) 819 820 # Set the channel registers. 821 self._channel(channell)
Sets the laser's operating channel.
MSA-01.2 lasers support a 16 bit channel value.
This defines how many spaces along the grid the laser's frequency is displaced from the first channel frequency.
See 9.6.1 Channel (Channel 0x30) [RW]
Parameters
- channel: :returns: None
823 def get_channel(self): 824 """gets the current channel setting 825 826 The channel defines how many spaces along the grid 827 the laser's frequency is displaced from the first channel frequency. 828 829 See 9.6.1 Channel (Channel 0x30) [RW] 830 831 :returns: channel as an integer. 832 833 """ 834 # This concatenates the data bytestrings 835 response = self._channel() 836 837 channel = int.from_bytes(response, "big") 838 839 return channel
gets the current channel setting
The channel defines how many spaces along the grid the laser's frequency is displaced from the first channel frequency.
See 9.6.1 Channel (Channel 0x30) [RW]
:returns: channel as an integer.
841 def set_fine_tuning(self, ftf): 842 """ 843 This function provides off grid tuning for the laser's frequency. 844 The adjustment applies to all channels uniformly. 845 This is typically used after the laser if locked and minor adjustments 846 are required to the frequency. 847 848 The command may be pending in the event that the laser output is 849 enabled. The pending bit is cleared once the fine tune frequency 850 has been achieved. 851 852 See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW] 853 854 **???** It seems like this can be done with the laser running. 855 856 :param ftf: The fine tune frequency adjustment in GHz 857 """ 858 859 ftf = round(ftf * 1e3) 860 861 # We will leave this bare. This way the user can set and handle 862 # their own timing and check to make sure the laser has reached the 863 # desired fine tuning frequency. 864 # It WILL throw a "pending" error if the laser is on when setting. 865 self._ftf(ftf)
This function provides off grid tuning for the laser's frequency. The adjustment applies to all channels uniformly. This is typically used after the laser if locked and minor adjustments are required to the frequency.
The command may be pending in the event that the laser output is enabled. The pending bit is cleared once the fine tune frequency has been achieved.
See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW]
??? It seems like this can be done with the laser running.
Parameters
- ftf: The fine tune frequency adjustment in GHz
867 def get_fine_tuning(self): 868 """ 869 This function returns the setting for the 870 off grid tuning for the laser's frequency. 871 872 See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW] 873 874 :return ftf: The fine tune frequency adjustment in GHz 875 """ 876 877 response = self._ftf() 878 ftf = int.from_bytes(response, "big", signed=True) 879 880 return ftf * 1e-3
This function returns the setting for the off grid tuning for the laser's frequency.
See 9.8.7 Fine Tune Frequency (FTF 0x62) [RW]
Returns
The fine tune frequency adjustment in GHz
882 def get_ftf_range(self): 883 """return the maximum and minimum off grid tuning for the laser's frequency. 884 885 See 9.7.1 Fine Tune Frequency Range (FTF 0x4F) [R] 886 887 :return ftfr: The fine tune frequency range [-ftfr, + ftfr] in GHz 888 """ 889 response = self._ftfr() 890 891 ftfr = int.from_bytes(response, "big") 892 893 return ftfr * 1e-3
return the maximum and minimum off grid tuning for the laser's frequency.
See 9.7.1 Fine Tune Frequency Range (FTF 0x4F) [R]
Returns
The fine tune frequency range [-ftfr, + ftfr] in GHz
895 def get_temps(self): 896 """returns a list of technology specific temperatures 897 units in deg C unless otherwise specified 898 899 **Technologies** 900 Technology 1: [Diode Temp, Case Temp] 901 Technology 2: [Diode Temp, Case Temp] 902 (why are they the same?) 903 904 See 9.8.2 Module Temperatures (Temps 0x58) [R] 905 906 :returns: list of temperatures in deg C 907 """ 908 # get response this should be a long byte string 909 response = self._temps() 910 911 data = [ 912 int.from_bytes(response[i : i + 2], "big", signed=True) / 100 913 for i in range(0, len(response), 2) 914 ] 915 916 return data
returns a list of technology specific temperatures units in deg C unless otherwise specified
Technologies Technology 1: [Diode Temp, Case Temp] Technology 2: [Diode Temp, Case Temp] (why are they the same?)
See 9.8.2 Module Temperatures (Temps 0x58) [R]
:returns: list of temperatures in deg C
918 def get_currents(self): 919 """returns a list of technology specific currents in mA. 920 921 **Technologies** 922 Technology 1: [TEC, Diode] 923 Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] 924 Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...] 925 926 See 9.8.1 Module Currents (Currents 0x57) [R] 927 928 :returns: list of currents in mA 929 """ 930 # get response this should be a long byte string 931 response = self._currents() 932 933 data = [ 934 int.from_bytes(response[i : i + 2], "big", signed=True) / 10 935 for i in range(0, len(response), 2) 936 ] 937 938 return data
returns a list of technology specific currents in mA.
Technologies Technology 1: [TEC, Diode] Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...]
See 9.8.1 Module Currents (Currents 0x57) [R]
:returns: list of currents in mA
940 def get_last_response(self): 941 """reads the last response sent from the laser 942 943 This function gets the most recent response sent from the laser. 944 The response is parsed for errors the way any normal response 945 would be. The variable `self._response` is set to the last response again. 946 947 See 9.4.12 Last Response (LstResp 0x13) [R] 948 949 """ 950 return self._lstresp()
reads the last response sent from the laser
This function gets the most recent response sent from the laser.
The response is parsed for errors the way any normal response
would be. The variable self._response
is set to the last response again.
See 9.4.12 Last Response (LstResp 0x13) [R]
952 def get_error_fatal(self, reset=False): 953 """reads fatal error register statusf and checks each bit against the 954 fatal error table to determine the fault 955 956 See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW] 957 958 :param reset: resets/clears latching errors 959 960 """ 961 response = self._statusf() 962 statusf = int.from_bytes(response, "big") 963 964 logger.debug("Current Status Fatal Error: %d", statusf) 965 966 if reset: 967 data_reset = 0x00FF 968 self._statusf(data_reset) 969 970 return FatalError(statusf)
reads fatal error register statusf and checks each bit against the fatal error table to determine the fault
See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW]
Parameters
- reset: resets/clears latching errors
972 def get_error_warning(self, reset=False): 973 """reads warning error register statusw and checks each bit against the 974 warning error table to determine the fault 975 976 If the laser is off then some of the latched warnings will be set on. 977 978 See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW] 979 980 :param reset: resets/clears latching errors 981 """ 982 response = self._statusw() 983 statusw = int.from_bytes(response, "big") 984 985 logger.debug("Current Status Warning Error: %d", statusw) 986 987 if reset: 988 data_reset = 0x00FF 989 self._statusw(data_reset) 990 991 return WarningError(statusw)
reads warning error register statusw and checks each bit against the warning error table to determine the fault
If the laser is off then some of the latched warnings will be set on.
See 9.5.1 StatusF, StatusW (0x20, 0x21) [RW]
Parameters
- reset: resets/clears latching errors
993 def get_fatal_power_thresh(self): 994 """reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted 995 996 See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW] 997 """ 998 response = self._fpowth() 999 pow_fatal = int.from_bytes(response, "big") / 100 1000 # correcting for proper order of magnitude 1001 return pow_fatal
reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted
See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW]
1003 def get_warning_power_thresh(self): 1004 """reads maximum plus/minus power deviation in dB for which the warning alarm is asserted 1005 1006 See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW] 1007 """ 1008 response = self._wpowth() 1009 pow_warn = int.from_bytes(response, "big") / 100 1010 # correcting for proper order of magnitude 1011 return pow_warn
reads maximum plus/minus power deviation in dB for which the warning alarm is asserted
See 9.5.2 Power Threshold (FPowTh, WPowTh 0x22, 0x23) [RW]
1013 def get_fatal_freq_thresh(self): 1014 """reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted 1015 1016 See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW] 1017 """ 1018 response = self._ffreqth() 1019 freq_fatal = int.from_bytes(response, "big") / 10 1020 return freq_fatal
reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted
See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW]
1022 def get_warning_freq_thresh(self): 1023 """reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted 1024 1025 See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW] 1026 """ 1027 response = self._wfreqth() 1028 freq_warn = int.from_bytes(response, "big") / 10 1029 return freq_warn
reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted
See 9.5.3 Frequency Threshold (FFreqTh, WFreqTh 0x24, 0x25) [RW]
1031 def get_fatal_therm_thresh(self): 1032 """reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted 1033 1034 See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW] 1035 """ 1036 response = self._fthermth() 1037 therm_fatal = int.from_bytes(response, "big") / 100 1038 # correcting for proper order of magnitude 1039 return therm_fatal
reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted
See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW]
1041 def get_warning_therm_thresh(self): 1042 """reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted 1043 1044 See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW] 1045 """ 1046 response = self._wthermth() 1047 therm_thresh = int.from_bytes(response, "big") / 100 1048 # correcting for proper order of magnitude 1049 return therm_thresh
reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted
See 9.5.4 Thermal Threshold (FThermTh, WThermTh 0x26, 0x27) [RW]
1051 def get_srq_trigger(self): 1052 """identifies corresponding bits in status registers StatusF, StatusW 1053 1054 Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers 1055 1056 See 9.5.5 SRQ* Triggers (SRQT 0x28) [RW] 1057 """ 1058 response = self._srqt() 1059 status = int.from_bytes(response, "big") 1060 1061 logger.debug("SRQT Status: %d", status) 1062 1063 return SQRTrigger(status)
identifies corresponding bits in status registers StatusF, StatusW
Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers
See 9.5.5 SRQ* Triggers (SRQT 0x28) [RW]
1065 def get_fatal_trigger(self): 1066 """identifies which fatal condition was asserted in StatusF and StatusW registers 1067 1068 See 9.5.6 FATAL Triggers (FatalT 0x29) [RW] 1069 """ 1070 response = self._fatalt() 1071 status = int.from_bytes(response, "big") 1072 1073 logger.debug("FatalT Status: %d", status) 1074 1075 return FatalTrigger(status)
identifies which fatal condition was asserted in StatusF and StatusW registers
See 9.5.6 FATAL Triggers (FatalT 0x29) [RW]
1077 def get_alm_trigger(self): 1078 """identifies why ALM was asserted in StatusF and StatusW registers 1079 1080 See 9.5.7 ALM Triggers (ALMT 0x2A) [RW] 1081 """ 1082 response = self._almt() 1083 status = int.from_bytes(response, "big") 1084 1085 logger.debug("AlarmT Status: %d", status) 1086 1087 return AlarmTrigger(status)
identifies why ALM was asserted in StatusF and StatusW registers
See 9.5.7 ALM Triggers (ALMT 0x2A) [RW]