itla.itla13
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 ITLA13(ITLABase): 26 """ 27 A class that represents the ITLA13 28 and exposes a user friendly API for controlling functionality. 29 30 OIF-ITLA-MSA-01.3 is the standard implemented here. 31 Certain functions such as set_frequency() dont fully implement this yet 32 because I dont know how to do checks to see if the optional FCF3 33 functions are implemented for a particular laser. 34 35 Things to figure out 36 37 * What should be exposed to the user? 38 * Can we abstract away the concept of registers and stuff 39 in here so you don't have to deal with it. 40 41 There are some functions that could be implemented like set_fatalstatus. 42 I think this is probably a bad idea even though it isnt write only. 43 44 Set Frequency is my platonic ideal for the higher level functions. 45 46 """ 47 48 def __init__( 49 self, serial_port, baudrate, timeout=0.5, register_files=None, sleep_time=0.1 50 ): 51 """Initializes the ITLA12 object. 52 53 :param serial_port: The serial port to connect to. 54 Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX' 55 :param baudrate: The baudrate for communication. I have primarily seen 56 lasers using 9600 as the default and then higher for firmware upgrades. 57 This may be laser specific. 58 :param timeout: How long should we wait to receive a response from the laser 59 :param register_files: Any additional register files you would like to include 60 beyond the default MSA-01.3 defined registers. These must be in a yaml format as 61 described in the project's README. 62 :param sleep_time: time in seconds. Use in wait function 63 """ 64 if register_files is None: 65 register_files = [] 66 67 register_files = ["registers_itla12.yaml", *register_files] 68 69 self.sleep_time = sleep_time 70 71 super().__init__( 72 serial_port, baudrate, timeout=timeout, register_files=register_files 73 ) 74 75 def nop(self, data=None): 76 """The No-Op operation. 77 78 This is a good test to see if your laser is communicating properly. 79 It should read 0000 data or echo whatever data you send it if you send it something. 80 The data you write to nop gets overwritten as soon as you read it back. 81 82 `nop()` also returns more informative errors following an ExecutionError. 83 This is not called by default so you must do this explicitly if you want 84 to see more informative error information. 85 86 :param data: Data to write 87 88 """ 89 # pretty sure the data does nothing 90 if data is not None: 91 response = self._nop(data) 92 else: 93 response = self._nop() 94 95 error_field = int(response.hex()[-1], 16) 96 if bool(error_field): 97 raise self._nop_errors[error_field] 98 99 def enable(self): 100 """Enables laser optical output. 101 There is a time delay after execution of this function to 102 proper stable laser output. 103 104 :returns: None 105 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 """Tells the laser to stop lasing. 113 114 :returns: None 115 116 """ 117 self._resena(0) 118 119 def hard_reset(self): 120 """TODO describe function 121 122 :returns: 123 124 """ 125 self._resena(Resena.MR) 126 127 def soft_reset(self): 128 """TODO describe function 129 130 :returns: 131 132 """ 133 self._resena(Resena.SR) 134 135 def get_reset_enable(self): 136 """ 137 Return ResEna register. 138 """ 139 response = self._resena() 140 return Resena(int.from_bytes(response, "big")) 141 142 def set_alarm_during_tuning(self): 143 """Set alarm during tuning 144 mcb(ADT) 145 """ 146 self._mcb(MCB.ADT) 147 148 def set_shutdown_on_fatal(self): 149 """Set shutdown on fatal. 150 mcb(SDF) 151 """ 152 self._mcb(MCB.SDF) 153 154 def get_configuration_behavior(self): 155 """ 156 Return MCB register. 157 """ 158 response = self._mcb() 159 return MCB(int.from_bytes(response, "big")) 160 161 def is_disabled(self): 162 """ 163 Return if disabled. 164 165 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI. 166 167 For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). 168 Consider overwriting this methods and monitoring FatalError.ALM. 169 """ 170 fatal_error = self.get_error_fatal() 171 fatal_trigger = self.get_fatal_trigger() 172 resena = self.get_reset_enable() 173 mcb = self.get_configuration_behavior() 174 175 sdf = MCB.SDF in mcb 176 sena = Resena.SENA in resena 177 dis = FatalError.DIS in fatal_error 178 179 return ((fatal_error & fatal_trigger) and sdf) or (not sena) or (not dis) 180 181 def is_enabled(self, *args): 182 """ 183 Return if enabled. See is_disabled. 184 """ 185 return not self.is_disabled(*args) 186 187 def get_device_type(self): 188 """ 189 returns a string containing the device type. 190 """ 191 response_bytes = self._devtyp() 192 193 return response_bytes.decode("utf-8") 194 195 def get_manufacturer(self): 196 """ 197 Return's a string containing the manufacturer's name. 198 """ 199 response_bytes = self._mfgr() 200 201 return response_bytes.decode("utf-8") 202 203 def get_model(self): 204 """ 205 return's the model as a string 206 """ 207 response_bytes = self._model() 208 209 return response_bytes.decode("utf-8") 210 211 def get_serialnumber(self): 212 """ 213 returns the serial number 214 """ 215 response_bytes = self._serno() 216 217 return response_bytes.decode("utf-8") 218 219 def get_manufacturing_date(self): 220 """returns the manufacturing date""" 221 response_bytes = self._mfgdate() 222 223 return response_bytes.decode("utf-8") 224 225 def get_firmware_release(self): 226 """ 227 returns a manufacturer specific firmware release 228 """ 229 response_bytes = self._release() 230 231 return response_bytes.decode("utf-8") 232 233 def get_backwardscompatibility(self): 234 """ 235 returns a manufacturer specific firmware backwards compatibility 236 as a null terminated string 237 """ 238 response_bytes = self._relback() 239 240 return response_bytes.decode("utf-8") 241 242 def read_aea(self): 243 """ 244 reads the AEA register data until an execution error is thrown. 245 """ 246 aea_response = b"" 247 try: 248 while True: 249 aea_response += self._aea_ear() 250 251 except ExecutionError: 252 try: 253 self.nop() 254 255 except EREError: 256 # If this throws an ERE error then we have completed reading AEA 257 pass 258 259 except NOPException as nop_e: 260 raise nop_e 261 262 return aea_response 263 264 def wait(self): 265 """Wait until operation is complete. It check if the operation is completed every self.sleep_time seconds.""" 266 while True: 267 try: 268 self.nop() 269 except CPException: 270 if self.sleep_time is not None: 271 sleep(self.sleep_time) 272 else: 273 break 274 275 def wait_until_enabled(self): 276 while self.is_disabled(): 277 if self.sleep_time is not None: 278 sleep(self.sleep_time) 279 280 def wait_until_disabled(self): 281 while self.is_enabled(): 282 if self.sleep_time is not None: 283 sleep(self.sleep_time) 284 285 def set_power(self, pwr_dBm): 286 """Sets the power of the ITLA laser. Units of dBm. 287 288 :param pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. 289 :returns: None 290 291 """ 292 try: 293 self._pwr(round(pwr_dBm * 100)) 294 295 except ExecutionError: 296 try: 297 self.nop() 298 299 except RVEError as error: 300 logger.error( 301 "The provided power %.2f dBm is outside of the range for this device. " 302 "The power is currently set to: %.2f dBm.", 303 pwr_dBm, 304 self.get_power_setting(), 305 ) 306 raise error 307 308 def get_power_setting(self): 309 """Gets current power setting set by set_power. Should be in dBm. 310 311 :returns: 312 313 """ 314 # Gets power setting, not actual optical output power. 315 response = self._pwr() 316 return int.from_bytes(response, "big", signed=True) / 100 317 318 def get_power_output(self): 319 """Gets the actual optical output power of the laser. 320 Only an approximation, apparently. Units dBm. 321 322 :returns: 323 324 """ 325 response = self._oop() 326 327 return int.from_bytes(response, "big", signed=True) / 100 328 329 def get_power_min(self): 330 """Gets the minimum optical power output of the module. Units dBm. 331 332 :returns: 333 334 """ 335 response = self._opsl() 336 return int.from_bytes(response, "big", signed=True) / 100 337 338 def get_power_max(self): 339 """Gets the maximum optical power output of the module. Units dBm. 340 341 :returns: 342 343 """ 344 response = self._opsh() 345 return int.from_bytes(response, "big", signed=True) / 100 346 347 def set_fcf(self, freq): 348 """ 349 This sets the first channel frequency. 350 It does not reset the channel so this frequency will only be equal 351 to the output frequency if channel=1. 352 353 """ 354 # convert frequency to MHz 355 freq = round(freq * 1e6) 356 357 freq_str = str(freq) 358 fcf1 = int(freq_str[0:3]) 359 fcf2 = int(freq_str[3:7]) 360 fcf3 = int(freq_str[7:]) 361 362 try: 363 # is it better to split these into their own try/except blocks? 364 self._fcf1(fcf1) 365 self._fcf2(fcf2) 366 self._fcf3(fcf3) 367 368 except ExecutionError: 369 try: 370 self.nop() 371 except RVEError as error: 372 logger.error( 373 "%.2f THz is out of bounds for this laser. " 374 "The frequency must be within the range %.2f - %.2f THz.", 375 fcf1, 376 self.get_frequency_min(), 377 self.get_frequency_max(), 378 ) 379 raise error 380 381 except CIEError as error: 382 logger.error( 383 "You cannot change the first channel frequency while the laser is enabled. " 384 "The current frequency is: %.2f THz", 385 self.get_frequency(), 386 ) 387 raise error 388 389 def set_frequency(self, freq): 390 """Sets the frequency of the laser in TeraHertz. 391 392 Has MHz resolution. Will round down. 393 394 This function sets the first channel frequency and then sets the 395 channel to 1 so that the frequency output when the laser is enabled 396 will be the frequency given to this function. 397 398 If you would like to only change the first channel frequency use 399 the function `set_fcf`. 400 401 Disable the laser before calling this function. 402 403 :param freq: The desired frequency setting in THz. 404 :returns: None 405 """ 406 # This does a check so this only runs if fine tuning has been turned on. 407 if self.get_fine_tuning() != 0: 408 # Set the fine tuning off! 409 while True: 410 try: 411 self.set_fine_tuning(0) 412 except ExecutionError: 413 sleep(self.sleep_time) 414 except CPException: 415 self.wait() 416 break 417 else: 418 break 419 420 try: 421 self.set_fcf(freq) 422 except CPException: 423 self.wait() 424 425 try: 426 self.set_channel(1) 427 except CPException: 428 self.wait() 429 430 def get_fcf(self): 431 """Get the currently set first channel frequency.""" 432 response = self._fcf1() 433 fcf1 = int.from_bytes(response, "big") 434 435 response = self._fcf2() 436 fcf2 = int.from_bytes(response, "big") 437 438 response = self._fcf3() 439 fcf3 = int.from_bytes(response, "big") 440 441 return fcf1 + fcf2 * 1e-4 + fcf3 * 1e-6 442 443 def get_frequency(self): 444 """gets the current laser operating frequency with channels 445 and everything accounted for. 446 447 :returns: 448 449 """ 450 response = self._lf1() 451 lf1 = int.from_bytes(response, "big") 452 453 response = self._lf2() 454 lf2 = int.from_bytes(response, "big") 455 456 response = self._lf3() 457 lf3 = int.from_bytes(response, "big") 458 459 return lf1 + lf2 * 1e-4 + lf3 * 1e-6 460 461 def dither_enable(self, waveform: Waveform = Waveform.SIN): 462 """enables dither 463 464 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 465 466 :param waveform: specifies the dither waveform 467 """ 468 data = (waveform << 4) | 2 469 470 self._dithere(data) 471 472 def dither_disable(self): 473 """disables digital dither""" 474 # TODO: This should preserve the waveform setting if possible 475 self._dithere(0) 476 477 def set_dither_rate(self, rate): 478 """ 479 set dither rate in kHz, utilizes DitherR register 480 :param rate: an unsigned short integer specifying dither rate in kHz 481 """ 482 self._ditherr(rate) 483 484 def get_dither_rate(self): 485 """ 486 get dither rate, utilizes DitherR register 487 """ 488 response = self._ditherr() 489 return int.from_bytes(response, "big") 490 491 def set_dither_frequency(self, rate): 492 """ 493 set dither modulation frequency, utilizes DitherF register 494 :param rate: an unsigned short integer encoded as the FM peak-to-peak frequency 495 deviation as Ghz*10 496 """ 497 self._ditherf(rate) 498 499 def get_dither_frequency(self): 500 """ 501 get dither modulation frequency, utilizes DitherF register 502 """ 503 response = self._ditherf() 504 return int.from_bytes(response, "big") 505 506 def set_dither_amplitude(self, amplitude): 507 """ 508 set dither modulation amplitude, utilizes DitherA register 509 :param amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude 510 deviation as 10*percentage of the optical power 511 """ 512 self._dithera(amplitude) 513 514 def get_dither_amplitude(self): 515 """ 516 get dither modulation amplitude, utilizes DitherA register 517 """ 518 response = self._dithera() 519 return int.from_bytes(response, "big") 520 521 def get_temp(self): 522 """Returns the current primary control temperature in deg C. 523 524 :returns: 525 526 """ 527 response = self._ctemp() 528 temp_100 = int.from_bytes(response, "big") 529 530 return temp_100 / 100 531 532 def get_frequency_min(self): 533 """command to read minimum frequency supported by the module 534 535 :returns: 536 537 """ 538 response = self._lfl1() 539 lfl1 = int.from_bytes(response, "big") 540 541 response = self._lfl2() 542 lfl2 = int.from_bytes(response, "big") 543 544 response = self._lfl3() 545 lfl3 = int.from_bytes(response, "big") 546 547 return lfl1 + lfl2 * 1e-4 + lfl3 * 1e-6 548 549 def get_frequency_max(self): 550 """command to read maximum frequency supported by the module 551 552 :returns: 553 554 """ 555 response = self._lfh1() 556 fcf1 = int.from_bytes(response, "big") 557 558 response = self._lfh2() 559 fcf2 = int.from_bytes(response, "big") 560 561 response = self._lfh3() 562 fcf3 = int.from_bytes(response, "big") 563 564 return fcf1 + fcf2 * 1e-4 + fcf3 * 1e-6 565 566 def get_grid_min(self): 567 """command to read minimum grid supported by the module 568 569 :returns: The minimum grid supported by the module in GHz 570 571 """ 572 try: 573 freq_lgrid = int.from_bytes(self._lgrid(), "big", signed=False) 574 freq_lgrid2 = int.from_bytes(self._lgrid2(), "big", signed=False) 575 576 except ExecutionError: 577 self.nop() 578 579 return freq_lgrid * 1e-1 + freq_lgrid2 * 1e-3 580 581 def set_grid(self, grid_freq): 582 """Set the grid spacing in GHz. 583 584 MHz resolution. 585 586 :param grid_freq: the grid frequency spacing in GHz 587 :returns: 588 589 """ 590 grid_freq = str(round(grid_freq * 1000)) 591 data = int(grid_freq[0:4]) 592 data_2 = int(grid_freq[4:]) 593 594 self._grid(data) 595 self._grid2(data_2) 596 597 def get_grid(self): 598 """get the grid spacing in GHz 599 600 :returns: The grid spacing in GHz. 601 602 """ 603 response = self._grid() 604 grid_freq = int.from_bytes(response, "big", signed=True) 605 606 response = self._grid2() 607 grid2_freq = int.from_bytes(response, "big", signed=True) 608 609 return grid_freq * 1e-1 + grid2_freq * 1e-3 610 611 def get_age(self): 612 """Returns the percentage of aging of the laser. 613 0% indicated a brand new laser 614 100% indicates the laser should be replaces. 615 616 :returns: 617 618 """ 619 response = self._age() 620 age = int.from_bytes(response, "big") 621 622 return age 623 624 def set_channel(self, channel): 625 """Sets the laser's operating channel. 626 627 MSA-01.3 lasers support a 32 bit channel value. 628 629 This defines how many spaces along the grid 630 the laser's frequency is displaced from the first channel frequency. 631 632 :param channel: 633 :returns: 634 635 """ 636 # check type and stuff 637 if not isinstance(channel, int): 638 raise TypeError("Channel must be an integer") 639 if channel < 0: 640 raise ValueError("Channel must be positive.") 641 if channel > 0xFFFFFFFF: 642 raise ValueError("Channel must be a 32 bit integer (<=0xFFFFFFFF).") 643 644 # Split the channel choice into two options. 645 channel_hex = f"{channel:08x}" 646 647 channell = int(channel_hex[4:], 16) 648 channelh = int(channel_hex[0:4], 16) 649 650 # Set the channel registers. 651 self._channel(channell) 652 self._channelh(channelh) 653 654 def get_channel(self): 655 """gets the current channel setting 656 657 The channel defines how many spaces along the grid 658 the laser's frequency is displaced from the first channel frequency. 659 660 :returns: channel as an integer. 661 662 """ 663 # This concatenates the data bytestrings 664 response = self._channelh() + self._channel() 665 666 channel = int.from_bytes(response, "big") 667 668 return channel 669 670 def set_fine_tuning(self, ftf): 671 """ 672 This function provides off grid tuning for the laser's frequency. 673 The adjustment applies to all channels uniformly. 674 This is typically used after the laser if locked and minor adjustments 675 are required to the frequency. 676 677 The command may be pending in the event that the laser output is 678 enabled. The pending bit is cleared once the fine tune frequency 679 has been achieved. 680 681 **???** It seems like this can be done with the laser running. 682 683 :param ftf: The fine tune frequency adjustment in GHz 684 """ 685 686 ftf = round(ftf * 1e3) 687 688 # We will leave this bare. This way the user can set and handle 689 # their own timing and check to make sure the laser has reached the 690 # desired fine tuning frequency. 691 # It WILL throw a "pending" error if the laser is on when setting. 692 self._ftf(ftf) 693 694 def get_fine_tuning(self): 695 """ 696 This function returns the setting for the 697 off grid tuning for the laser's frequency. 698 699 :return ftf: The fine tune frequency adjustment in GHz 700 """ 701 702 response = self._ftf() 703 ftf = int.from_bytes(response, "big", signed=True) 704 705 return ftf * 1e-3 706 707 def get_ftf_range(self): 708 """ 709 Return the maximum and minimum off grid tuning for the laser's frequency. 710 711 :return ftfr: The fine tune frequency range [-ftfr, + ftfr] in GHz 712 """ 713 response = self._ftfr() 714 715 ftfr = int.from_bytes(response, "big") 716 717 return ftfr * 1e-3 718 719 def get_temps(self): 720 """ 721 Returns a list of currents in degrees Celcius. 722 In the following order: 723 724 Technology 1: [Diode Temp, Case Temp] 725 """ 726 # get response this should be a long byte string 727 response = self._temps() 728 729 data = [ 730 int.from_bytes(response[i : i + 2], "big", signed=True) / 100 731 for i in range(0, len(response), 2) 732 ] 733 734 return data 735 736 def get_currents(self): 737 """ 738 Returns a list of currents in mA. 739 In the following order: 740 741 Technology 1: [TEC, Diode] 742 Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] 743 Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...] 744 """ 745 # get response this should be a long byte string 746 response = self._currents() 747 748 data = [ 749 int.from_bytes(response[i : i + 2], "big", signed=True) / 10 750 for i in range(0, len(response), 2) 751 ] 752 753 return data 754 755 def get_last_response(self): 756 """This function gets the most recent response sent from the laser. 757 The response is parsed for errors and stuff the way any normal response 758 would be. The variable `self._response` is set to the last response again. 759 760 I dont know why you would need to do this. 761 """ 762 return self._lstresp() 763 764 def get_error_fatal(self, reset=False): 765 """ 766 reads fatal error register statusf and checks each bit against the 767 fatal error table to determine the fault 768 769 :param reset: resets/clears latching errors 770 771 """ 772 response = self._statusf() 773 statusf = int.from_bytes(response, "big") 774 775 logger.debug("Current Status Fatal Error: %d", statusf) 776 777 if reset: 778 data_reset = 0x00FF 779 self._statusf(data_reset) 780 781 return FatalError(statusf) 782 783 def get_error_warning(self, reset=False): 784 """ 785 reads warning error register statusw and checks each bit against the 786 warning error table to determine the fault 787 788 If the laser is off then some of the latched warnings will be set on. 789 790 :param reset: resets/clears latching errors 791 """ 792 response = self._statusw() 793 statusw = int.from_bytes(response, "big") 794 795 logger.debug("Current Status Warning Error: %d", statusw) 796 797 if reset: 798 data_reset = 0x00FF 799 self._statusw(data_reset) 800 801 return WarningError(statusw) 802 803 def get_fatal_power_thresh(self): 804 """ 805 reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted 806 807 """ 808 response = self._fpowth() 809 pow_fatal = int.from_bytes(response, "big") / 100 810 # correcting for proper order of magnitude 811 return pow_fatal 812 813 def get_warning_power_thresh(self): 814 """ 815 reads maximum plus/minus power deviation in dB for which the warning alarm is asserted 816 817 """ 818 response = self._wpowth() 819 pow_warn = int.from_bytes(response, "big") / 100 820 # correcting for proper order of magnitude 821 return pow_warn 822 823 def get_fatal_freq_thresh(self): 824 """ 825 reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted 826 827 """ 828 response = self._ffreqth() 829 freq_fatal = int.from_bytes(response, "big") / 10 830 # correcting for proper order of magnitude 831 response2 = self._ffreqth2() 832 freq_fatal2 = int.from_bytes(response2, "big") / 100 833 # get frequency deviation in MHz and add to GHz value 834 return freq_fatal + freq_fatal2 835 836 def get_warning_freq_thresh(self): 837 """ 838 reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted 839 840 """ 841 response = self._wfreqth() 842 freq_warn = int.from_bytes(response, "big") / 10 843 # correcting for proper order of magnitude 844 response2 = self._wfreqth2() 845 freq_warn2 = int.from_bytes(response2, "big") / 100 846 # get frequency deviation in MHz and add to GHz value 847 return freq_warn + freq_warn2 848 849 def get_fatal_therm_thresh(self): 850 """ 851 reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted 852 853 """ 854 response = self._fthermth() 855 therm_fatal = int.from_bytes(response, "big") / 100 856 # correcting for proper order of magnitude 857 return therm_fatal 858 859 def get_warning_therm_thresh(self): 860 """ 861 reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted 862 """ 863 response = self._wthermth() 864 therm_thresh = int.from_bytes(response, "big") / 100 865 # correcting for proper order of magnitude 866 return therm_thresh 867 868 def get_srq_trigger(self): 869 """ 870 Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers 871 872 """ 873 response = self._srqt() 874 status = int.from_bytes(response, "big") 875 876 logger.debug("SRQT Status: %d", status) 877 878 return SQRTrigger(status) 879 880 def get_fatal_trigger(self): 881 """ 882 Utilizes FatalT register to identify which fatal conditon was asserted in StatusF and StatusW registers 883 884 """ 885 response = self._fatalt() 886 status = int.from_bytes(response, "big") 887 888 logger.debug("FatalT Status: %d", status) 889 890 return FatalTrigger(status) 891 892 def get_alm_trigger(self): 893 """ 894 Utilizes ALMT register to identify why ALM was asserted in StatusF and StatusW registers 895 896 """ 897 response = self._almt() 898 status = int.from_bytes(response, "big") 899 900 logger.debug("AlarmT Status: %d", status) 901 902 return AlarmTrigger(status)
26class ITLA13(ITLABase): 27 """ 28 A class that represents the ITLA13 29 and exposes a user friendly API for controlling functionality. 30 31 OIF-ITLA-MSA-01.3 is the standard implemented here. 32 Certain functions such as set_frequency() dont fully implement this yet 33 because I dont know how to do checks to see if the optional FCF3 34 functions are implemented for a particular laser. 35 36 Things to figure out 37 38 * What should be exposed to the user? 39 * Can we abstract away the concept of registers and stuff 40 in here so you don't have to deal with it. 41 42 There are some functions that could be implemented like set_fatalstatus. 43 I think this is probably a bad idea even though it isnt write only. 44 45 Set Frequency is my platonic ideal for the higher level functions. 46 47 """ 48 49 def __init__( 50 self, serial_port, baudrate, timeout=0.5, register_files=None, sleep_time=0.1 51 ): 52 """Initializes the ITLA12 object. 53 54 :param serial_port: The serial port to connect to. 55 Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX' 56 :param baudrate: The baudrate for communication. I have primarily seen 57 lasers using 9600 as the default and then higher for firmware upgrades. 58 This may be laser specific. 59 :param timeout: How long should we wait to receive a response from the laser 60 :param register_files: Any additional register files you would like to include 61 beyond the default MSA-01.3 defined registers. These must be in a yaml format as 62 described in the project's README. 63 :param sleep_time: time in seconds. Use in wait function 64 """ 65 if register_files is None: 66 register_files = [] 67 68 register_files = ["registers_itla12.yaml", *register_files] 69 70 self.sleep_time = sleep_time 71 72 super().__init__( 73 serial_port, baudrate, timeout=timeout, register_files=register_files 74 ) 75 76 def nop(self, data=None): 77 """The No-Op operation. 78 79 This is a good test to see if your laser is communicating properly. 80 It should read 0000 data or echo whatever data you send it if you send it something. 81 The data you write to nop gets overwritten as soon as you read it back. 82 83 `nop()` also returns more informative errors following an ExecutionError. 84 This is not called by default so you must do this explicitly if you want 85 to see more informative error information. 86 87 :param data: Data to write 88 89 """ 90 # pretty sure the data does nothing 91 if data is not None: 92 response = self._nop(data) 93 else: 94 response = self._nop() 95 96 error_field = int(response.hex()[-1], 16) 97 if bool(error_field): 98 raise self._nop_errors[error_field] 99 100 def enable(self): 101 """Enables laser optical output. 102 There is a time delay after execution of this function to 103 proper stable laser output. 104 105 :returns: None 106 107 """ 108 # I'm writing this out partially for transparency 109 # Maybe unnecessary or non-optimal 110 self._resena(Resena.SENA) 111 112 def disable(self): 113 """Tells the laser to stop lasing. 114 115 :returns: None 116 117 """ 118 self._resena(0) 119 120 def hard_reset(self): 121 """TODO describe function 122 123 :returns: 124 125 """ 126 self._resena(Resena.MR) 127 128 def soft_reset(self): 129 """TODO describe function 130 131 :returns: 132 133 """ 134 self._resena(Resena.SR) 135 136 def get_reset_enable(self): 137 """ 138 Return ResEna register. 139 """ 140 response = self._resena() 141 return Resena(int.from_bytes(response, "big")) 142 143 def set_alarm_during_tuning(self): 144 """Set alarm during tuning 145 mcb(ADT) 146 """ 147 self._mcb(MCB.ADT) 148 149 def set_shutdown_on_fatal(self): 150 """Set shutdown on fatal. 151 mcb(SDF) 152 """ 153 self._mcb(MCB.SDF) 154 155 def get_configuration_behavior(self): 156 """ 157 Return MCB register. 158 """ 159 response = self._mcb() 160 return MCB(int.from_bytes(response, "big")) 161 162 def is_disabled(self): 163 """ 164 Return if disabled. 165 166 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI. 167 168 For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). 169 Consider overwriting this methods and monitoring FatalError.ALM. 170 """ 171 fatal_error = self.get_error_fatal() 172 fatal_trigger = self.get_fatal_trigger() 173 resena = self.get_reset_enable() 174 mcb = self.get_configuration_behavior() 175 176 sdf = MCB.SDF in mcb 177 sena = Resena.SENA in resena 178 dis = FatalError.DIS in fatal_error 179 180 return ((fatal_error & fatal_trigger) and sdf) or (not sena) or (not dis) 181 182 def is_enabled(self, *args): 183 """ 184 Return if enabled. See is_disabled. 185 """ 186 return not self.is_disabled(*args) 187 188 def get_device_type(self): 189 """ 190 returns a string containing the device type. 191 """ 192 response_bytes = self._devtyp() 193 194 return response_bytes.decode("utf-8") 195 196 def get_manufacturer(self): 197 """ 198 Return's a string containing the manufacturer's name. 199 """ 200 response_bytes = self._mfgr() 201 202 return response_bytes.decode("utf-8") 203 204 def get_model(self): 205 """ 206 return's the model as a string 207 """ 208 response_bytes = self._model() 209 210 return response_bytes.decode("utf-8") 211 212 def get_serialnumber(self): 213 """ 214 returns the serial number 215 """ 216 response_bytes = self._serno() 217 218 return response_bytes.decode("utf-8") 219 220 def get_manufacturing_date(self): 221 """returns the manufacturing date""" 222 response_bytes = self._mfgdate() 223 224 return response_bytes.decode("utf-8") 225 226 def get_firmware_release(self): 227 """ 228 returns a manufacturer specific firmware release 229 """ 230 response_bytes = self._release() 231 232 return response_bytes.decode("utf-8") 233 234 def get_backwardscompatibility(self): 235 """ 236 returns a manufacturer specific firmware backwards compatibility 237 as a null terminated string 238 """ 239 response_bytes = self._relback() 240 241 return response_bytes.decode("utf-8") 242 243 def read_aea(self): 244 """ 245 reads the AEA register data until an execution error is thrown. 246 """ 247 aea_response = b"" 248 try: 249 while True: 250 aea_response += self._aea_ear() 251 252 except ExecutionError: 253 try: 254 self.nop() 255 256 except EREError: 257 # If this throws an ERE error then we have completed reading AEA 258 pass 259 260 except NOPException as nop_e: 261 raise nop_e 262 263 return aea_response 264 265 def wait(self): 266 """Wait until operation is complete. It check if the operation is completed every self.sleep_time seconds.""" 267 while True: 268 try: 269 self.nop() 270 except CPException: 271 if self.sleep_time is not None: 272 sleep(self.sleep_time) 273 else: 274 break 275 276 def wait_until_enabled(self): 277 while self.is_disabled(): 278 if self.sleep_time is not None: 279 sleep(self.sleep_time) 280 281 def wait_until_disabled(self): 282 while self.is_enabled(): 283 if self.sleep_time is not None: 284 sleep(self.sleep_time) 285 286 def set_power(self, pwr_dBm): 287 """Sets the power of the ITLA laser. Units of dBm. 288 289 :param pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. 290 :returns: None 291 292 """ 293 try: 294 self._pwr(round(pwr_dBm * 100)) 295 296 except ExecutionError: 297 try: 298 self.nop() 299 300 except RVEError as error: 301 logger.error( 302 "The provided power %.2f dBm is outside of the range for this device. " 303 "The power is currently set to: %.2f dBm.", 304 pwr_dBm, 305 self.get_power_setting(), 306 ) 307 raise error 308 309 def get_power_setting(self): 310 """Gets current power setting set by set_power. Should be in dBm. 311 312 :returns: 313 314 """ 315 # Gets power setting, not actual optical output power. 316 response = self._pwr() 317 return int.from_bytes(response, "big", signed=True) / 100 318 319 def get_power_output(self): 320 """Gets the actual optical output power of the laser. 321 Only an approximation, apparently. Units dBm. 322 323 :returns: 324 325 """ 326 response = self._oop() 327 328 return int.from_bytes(response, "big", signed=True) / 100 329 330 def get_power_min(self): 331 """Gets the minimum optical power output of the module. Units dBm. 332 333 :returns: 334 335 """ 336 response = self._opsl() 337 return int.from_bytes(response, "big", signed=True) / 100 338 339 def get_power_max(self): 340 """Gets the maximum optical power output of the module. Units dBm. 341 342 :returns: 343 344 """ 345 response = self._opsh() 346 return int.from_bytes(response, "big", signed=True) / 100 347 348 def set_fcf(self, freq): 349 """ 350 This sets the first channel frequency. 351 It does not reset the channel so this frequency will only be equal 352 to the output frequency if channel=1. 353 354 """ 355 # convert frequency to MHz 356 freq = round(freq * 1e6) 357 358 freq_str = str(freq) 359 fcf1 = int(freq_str[0:3]) 360 fcf2 = int(freq_str[3:7]) 361 fcf3 = int(freq_str[7:]) 362 363 try: 364 # is it better to split these into their own try/except blocks? 365 self._fcf1(fcf1) 366 self._fcf2(fcf2) 367 self._fcf3(fcf3) 368 369 except ExecutionError: 370 try: 371 self.nop() 372 except RVEError as error: 373 logger.error( 374 "%.2f THz is out of bounds for this laser. " 375 "The frequency must be within the range %.2f - %.2f THz.", 376 fcf1, 377 self.get_frequency_min(), 378 self.get_frequency_max(), 379 ) 380 raise error 381 382 except CIEError as error: 383 logger.error( 384 "You cannot change the first channel frequency while the laser is enabled. " 385 "The current frequency is: %.2f THz", 386 self.get_frequency(), 387 ) 388 raise error 389 390 def set_frequency(self, freq): 391 """Sets the frequency of the laser in TeraHertz. 392 393 Has MHz resolution. Will round down. 394 395 This function sets the first channel frequency and then sets the 396 channel to 1 so that the frequency output when the laser is enabled 397 will be the frequency given to this function. 398 399 If you would like to only change the first channel frequency use 400 the function `set_fcf`. 401 402 Disable the laser before calling this function. 403 404 :param freq: The desired frequency setting in THz. 405 :returns: None 406 """ 407 # This does a check so this only runs if fine tuning has been turned on. 408 if self.get_fine_tuning() != 0: 409 # Set the fine tuning off! 410 while True: 411 try: 412 self.set_fine_tuning(0) 413 except ExecutionError: 414 sleep(self.sleep_time) 415 except CPException: 416 self.wait() 417 break 418 else: 419 break 420 421 try: 422 self.set_fcf(freq) 423 except CPException: 424 self.wait() 425 426 try: 427 self.set_channel(1) 428 except CPException: 429 self.wait() 430 431 def get_fcf(self): 432 """Get the currently set first channel frequency.""" 433 response = self._fcf1() 434 fcf1 = int.from_bytes(response, "big") 435 436 response = self._fcf2() 437 fcf2 = int.from_bytes(response, "big") 438 439 response = self._fcf3() 440 fcf3 = int.from_bytes(response, "big") 441 442 return fcf1 + fcf2 * 1e-4 + fcf3 * 1e-6 443 444 def get_frequency(self): 445 """gets the current laser operating frequency with channels 446 and everything accounted for. 447 448 :returns: 449 450 """ 451 response = self._lf1() 452 lf1 = int.from_bytes(response, "big") 453 454 response = self._lf2() 455 lf2 = int.from_bytes(response, "big") 456 457 response = self._lf3() 458 lf3 = int.from_bytes(response, "big") 459 460 return lf1 + lf2 * 1e-4 + lf3 * 1e-6 461 462 def dither_enable(self, waveform: Waveform = Waveform.SIN): 463 """enables dither 464 465 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 466 467 :param waveform: specifies the dither waveform 468 """ 469 data = (waveform << 4) | 2 470 471 self._dithere(data) 472 473 def dither_disable(self): 474 """disables digital dither""" 475 # TODO: This should preserve the waveform setting if possible 476 self._dithere(0) 477 478 def set_dither_rate(self, rate): 479 """ 480 set dither rate in kHz, utilizes DitherR register 481 :param rate: an unsigned short integer specifying dither rate in kHz 482 """ 483 self._ditherr(rate) 484 485 def get_dither_rate(self): 486 """ 487 get dither rate, utilizes DitherR register 488 """ 489 response = self._ditherr() 490 return int.from_bytes(response, "big") 491 492 def set_dither_frequency(self, rate): 493 """ 494 set dither modulation frequency, utilizes DitherF register 495 :param rate: an unsigned short integer encoded as the FM peak-to-peak frequency 496 deviation as Ghz*10 497 """ 498 self._ditherf(rate) 499 500 def get_dither_frequency(self): 501 """ 502 get dither modulation frequency, utilizes DitherF register 503 """ 504 response = self._ditherf() 505 return int.from_bytes(response, "big") 506 507 def set_dither_amplitude(self, amplitude): 508 """ 509 set dither modulation amplitude, utilizes DitherA register 510 :param amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude 511 deviation as 10*percentage of the optical power 512 """ 513 self._dithera(amplitude) 514 515 def get_dither_amplitude(self): 516 """ 517 get dither modulation amplitude, utilizes DitherA register 518 """ 519 response = self._dithera() 520 return int.from_bytes(response, "big") 521 522 def get_temp(self): 523 """Returns the current primary control temperature in deg C. 524 525 :returns: 526 527 """ 528 response = self._ctemp() 529 temp_100 = int.from_bytes(response, "big") 530 531 return temp_100 / 100 532 533 def get_frequency_min(self): 534 """command to read minimum frequency supported by the module 535 536 :returns: 537 538 """ 539 response = self._lfl1() 540 lfl1 = int.from_bytes(response, "big") 541 542 response = self._lfl2() 543 lfl2 = int.from_bytes(response, "big") 544 545 response = self._lfl3() 546 lfl3 = int.from_bytes(response, "big") 547 548 return lfl1 + lfl2 * 1e-4 + lfl3 * 1e-6 549 550 def get_frequency_max(self): 551 """command to read maximum frequency supported by the module 552 553 :returns: 554 555 """ 556 response = self._lfh1() 557 fcf1 = int.from_bytes(response, "big") 558 559 response = self._lfh2() 560 fcf2 = int.from_bytes(response, "big") 561 562 response = self._lfh3() 563 fcf3 = int.from_bytes(response, "big") 564 565 return fcf1 + fcf2 * 1e-4 + fcf3 * 1e-6 566 567 def get_grid_min(self): 568 """command to read minimum grid supported by the module 569 570 :returns: The minimum grid supported by the module in GHz 571 572 """ 573 try: 574 freq_lgrid = int.from_bytes(self._lgrid(), "big", signed=False) 575 freq_lgrid2 = int.from_bytes(self._lgrid2(), "big", signed=False) 576 577 except ExecutionError: 578 self.nop() 579 580 return freq_lgrid * 1e-1 + freq_lgrid2 * 1e-3 581 582 def set_grid(self, grid_freq): 583 """Set the grid spacing in GHz. 584 585 MHz resolution. 586 587 :param grid_freq: the grid frequency spacing in GHz 588 :returns: 589 590 """ 591 grid_freq = str(round(grid_freq * 1000)) 592 data = int(grid_freq[0:4]) 593 data_2 = int(grid_freq[4:]) 594 595 self._grid(data) 596 self._grid2(data_2) 597 598 def get_grid(self): 599 """get the grid spacing in GHz 600 601 :returns: The grid spacing in GHz. 602 603 """ 604 response = self._grid() 605 grid_freq = int.from_bytes(response, "big", signed=True) 606 607 response = self._grid2() 608 grid2_freq = int.from_bytes(response, "big", signed=True) 609 610 return grid_freq * 1e-1 + grid2_freq * 1e-3 611 612 def get_age(self): 613 """Returns the percentage of aging of the laser. 614 0% indicated a brand new laser 615 100% indicates the laser should be replaces. 616 617 :returns: 618 619 """ 620 response = self._age() 621 age = int.from_bytes(response, "big") 622 623 return age 624 625 def set_channel(self, channel): 626 """Sets the laser's operating channel. 627 628 MSA-01.3 lasers support a 32 bit channel value. 629 630 This defines how many spaces along the grid 631 the laser's frequency is displaced from the first channel frequency. 632 633 :param channel: 634 :returns: 635 636 """ 637 # check type and stuff 638 if not isinstance(channel, int): 639 raise TypeError("Channel must be an integer") 640 if channel < 0: 641 raise ValueError("Channel must be positive.") 642 if channel > 0xFFFFFFFF: 643 raise ValueError("Channel must be a 32 bit integer (<=0xFFFFFFFF).") 644 645 # Split the channel choice into two options. 646 channel_hex = f"{channel:08x}" 647 648 channell = int(channel_hex[4:], 16) 649 channelh = int(channel_hex[0:4], 16) 650 651 # Set the channel registers. 652 self._channel(channell) 653 self._channelh(channelh) 654 655 def get_channel(self): 656 """gets the current channel setting 657 658 The channel defines how many spaces along the grid 659 the laser's frequency is displaced from the first channel frequency. 660 661 :returns: channel as an integer. 662 663 """ 664 # This concatenates the data bytestrings 665 response = self._channelh() + self._channel() 666 667 channel = int.from_bytes(response, "big") 668 669 return channel 670 671 def set_fine_tuning(self, ftf): 672 """ 673 This function provides off grid tuning for the laser's frequency. 674 The adjustment applies to all channels uniformly. 675 This is typically used after the laser if locked and minor adjustments 676 are required to the frequency. 677 678 The command may be pending in the event that the laser output is 679 enabled. The pending bit is cleared once the fine tune frequency 680 has been achieved. 681 682 **???** It seems like this can be done with the laser running. 683 684 :param ftf: The fine tune frequency adjustment in GHz 685 """ 686 687 ftf = round(ftf * 1e3) 688 689 # We will leave this bare. This way the user can set and handle 690 # their own timing and check to make sure the laser has reached the 691 # desired fine tuning frequency. 692 # It WILL throw a "pending" error if the laser is on when setting. 693 self._ftf(ftf) 694 695 def get_fine_tuning(self): 696 """ 697 This function returns the setting for the 698 off grid tuning for the laser's frequency. 699 700 :return ftf: The fine tune frequency adjustment in GHz 701 """ 702 703 response = self._ftf() 704 ftf = int.from_bytes(response, "big", signed=True) 705 706 return ftf * 1e-3 707 708 def get_ftf_range(self): 709 """ 710 Return the maximum and minimum off grid tuning for the laser's frequency. 711 712 :return ftfr: The fine tune frequency range [-ftfr, + ftfr] in GHz 713 """ 714 response = self._ftfr() 715 716 ftfr = int.from_bytes(response, "big") 717 718 return ftfr * 1e-3 719 720 def get_temps(self): 721 """ 722 Returns a list of currents in degrees Celcius. 723 In the following order: 724 725 Technology 1: [Diode Temp, Case Temp] 726 """ 727 # get response this should be a long byte string 728 response = self._temps() 729 730 data = [ 731 int.from_bytes(response[i : i + 2], "big", signed=True) / 100 732 for i in range(0, len(response), 2) 733 ] 734 735 return data 736 737 def get_currents(self): 738 """ 739 Returns a list of currents in mA. 740 In the following order: 741 742 Technology 1: [TEC, Diode] 743 Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] 744 Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...] 745 """ 746 # get response this should be a long byte string 747 response = self._currents() 748 749 data = [ 750 int.from_bytes(response[i : i + 2], "big", signed=True) / 10 751 for i in range(0, len(response), 2) 752 ] 753 754 return data 755 756 def get_last_response(self): 757 """This function gets the most recent response sent from the laser. 758 The response is parsed for errors and stuff the way any normal response 759 would be. The variable `self._response` is set to the last response again. 760 761 I dont know why you would need to do this. 762 """ 763 return self._lstresp() 764 765 def get_error_fatal(self, reset=False): 766 """ 767 reads fatal error register statusf and checks each bit against the 768 fatal error table to determine the fault 769 770 :param reset: resets/clears latching errors 771 772 """ 773 response = self._statusf() 774 statusf = int.from_bytes(response, "big") 775 776 logger.debug("Current Status Fatal Error: %d", statusf) 777 778 if reset: 779 data_reset = 0x00FF 780 self._statusf(data_reset) 781 782 return FatalError(statusf) 783 784 def get_error_warning(self, reset=False): 785 """ 786 reads warning error register statusw and checks each bit against the 787 warning error table to determine the fault 788 789 If the laser is off then some of the latched warnings will be set on. 790 791 :param reset: resets/clears latching errors 792 """ 793 response = self._statusw() 794 statusw = int.from_bytes(response, "big") 795 796 logger.debug("Current Status Warning Error: %d", statusw) 797 798 if reset: 799 data_reset = 0x00FF 800 self._statusw(data_reset) 801 802 return WarningError(statusw) 803 804 def get_fatal_power_thresh(self): 805 """ 806 reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted 807 808 """ 809 response = self._fpowth() 810 pow_fatal = int.from_bytes(response, "big") / 100 811 # correcting for proper order of magnitude 812 return pow_fatal 813 814 def get_warning_power_thresh(self): 815 """ 816 reads maximum plus/minus power deviation in dB for which the warning alarm is asserted 817 818 """ 819 response = self._wpowth() 820 pow_warn = int.from_bytes(response, "big") / 100 821 # correcting for proper order of magnitude 822 return pow_warn 823 824 def get_fatal_freq_thresh(self): 825 """ 826 reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted 827 828 """ 829 response = self._ffreqth() 830 freq_fatal = int.from_bytes(response, "big") / 10 831 # correcting for proper order of magnitude 832 response2 = self._ffreqth2() 833 freq_fatal2 = int.from_bytes(response2, "big") / 100 834 # get frequency deviation in MHz and add to GHz value 835 return freq_fatal + freq_fatal2 836 837 def get_warning_freq_thresh(self): 838 """ 839 reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted 840 841 """ 842 response = self._wfreqth() 843 freq_warn = int.from_bytes(response, "big") / 10 844 # correcting for proper order of magnitude 845 response2 = self._wfreqth2() 846 freq_warn2 = int.from_bytes(response2, "big") / 100 847 # get frequency deviation in MHz and add to GHz value 848 return freq_warn + freq_warn2 849 850 def get_fatal_therm_thresh(self): 851 """ 852 reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted 853 854 """ 855 response = self._fthermth() 856 therm_fatal = int.from_bytes(response, "big") / 100 857 # correcting for proper order of magnitude 858 return therm_fatal 859 860 def get_warning_therm_thresh(self): 861 """ 862 reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted 863 """ 864 response = self._wthermth() 865 therm_thresh = int.from_bytes(response, "big") / 100 866 # correcting for proper order of magnitude 867 return therm_thresh 868 869 def get_srq_trigger(self): 870 """ 871 Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers 872 873 """ 874 response = self._srqt() 875 status = int.from_bytes(response, "big") 876 877 logger.debug("SRQT Status: %d", status) 878 879 return SQRTrigger(status) 880 881 def get_fatal_trigger(self): 882 """ 883 Utilizes FatalT register to identify which fatal conditon was asserted in StatusF and StatusW registers 884 885 """ 886 response = self._fatalt() 887 status = int.from_bytes(response, "big") 888 889 logger.debug("FatalT Status: %d", status) 890 891 return FatalTrigger(status) 892 893 def get_alm_trigger(self): 894 """ 895 Utilizes ALMT register to identify why ALM was asserted in StatusF and StatusW registers 896 897 """ 898 response = self._almt() 899 status = int.from_bytes(response, "big") 900 901 logger.debug("AlarmT Status: %d", status) 902 903 return AlarmTrigger(status)
A class that represents the ITLA13 and exposes a user friendly API for controlling functionality.
OIF-ITLA-MSA-01.3 is the standard implemented here. Certain functions such as set_frequency() dont fully implement this yet because I dont know how to do checks to see if the optional FCF3 functions are implemented for a particular laser.
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.
49 def __init__( 50 self, serial_port, baudrate, timeout=0.5, register_files=None, sleep_time=0.1 51 ): 52 """Initializes the ITLA12 object. 53 54 :param serial_port: The serial port to connect to. 55 Can either be linux/mac type such as '/dev/ttyUSBX' or windows type 'comX' 56 :param baudrate: The baudrate for communication. I have primarily seen 57 lasers using 9600 as the default and then higher for firmware upgrades. 58 This may be laser specific. 59 :param timeout: How long should we wait to receive a response from the laser 60 :param register_files: Any additional register files you would like to include 61 beyond the default MSA-01.3 defined registers. These must be in a yaml format as 62 described in the project's README. 63 :param sleep_time: time in seconds. Use in wait function 64 """ 65 if register_files is None: 66 register_files = [] 67 68 register_files = ["registers_itla12.yaml", *register_files] 69 70 self.sleep_time = sleep_time 71 72 super().__init__( 73 serial_port, baudrate, timeout=timeout, register_files=register_files 74 )
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.3 defined registers. These must be in a yaml format as described in the project's README.
- sleep_time: time in seconds. Use in wait function
76 def nop(self, data=None): 77 """The No-Op operation. 78 79 This is a good test to see if your laser is communicating properly. 80 It should read 0000 data or echo whatever data you send it if you send it something. 81 The data you write to nop gets overwritten as soon as you read it back. 82 83 `nop()` also returns more informative errors following an ExecutionError. 84 This is not called by default so you must do this explicitly if you want 85 to see more informative error information. 86 87 :param data: Data to write 88 89 """ 90 # pretty sure the data does nothing 91 if data is not None: 92 response = self._nop(data) 93 else: 94 response = self._nop() 95 96 error_field = int(response.hex()[-1], 16) 97 if bool(error_field): 98 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.
Parameters
- data: Data to write
100 def enable(self): 101 """Enables laser optical output. 102 There is a time delay after execution of this function to 103 proper stable laser output. 104 105 :returns: None 106 107 """ 108 # I'm writing this out partially for transparency 109 # Maybe unnecessary or non-optimal 110 self._resena(Resena.SENA)
Enables laser optical output. There is a time delay after execution of this function to proper stable laser output.
:returns: None
112 def disable(self): 113 """Tells the laser to stop lasing. 114 115 :returns: None 116 117 """ 118 self._resena(0)
Tells the laser to stop lasing.
:returns: None
120 def hard_reset(self): 121 """TODO describe function 122 123 :returns: 124 125 """ 126 self._resena(Resena.MR)
TODO describe function
:returns:
128 def soft_reset(self): 129 """TODO describe function 130 131 :returns: 132 133 """ 134 self._resena(Resena.SR)
TODO describe function
:returns:
136 def get_reset_enable(self): 137 """ 138 Return ResEna register. 139 """ 140 response = self._resena() 141 return Resena(int.from_bytes(response, "big"))
Return ResEna register.
143 def set_alarm_during_tuning(self): 144 """Set alarm during tuning 145 mcb(ADT) 146 """ 147 self._mcb(MCB.ADT)
Set alarm during tuning mcb(ADT)
149 def set_shutdown_on_fatal(self): 150 """Set shutdown on fatal. 151 mcb(SDF) 152 """ 153 self._mcb(MCB.SDF)
Set shutdown on fatal. mcb(SDF)
155 def get_configuration_behavior(self): 156 """ 157 Return MCB register. 158 """ 159 response = self._mcb() 160 return MCB(int.from_bytes(response, "big"))
Return MCB register.
162 def is_disabled(self): 163 """ 164 Return if disabled. 165 166 See 9.6.3 Reset/Enable (ResEna 0x32) [RW] in OIF-ITLA-MSI. 167 168 For some lasers, FatalError.DIS is not triggered (even if TriggerT allows it). 169 Consider overwriting this methods and monitoring FatalError.ALM. 170 """ 171 fatal_error = self.get_error_fatal() 172 fatal_trigger = self.get_fatal_trigger() 173 resena = self.get_reset_enable() 174 mcb = self.get_configuration_behavior() 175 176 sdf = MCB.SDF in mcb 177 sena = Resena.SENA in resena 178 dis = FatalError.DIS in fatal_error 179 180 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.
182 def is_enabled(self, *args): 183 """ 184 Return if enabled. See is_disabled. 185 """ 186 return not self.is_disabled(*args)
Return if enabled. See is_disabled.
188 def get_device_type(self): 189 """ 190 returns a string containing the device type. 191 """ 192 response_bytes = self._devtyp() 193 194 return response_bytes.decode("utf-8")
returns a string containing the device type.
196 def get_manufacturer(self): 197 """ 198 Return's a string containing the manufacturer's name. 199 """ 200 response_bytes = self._mfgr() 201 202 return response_bytes.decode("utf-8")
Return's a string containing the manufacturer's name.
204 def get_model(self): 205 """ 206 return's the model as a string 207 """ 208 response_bytes = self._model() 209 210 return response_bytes.decode("utf-8")
return's the model as a string
212 def get_serialnumber(self): 213 """ 214 returns the serial number 215 """ 216 response_bytes = self._serno() 217 218 return response_bytes.decode("utf-8")
returns the serial number
220 def get_manufacturing_date(self): 221 """returns the manufacturing date""" 222 response_bytes = self._mfgdate() 223 224 return response_bytes.decode("utf-8")
returns the manufacturing date
226 def get_firmware_release(self): 227 """ 228 returns a manufacturer specific firmware release 229 """ 230 response_bytes = self._release() 231 232 return response_bytes.decode("utf-8")
returns a manufacturer specific firmware release
234 def get_backwardscompatibility(self): 235 """ 236 returns a manufacturer specific firmware backwards compatibility 237 as a null terminated string 238 """ 239 response_bytes = self._relback() 240 241 return response_bytes.decode("utf-8")
returns a manufacturer specific firmware backwards compatibility as a null terminated string
243 def read_aea(self): 244 """ 245 reads the AEA register data until an execution error is thrown. 246 """ 247 aea_response = b"" 248 try: 249 while True: 250 aea_response += self._aea_ear() 251 252 except ExecutionError: 253 try: 254 self.nop() 255 256 except EREError: 257 # If this throws an ERE error then we have completed reading AEA 258 pass 259 260 except NOPException as nop_e: 261 raise nop_e 262 263 return aea_response
reads the AEA register data until an execution error is thrown.
265 def wait(self): 266 """Wait until operation is complete. It check if the operation is completed every self.sleep_time seconds.""" 267 while True: 268 try: 269 self.nop() 270 except CPException: 271 if self.sleep_time is not None: 272 sleep(self.sleep_time) 273 else: 274 break
Wait until operation is complete. It check if the operation is completed every self.sleep_time seconds.
286 def set_power(self, pwr_dBm): 287 """Sets the power of the ITLA laser. Units of dBm. 288 289 :param pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. 290 :returns: None 291 292 """ 293 try: 294 self._pwr(round(pwr_dBm * 100)) 295 296 except ExecutionError: 297 try: 298 self.nop() 299 300 except RVEError as error: 301 logger.error( 302 "The provided power %.2f dBm is outside of the range for this device. " 303 "The power is currently set to: %.2f dBm.", 304 pwr_dBm, 305 self.get_power_setting(), 306 ) 307 raise error
Sets the power of the ITLA laser. Units of dBm.
Parameters
- pwr_dBm: The power setting for the laser in dBm. Has precision XX.XX. :returns: None
309 def get_power_setting(self): 310 """Gets current power setting set by set_power. Should be in dBm. 311 312 :returns: 313 314 """ 315 # Gets power setting, not actual optical output power. 316 response = self._pwr() 317 return int.from_bytes(response, "big", signed=True) / 100
Gets current power setting set by set_power. Should be in dBm.
:returns:
319 def get_power_output(self): 320 """Gets the actual optical output power of the laser. 321 Only an approximation, apparently. Units dBm. 322 323 :returns: 324 325 """ 326 response = self._oop() 327 328 return int.from_bytes(response, "big", signed=True) / 100
Gets the actual optical output power of the laser. Only an approximation, apparently. Units dBm.
:returns:
330 def get_power_min(self): 331 """Gets the minimum optical power output of the module. Units dBm. 332 333 :returns: 334 335 """ 336 response = self._opsl() 337 return int.from_bytes(response, "big", signed=True) / 100
Gets the minimum optical power output of the module. Units dBm.
:returns:
339 def get_power_max(self): 340 """Gets the maximum optical power output of the module. Units dBm. 341 342 :returns: 343 344 """ 345 response = self._opsh() 346 return int.from_bytes(response, "big", signed=True) / 100
Gets the maximum optical power output of the module. Units dBm.
:returns:
348 def set_fcf(self, freq): 349 """ 350 This sets the first channel frequency. 351 It does not reset the channel so this frequency will only be equal 352 to the output frequency if channel=1. 353 354 """ 355 # convert frequency to MHz 356 freq = round(freq * 1e6) 357 358 freq_str = str(freq) 359 fcf1 = int(freq_str[0:3]) 360 fcf2 = int(freq_str[3:7]) 361 fcf3 = int(freq_str[7:]) 362 363 try: 364 # is it better to split these into their own try/except blocks? 365 self._fcf1(fcf1) 366 self._fcf2(fcf2) 367 self._fcf3(fcf3) 368 369 except ExecutionError: 370 try: 371 self.nop() 372 except RVEError as error: 373 logger.error( 374 "%.2f THz is out of bounds for this laser. " 375 "The frequency must be within the range %.2f - %.2f THz.", 376 fcf1, 377 self.get_frequency_min(), 378 self.get_frequency_max(), 379 ) 380 raise error 381 382 except CIEError as error: 383 logger.error( 384 "You cannot change the first channel frequency while the laser is enabled. " 385 "The current frequency is: %.2f THz", 386 self.get_frequency(), 387 ) 388 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.
390 def set_frequency(self, freq): 391 """Sets the frequency of the laser in TeraHertz. 392 393 Has MHz resolution. Will round down. 394 395 This function sets the first channel frequency and then sets the 396 channel to 1 so that the frequency output when the laser is enabled 397 will be the frequency given to this function. 398 399 If you would like to only change the first channel frequency use 400 the function `set_fcf`. 401 402 Disable the laser before calling this function. 403 404 :param freq: The desired frequency setting in THz. 405 :returns: None 406 """ 407 # This does a check so this only runs if fine tuning has been turned on. 408 if self.get_fine_tuning() != 0: 409 # Set the fine tuning off! 410 while True: 411 try: 412 self.set_fine_tuning(0) 413 except ExecutionError: 414 sleep(self.sleep_time) 415 except CPException: 416 self.wait() 417 break 418 else: 419 break 420 421 try: 422 self.set_fcf(freq) 423 except CPException: 424 self.wait() 425 426 try: 427 self.set_channel(1) 428 except CPException: 429 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.
Parameters
- freq: The desired frequency setting in THz. :returns: None
431 def get_fcf(self): 432 """Get the currently set first channel frequency.""" 433 response = self._fcf1() 434 fcf1 = int.from_bytes(response, "big") 435 436 response = self._fcf2() 437 fcf2 = int.from_bytes(response, "big") 438 439 response = self._fcf3() 440 fcf3 = int.from_bytes(response, "big") 441 442 return fcf1 + fcf2 * 1e-4 + fcf3 * 1e-6
Get the currently set first channel frequency.
444 def get_frequency(self): 445 """gets the current laser operating frequency with channels 446 and everything accounted for. 447 448 :returns: 449 450 """ 451 response = self._lf1() 452 lf1 = int.from_bytes(response, "big") 453 454 response = self._lf2() 455 lf2 = int.from_bytes(response, "big") 456 457 response = self._lf3() 458 lf3 = int.from_bytes(response, "big") 459 460 return lf1 + lf2 * 1e-4 + lf3 * 1e-6
gets the current laser operating frequency with channels and everything accounted for.
:returns:
462 def dither_enable(self, waveform: Waveform = Waveform.SIN): 463 """enables dither 464 465 See 9.8.3 Digital Dither (Dither(E,R,A,F) 0x59-0x5C) [RW] [Optional] 466 467 :param waveform: specifies the dither waveform 468 """ 469 data = (waveform << 4) | 2 470 471 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
473 def dither_disable(self): 474 """disables digital dither""" 475 # TODO: This should preserve the waveform setting if possible 476 self._dithere(0)
disables digital dither
478 def set_dither_rate(self, rate): 479 """ 480 set dither rate in kHz, utilizes DitherR register 481 :param rate: an unsigned short integer specifying dither rate in kHz 482 """ 483 self._ditherr(rate)
set dither rate in kHz, utilizes DitherR register
Parameters
- rate: an unsigned short integer specifying dither rate in kHz
485 def get_dither_rate(self): 486 """ 487 get dither rate, utilizes DitherR register 488 """ 489 response = self._ditherr() 490 return int.from_bytes(response, "big")
get dither rate, utilizes DitherR register
492 def set_dither_frequency(self, rate): 493 """ 494 set dither modulation frequency, utilizes DitherF register 495 :param rate: an unsigned short integer encoded as the FM peak-to-peak frequency 496 deviation as Ghz*10 497 """ 498 self._ditherf(rate)
set dither modulation frequency, utilizes DitherF register
Parameters
- rate: an unsigned short integer encoded as the FM peak-to-peak frequency deviation as Ghz*10
500 def get_dither_frequency(self): 501 """ 502 get dither modulation frequency, utilizes DitherF register 503 """ 504 response = self._ditherf() 505 return int.from_bytes(response, "big")
get dither modulation frequency, utilizes DitherF register
507 def set_dither_amplitude(self, amplitude): 508 """ 509 set dither modulation amplitude, utilizes DitherA register 510 :param amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude 511 deviation as 10*percentage of the optical power 512 """ 513 self._dithera(amplitude)
set dither modulation amplitude, utilizes DitherA register
Parameters
- amplitude: an unsigned short integer encoded as the AM peak-to-peak amplitude deviation as 10*percentage of the optical power
515 def get_dither_amplitude(self): 516 """ 517 get dither modulation amplitude, utilizes DitherA register 518 """ 519 response = self._dithera() 520 return int.from_bytes(response, "big")
get dither modulation amplitude, utilizes DitherA register
522 def get_temp(self): 523 """Returns the current primary control temperature in deg C. 524 525 :returns: 526 527 """ 528 response = self._ctemp() 529 temp_100 = int.from_bytes(response, "big") 530 531 return temp_100 / 100
Returns the current primary control temperature in deg C.
:returns:
533 def get_frequency_min(self): 534 """command to read minimum frequency supported by the module 535 536 :returns: 537 538 """ 539 response = self._lfl1() 540 lfl1 = int.from_bytes(response, "big") 541 542 response = self._lfl2() 543 lfl2 = int.from_bytes(response, "big") 544 545 response = self._lfl3() 546 lfl3 = int.from_bytes(response, "big") 547 548 return lfl1 + lfl2 * 1e-4 + lfl3 * 1e-6
command to read minimum frequency supported by the module
:returns:
550 def get_frequency_max(self): 551 """command to read maximum frequency supported by the module 552 553 :returns: 554 555 """ 556 response = self._lfh1() 557 fcf1 = int.from_bytes(response, "big") 558 559 response = self._lfh2() 560 fcf2 = int.from_bytes(response, "big") 561 562 response = self._lfh3() 563 fcf3 = int.from_bytes(response, "big") 564 565 return fcf1 + fcf2 * 1e-4 + fcf3 * 1e-6
command to read maximum frequency supported by the module
:returns:
567 def get_grid_min(self): 568 """command to read minimum grid supported by the module 569 570 :returns: The minimum grid supported by the module in GHz 571 572 """ 573 try: 574 freq_lgrid = int.from_bytes(self._lgrid(), "big", signed=False) 575 freq_lgrid2 = int.from_bytes(self._lgrid2(), "big", signed=False) 576 577 except ExecutionError: 578 self.nop() 579 580 return freq_lgrid * 1e-1 + freq_lgrid2 * 1e-3
command to read minimum grid supported by the module
:returns: The minimum grid supported by the module in GHz
582 def set_grid(self, grid_freq): 583 """Set the grid spacing in GHz. 584 585 MHz resolution. 586 587 :param grid_freq: the grid frequency spacing in GHz 588 :returns: 589 590 """ 591 grid_freq = str(round(grid_freq * 1000)) 592 data = int(grid_freq[0:4]) 593 data_2 = int(grid_freq[4:]) 594 595 self._grid(data) 596 self._grid2(data_2)
Set the grid spacing in GHz.
MHz resolution.
Parameters
- grid_freq: the grid frequency spacing in GHz :returns:
598 def get_grid(self): 599 """get the grid spacing in GHz 600 601 :returns: The grid spacing in GHz. 602 603 """ 604 response = self._grid() 605 grid_freq = int.from_bytes(response, "big", signed=True) 606 607 response = self._grid2() 608 grid2_freq = int.from_bytes(response, "big", signed=True) 609 610 return grid_freq * 1e-1 + grid2_freq * 1e-3
get the grid spacing in GHz
:returns: The grid spacing in GHz.
612 def get_age(self): 613 """Returns the percentage of aging of the laser. 614 0% indicated a brand new laser 615 100% indicates the laser should be replaces. 616 617 :returns: 618 619 """ 620 response = self._age() 621 age = int.from_bytes(response, "big") 622 623 return age
Returns the percentage of aging of the laser. 0% indicated a brand new laser 100% indicates the laser should be replaces.
:returns:
625 def set_channel(self, channel): 626 """Sets the laser's operating channel. 627 628 MSA-01.3 lasers support a 32 bit channel value. 629 630 This defines how many spaces along the grid 631 the laser's frequency is displaced from the first channel frequency. 632 633 :param channel: 634 :returns: 635 636 """ 637 # check type and stuff 638 if not isinstance(channel, int): 639 raise TypeError("Channel must be an integer") 640 if channel < 0: 641 raise ValueError("Channel must be positive.") 642 if channel > 0xFFFFFFFF: 643 raise ValueError("Channel must be a 32 bit integer (<=0xFFFFFFFF).") 644 645 # Split the channel choice into two options. 646 channel_hex = f"{channel:08x}" 647 648 channell = int(channel_hex[4:], 16) 649 channelh = int(channel_hex[0:4], 16) 650 651 # Set the channel registers. 652 self._channel(channell) 653 self._channelh(channelh)
Sets the laser's operating channel.
MSA-01.3 lasers support a 32 bit channel value.
This defines how many spaces along the grid the laser's frequency is displaced from the first channel frequency.
Parameters
- channel: :returns:
655 def get_channel(self): 656 """gets the current channel setting 657 658 The channel defines how many spaces along the grid 659 the laser's frequency is displaced from the first channel frequency. 660 661 :returns: channel as an integer. 662 663 """ 664 # This concatenates the data bytestrings 665 response = self._channelh() + self._channel() 666 667 channel = int.from_bytes(response, "big") 668 669 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.
:returns: channel as an integer.
671 def set_fine_tuning(self, ftf): 672 """ 673 This function provides off grid tuning for the laser's frequency. 674 The adjustment applies to all channels uniformly. 675 This is typically used after the laser if locked and minor adjustments 676 are required to the frequency. 677 678 The command may be pending in the event that the laser output is 679 enabled. The pending bit is cleared once the fine tune frequency 680 has been achieved. 681 682 **???** It seems like this can be done with the laser running. 683 684 :param ftf: The fine tune frequency adjustment in GHz 685 """ 686 687 ftf = round(ftf * 1e3) 688 689 # We will leave this bare. This way the user can set and handle 690 # their own timing and check to make sure the laser has reached the 691 # desired fine tuning frequency. 692 # It WILL throw a "pending" error if the laser is on when setting. 693 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.
??? It seems like this can be done with the laser running.
Parameters
- ftf: The fine tune frequency adjustment in GHz
695 def get_fine_tuning(self): 696 """ 697 This function returns the setting for the 698 off grid tuning for the laser's frequency. 699 700 :return ftf: The fine tune frequency adjustment in GHz 701 """ 702 703 response = self._ftf() 704 ftf = int.from_bytes(response, "big", signed=True) 705 706 return ftf * 1e-3
This function returns the setting for the off grid tuning for the laser's frequency.
Returns
The fine tune frequency adjustment in GHz
708 def get_ftf_range(self): 709 """ 710 Return the maximum and minimum off grid tuning for the laser's frequency. 711 712 :return ftfr: The fine tune frequency range [-ftfr, + ftfr] in GHz 713 """ 714 response = self._ftfr() 715 716 ftfr = int.from_bytes(response, "big") 717 718 return ftfr * 1e-3
Return the maximum and minimum off grid tuning for the laser's frequency.
Returns
The fine tune frequency range [-ftfr, + ftfr] in GHz
720 def get_temps(self): 721 """ 722 Returns a list of currents in degrees Celcius. 723 In the following order: 724 725 Technology 1: [Diode Temp, Case Temp] 726 """ 727 # get response this should be a long byte string 728 response = self._temps() 729 730 data = [ 731 int.from_bytes(response[i : i + 2], "big", signed=True) / 100 732 for i in range(0, len(response), 2) 733 ] 734 735 return data
Returns a list of currents in degrees Celcius. In the following order:
Technology 1: [Diode Temp, Case Temp]
737 def get_currents(self): 738 """ 739 Returns a list of currents in mA. 740 In the following order: 741 742 Technology 1: [TEC, Diode] 743 Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] 744 Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...] 745 """ 746 # get response this should be a long byte string 747 response = self._currents() 748 749 data = [ 750 int.from_bytes(response[i : i + 2], "big", signed=True) / 10 751 for i in range(0, len(response), 2) 752 ] 753 754 return data
Returns a list of currents in mA. In the following order:
Technology 1: [TEC, Diode] Technology 2: [TED, Diode 1, Diode 2, Diode 3, Diode 4, SOA] Technology 3: [TEC, Diode 1, tbd, tbd, tbd, ...]
756 def get_last_response(self): 757 """This function gets the most recent response sent from the laser. 758 The response is parsed for errors and stuff the way any normal response 759 would be. The variable `self._response` is set to the last response again. 760 761 I dont know why you would need to do this. 762 """ 763 return self._lstresp()
This function gets the most recent response sent from the laser.
The response is parsed for errors and stuff the way any normal response
would be. The variable self._response
is set to the last response again.
I dont know why you would need to do this.
765 def get_error_fatal(self, reset=False): 766 """ 767 reads fatal error register statusf and checks each bit against the 768 fatal error table to determine the fault 769 770 :param reset: resets/clears latching errors 771 772 """ 773 response = self._statusf() 774 statusf = int.from_bytes(response, "big") 775 776 logger.debug("Current Status Fatal Error: %d", statusf) 777 778 if reset: 779 data_reset = 0x00FF 780 self._statusf(data_reset) 781 782 return FatalError(statusf)
reads fatal error register statusf and checks each bit against the fatal error table to determine the fault
Parameters
- reset: resets/clears latching errors
784 def get_error_warning(self, reset=False): 785 """ 786 reads warning error register statusw and checks each bit against the 787 warning error table to determine the fault 788 789 If the laser is off then some of the latched warnings will be set on. 790 791 :param reset: resets/clears latching errors 792 """ 793 response = self._statusw() 794 statusw = int.from_bytes(response, "big") 795 796 logger.debug("Current Status Warning Error: %d", statusw) 797 798 if reset: 799 data_reset = 0x00FF 800 self._statusw(data_reset) 801 802 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.
Parameters
- reset: resets/clears latching errors
804 def get_fatal_power_thresh(self): 805 """ 806 reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted 807 808 """ 809 response = self._fpowth() 810 pow_fatal = int.from_bytes(response, "big") / 100 811 # correcting for proper order of magnitude 812 return pow_fatal
reads maximum plus/minus power deviation in dB for which the fatal alarm is asserted
814 def get_warning_power_thresh(self): 815 """ 816 reads maximum plus/minus power deviation in dB for which the warning alarm is asserted 817 818 """ 819 response = self._wpowth() 820 pow_warn = int.from_bytes(response, "big") / 100 821 # correcting for proper order of magnitude 822 return pow_warn
reads maximum plus/minus power deviation in dB for which the warning alarm is asserted
824 def get_fatal_freq_thresh(self): 825 """ 826 reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted 827 828 """ 829 response = self._ffreqth() 830 freq_fatal = int.from_bytes(response, "big") / 10 831 # correcting for proper order of magnitude 832 response2 = self._ffreqth2() 833 freq_fatal2 = int.from_bytes(response2, "big") / 100 834 # get frequency deviation in MHz and add to GHz value 835 return freq_fatal + freq_fatal2
reads maximum plus/minus frequency deviation in GHz for which the fatal alarm is asserted
837 def get_warning_freq_thresh(self): 838 """ 839 reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted 840 841 """ 842 response = self._wfreqth() 843 freq_warn = int.from_bytes(response, "big") / 10 844 # correcting for proper order of magnitude 845 response2 = self._wfreqth2() 846 freq_warn2 = int.from_bytes(response2, "big") / 100 847 # get frequency deviation in MHz and add to GHz value 848 return freq_warn + freq_warn2
reads maximum plus/minus frequency deviation in GHz for which the warning alarm is asserted
850 def get_fatal_therm_thresh(self): 851 """ 852 reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted 853 854 """ 855 response = self._fthermth() 856 therm_fatal = int.from_bytes(response, "big") / 100 857 # correcting for proper order of magnitude 858 return therm_fatal
reads maximum plus/minus thermal deviation in degree celcius for which the fatal alarm is asserted
860 def get_warning_therm_thresh(self): 861 """ 862 reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted 863 """ 864 response = self._wthermth() 865 therm_thresh = int.from_bytes(response, "big") / 100 866 # correcting for proper order of magnitude 867 return therm_thresh
reads maximum plus/minus thermal deviation in degree celcius for which the warning alarm is asserted
869 def get_srq_trigger(self): 870 """ 871 Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers 872 873 """ 874 response = self._srqt() 875 status = int.from_bytes(response, "big") 876 877 logger.debug("SRQT Status: %d", status) 878 879 return SQRTrigger(status)
Utilizes SRQT register to identify why SRQ was asserted in StatusF and StatusW registers
881 def get_fatal_trigger(self): 882 """ 883 Utilizes FatalT register to identify which fatal conditon was asserted in StatusF and StatusW registers 884 885 """ 886 response = self._fatalt() 887 status = int.from_bytes(response, "big") 888 889 logger.debug("FatalT Status: %d", status) 890 891 return FatalTrigger(status)
Utilizes FatalT register to identify which fatal conditon was asserted in StatusF and StatusW registers
893 def get_alm_trigger(self): 894 """ 895 Utilizes ALMT register to identify why ALM was asserted in StatusF and StatusW registers 896 897 """ 898 response = self._almt() 899 status = int.from_bytes(response, "big") 900 901 logger.debug("AlarmT Status: %d", status) 902 903 return AlarmTrigger(status)
Utilizes ALMT register to identify why ALM was asserted in StatusF and StatusW registers