diff --git a/Dockerfile b/Dockerfile index b07b76a..5a68e2e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,13 +12,27 @@ RUN apt-get -y install udev RUN apt-get -y install ffmpeg RUN apt-get -y install libusb-1.0-0 -# -WORKDIR /platform -COPY . /platform/ + # RUN python3.11 -m pip install coverage -RUN python3.11 -m pip install -r /platform/requirements.txt +RUN python3.11 -m pip install colorama==0.4.6 +RUN python3.11 -m pip install paho-mqtt==1.6.1 +RUN python3.11 -m pip install pyftdi==0.55.0 +RUN python3.11 -m pip install pymodbus==3.3.2 +RUN python3.11 -m pip install pyserial==3.5 +RUN python3.11 -m pip install pyudev==0.24.0 +RUN python3.11 -m pip install pyusb==1.2.1 +RUN python3.11 -m pip install PyHamcrest==2.0.4 +RUN python3.11 -m pip install aiofiles==23.2.1 +RUN python3.11 -m pip install aiomonitor==0.6.0 +RUN python3.11 -m pip install aioserial==1.3.1 +RUN python3.11 -m pip install ThorlabsPM100==1.2.2 +RUN python3.11 -m pip install python-usbtmc==0.8 + +# +WORKDIR /platform +COPY . /platform/ # Create the mirror directory RUN mkdir -p /etc/panduza diff --git a/panduza_platform/connectors/__init__.py b/panduza_platform/connectors/__init__.py index e69de29..03093b3 100644 --- a/panduza_platform/connectors/__init__.py +++ b/panduza_platform/connectors/__init__.py @@ -0,0 +1,4 @@ + + +# from .thorlabs_pm100.connector import ConnectorThorlabsPM100 + diff --git a/panduza_platform/connectors/serial_low_usb.py b/panduza_platform/connectors/serial_low_usb.py new file mode 100644 index 0000000..d89a89c --- /dev/null +++ b/panduza_platform/connectors/serial_low_usb.py @@ -0,0 +1,182 @@ +import time +import asyncio +import logging +import aioserial + +import usb + + +from .serial_base import ConnectorSerialBase +from panduza_platform.log.driver import driver_logger + +from .udev_tty import SerialPortFromUsbSetting + +class ConnectorSerialLowUsb(ConnectorSerialBase): + """ + """ + + # Hold instances mutex + __MUTEX = asyncio.Lock() + + # Contains instances + __INSTANCES = {} + + # Local logs + log = driver_logger("ConnectorSerialLowUsb") + + ########################################################################### + ########################################################################### + + @staticmethod + async def Get(**kwargs): + """Singleton main getter + + :Keyword Arguments: + * *usb_vendor* (``str``) -- + ID_VENDOR_ID + * *usb_model* (``str``) -- + ID_MODEL_ID + + """ + # Log + ConnectorSerialLowUsb.log.debug(f"Get connector for {kwargs}") + + async with ConnectorSerialLowUsb.__MUTEX: + + # Log + ConnectorSerialLowUsb.log.debug(f"Lock acquired !") + + if "usb_vendor" in kwargs: + usb_vendor = kwargs["usb_vendor"] + if "usb_model" in kwargs: + usb_model = kwargs["usb_model"] + + usb_serial_short="" + if "usb_serial_short" in kwargs: + usb_serial_short = kwargs["usb_serial_short"] + + instance_name = str(f"{usb_vendor}_{usb_model}_{usb_serial_short}") + print(instance_name) + + # Create the new connector + if not (instance_name in ConnectorSerialLowUsb.__INSTANCES): + ConnectorSerialLowUsb.__INSTANCES[instance_name] = None + try: + new_instance = ConnectorSerialLowUsb(**kwargs) + # await new_instance.connect() + + ConnectorSerialLowUsb.__INSTANCES[instance_name] = new_instance + ConnectorSerialLowUsb.log.info("connector created") + except Exception as e: + ConnectorSerialLowUsb.__INSTANCES.pop(instance_name) + raise Exception('Error during initialization').with_traceback(e.__traceback__) + else: + ConnectorSerialLowUsb.log.info("connector already created, use existing instance") + + # Return the previously created + return ConnectorSerialLowUsb.__INSTANCES[instance_name] + + ########################################################################### + ########################################################################### + + def __init__(self, **kwargs): + """Constructor + """ + + # Init local mutex + self._mutex = asyncio.Lock() + + # Init command mutex + self._cmd_mutex = asyncio.Lock() + + + usb_vendor = kwargs.get("usb_vendor") + usb_model = kwargs.get("usb_model") + usb_serial_short = kwargs.get("usb_serial_short") + + # ConnectorSerialLowUsb.log.error("pooook") + # print(usb_vendor, usb_model, usb_serial_short) + + dev = None + devs = usb.core.find(idVendor=usb_vendor, idProduct=usb_model, find_all=True) + for dev in devs: + if dev.serial_number == usb_serial_short: + dev = dev + + + if dev is None: + raise ValueError('Device not found') + # print(dev) + dev.reset() + dev.set_configuration() + + cfg = dev.get_active_configuration() + intf = cfg[(0,0)] + # print(intf) + + # Take first output/input endpoint avaiable + + self.ep_out = usb.util.find_descriptor( + intf, + # match the first OUT endpoint + custom_match = \ + lambda e: \ + usb.util.endpoint_direction(e.bEndpointAddress) == \ + usb.util.ENDPOINT_OUT) + + self.ep_in = usb.util.find_descriptor( + intf, + # match the first OUT endpoint + custom_match = \ + lambda e: \ + usb.util.endpoint_direction(e.bEndpointAddress) == \ + usb.util.ENDPOINT_IN) + + + + # ============================================================================= + # OVERRIDE FROM SERIAL_BASE + + # --- + + async def read(self, n_bytes = None): + """Read from UART using asynchronous mode + """ + async with self._mutex: + data_array_b = self.ep_in.read(self.ep_in.wMaxPacketSize) + bytes_object = data_array_b.tobytes() + return bytes_object + + # --- + + async def write(self, message, time_lock_s=None): + """write to UART using asynchronous mode + """ + + async with self._mutex: + try: + # write the data + cmd = message.encode() + packet_to_send = cmd + b'\x00' * (self.ep_out.wMaxPacketSize - len(cmd)) + self.ep_out.write(packet_to_send) + + except Exception as e: + raise Exception('Error during writing to uart').with_traceback(e.__traceback__) + + + async def write_and_read(self, message, time_lock_s=None, n_bytes = None): + """Read from UART using asynchronous mode + """ + async with self._mutex: + try: + # write the data + cmd = message.encode() + packet_to_send = cmd + b'\x00' * (self.ep_out.wMaxPacketSize - len(cmd)) + self.ep_out.write(packet_to_send) + + # Read + data_array_b = self.ep_in.read(self.ep_in.wMaxPacketSize) + bytes_object = data_array_b.tobytes() + return bytes_object + except Exception as e: + raise Exception('Error during writing to uart').with_traceback(e.__traceback__) diff --git a/panduza_platform/connectors/serial_tty.py b/panduza_platform/connectors/serial_tty.py index 2a4ba29..0069ece 100644 --- a/panduza_platform/connectors/serial_tty.py +++ b/panduza_platform/connectors/serial_tty.py @@ -2,10 +2,9 @@ import asyncio import logging import aioserial -# import serial_asyncio from .serial_base import ConnectorSerialBase -from log.driver import driver_logger +from panduza_platform.log.driver import driver_logger from .udev_tty import SerialPortFromUsbSetting @@ -112,6 +111,10 @@ def __init__(self, **kwargs): self.serial_port_name = kwargs.get("serial_port_name", "/dev/ttyUSB0") self.serial_baudrate = kwargs.get("serial_baudrate", 9600) + + + print(self.serial_port_name) + print(self.serial_baudrate) # --- @@ -120,24 +123,14 @@ async def connect(self): """ self.aioserial_instance = aioserial.AioSerial(port=self.serial_port_name, baudrate=self.serial_baudrate) - - - # ============================================================================= # PRIVATE HELPER # --- - async def _read(self, n_bytes = None): - """ + async def __write(self, message, time_lock_s=None): """ - return await asyncio.wait_for(self.aioserial_instance.read_async(n_bytes), timeout=1.0) - - # --- - - async def _write(self, message, time_lock_s=None): """ - """ try: # Manage time lock by waiting for the remaining duration if self._time_lock_s: @@ -162,24 +155,9 @@ async def _write(self, message, time_lock_s=None): except Exception as e: raise Exception('Error during writing to uart').with_traceback(e.__traceback__) - # --- - - async def __accumulate_date(self, data): - while True: - data.append(await self.aioserial_instance.read_async(1)) - - # --- - # ============================================================================= # OVERRIDE FROM SERIAL_BASE - - # async def beg_cmd(self): - # await self._cmd_mutex.acquire() - - # async def end_cmd(self): - # self._cmd_mutex.release() - # --- async def read(self, n_bytes = None): @@ -190,27 +168,10 @@ async def read(self, n_bytes = None): # --- - async def read_during(self, duration_s = 0.5): - """ - """ - async with self._mutex: - data = [] - try: - await asyncio.wait_for(self.__accumulate_date(data), timeout=duration_s) - except asyncio.TimeoutError as e: - pass - # raise Exception('Error during reading uart').with_traceback(e.__traceback__) - - # Convert bytearray into bytes - return b''.join(data) - - # --- - async def write(self, message, time_lock_s=None): """write to UART using asynchronous mode """ async with self._mutex: - try: # Manage time lock by waiting for the remaining duration if self._time_lock_s: @@ -235,26 +196,82 @@ async def write(self, message, time_lock_s=None): except Exception as e: raise Exception('Error during writing to uart').with_traceback(e.__traceback__) + # --- + + # ============================================================================= + # **** UNTIL **** + # Read until find the expected character + # LF = \n + + # --- - # async def write_and_read(self, message, time_lock_s=0, n_bytes_to_read=10): - # async with self._mutex: - # await self._write(message, time_lock_s) - # await asyncio.sleep(time_lock_s) - # return await self._read(n_bytes_to_read) + async def __read_until(self, expected: bytes = aioserial.LF): + """Read until find the expected character (internal helper) + """ + return await self.aioserial_instance.read_until_async(expected) # --- - async def write_and_read_until(self, message, time_lock_s=0, read_duration_s=0.5): + async def read_until(self, expected: bytes = aioserial.LF): + """Read data for specified duration + """ async with self._mutex: - await self._write(message, time_lock_s) - data = [] - try: - await asyncio.wait_for(self.__accumulate_date(data), timeout=read_duration_s) - except asyncio.TimeoutError as e: - pass - return b''.join(data) + return await self.__read_until(expected) + + # --- + + async def write_and_read_until(self, message, time_lock_s=0, expected: bytes = aioserial.LF): + """Write command then read data for specified duration + """ + async with self._mutex: + await self.__write(message, time_lock_s) + return await self.__read_until(expected) + + # --- + + # ============================================================================= + # **** DURING **** + # Read during a certain amount of time then return data + # Those methods are not recommended, but can be usefull when protocol is badly implemented or uncertain + + # --- + + async def __accumulate_data(self, data): + """Accumulate byte after byte in a buffer + """ + while True: + data.append(await self.aioserial_instance.read_async(1)) + + # --- + + async def __read_during(self, read_duration_s = 0.5): + """Read data for specified duration (internal helper) + """ + data = [] + try: + await asyncio.wait_for(self.__accumulate_data(data), timeout=read_duration_s) + except asyncio.TimeoutError as e: + # Ignore timeout error because it is the purpose of this mode + pass + + # Convert bytearray into bytes + return b''.join(data) # --- + async def read_during(self, read_duration_s = 0.5): + """Read data for specified duration + """ + async with self._mutex: + return await self.__read_during(read_duration_s) + + # --- + async def write_and_read_during(self, message, time_lock_s=0, read_duration_s=0.5): + """Write command then read data for specified duration + """ + async with self._mutex: + await self.__write(message, time_lock_s) + return await self.__read_during(read_duration_s) + # --- diff --git a/panduza_platform/connectors/tests/README.md b/panduza_platform/connectors/tests/README.md new file mode 100644 index 0000000..ab93690 --- /dev/null +++ b/panduza_platform/connectors/tests/README.md @@ -0,0 +1,4 @@ +# Connectors Tests + +Here is the zone to create small codes to tests connectors + diff --git a/panduza_platform/connectors/tests/serial_tty/README.md b/panduza_platform/connectors/tests/serial_tty/README.md new file mode 100644 index 0000000..181c03a --- /dev/null +++ b/panduza_platform/connectors/tests/serial_tty/README.md @@ -0,0 +1,17 @@ +# Test for serial tty + +Tests performed with an arduino in echo mode + +```c +void setup() { + Serial.begin(9600); // Set the baud rate of the serial communication +} + +void loop() { + while (Serial.available()) { // Check if there is any data available on the serial port + char incomingByte = Serial.read(); // Read the incoming byte + Serial.write(incomingByte); // Send the incoming byte back out the serial port + } +} +``` + diff --git a/panduza_platform/connectors/tests/serial_tty/test.py b/panduza_platform/connectors/tests/serial_tty/test.py new file mode 100644 index 0000000..953c22f --- /dev/null +++ b/panduza_platform/connectors/tests/serial_tty/test.py @@ -0,0 +1,39 @@ +import asyncio +from panduza_platform.connectors.serial_tty import ConnectorSerialTty + +# +SERIALPORT="/dev/ttyACM0" +BAUDRATE=115200 + + + +async def main(): + c = await ConnectorSerialTty.Get( + serial_port_name=SERIALPORT, + serial_baudrate=BAUDRATE + ) + + test_string = "pok\r\n" + + print("write", test_string) + result = await c.write_and_read_during(test_string) + print("read", result) + + print("write", test_string) + await c.write(test_string) + result = await c.read_during() + print("read", result) + + print("write", test_string) + result = await c.write_and_read_until(test_string) + print("read", result) + + print("write", test_string) + await c.write(test_string) + result = await c.read_until() + print("read", result) + +# Run test +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) diff --git a/panduza_platform/connectors/test/test_boundary_scan_ftdi.py b/panduza_platform/connectors/tests/test_boundary_scan_ftdi.py similarity index 100% rename from panduza_platform/connectors/test/test_boundary_scan_ftdi.py rename to panduza_platform/connectors/tests/test_boundary_scan_ftdi.py diff --git a/panduza_platform/connectors/thorlabs_pm100/__init__.py b/panduza_platform/connectors/thorlabs_pm100/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/connectors/thorlabs_pm100/connector.py b/panduza_platform/connectors/thorlabs_pm100/connector.py new file mode 100644 index 0000000..d824b32 --- /dev/null +++ b/panduza_platform/connectors/thorlabs_pm100/connector.py @@ -0,0 +1,155 @@ +import time +import asyncio +import logging +import aioserial +import os + +import concurrent.futures + +import pyudev +from log.driver import driver_logger + +# from .udev_tty import SerialPortFromUsbSetting + +import usbtmc +from connectors.utils.usbtmc import FindUsbtmcDev + +from connectors.udev_tty import HuntUsbDevs + +from ThorlabsPM100 import ThorlabsPM100, USBTMC + +class ConnectorThorlabsPM100(): + """ + """ + + # Hold instances mutex + __MUTEX = asyncio.Lock() + + # Contains instances + __INSTANCES = {} + + # Local logs + log = driver_logger("ConnectorThorlabsPM100") + + ########################################################################### + ########################################################################### + + @staticmethod + async def Get(**kwargs): + """Singleton main getter + + :Keyword Arguments: + + * *usb_vendor* (``str``) -- + ID_VENDOR_ID + * *usb_model* (``str``) -- + ID_MODEL_ID + * *usb_serial_short* (``str``) -- + ID_SERIAL_SHORT + """ + # Log + ConnectorThorlabsPM100.log.info(f"Get connector for {kwargs}") + + async with ConnectorThorlabsPM100.__MUTEX: + + # Log + ConnectorThorlabsPM100.log.info(f"Lock acquired !") + + # Get the serial port name + usbtmc_key = f"{kwargs['usb_vendor']}_{kwargs['usb_model']}_{kwargs['usb_serial_short']}" + ConnectorThorlabsPM100.log.debug(f"Key {usbtmc_key}") + + # Create the new connector + if not (usbtmc_key in ConnectorThorlabsPM100.__INSTANCES): + ConnectorThorlabsPM100.__INSTANCES[usbtmc_key] = None + try: + new_instance = ConnectorThorlabsPM100(**kwargs) + + ConnectorThorlabsPM100.__INSTANCES[usbtmc_key] = new_instance + ConnectorThorlabsPM100.log.info("connector created") + except Exception as e: + ConnectorThorlabsPM100.__INSTANCES.pop(usbtmc_key) + raise Exception('Error during initialization').with_traceback(e.__traceback__) + else: + ConnectorThorlabsPM100.log.info("connector already created, use existing instance") + + # Return the previously created + return ConnectorThorlabsPM100.__INSTANCES[usbtmc_key] + + ########################################################################### + ########################################################################### + + def __init__(self, **kwargs): + """Constructor + """ + + # Init local mutex + self._mutex = asyncio.Lock() + + # Init command mutex + self._cmd_mutex = asyncio.Lock() + + + # # self.log.info(kwargs) + # device_ref = FindUsbtmcDev(usb_vendor= kwargs["usb_vendor"], + # usb_model= kwargs["usb_model"], + # usb_serial_short=kwargs['usb_serial_short']) + + # print(device_ref) + # print(type(device_ref)) + + print("================") + + # devsss = HuntUsbDevs(usb_vendor= kwargs["usb_vendor"], usb_model= kwargs["usb_model"]) + # print(devsss) + + usb_vendor = kwargs["usb_vendor"] + if(type(usb_vendor) == int): + usb_vendor = str(hex(usb_vendor))[2:] + + usb_model = kwargs["usb_model"] + if(type(usb_model) == int): + usb_model = str(hex(usb_model))[2:] + + usb_serial_short = kwargs['usb_serial_short'] + usbtmc_devname=f"/dev/usbtmc-{usb_vendor}-{usb_model}-{usb_serial_short}" + real_path = os.path.realpath(usbtmc_devname) + print(real_path) + + print("================") + + + inst = USBTMC(device=real_path) + self.pm_instance = ThorlabsPM100(inst=inst) + + + + + # --- + + def read(self): + """ + """ + return self.pm_instance.read + + # --- + + async def run_async_function(self,function,*args): + async with self._mutex: + with concurrent.futures.ThreadPoolExecutor() as executor: + # Submit the function to the executor + future = executor.submit(function, *args) + + # Wait for the future to complete + while not future.done(): + await asyncio.sleep(0.1) + #print(f"Waiting for the thread to complete...") + + # Retrieve the result from the future + result = future.result() + #print("Result:", result) + + return result + + + diff --git a/panduza_platform/connectors/udev_tty.py b/panduza_platform/connectors/udev_tty.py index 5772aa7..5b7f9ce 100644 --- a/panduza_platform/connectors/udev_tty.py +++ b/panduza_platform/connectors/udev_tty.py @@ -88,6 +88,7 @@ def HuntUsbDevs(usb_vendor, usb_model=None, subsystem=None): for device in udev_context.list_devices(): properties = dict(device.properties) + if usb_vendor is not None and properties.get("ID_VENDOR_ID") != usb_vendor: continue if usb_model is not None and properties.get("ID_MODEL_ID") != usb_model : @@ -97,7 +98,7 @@ def HuntUsbDevs(usb_vendor, usb_model=None, subsystem=None): results.append(properties) # For debugging purpose - # logger.debug(f"{properties}") - + # print(f"{properties}") + return results diff --git a/panduza_platform/connectors/usbtmc.py b/panduza_platform/connectors/usbtmc.py new file mode 100644 index 0000000..fb5b520 --- /dev/null +++ b/panduza_platform/connectors/usbtmc.py @@ -0,0 +1,221 @@ +import time +import asyncio +import logging +import aioserial + +import concurrent.futures + + +from log.driver import driver_logger + +# from .udev_tty import SerialPortFromUsbSetting + +import usbtmc +from .utils.usbtmc import FindUsbtmcDev + +class ConnectorUsbtmc(): + """ + """ + + # Hold instances mutex + __MUTEX = asyncio.Lock() + + # Contains instances + __INSTANCES = {} + + # Local logs + log = driver_logger("ConnectorUsbtmc") + + ########################################################################### + ########################################################################### + + @staticmethod + async def Get(**kwargs): + """Singleton main getter + + :Keyword Arguments: + + * *usb_vendor* (``str``) -- + ID_VENDOR_ID + * *usb_model* (``str``) -- + ID_MODEL_ID + * *usb_serial_short* (``str``) -- + ID_SERIAL_SHORT + """ + # Log + ConnectorUsbtmc.log.info(f"Get connector for {kwargs}") + + async with ConnectorUsbtmc.__MUTEX: + + # Log + ConnectorUsbtmc.log.info(f"Lock acquired !") + + # Get the serial port name + usbtmc_key = f"{kwargs['usb_vendor']}_{kwargs['usb_model']}_{kwargs['usb_serial_short']}" + ConnectorUsbtmc.log.debug(f"Key {usbtmc_key}") + + # Create the new connector + if not (usbtmc_key in ConnectorUsbtmc.__INSTANCES): + ConnectorUsbtmc.__INSTANCES[usbtmc_key] = None + try: + new_instance = ConnectorUsbtmc(**kwargs) + + ConnectorUsbtmc.__INSTANCES[usbtmc_key] = new_instance + ConnectorUsbtmc.log.info("connector created") + except Exception as e: + ConnectorUsbtmc.__INSTANCES.pop(usbtmc_key) + raise Exception('Error during initialization').with_traceback(e.__traceback__) + else: + ConnectorUsbtmc.log.info("connector already created, use existing instance") + + # Return the previously created + return ConnectorUsbtmc.__INSTANCES[usbtmc_key] + + ########################################################################### + ########################################################################### + + def __init__(self, **kwargs): + """Constructor + """ + + # Init local mutex + self._mutex = asyncio.Lock() + + # Init command mutex + self._cmd_mutex = asyncio.Lock() + + + self.log.info(kwargs) + device_ref = FindUsbtmcDev(usb_vendor= kwargs["usb_vendor"], + usb_model= kwargs["usb_model"], + usb_serial_short=kwargs['usb_serial_short']) + + self.usbtmc_instance = usbtmc.Instrument(device_ref) + self.usbtmc_instance.open() + + # --- + + # async def connect(self): + # """Start the serial connection + # """ + # self.aioserial_instance = aioserial.AioSerial(port=self.serial_port_name, baudrate=self.serial_baudrate) + + # # ============================================================================= + # # OVERRIDE FROM SERIAL_BASE + + + # # async def beg_cmd(self): + # # await self._cmd_mutex.acquire() + + # # async def end_cmd(self): + # # self._cmd_mutex.release() + + + def close(self): + self.usbtmc_instance.close() + + # --- + + async def ask(self, command): + """ + """ + return await asyncio.wait_for( + self.run_async_function(self.usbtmc_instance.ask, command), + timeout=2.0) + + # --- + + def ask_e(self, command): + """ + """ + return self.usbtmc_instance.ask(command) + + # --- + + async def run_async_function(self,function,*args): + async with self._mutex: + with concurrent.futures.ThreadPoolExecutor() as executor: + # Submit the function to the executor + future = executor.submit(function, *args) + + # Wait for the future to complete + while not future.done(): + await asyncio.sleep(0.1) + #print(f"Waiting for the thread to complete...") + + # Retrieve the result from the future + result = future.result() + #print("Result:", result) + + return result + + # --- + + # async def read_during(self, duration_s = 0.5): + # """ + # """ + # async with self._mutex: + # data = [] + # try: + # await asyncio.wait_for(self.__accumulate_date(data), timeout=duration_s) + # except asyncio.TimeoutError as e: + # pass + # # raise Exception('Error during reading uart').with_traceback(e.__traceback__) + + # # Convert bytearray into bytes + # return b''.join(data) + + # # --- + + # async def write(self, message, time_lock_s=None): + # """write to UART using asynchronous mode + # """ + # async with self._mutex: + + # try: + # # Manage time lock by waiting for the remaining duration + # if self._time_lock_s: + # elapsed = time.time() - self._time_lock_s["t0"] + # if elapsed < self._time_lock_s["duration"]: + + # wait_time = self._time_lock_s["duration"] - elapsed + # self.log.info(f"wait lock {wait_time}") + # await asyncio.sleep(wait_time) + # self._time_lock_s = None + + # # Start sending the message + # await self.aioserial_instance.write_async(message.encode()) + + # # Set the time lock if requested by the user + # if time_lock_s != None: + # self._time_lock_s = { + # "duration": time_lock_s, + # "t0": time.time() + # } + + # except Exception as e: + # raise Exception('Error during writing to uart').with_traceback(e.__traceback__) + + + # # async def write_and_read(self, message, time_lock_s=0, n_bytes_to_read=10): + # # async with self._mutex: + # # await self._write(message, time_lock_s) + # # await asyncio.sleep(time_lock_s) + # # return await self._read(n_bytes_to_read) + + # # --- + + # async def write_and_read_during(self, message, time_lock_s=0, read_duration_s=0.5): + # async with self._mutex: + # await self._write(message, time_lock_s) + # data = [] + # try: + # await asyncio.wait_for(self.__accumulate_date(data), timeout=read_duration_s) + # except asyncio.TimeoutError as e: + # pass + # return b''.join(data) + + # # --- + + + diff --git a/panduza_platform/connectors/utils/__init__.py b/panduza_platform/connectors/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/connectors/utils/usbtmc.py b/panduza_platform/connectors/utils/usbtmc.py new file mode 100644 index 0000000..4b25365 --- /dev/null +++ b/panduza_platform/connectors/utils/usbtmc.py @@ -0,0 +1,42 @@ +import usbtmc +import logging + +def HuntUsbtmcDevs(usb_vendor, usb_model=None): + """Return a list with devices fond with those criterias + """ + results = [] + + # Explore usbtmc devices + usbtmc_devices = usbtmc.list_devices() + + # + for device in usbtmc_devices: + + if usb_vendor is not None and device.idVendor != usb_vendor: + continue + if usb_model is not None and device.idProduct != usb_model : + continue + + results.append(device) + # For debugging purpose + # logger.debug(f"{properties}") + + return results + + +def FindUsbtmcDev(usb_vendor, usb_model=None, usb_serial_short=None): + + availables = HuntUsbtmcDevs(usb_vendor, usb_model) + for dev in availables: + # Check serial if required + if usb_serial_short != None: + if dev.serial_number == usb_serial_short: + return dev + + # Else return the first in the list + else: + return dev + + return None + + diff --git a/panduza_platform/core/platform.py b/panduza_platform/core/platform.py index 05bbee1..4bb8561 100644 --- a/panduza_platform/core/platform.py +++ b/panduza_platform/core/platform.py @@ -250,6 +250,9 @@ async def load_config_content_task(self, new_dtree): # Internal store of the platform config self.dtree = new_dtree + with open('/etc/panduza/tree.json', 'w') as f: + json.dump(self.dtree, f) + # --- async def handle_worker_panic(self, name, status): diff --git a/panduza_platform/core/platform_device_factory.py b/panduza_platform/core/platform_device_factory.py index 7407ea1..76d2dd9 100644 --- a/panduza_platform/core/platform_device_factory.py +++ b/panduza_platform/core/platform_device_factory.py @@ -77,6 +77,11 @@ def register_device(self, device_builder): # --- + def get_number_of_drivers(self): + return len(self.__device_store.keys()) + + # --- + def get_devices_store(self): return self.__device_store diff --git a/panduza_platform/devices/__init__.py b/panduza_platform/devices/__init__.py index 2576ab1..aecbd11 100644 --- a/panduza_platform/devices/__init__.py +++ b/panduza_platform/devices/__init__.py @@ -1,12 +1,18 @@ +from .cobolt import PZA_DEVICES_LIST as COBOLT_DEVICES +from .ftdi import PZA_DEVICES_LIST as FTDI_DEVICES from .hanmatek import PZA_DEVICES_LIST as HANMATEK_DEVICES +from .korad import PZA_DEVICES_LIST as KORAD_DEVICES +from .oxxius import PZA_DEVICES_LIST as OXXIUS_DEVICES from .panduza import PZA_DEVICES_LIST as PANDUZA_DEVICES -from .ftdi import PZA_DEVICES_LIST as FTDI_DEVICES from .tenma import PZA_DEVICES_LIST as TENMA_DEVICES -from .korad import PZA_DEVICES_LIST as KORAD_DEVICES +from .thorlabs import PZA_DEVICES_LIST as THORLABS_DEVICES PZA_DEVICES_LIST = [] \ + + COBOLT_DEVICES \ + + FTDI_DEVICES \ + HANMATEK_DEVICES \ + + KORAD_DEVICES \ + + OXXIUS_DEVICES \ + PANDUZA_DEVICES \ - + FTDI_DEVICES \ + TENMA_DEVICES \ - + KORAD_DEVICES + + THORLABS_DEVICES diff --git a/panduza_platform/devices/cobolt/__init__.py b/panduza_platform/devices/cobolt/__init__.py new file mode 100644 index 0000000..ea2725f --- /dev/null +++ b/panduza_platform/devices/cobolt/__init__.py @@ -0,0 +1,5 @@ +from .s0501.dev_0501 import DeviceCobolt0501 + +PZA_DEVICES_LIST= [ + DeviceCobolt0501 +] diff --git a/panduza_platform/devices/cobolt/s0501/__init__.py b/panduza_platform/devices/cobolt/s0501/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/devices/cobolt/s0501/dev_0501.py b/panduza_platform/devices/cobolt/s0501/dev_0501.py new file mode 100644 index 0000000..0f344ee --- /dev/null +++ b/panduza_platform/devices/cobolt/s0501/dev_0501.py @@ -0,0 +1,94 @@ +import asyncio +from core.platform_device import PlatformDevice + + +from pathlib import Path +# from ThorlabsPM100 import ThorlabsPM100, USBTMC +import os + +import usb +# import usbtmc + +# from connectors.utils.usbtmc import HuntUsbtmcDevs +from connectors.udev_tty import HuntUsbDevs + +from .itf_cobolt_0501_blc import InterfaceCobolt0501Blc + +class DeviceCobolt0501(PlatformDevice): + """Power Supply From Korad + """ + + def _PZA_DEV_config(self): + """ + """ + return { + "family": "laser", + "model": "0501", + "manufacturer": "Cobolt", + "settings_props": [ + { + 'name': 'usb_serial_short', + 'type': 'string', + 'default': '' + } + ] + } + + # --- + + async def _PZA_DEV_hunt(self): + """ + """ + bag = [] + + try: + # 25dc:0006 Cobolt AB Cobolt Laser Driver 05-71 + matches = HuntUsbDevs('25dc', '0006', 'tty') + for match in matches: + print('------', match) + devname = match.get('DEVNAME', None) + if devname: + self.log.info(devname) + + ma = self._PZA_DEV_config()["manufacturer"] + mo = self._PZA_DEV_config()["model"] + ref = f"{ma}.{mo}" + bag.append({ + "ref": ref, + "settings": { + "usb_serial_short": match.get('ID_SERIAL_SHORT', None) + } + }) + + except Exception as e: + print("error") + print(e) + + return bag + + # --- + + async def _PZA_DEV_mount_interfaces(self): + """ + """ + + settings = self.get_settings() + print("in blc !!!") + print(settings) + + const_settings = { + "usb_vendor": '25dc', + "usb_model": '0006', + "serial_baudrate": 115200 + } + settings.update(const_settings) + + self.log.info(f"=> {settings}") + + self.mount_interface( + InterfaceCobolt0501Blc(name=f"blc", settings=settings) + ) + + + + diff --git a/panduza_platform/devices/cobolt/s0501/itf_cobolt_0501_blc.py b/panduza_platform/devices/cobolt/s0501/itf_cobolt_0501_blc.py new file mode 100644 index 0000000..31331d4 --- /dev/null +++ b/panduza_platform/devices/cobolt/s0501/itf_cobolt_0501_blc.py @@ -0,0 +1,240 @@ +import asyncio +from meta_drivers.blc import MetaDriverBlc +from connectors.serial_tty import ConnectorSerialTty + +COMMAND_TIME_LOCK=1 + +### Commands Definitions + +def cmd(cmd): + """Append the correct command termination to the command + """ + termination = "\r" # only one "\r" is OK, do not use "\n" + return (cmd + termination) + +### Interface Definition + +class InterfaceCobolt0501Blc(MetaDriverBlc): + """ + + https://github.com/cobolt-lasers/pycobolt + + """ + + # --- + + def __init__(self, name=None, settings={}) -> None: + """Constructor + """ + self.settings = settings + super().__init__(name=name) + + + # ============================================================================= + # FROM MetaDriverBlc + + # --- + + # def _PZA_DRV_BLC_config(self): + # """ + # """ + # return { + # "name": "panduza.fake.bpc", + # "description": "Virtual BPC" + # } + + # --- + + async def _PZA_DRV_loop_init(self): + """Init function + Reset fake parameters + """ + + self.serial_connector = await ConnectorSerialTty.Get(**self.settings) + + print("clear", await self.serial_connector.write_and_read_until(cmd(""), expected=b"\n")) + print("clear", await self.serial_connector.write_and_read_until(cmd(""), expected=b"\n")) + print("clear", await self.serial_connector.write_and_read_until(cmd(""), expected=b"\n")) + print("l?", await self.serial_connector.write_and_read_until(cmd("l?"), expected=b"\n")) + print("l0", await self.serial_connector.write_and_read_until(cmd("l0"), expected=b"\n")) + + print("slc", ) + # print("slc?", await self.serial_connector.write_and_read_until(cmd("slc?"), expected=b"\n")) + print("i?", await self.serial_connector.write_and_read_until(cmd("i?"), expected=b"\n")) + + + + + # command = "sn?" + # termination = "\n\r" + # cmd = (command + termination) + # print(cmd.encode()) + # # print("pok") + # idn = await self.serial_connector.write_and_read_until(cmd, expected=b"\n") + # print(f"ddddddd {idn}") + # idn = await self.serial_connector.write_and_read_until(cmd, expected=b"\n") + # print(f"ddddddd {idn}") + # idn = await self.serial_connector.write_and_read_until(cmd, expected=b"\n") + # print(f"ddddddd {idn}") + # leds = await self.serial_connector.write_and_read_during("leds?\r\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=1) + # print(f"leds {leds}") + + + await self.__debug_print_all_registers() + + self.__fakes = { + "enable": { + "value": False + }, + "power": { + "value": 0, + "min": -1000, + "max": 1000, + "decimals": 2 + }, + "current": { + "value": 0, + "min": 0, + "max": 50, + "decimals": 3 + }, + "misc": { + "model": "DEATHSTAR (Panduza Fake Laser Control)" + } + } + + # Call meta class BPC ini + await super()._PZA_DRV_loop_init() + + ########################################################################### + + # --- + + async def _PZA_DRV_BLC_read_mode_value(self): + """Must get the mode value on the BPC and return it + """ + mode_b = await self.serial_connector.write_and_read_until(cmd("gam?"), expected=b"\n") + print(f"mode {mode_b}") + mode_i = int(mode_b.decode('utf-8').rstrip()) + print(f"mode {mode_i}") + + if mode_i == 0: + return "constant_current" + if mode_i == 1: + return "constant_power" + # if mode_i == 2: + # return "modulation" + + return "no_regulation" + + + async def _PZA_DRV_BLC_write_mode_value(self, v): + """Must set *v* as the new mode value on the BPC + """ + self.log.info(f"write enable : {v}") + if v == "constant_current": + await self.serial_connector.write_and_read_until(cmd("ci"), expected=b"\n") + + if v == "constant_power": + await self.serial_connector.write_and_read_until(cmd("cp"), expected=b"\n") + + # --- + + async def _PZA_DRV_BLC_read_enable_value(self): + """Get laser ON/OFF state + """ + # 0 = OFF + # 1 = ON + value_b = await self.serial_connector.write_and_read_until(cmd("l?"), expected=b"\n") + value_i = int(value_b.decode('utf-8').rstrip()) + self.log.info(f"read enable value : {value_i} | {bool(value_i)}") + return bool(value_i) + + # --- + + async def _PZA_DRV_BLC_write_enable_value(self, v): + """Set laser ON/OFF state + """ + val_int = 0 + if v: + val_int = 1 + self.log.info(f"write enable value : {val_int}") + await self.serial_connector.write_and_read_until(cmd(f"l{val_int}"), expected=b"\n") + + # Wait for it + value_i = 5 + while(value_i != val_int): + value_b = await self.serial_connector.write_and_read_until(cmd("l?"), expected=b"\n") + value_i = int(value_b.decode('utf-8').rstrip()) + # print(value_i) + + # --- + + ########################################################################### + + async def _PZA_DRV_BLC_read_power_value(self): + power_b = await self.serial_connector.write_and_read_until(cmd(f"p?"), expected=b"\n") + power_f = float(power_b.decode('utf-8').rstrip()) + return power_f + + # --- + + async def _PZA_DRV_BLC_write_power_value(self, v): + self.log.info(f"write power : {v}") + await self.serial_connector.write_and_read_until(cmd(f"p {v}"), expected=b"\n") + + # --- + + async def _PZA_DRV_BLC_power_value_min_max(self): + return { + "min": 0, + "max": 0.3 + } + + # --- + + async def _PZA_DRV_BLC_read_power_decimals(self): + return 3 + + ########################################################################### + + # --- + + async def _PZA_DRV_BLC_read_current_value(self): + current_b = await self.serial_connector.write_and_read_until(cmd(f"glc?"), expected=b"\n") + current_f = float(current_b.decode('utf-8').rstrip()) + self.log.debug(f"read current : {current_f}") + return current_f + + # --- + + async def _PZA_DRV_BLC_write_current_value(self, v): + self.log.info(f"write current : {v}") + await self.serial_connector.write_and_read_until(cmd(f"slc {v}"), expected=b"\n") + + # --- + + async def _PZA_DRV_BLC_current_value_min_max(self): + return { + "min": 0, + "max": 0.5 + } + + # --- + + async def _PZA_DRV_BLC_read_current_decimals(self): + return 1 + + + ########################################################################### + + async def __debug_print_all_registers(self): + """Print all read registers + """ + cmds = [ + "l?", "p?", "pa?", "i?", "ilk?", "leds?", "f?", "sn?", "hrs?", "glm?", "glc?" + ] + for cmd_str in cmds: + print(cmd_str, " = ", await self.serial_connector.write_and_read_until(cmd(cmd_str), expected=b"\n") ) + + diff --git a/panduza_platform/devices/korad/ka3005/dev_ka3005.py b/panduza_platform/devices/korad/ka3005/dev_ka3005.py index 2a71c74..80fe6d5 100644 --- a/panduza_platform/devices/korad/ka3005/dev_ka3005.py +++ b/panduza_platform/devices/korad/ka3005/dev_ka3005.py @@ -48,7 +48,13 @@ async def _PZA_DEV_hunt(self): serial_port_name=devname, serial_baudrate=9600 ) - IDN = await connector.write_and_read_until("*IDN?", time_lock_s=0.5) + + IDN=None + try: + IDN = await asyncio.wait_for(connector.write_and_read_during("*IDN?", time_lock_s=0.5), timeout=1) + except asyncio.TimeoutError: + self.log.debug("timeout comminucation") + continue self.log.info(f">>>>>>>-------------------- {IDN}") if IDN.decode().startswith("KORAD KD3005P"): diff --git a/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_ammeter.py b/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_ammeter.py index 725f001..f68f483 100644 --- a/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_ammeter.py +++ b/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_ammeter.py @@ -33,7 +33,7 @@ async def _PZA_DRV_loop_init(self): # --- async def _PZA_DRV_AMMETER_read_measure_value(self): - current = await self.serial_connector.write_and_read_until(f"IOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + current = await self.serial_connector.write_and_read_during(f"IOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(current.strip()) # return float(current[1:].strip()) diff --git a/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_bpc.py b/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_bpc.py index 1d01e61..49183f6 100644 --- a/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_bpc.py +++ b/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_bpc.py @@ -47,7 +47,7 @@ async def _PZA_DRV_loop_init(self): async def _PZA_DRV_BPC_read_enable_value(self): # await asyncio.sleep(1) - status = await self.serial_connector.write_and_read_until(f"STATUS?", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + status = await self.serial_connector.write_and_read_during(f"STATUS?", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return bool(status[0] & (1 << 6)) @@ -57,7 +57,7 @@ async def _PZA_DRV_BPC_write_enable_value(self, v): # --- async def _PZA_DRV_BPC_read_voltage_value(self): - voltage = await self.serial_connector.write_and_read_until(f"VSET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + voltage = await self.serial_connector.write_and_read_during(f"VSET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(voltage) async def _PZA_DRV_BPC_write_voltage_value(self, v): @@ -72,7 +72,7 @@ async def _PZA_DRV_BPC_read_voltage_decimals(self): # --- async def _PZA_DRV_BPC_read_current_value(self): - current = await self.serial_connector.write_and_read_until(f"ISET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + current = await self.serial_connector.write_and_read_during(f"ISET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(current[0:5]) async def _PZA_DRV_BPC_write_current_value(self, v): diff --git a/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_voltmeter.py b/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_voltmeter.py index bbc4657..1f9fde9 100644 --- a/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_voltmeter.py +++ b/panduza_platform/devices/korad/ka3005/itf_korad_ka3005p_voltmeter.py @@ -32,7 +32,7 @@ async def _PZA_DRV_loop_init(self): # --- async def _PZA_DRV_VOLTMETER_read_measure_value(self): - voltage = await self.serial_connector.write_and_read_until(f"VOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + voltage = await self.serial_connector.write_and_read_during(f"VOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(voltage.strip()) # return float(voltage[1:].strip()) diff --git a/panduza_platform/devices/oxxius/LBX_488/__init__.py b/panduza_platform/devices/oxxius/LBX_488/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/devices/oxxius/LBX_488/dev_lbx_488.py b/panduza_platform/devices/oxxius/LBX_488/dev_lbx_488.py new file mode 100644 index 0000000..d97e826 --- /dev/null +++ b/panduza_platform/devices/oxxius/LBX_488/dev_lbx_488.py @@ -0,0 +1,176 @@ +import asyncio +from core.platform_device import PlatformDevice +from pathlib import Path + +import os + +import usb + + +from pyftdi.ftdi import Ftdi + +import usb.core +import usb.util + +from .itf_lbx_488_blc import InterfaceLbx488Blc + +class DeviceOxxiusLbx488(PlatformDevice): + """ + """ + + def _PZA_DEV_config(self): + """ + """ + return { + "family": "Laser", + "model": "LBX-488", + "manufacturer": "Oxxius", + "settings_props": [ + { + 'name': 'usb_serial_short', + 'type': 'string', + 'default': '' + } + ] + } + + # --- + + async def _PZA_DEV_hunt(self): + """ + """ + bag = [] + + # 0403:90d9 Future Technology Devices International, Ltd LaserBoxx + + try: + # Get the list of available devices + Ftdi.add_custom_product(Ftdi.DEFAULT_VENDOR, 0x90d9) + Ftdi.show_devices() + except Exception as e: + print(e) + + + + try: + + usb_matches = Ftdi.list_devices() + print(usb_matches) + + for usb_match in usb_matches: + + match = usb_match[0] + + if match.pid == 0x90d9: + # print('------', match) + # devlink = match.get('DEVLINKS', None) + # if devlink: + # print(f"pooookkkkk1 {devlink}") + + ma = self._PZA_DEV_config()["manufacturer"] + mo = self._PZA_DEV_config()["model"] + ref = f"{ma}.{mo}" + bag.append({ + "ref": ref, + "settings": { + # "usb_vendor": match.idVendor, + # "usb_model": match.idProduct, + "usb_serial_short": match.sn + } + }) + + except Exception as e: + print("errororoorroorororo") + print(e) + + + + + + + # # write the data + + # patcket_sizeee = 32 + # cmd = b"?SV" + # packet_to_send = cmd + b'\x00' * (patcket_sizeee - len(cmd)) + # # packet_to_send = cmd + # ep.write(packet_to_send) + + + # ep_in = usb.util.find_descriptor( + # intf, + # # match the first OUT endpoint + # custom_match = \ + # lambda e: \ + # usb.util.endpoint_direction(e.bEndpointAddress) == \ + # usb.util.ENDPOINT_IN) + + + + # data_array_b = ep_in.read(patcket_sizeee) + # print(data_array_b) + # bytes_object = data_array_b.tobytes() + # print(bytes_object) + + # data = ep_in.read(1) + # print(data) + + + + + # _usb_dev = dev + # # _usb_dev.open() + + # _usb_dev.write(0x2, "?SV\n") + + # # Create a read buffer + # data = _usb_dev.read(0x81,100000,1000) + + # print( data) + + # # config = _usb_dev.get_active_configuration() + # # print(config) + + # # for endpoint in _usb_dev.get_active_configuration().endpoints: + # # print("Endpoint Address: {}".format(endpoint.address)) + # # print("Endpoint Type: {}".format(endpoint.type)) + # # print("Endpoint Direction: {}".format(endpoint.direction)) + + + # except Exception as e: + # print("errororoorroorororo") + # print(e) + + + + + return bag + + + # --- + + async def _PZA_DEV_mount_interfaces(self): + """ + """ + + settings = self.get_settings() + print(settings) + + # if ('usb_serial_short' not in settings) and ('serial_port_name' not in settings): + # raise Exception("At least one settings must be set") + + const_settings = { + "usb_vendor": Ftdi.DEFAULT_VENDOR, + "usb_model": 0x90d9, # for the laser oxxius + } + settings.update(const_settings) + + # self.log.info(f"=> {settings}") + + self.mount_interface( + InterfaceLbx488Blc(name=f"blc", settings=settings) + ) + + + + diff --git a/panduza_platform/devices/oxxius/LBX_488/itf_lbx_488_blc.py b/panduza_platform/devices/oxxius/LBX_488/itf_lbx_488_blc.py new file mode 100644 index 0000000..7b06cbf --- /dev/null +++ b/panduza_platform/devices/oxxius/LBX_488/itf_lbx_488_blc.py @@ -0,0 +1,216 @@ + +from meta_drivers.blc import MetaDriverBlc + + +from connectors.serial_low_usb import ConnectorSerialLowUsb + + + +class InterfaceLbx488Blc(MetaDriverBlc): + """Fake BLC driver + """ + + # --- + + def __init__(self, name=None, settings={}) -> None: + """Constructor + """ + self.settings = settings + super().__init__(name=name) + + def msg_to_float(self, bytes_value): + return float(bytes_value[:-1]) + + def mA_to_A(self, ma_value): + return round((ma_value * 0.001), 3) + + def A_to_mA(self, ma_value): + return (ma_value * 1000) + + def mW_to_W(self, ma_value): + return round((ma_value * 0.001), 3) + + def W_to_mW(self, ma_value): + return (ma_value * 1000) + + # ============================================================================= + # FROM MetaDriverBlc + + # --- + + async def _PZA_DRV_loop_init(self): + """Init function + Reset fake parameters + """ + + self.usb_conn = await ConnectorSerialLowUsb.Get(**self.settings) + + + # Get min and max value for current set point + # When setting the current, the unit used is the percentage of the nominal current. When reading the + # current, the returned value is expressed in milliAmperes. + # Although the setting of “100%” is designed to drive the LBX at nominal power at the beginning of the + # unit’s lifespan, the user is allowed to set this current up to 125% of the nominal current in order to + # cope for a potential loss of efficiency due to aging + current_save = self.msg_to_float(await self.usb_conn.write_and_read("?SC")) + current_save = int(current_save) + await self.usb_conn.write_and_read("CM 125") + current_max = self.msg_to_float(await self.usb_conn.write_and_read("?SC")) + self.min_max_current={ + "min": 0, + "max": self.mA_to_A(current_max) + } + await self.usb_conn.write_and_read(f"CM {current_save}") # restore previous value + # print(self.min_max_current) + + # Get min and max value for power set point + power_max = self.msg_to_float(await self.usb_conn.write_and_read("?MAXLP")) + self.min_max_power={ + "min": 0, + "max": self.mW_to_W(power_max) + } + + + + await self.__debug_print_all_registers() + # data = await self.usb_conn.write_and_read("?SV") + # print(data) + # data = await self.usb_conn.write_and_read("?SV") + # print(data) + + + # Call meta class BLC ini + await super()._PZA_DRV_loop_init() + + + # ============================================================================= + # **** MODE/VALUE **** + + # --- + + async def _PZA_DRV_BLC_read_mode_value(self): + """ + """ + ACC = await self.usb_conn.write_and_read("?ACC") + if ACC == b'1\x00': + return "constant_current" + + APC = await self.usb_conn.write_and_read("?APC") + if APC == b'1\x00': + return "constant_power" + + return "no_regulation" + + # --- + + async def _PZA_DRV_BLC_write_mode_value(self, v): + """ + """ + self.log.info(f"write enable : {v}") + if v == "constant_current": + await self.usb_conn.write_and_read("ACC 1") + if v == "constant_power": + await self.usb_conn.write_and_read("APC 1") + + await self.__debug_print_all_registers() + + # --- + + # ============================================================================= + # **** ENABLE/VALUE **** + + # --- + + async def _PZA_DRV_BLC_read_enable_value(self): + """Read emission status and convert to bool + """ + EMISSION = await self.usb_conn.write_and_read("?L") + if EMISSION == b'1\x00': + return True + else: + return False + + # --- + + async def _PZA_DRV_BLC_write_enable_value(self, v): + self.log.info(f"write enable : {v}") + int_value = 0 # off by default + if v: + int_value = 1 + status = await self.usb_conn.write_and_read(f"L {int_value}") + self.log.info(f"status={status}") + + # Wait for it + value_i = None + while(value_i != v): + value_i = await self._PZA_DRV_BLC_read_enable_value() + + # --- + + ########################################################################### + + async def _PZA_DRV_BLC_read_power_value(self): + c = self.msg_to_float(await self.usb_conn.write_and_read(f"?SP")) + print(c) + print(self.mW_to_W(c)) + return self.mW_to_W(c) + + # --- + + async def _PZA_DRV_BLC_write_power_value(self, v): + self.log.info(f"write power : {v}") + val_mW = self.W_to_mW(v) + await self.usb_conn.write_and_read(f"PM {val_mW}") + + # --- + + async def _PZA_DRV_BLC_power_value_min_max(self): + return self.min_max_power + + # --- + + async def _PZA_DRV_BLC_read_power_decimals(self): + return 3 + + + # ============================================================================= + # **** DEBUG **** + + async def _PZA_DRV_BLC_read_current_value(self): + c = self.msg_to_float(await self.usb_conn.write_and_read(f"?SC")) + return self.mA_to_A(c) + + # --- + + async def _PZA_DRV_BLC_write_current_value(self, v): + self.log.info(f"write current : {v}") + val_mA = self.A_to_mA(v) + await self.usb_conn.write_and_read(f"CM {val_mA}") + + # --- + + async def _PZA_DRV_BLC_current_value_min_max(self): + return self.min_max_current + + # --- + + async def _PZA_DRV_BLC_read_current_decimals(self): + return 3 + + # --- + + # ============================================================================= + # **** DEBUG **** + + async def __debug_print_all_registers(self): + """Print all read registers + """ + cmds = [ + "?SV", "?ACC", "?APC", "?BT", "?C", "?CDRH", "?CW", + "?DST", "?DT", "?F", "?HH", "?HID", "?INT", "?IV", "?L", + "?MAXLC", "?MAXLP", "?P", "?PST", "?SC", "?SP", "?STA", + "?T" + ] + for cmd_str in cmds: + print(cmd_str, " = ", await self.usb_conn.write_and_read(cmd_str) ) + diff --git a/panduza_platform/devices/oxxius/__init__.py b/panduza_platform/devices/oxxius/__init__.py new file mode 100644 index 0000000..1562834 --- /dev/null +++ b/panduza_platform/devices/oxxius/__init__.py @@ -0,0 +1,7 @@ +from .LBX_488.dev_lbx_488 import DeviceOxxiusLbx488 + + +PZA_DEVICES_LIST= [ + DeviceOxxiusLbx488 + ] + diff --git a/panduza_platform/devices/panduza/__init__.py b/panduza_platform/devices/panduza/__init__.py index 2ecbcc5..277a6dc 100644 --- a/panduza_platform/devices/panduza/__init__.py +++ b/panduza_platform/devices/panduza/__init__.py @@ -4,11 +4,17 @@ from .server.dev_server import DevicePanduzaServer from .fake_webcam.dev_fake_webcam import DevicePanduzaFakeWebcam +from .fake_laser.dev_fake_laser import DevicePanduzaFakeLaser +from .fake_powermeter.dev_fake_powermeter import DevicePanduzaFakePowermeter + + PZA_DEVICES_LIST= [ DevicePanduzaFakeDioController, DevicePanduzaFakeBps, DevicePanduzaFakeRelayController, DevicePanduzaServer, - DevicePanduzaFakeWebcam + DevicePanduzaFakeWebcam, + DevicePanduzaFakeLaser, + DevicePanduzaFakePowermeter ] diff --git a/panduza_platform/devices/panduza/fake_laser/__init__.py b/panduza_platform/devices/panduza/fake_laser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/devices/panduza/fake_laser/dev_fake_laser.py b/panduza_platform/devices/panduza/fake_laser/dev_fake_laser.py new file mode 100644 index 0000000..746e7cc --- /dev/null +++ b/panduza_platform/devices/panduza/fake_laser/dev_fake_laser.py @@ -0,0 +1,48 @@ +from core.platform_device import PlatformDevice, array_name + +from .itf_fake_blc import InterfacePanduzaFakeBlc +from .itf_fake_thermometer import InterfacePanduzaFakeThermometer + +class DevicePanduzaFakeLaser(PlatformDevice): + + # --- + + def _PZA_DEV_config(self): + return { + "family": "LASER", + "model": "FakeLaser", + "manufacturer": "Panduza", + "settings_props": [ + { + 'name': 'number_of_channel', + 'type': 'int' + }, + { + 'name': 'has_thermometer', + 'type': 'bool' + } + ] + } + + # --- + + async def _PZA_DEV_mount_interfaces(self): + """ + """ + + number_of_channel = self.settings.get("number_of_channel", 1) + has_thermo = self.settings.get("has_thermometer", False) + + for chan in range(0, number_of_channel): + + self.mount_interface( + InterfacePanduzaFakeBlc(name=array_name("channel", chan, "ctrl")) + ) + + if has_thermo: + self.mount_interface( + InterfacePanduzaFakeThermometer(name=array_name("channel", chan, "am")) + ) + + # --- + diff --git a/panduza_platform/devices/panduza/fake_laser/itf_fake_blc.py b/panduza_platform/devices/panduza/fake_laser/itf_fake_blc.py new file mode 100644 index 0000000..b0d89cf --- /dev/null +++ b/panduza_platform/devices/panduza/fake_laser/itf_fake_blc.py @@ -0,0 +1,127 @@ + +from meta_drivers.blc import MetaDriverBlc + +class InterfacePanduzaFakeBlc(MetaDriverBlc): + """Fake BLC driver + """ + + # ============================================================================= + # FROM MetaDriverBlc + + # # --- + + # def _PZA_DRV_BLC_config(self): + # """ + # """ + # return { + # "name": "panduza.fake.bLc", + # "description": "Virtual BLC" + # } + + # --- + + async def _PZA_DRV_loop_init(self): + """Init function + Reset fake parameters + """ + self.__fakes = { + "mode": { + "value": "constant_power" + }, + "enable": { + "value": False + }, + "power": { + "value": 0, + "min": -1000, + "max": 1000, + "decimals": 2 + }, + "current": { + "value": 0, + "min": 0, + "max": 50, + "decimals": 3 + }, + "misc": { + "model": "DEATHSTAR (Panduza Fake Laser Control)" + } + } + + # Call meta class BLC ini + await super()._PZA_DRV_loop_init() + + ########################################################################### + + + async def _PZA_DRV_BLC_read_mode_value(self): + return self.__fakes["mode"]["value"] + + + async def _PZA_DRV_BLC_write_mode_value(self, v): + self.log.info(f"write enable : {v}") + self.__fakes["mode"]["value"] = v + + + async def _PZA_DRV_BLC_read_enable_value(self): + # self.log.debug(f"read enable !") + return self.__fakes["enable"]["value"] + + # --- + + async def _PZA_DRV_BLC_write_enable_value(self, v): + self.log.info(f"write enable : {v}") + self.__fakes["enable"]["value"] = v + + ########################################################################### + + async def _PZA_DRV_BLC_read_power_value(self): + # self.log.debug(f"read power value !") + return self.__fakes["power"]["value"] + + # --- + + async def _PZA_DRV_BLC_write_power_value(self, v): + self.log.info(f"write power : {v}") + self.__fakes["power"]["value"] = v + self.__fakes["power"]["real"] = v + + # --- + + async def _PZA_DRV_BLC_power_value_min_max(self): + return { + "min": self.__fakes["power"]["min"], + "max": self.__fakes["power"]["max"] + } + + # --- + + async def _PZA_DRV_BLC_read_power_decimals(self): + return self.__fakes["power"]["decimals"] + + ########################################################################### + + async def _PZA_DRV_BLC_read_current_value(self): + # self.log.debug(f"read current value !") + return self.__fakes["current"]["value"] + + # --- + + async def _PZA_DRV_BLC_write_current_value(self, v): + self.log.info(f"write current : {v}") + self.__fakes["current"]["value"] = v + self.__fakes["current"]["real"] = v + + # --- + + async def _PZA_DRV_BLC_current_value_min_max(self): + return { + "min": self.__fakes["current"]["min"], + "max": self.__fakes["current"]["max"] + } + + # --- + + async def _PZA_DRV_BLC_read_current_decimals(self): + return self.__fakes["current"]["decimals"] + diff --git a/panduza_platform/devices/panduza/fake_laser/itf_fake_thermometer.py b/panduza_platform/devices/panduza/fake_laser/itf_fake_thermometer.py new file mode 100644 index 0000000..b9c37d6 --- /dev/null +++ b/panduza_platform/devices/panduza/fake_laser/itf_fake_thermometer.py @@ -0,0 +1,45 @@ +import asyncio +from meta_drivers.thermometer import MetaDriverThermometer + + +class InterfacePanduzaFakeThermometer(MetaDriverThermometer): + """Fake ThermoMeter driver + """ + + # --- + + async def _PZA_DRV_loop_init(self): + """Init function + Reset fake parameters + """ + + # settings = tree.get("settings", {}) + # self.log.info(settings) + + # work_with_fake_bpc = settings.get("work_with_fake_bpc", None) + # self.bpc_obj = self.get_interface_instance_from_pointer(work_with_fake_bpc) + + + self.platform.load_task(self.__increment_task()) + + + self.__fakes = { + "measure": { + "value": 0 + } + } + + # Call meta class BPC ini + await super()._PZA_DRV_loop_init() + + # --- + + async def _PZA_DRV_AMMETER_read_measure_value(self): + return self.__fakes["measure"]["value"] + + # --- + + async def __increment_task(self): + while self.alive: + await asyncio.sleep(0.2) + self.__fakes["measure"]["value"] += 0.001 diff --git a/panduza_platform/devices/panduza/fake_powermeter/__init__.py b/panduza_platform/devices/panduza/fake_powermeter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/devices/panduza/fake_powermeter/dev_fake_powermeter.py b/panduza_platform/devices/panduza/fake_powermeter/dev_fake_powermeter.py new file mode 100644 index 0000000..a6c67d8 --- /dev/null +++ b/panduza_platform/devices/panduza/fake_powermeter/dev_fake_powermeter.py @@ -0,0 +1,29 @@ +from core.platform_device import PlatformDevice, array_name + +from .itf_fake_powermeter import InterfacePanduzaFakePowermeter + +class DevicePanduzaFakePowermeter(PlatformDevice): + + # --- + + def _PZA_DEV_config(self): + return { + "family": "LASER", + "model": "FakePowermeter", + "manufacturer": "Panduza", + "settings_props": [ + ] + } + + # --- + + async def _PZA_DEV_mount_interfaces(self): + """ + """ + + self.mount_interface( + InterfacePanduzaFakePowermeter(name="power") + ) + + # --- + diff --git a/panduza_platform/devices/panduza/fake_powermeter/itf_fake_powermeter.py b/panduza_platform/devices/panduza/fake_powermeter/itf_fake_powermeter.py new file mode 100644 index 0000000..e8e2729 --- /dev/null +++ b/panduza_platform/devices/panduza/fake_powermeter/itf_fake_powermeter.py @@ -0,0 +1,51 @@ +import asyncio +from meta_drivers.powermeter import MetaDriverPowermeter + + +class InterfacePanduzaFakePowermeter(MetaDriverPowermeter): + """Fake Powermeter driver + """ + + # --- + + async def _PZA_DRV_loop_init(self): + """Init function + Reset fake parameters + """ + + # settings = tree.get("settings", {}) + # self.log.info(settings) + + # work_with_fake_bpc = settings.get("work_with_fake_bpc", None) + # self.bpc_obj = self.get_interface_instance_from_pointer(work_with_fake_bpc) + + + self.platform.load_task(self.__increment_task()) + + + self.__decimals = 3 + self.__fakes = { + "measure": { + "value": 0 + } + } + + # Call meta class BPC ini + await super()._PZA_DRV_loop_init() + + # --- + + async def _PZA_DRV_POWERMETER_read_measure_value(self): + return self.__fakes["measure"]["value"] + + # --- + + async def _PZA_DRV_POWERMETER_read_measure_decimals(self): + return self.__decimals + + # --- + + async def __increment_task(self): + while self.alive: + await asyncio.sleep(0.2) + self.__fakes["measure"]["value"] += 0.001 diff --git a/panduza_platform/devices/panduza/server/itf_platform.py b/panduza_platform/devices/panduza/server/itf_platform.py index 8b7209c..1b1ba75 100644 --- a/panduza_platform/devices/panduza/server/itf_platform.py +++ b/panduza_platform/devices/panduza/server/itf_platform.py @@ -181,7 +181,15 @@ async def hunt_task(self): if not has_next: break self.hunting = False - await self._update_attribute("devices", "hunting", self.hunting) + + await self._update_attributes_from_dict({ + "devices": { + "hunting": self.hunting, + "max": self.platform.device_factory.get_number_of_drivers(), + "hunted": 1, + "store": self.platform.device_factory.get_devices_store(), + } + }) print(f"!!!!!!!!!!!!!! HUNT !!!!!!!!!!!!!!!") diff --git a/panduza_platform/devices/tenma/t722710/dev_t722710.py b/panduza_platform/devices/tenma/t722710/dev_t722710.py index 1b0dad2..17226e0 100644 --- a/panduza_platform/devices/tenma/t722710/dev_t722710.py +++ b/panduza_platform/devices/tenma/t722710/dev_t722710.py @@ -60,7 +60,17 @@ async def _PZA_DEV_hunt(self): serial_port_name=devname, serial_baudrate=9600 ) - IDN = await connector.write_and_read_until("*IDN?", time_lock_s=0.5) + + IDN=None + try: + IDN = await asyncio.wait_for(connector.write_and_read_during("*IDN?", time_lock_s=0.5), timeout=1) + except asyncio.TimeoutError: + self.log.debug("timeout comminucation") + continue + + self.log.debug(f"IDN = {IDN}") + + # await # self.log.info(IDN) if IDN.decode().startswith("TENMA 72-2710"): diff --git a/panduza_platform/devices/tenma/t722710/itf_tenma_722710_ammeter.py b/panduza_platform/devices/tenma/t722710/itf_tenma_722710_ammeter.py index f08d97c..89116a0 100644 --- a/panduza_platform/devices/tenma/t722710/itf_tenma_722710_ammeter.py +++ b/panduza_platform/devices/tenma/t722710/itf_tenma_722710_ammeter.py @@ -33,7 +33,7 @@ async def _PZA_DRV_loop_init(self): # --- async def _PZA_DRV_AMMETER_read_measure_value(self): - current = await self.serial_connector.write_and_read_until(f"IOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + current = await self.serial_connector.write_and_read_during(f"IOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(current) # --- diff --git a/panduza_platform/devices/tenma/t722710/itf_tenma_722710_bpc.py b/panduza_platform/devices/tenma/t722710/itf_tenma_722710_bpc.py index 3b4765d..7541e07 100644 --- a/panduza_platform/devices/tenma/t722710/itf_tenma_722710_bpc.py +++ b/panduza_platform/devices/tenma/t722710/itf_tenma_722710_bpc.py @@ -51,7 +51,7 @@ async def _PZA_DRV_loop_init(self): async def _PZA_DRV_BPC_read_enable_value(self): # Send "STATUS?" to get back the output state - statusBytes = await self.serial_connector.write_and_read_until("STATUS?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + statusBytes = await self.serial_connector.write_and_read_during("STATUS?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) self.log.debug(f"{statusBytes.strip()}") status = ord(statusBytes.strip()) @@ -74,7 +74,7 @@ async def _PZA_DRV_BPC_write_enable_value(self, v): async def _PZA_DRV_BPC_read_voltage_value(self): # Send "VSET1?" to get the voltage value - voltage = await self.serial_connector.write_and_read_until(f"VSET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + voltage = await self.serial_connector.write_and_read_during(f"VSET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(voltage) # --- @@ -96,7 +96,7 @@ async def _PZA_DRV_BPC_read_voltage_decimals(self): # CURRENT # async def _PZA_DRV_BPC_read_current_value(self): - current = await self.serial_connector.write_and_read_until(f"ISET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + current = await self.serial_connector.write_and_read_during(f"ISET{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(current) # --- diff --git a/panduza_platform/devices/tenma/t722710/itf_tenma_722710_voltmeter.py b/panduza_platform/devices/tenma/t722710/itf_tenma_722710_voltmeter.py index 77d584c..66ecc8f 100644 --- a/panduza_platform/devices/tenma/t722710/itf_tenma_722710_voltmeter.py +++ b/panduza_platform/devices/tenma/t722710/itf_tenma_722710_voltmeter.py @@ -33,7 +33,7 @@ async def _PZA_DRV_loop_init(self): # --- async def _PZA_DRV_VOLTMETER_read_measure_value(self): - voltage = await self.serial_connector.write_and_read_until(f"VOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) + voltage = await self.serial_connector.write_and_read_during(f"VOUT{self.channel}?\n", time_lock_s=COMMAND_TIME_LOCK, read_duration_s=0.1) return float(voltage) # --- diff --git a/panduza_platform/devices/thorlabs/PM100A/__init__.py b/panduza_platform/devices/thorlabs/PM100A/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/panduza_platform/devices/thorlabs/PM100A/dev_PM100A.py b/panduza_platform/devices/thorlabs/PM100A/dev_PM100A.py new file mode 100644 index 0000000..1a40d98 --- /dev/null +++ b/panduza_platform/devices/thorlabs/PM100A/dev_PM100A.py @@ -0,0 +1,118 @@ +import asyncio +from core.platform_device import PlatformDevice + + +from pathlib import Path +# from ThorlabsPM100 import ThorlabsPM100, USBTMC +import os + +import usb +import usbtmc + +from connectors.utils.usbtmc import HuntUsbtmcDevs +# from connectors.udev_tty import HuntUsbDevs + +from .itf_PM100A_powermeter import InterfaceThorlabsPM100APowermeter + +class DeviceThorlabsPM100A(PlatformDevice): + """Power Supply From Korad + """ + + def _PZA_DEV_config(self): + """ + """ + return { + "family": "Powermeter", + "model": "PM100A", + "manufacturer": "Thorlabs", + "settings_props": [ + { + 'name': 'usb_serial_short', + 'type': 'string', + 'default': '' + } + ] + } + + # --- + + async def _PZA_DEV_hunt(self): + """ + """ + bag = [] + + # 1313:8079 ThorLabs PM100A + + # try: + # print("start") + # a = usbtmc.list_devices() + # print(a[0]) + # print(a[0].idVendor) + # print(a[0].idProduct) + # print(a[0].iSerialNumber) + # print(a[0].serial_number) # ok + # # inst = usbtmc.Instrument(a[0]) + # # print(inst.ask("*IDN?")) + # # print(dev) + # print("stop") + # except Exception as e: + # print("errororoorroorororo") + # print(e) + + + try: + matches = HuntUsbtmcDevs(0x1313, 0x8079) + for match in matches: + # print('------', match) + # devlink = match.get('DEVLINKS', None) + # if devlink: + # print(f"pooookkkkk1 {devlink}") + + ma = self._PZA_DEV_config()["manufacturer"] + mo = self._PZA_DEV_config()["model"] + ref = f"{ma}.{mo}" + bag.append({ + "ref": ref, + "settings": { + # "usb_vendor": match.idVendor, + # "usb_model": match.idProduct, + "usb_serial_short": match.serial_number + } + }) + + except Exception as e: + print("errororoorroorororo") + print(e) + + + return bag + + + # --- + + async def _PZA_DEV_mount_interfaces(self): + """ + """ + + settings = self.get_settings() + print("in device !!!") + print(settings) + + # if ('usb_serial_short' not in settings) and ('serial_port_name' not in settings): + # raise Exception("At least one settings must be set") + + const_settings = { + "usb_vendor": 0x1313, + "usb_model": 0x8079, + } + settings.update(const_settings) + + self.log.info(f"=> {settings}") + + self.mount_interface( + InterfaceThorlabsPM100APowermeter(name=f"powermeter", settings=settings) + ) + + + + diff --git a/panduza_platform/devices/thorlabs/PM100A/itf_PM100A_powermeter.py b/panduza_platform/devices/thorlabs/PM100A/itf_PM100A_powermeter.py new file mode 100644 index 0000000..4d0e1e2 --- /dev/null +++ b/panduza_platform/devices/thorlabs/PM100A/itf_PM100A_powermeter.py @@ -0,0 +1,43 @@ +import asyncio +from meta_drivers.powermeter import MetaDriverPowermeter + +from panduza_platform.connectors.thorlabs_pm100.connector import ConnectorThorlabsPM100 + + +class InterfaceThorlabsPM100APowermeter(MetaDriverPowermeter): + """Fake Powermeter driver + """ + + # --- + + def __init__(self, name=None, settings={}) -> None: + """Constructor + """ + self.settings = settings + super().__init__(name=name) + + # --- + + async def _PZA_DRV_loop_init(self): + """Init function + Reset fake parameters + """ + + self.conn = await ConnectorThorlabsPM100.Get(**self.settings) + # print(self.conn.read()) + + # Call meta class BPC ini + await super()._PZA_DRV_loop_init() + + # --- + + async def _PZA_DRV_POWERMETER_read_measure_value(self): + return self.conn.read() + + # --- + + async def _PZA_DRV_POWERMETER_read_measure_decimals(self): + """ + """ + return 5 + diff --git a/panduza_platform/devices/thorlabs/__init__.py b/panduza_platform/devices/thorlabs/__init__.py new file mode 100644 index 0000000..e57a4de --- /dev/null +++ b/panduza_platform/devices/thorlabs/__init__.py @@ -0,0 +1,7 @@ +from .PM100A.dev_PM100A import DeviceThorlabsPM100A + + +PZA_DEVICES_LIST= [ + DeviceThorlabsPM100A + ] + diff --git a/panduza_platform/meta_drivers/blc.py b/panduza_platform/meta_drivers/blc.py new file mode 100644 index 0000000..ab3c858 --- /dev/null +++ b/panduza_platform/meta_drivers/blc.py @@ -0,0 +1,358 @@ +import abc +import time +import asyncio +from collections import ChainMap +from core.platform_driver import PlatformDriver + +class MetaDriverBlc(PlatformDriver): + """Abstract Driver with helper class to manage Bench Laser Control interfaces + """ + + # ============================================================================= + # PLATFORM DRIVERS FUNCTIONS + + def _PZA_DRV_config(self): + """Driver base configuration + """ + base = { + "info": { + "type": "blc", + "version": "0.0" + } + } + return base + + # ============================================================================= + # TO OVERRIDE IN DRIVER + + # --- + + async def _PZA_DRV_BLC_read_mode_value(self): + """Must get the mode value on the BPC and return it + """ + raise NotImplementedError("Must be implemented !") + + async def _PZA_DRV_BLC_write_mode_value(self, v): + """Must set *v* as the new mode value on the BPC + """ + raise NotImplementedError("Must be implemented !") + + # --- + + async def _PZA_DRV_BLC_read_enable_value(self): + """Must get the state value on the BPC and return it + """ + raise NotImplementedError("Must be implemented !") + + async def _PZA_DRV_BLC_write_enable_value(self, v): + """Must set *v* as the new state value on the BPC + """ + raise NotImplementedError("Must be implemented !") + + # --- + + async def _PZA_DRV_BLC_read_power_value(self): + """Must get the power value value on the BPC and return it + """ + raise NotImplementedError("Must be implemented !") + + async def _PZA_DRV_BLC_write_power_value(self, v): + """Must set *v* as the new power value value on the BPC + """ + raise NotImplementedError("Must be implemented !") + + async def _PZA_DRV_BLC_power_value_min_max(self): + """Must return the power value range of the power supply + """ + return {"min": 0, "max": 0 } + + async def _PZA_DRV_BLC_read_power_decimals(self): + """Must return the number of decimals supported for the power + """ + raise NotImplementedError("Must be implemented !") + + # --- + + async def _PZA_DRV_BLC_read_current_value(self): + """Must get the current value value on the BPC and return it + """ + raise NotImplementedError("Must be implemented !") + + async def _PZA_DRV_BLC_write_current_value(self, v): + """Must set *v* as the new current value value on the BPC + """ + raise NotImplementedError("Must be implemented !") + + async def _PZA_DRV_BLC_current_value_min_max(self): + """Must return the current range of the power supply + """ + return {"min": 0, "max": 0 } + + async def _PZA_DRV_BLC_read_current_decimals(self): + """Must return the number of decimals supported for the amperage + """ + raise NotImplementedError("Must be implemented !") + + ########################################################################### + ########################################################################### + # + # FOR SUBCLASS USE ONLY + # + ########################################################################### + ########################################################################### + + # --- + + async def _PZA_DRV_loop_init(self): + # Set command handlers + self.__cmd_handlers = { + "mode": self.__handle_cmds_set_mode, + "enable": self.__handle_cmds_set_enable, + "power": self.__handle_cmds_set_power, + "current": self.__handle_cmds_set_current, + # "settings": self.__handle_cmds_set_settings, + } + + # First update + await self.__update_attribute_initial() + + # Polling cycle reset + start_time = time.perf_counter() + self.polling_ref = { + "enable": start_time, + "power" : start_time, + "current" : start_time, + } + + # Start polling task + self.load_worker_task(self.__polling_task_att_enable()) + self.load_worker_task(self.__polling_task_att_power()) + self.load_worker_task(self.__polling_task_att_current()) + + # Init success, the driver can pass into the run mode + self._PZA_DRV_init_success() + + # --- + + async def _PZA_DRV_cmds_set(self, payload): + """From MetaDriver + """ + cmds = self.payload_to_dict(payload) + # self.log.debug(f"cmds as json : {cmds}") + for att in self.__cmd_handlers: + if att in cmds: + await self.__cmd_handlers[att](cmds[att]) + + # ============================================================================= + # PRIVATE FUNCTIONS + + # --- + + async def __set_poll_cycle_enable(self, v): + self.polling_ref["enable"] = v + + async def __get_poll_cycle_enable(self): + return self.polling_ref["enable"] + + # --- + + async def __set_poll_cycle_power(self, v): + self.polling_ref["power"] = v + + async def __get_poll_cycle_power(self): + return self.polling_ref["power"] + + # --- + + async def __set_poll_cycle_current(self, v): + self.polling_ref["current"] = v + + async def __get_poll_cycle_current(self): + return self.polling_ref["current"] + + # --- + + async def __polling_task_att_enable(self): + """Task to poll the value + """ + while self.alive: + await asyncio.sleep(self.polling_ref["enable"]) + await self._update_attributes_from_dict({ + "enable": { + "value": await self._PZA_DRV_BLC_read_enable_value() + } + }) + + # --- + + async def __polling_task_att_power(self): + """Task to poll the value + """ + while self.alive: + await asyncio.sleep(self.polling_ref["power"]) + await self._update_attributes_from_dict({ + "power": { + "value": await self._PZA_DRV_BLC_read_power_value() + } + }) + + # --- + + async def __polling_task_att_current(self): + """Task to poll the value + """ + while self.alive: + await asyncio.sleep(self.polling_ref["current"]) + await self._update_attributes_from_dict({ + "current": { + "value": await self._PZA_DRV_BLC_read_current_value() + } + }) + + # --- + + async def __update_attribute_initial(self): + """ + """ + await self.__att_mode_full_update() + await self.__att_enable_full_update() + await self.__att_power_full_update() + await self.__att_current_full_update() + + # --- + + async def __handle_cmds_set_mode(self, cmd_att): + """Manage output mode commands + """ + update_obj = {} + await self._prepare_update(update_obj, + "mode", cmd_att, + "value", [str] + , self._PZA_DRV_BLC_write_mode_value + , self._PZA_DRV_BLC_read_mode_value) + + await self._update_attributes_from_dict(update_obj) + + # --- + + async def __handle_cmds_set_enable(self, cmd_att): + """Manage output enable commands + """ + update_obj = {} + + await self._prepare_update(update_obj, + "enable", cmd_att, + "value", [bool] + , self._PZA_DRV_BLC_write_enable_value + , self._PZA_DRV_BLC_read_enable_value) + + await self._prepare_update(update_obj, + "enable", cmd_att, + "polling_cycle", [float, int] + , self.__set_poll_cycle_enable + , self.__get_poll_cycle_enable) + await self._update_attributes_from_dict(update_obj) + + # --- + + async def __handle_cmds_set_power(self, cmd_att): + """Manage power commands + """ + update_obj = {} + + # TODO + # if self._get_field("power", "min") <= v <= self._get_field("power", "max"): + + await self._prepare_update(update_obj, + "power", cmd_att, + "value", [float, int] + , self._PZA_DRV_BLC_write_power_value + , self._PZA_DRV_BLC_read_power_value) + + await self._prepare_update(update_obj, + "power", cmd_att, + "polling_cycle", [float, int] + , self.__set_poll_cycle_power + , self.__get_poll_cycle_power) + + await self._update_attributes_from_dict(update_obj) + + # --- + + async def __handle_cmds_set_current(self, cmd_att): + """Manage ampere commands + """ + update_obj = {} + + # TODO + # if self._get_field("current", "min") <= v <= self._get_field("current", "max"): + + await self._prepare_update(update_obj, + "current", cmd_att, + "value", [float, int] + , self._PZA_DRV_BLC_write_current_value + , self._PZA_DRV_BLC_read_current_value) + + await self._prepare_update(update_obj, + "current", cmd_att, + "polling_cycle", [float, int] + , self.__set_poll_cycle_current + , self.__get_poll_cycle_current) + + await self._update_attributes_from_dict(update_obj) + + # --- + + async def __att_mode_full_update(self): + """ + """ + await self._update_attributes_from_dict({ + "mode": { + "value": await self._PZA_DRV_BLC_read_mode_value() + } + }) + + # --- + + async def __att_enable_full_update(self): + """ + """ + await self._update_attributes_from_dict({ + "enable": { + "value": await self._PZA_DRV_BLC_read_enable_value(), + "polling_cycle": 1 + } + }) + + # --- + + async def __att_power_full_update(self): + """ + """ + min_max = await self._PZA_DRV_BLC_power_value_min_max() + await self._update_attributes_from_dict({ + "power": { + "min": min_max.get("min", 0), + "max": min_max.get("max", 0), + "value": await self._PZA_DRV_BLC_read_power_value(), + "decimals": await self._PZA_DRV_BLC_read_power_decimals(), + "polling_cycle": 1 + } + }) + + # --- + + async def __att_current_full_update(self): + """ + """ + min_max = await self._PZA_DRV_BLC_current_value_min_max() + await self._update_attributes_from_dict({ + "current": { + "min": min_max.get("min", 0), + "max": min_max.get("max", 0), + "value": await self._PZA_DRV_BLC_read_current_value(), + "decimals": await self._PZA_DRV_BLC_read_current_decimals(), + "polling_cycle": 1 + } + }) + diff --git a/panduza_platform/meta_drivers/powermeter.py b/panduza_platform/meta_drivers/powermeter.py new file mode 100644 index 0000000..3ac3b62 --- /dev/null +++ b/panduza_platform/meta_drivers/powermeter.py @@ -0,0 +1,143 @@ +import abc +import time +import asyncio +import inspect +from collections import ChainMap +from core.platform_driver import PlatformDriver + +class MetaDriverPowermeter(PlatformDriver): + """Abstract Driver with helper class to manage PowerMeter interface + """ + + # ============================================================================= + # PLATFORM DRIVERS FUNCTIONS + + def _PZA_DRV_config(self): + """Driver base configuration + """ + base = { + "info": { + "type": "powermeter", + "version": "0.0" + } + } + return base + + # --- + + async def _PZA_DRV_loop_init(self): + """From PlatformDriver + """ + # Set handers + self.__cmd_handlers = { + "measure" : self.__handle_cmds_set_measure + } + + # + await self.__set_acquisition_frequency(1) + + # first update + await self.__update_attribute_initial() + + # Start polling task + self.platform.load_task(self.__polling_task()) + + # Init Success + await super()._PZA_DRV_loop_init() + + # --- + + async def _PZA_DRV_cmds_set(self, payload): + cmds = self.payload_to_dict(payload) + # self.log.debug(f"cmds as json : {cmds}") + for att in self.__cmd_handlers: + if att in cmds: + await self.__cmd_handlers[att](cmds[att]) + + # ============================================================================= + # TO OVERRIDE IN DRIVER + + # --- + + async def _PZA_DRV_POWERMETER_read_measure_value(self): + """ + """ + file_name = inspect.stack()[0][1] + function_name = inspect.stack()[0][3] + raise NotImplementedError(f"Function not implemented ! '{function_name}' => %{file_name}%") + + # --- + + async def _PZA_DRV_POWERMETER_read_measure_decimals(self): + """ + """ + return 3 + + # ============================================================================= + # PRIVATE FUNCTIONS + + # --- + + async def __read_and_cast(self): + decimals = await self._PZA_DRV_POWERMETER_read_measure_decimals() + return round(await self._PZA_DRV_POWERMETER_read_measure_value(), decimals) + + # --- + + async def __polling_task(self): + """Task to poll the value + """ + while self.alive: + await asyncio.sleep(self._poll_period) + await self._update_attributes_from_dict({ + "measure": { + "value": await self.__read_and_cast() + } + }) + + # --- + + async def __update_attribute_initial(self): + """Function to perform the initial init + """ + await self.__att_measure_full_update() + + # --- + + async def __handle_cmds_set_measure(self, cmd_att): + """ + """ + update_obj = {} + await self._prepare_update(update_obj, + "measure", cmd_att, + "frequency", [float, int] + , self.__set_acquisition_frequency + , self.__get_acquisition_frequency) + await self._update_attributes_from_dict(update_obj) + + # --- + + async def __set_acquisition_frequency(self, v): + self.__acquisition_frequency = v + self._poll_period = 1.0/self.__acquisition_frequency + + # --- + + async def __get_acquisition_frequency(self): + return self.__acquisition_frequency + + # --- + + async def __att_measure_full_update(self): + """ + """ + await self._update_attributes_from_dict({ + "measure": { + "value": await self.__read_and_cast(), + "decimals": await self._PZA_DRV_POWERMETER_read_measure_decimals(), + "frequency": await self.__get_acquisition_frequency() + } + }) + + # --- + diff --git a/panduza_platform/meta_drivers/thermometer.py b/panduza_platform/meta_drivers/thermometer.py new file mode 100644 index 0000000..499d225 --- /dev/null +++ b/panduza_platform/meta_drivers/thermometer.py @@ -0,0 +1,128 @@ +import abc +import time +import asyncio +import inspect +from collections import ChainMap +from core.platform_driver import PlatformDriver + +class MetaDriverThermometer(PlatformDriver): + """Abstract Driver with helper class to manage ThermoMeter interface + """ + + # ============================================================================= + # PLATFORM DRIVERS FUNCTIONS + + def _PZA_DRV_config(self): + """Driver base configuration + """ + base = { + "info": { + "type": "thermometer", + "version": "0.0" + } + } + return base + + # --- + + async def _PZA_DRV_loop_init(self): + """From PlatformDriver + """ + # Set handers + self.__cmd_handlers = { + "measure" : self.__handle_cmds_set_measure + } + + # + self.__polling_cycle = 1 + + # first update + await self.__update_attribute_initial() + + # Start polling task + self.platform.load_task(self.__polling_task()) + + # Init Success + await super()._PZA_DRV_loop_init() + + # --- + + async def _PZA_DRV_cmds_set(self, payload): + cmds = self.payload_to_dict(payload) + # self.log.debug(f"cmds as json : {cmds}") + for att in self.__cmd_handlers: + if att in cmds: + await self.__cmd_handlers[att](cmds[att]) + + # ============================================================================= + # TO OVERRIDE IN DRIVER + + # --- + + async def _PZA_DRV_THERMOMETER_read_measure_value(self): + """ + """ + file_name = inspect.stack()[0][1] + function_name = inspect.stack()[0][3] + raise NotImplementedError(f"Function not implemented ! '{function_name}' => %{file_name}%") + + # ============================================================================= + # PRIVATE FUNCTIONS + + # --- + + async def __polling_task(self): + """Task to poll the value + """ + while self.alive: + await asyncio.sleep(self.__polling_cycle) + await self._update_attributes_from_dict({ + "measure": { + "value": await self._PZA_DRV_THERMOMETER_read_measure_value() + } + }) + + # --- + + async def __update_attribute_initial(self): + """Function to perform the initial init + """ + await self.__att_measure_full_update() + + # --- + + async def __handle_cmds_set_measure(self, cmd_att): + """ + """ + update_obj = {} + await self._prepare_update(update_obj, + "measure", cmd_att, + "polling_cycle", [float, int] + , self.__set_poll_cycle + , self.__get_poll_cycle) + await self._update_attributes_from_dict(update_obj) + + # --- + + async def __set_poll_cycle(self, v): + self.__polling_cycle = v + + # --- + + async def __get_poll_cycle(self): + return self.__polling_cycle + + # --- + + async def __att_measure_full_update(self): + """ + """ + await self._update_attributes_from_dict({ + "measure": { + "value": await self._PZA_DRV_THERMOMETER_read_measure_value(), + "polling_cycle": await self.__get_poll_cycle() + } + }) + + # --- + diff --git a/requirements.txt b/requirements.txt index 7eb0301..ed880eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,5 @@ PyHamcrest==2.0.4 aiofiles==23.2.1 aiomonitor==0.6.0 aioserial==1.3.1 +python-usbtmc==0.8 +ThorlabsPM100==1.2.2 diff --git a/scripts/go.sh b/scripts/go.sh index 786aae5..56d4dbe 100755 --- a/scripts/go.sh +++ b/scripts/go.sh @@ -1,5 +1,13 @@ -#!/bin/bash -docker build -t local/panduza-py-platform:latest . +PYTHON_BIN=python3.11 +PYTHON_VENV_PATH=/opt/panduza/venv + +script_dir=$(dirname $0) +script_dir=$(readlink -f $script_dir) +echo "Script directory: $script_dir" + +# +${PYTHON_VENV_PATH}/bin/pip3 install ${script_dir}/.. + +# +${PYTHON_VENV_PATH}/bin/${PYTHON_BIN} ${PYTHON_VENV_PATH}/lib/python3.11/site-packages/panduza_platform/__main__.py -cd /etc/panduza -docker compose run platformpy diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..861c25d --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,19 @@ +PYTHON_BIN=python3.11 +PYTHON_VENV_PATH=/opt/panduza/venv + + +script_dir=$(dirname $0) +script_dir=$(readlink -f $script_dir) +echo "Script directory: $script_dir" + + +mkdir -p ${PYTHON_VENV_PATH} + +# Create venv +${PYTHON_BIN} -m venv ${PYTHON_VENV_PATH} + +# +${PYTHON_VENV_PATH}/bin/pip3 install -r ${script_dir}/../requirements.txt + +# +${PYTHON_VENV_PATH}/bin/pip3 install ${script_dir}/.. diff --git a/scripts/run.sh b/scripts/run.sh index 29d288f..f53bfea 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,3 +1,8 @@ +PYTHON_BIN=python3.11 +PYTHON_VENV_PATH=/opt/panduza/venv + + +# +${PYTHON_VENV_PATH}/bin/${PYTHON_BIN} ${PYTHON_VENV_PATH}/lib/python3.11/site-packages/panduza_platform/__main__.py + -cd /etc/panduza -docker compose run platformpy diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..8c80999 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,11 @@ +PYTHON_BIN=python3.11 +PYTHON_VENV_PATH=/opt/panduza/venv + +script_dir=$(dirname $0) +script_dir=$(readlink -f $script_dir) +echo "Script directory: $script_dir" + +# +${PYTHON_VENV_PATH}/bin/pip3 install ${script_dir}/.. + +