From 90d3d53133b3f4261389e4232817f7481defc9c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 5 Jan 2026 01:08:22 +0000 Subject: [PATCH 01/65] Update GitHub Action Versions --- .github/workflows/Testbase.yml | 4 ++-- .github/workflows/python-publish.yml | 4 ++-- .github/workflows/updater.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/Testbase.yml b/.github/workflows/Testbase.yml index 989a2a0..b5bfd9d 100644 --- a/.github/workflows/Testbase.yml +++ b/.github/workflows/Testbase.yml @@ -18,9 +18,9 @@ jobs: QT_DEBUG_PLUGINS: 1 steps: - name: Set up Python ${{ inputs.python }} - uses: actions/checkout@v4 + uses: actions/checkout@v6.0.1 - name: Install dependencies - uses: actions/setup-python@v4 + uses: actions/setup-python@v6.1.0 with: python-version: ${{ inputs.python }} - name: Install package diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 69806c5..6bfb6f7 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6.0.1 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6.1.0 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index f2d0fcd..1c948f9 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.1 with: # [Required] Access token with `workflow` scope. token: ${{ secrets.WORKFLOW_SECRET }} From 3527c7d4007b7e27dc1b6ee9a13644acca96ce53 Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Tue, 24 Mar 2026 09:02:56 +0100 Subject: [PATCH 02/65] feat : add telemetrix-esp32 dependency in pyproject.toml --- pyproject.toml | 1 + .../hardware/arduino_telemetrix_esp32_wifi.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py diff --git a/pyproject.toml b/pyproject.toml index 2e2eb64..c145afa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ description = 'Set of instrument plugins implemented using an Arduino Board' dependencies = [ "pymodaq>=5.0.0", 'telemetrix', + 'telemetrix-esp32', 'pyvisa', 'pyvisa-py', diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py new file mode 100644 index 0000000..fe88e71 --- /dev/null +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py @@ -0,0 +1,13 @@ +from typing import Optional +import numbers +from threading import Lock + +from telemetrix_esp32 import telemetrix_esp32 +from pymodaq_plugins_arduino.hardware.arduino_telemetrix import Arduino + +lock = Lock() + +class ArduinoWifi(Arduino, telemetrix_esp32.TelemetrixEsp32): + """Arduino + + """ \ No newline at end of file From 4158c06304e469df48ac924640f668757b4ef72c Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Tue, 24 Mar 2026 10:45:51 +0100 Subject: [PATCH 03/65] add ESP32 WiFi hardware wrapper --- .../hardware/arduino_telemetrix_esp32_wifi.py | 105 +++++++++++++++++- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py index fe88e71..9a0313b 100644 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py @@ -1,13 +1,106 @@ -from typing import Optional import numbers +from typing import Optional from threading import Lock +import asyncio -from telemetrix_esp32 import telemetrix_esp32 -from pymodaq_plugins_arduino.hardware.arduino_telemetrix import Arduino +from telemetrix_aio_esp32 import telemetrix_aio_esp32 lock = Lock() -class ArduinoWifi(Arduino, telemetrix_esp32.TelemetrixEsp32): - """Arduino - """ \ No newline at end of file +class ArduinoWiFi(telemetrix_aio_esp32.TelemetrixAioEsp32): + """Arduino Nano ESP32 WiFi connection wrapper. + + Child class of TelemetrixAioEsp32. Provides a synchronous interface + compatible with PyMoDAQ plugins, mirroring the Arduino API. + + Attributes + ---------- + ip_address : str + pin_values_output : dict + analog_pin_values_input : dict + """ + + COM_PORTS = [] # No serial port for WiFi — kept for UI compatibility + + def __init__(self, ip_address: Optional[str] = None, ip_port: int = 31336, *args, **kwargs): + telemetrix_aio_esp32.TelemetrixAioEsp32.__init__( + self, transport_address=ip_address, ip_port=ip_port, + autostart=False, *args, **kwargs) + self.pin_values_output = {} + self.analog_pin_values_input = {0: 0, + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0} # Initialized dictionary for 6 analog channels + + + def _run(self, coro): + """Run a coroutine from a synchronous context.""" + loop = asyncio.get_event_loop() + return loop.run_until_complete(coro) + + + @staticmethod + def round_value(value): + return max(0, min(255, int(value))) + + def set_pin_mode_analog_output(self, pin: int): + """Configure a pin as PWM analog output.""" + self._run(super().set_pin_mode_analog_output(pin)) + + def set_pins_output_to(self, value: int): + lock.acquire() + for pin in self.pin_values_output: + self._run(self.analog_write(pin, int(value))) + lock.release() + + def analog_write_and_memorize(self, pin: int, value: int): + lock.acquire() + value = self.round_value(value) + self._run(self.analog_write(pin, value)) + self.pin_values_output[pin] = value + lock.release() + + def get_output_pin_value(self, pin: int) -> numbers.Number: + value = self.pin_values_output.get(pin, 0) + return value + + + def read_analog_pin(self, data): + """ + Used as a callback function to read the value of the analog inputs. + Data[0]: pin_type (not used here) + Data[1]: pin_number: i.e. 0 is A0 etc. + Data[2]: pin_value: an integer between 0 and 1023 + Data[3]: raw_time_stamp (not used here) + :param data: a list in which are loaded the acquisition parameter analog input + :return: a dictionary with the following structure {pin_number(int):pin_value(int)} + """ + self.analog_pin_values_input[data[1]] = data[2] + + def set_analog_input(self, pin: int): + """ + Activate the analog pin, make an acquisition, write in the callback, stop the analog reporting. + :param pin: pin number, 1 is A1 etc. + :return: acquisition parameters in the declared callback + """ + lock.acquire() + self._run(self.set_pin_mode_analog_input(pin, differential=0, callback=self.read_analog_pin)) + lock.release() + + + async def shutdown(self): + """Terminate the WiFi connection.""" + await super().shutdown() + + +if __name__ == '__main__': + async def test(): + tele = ArduinoWiFi(ip_address='172.17.50.236', ip_port=31336) + await tele.start_aio() + print("Connexion OK !") + await tele.shutdown() + + asyncio.run(test()) \ No newline at end of file From 925205003d0b3903bd70bf226944d8ca991bc6cd Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Tue, 24 Mar 2026 10:53:59 +0100 Subject: [PATCH 04/65] add Fan, Heater, Address IP and Port config --- .../daq_move_plugins/daq_move_FanHeater.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py new file mode 100644 index 0000000..56e6802 --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -0,0 +1 @@ +from typing \ No newline at end of file From 72d8d5b9e700d2ad750af00f35c3d57b0df7bca3 Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Tue, 24 Mar 2026 11:18:36 +0100 Subject: [PATCH 05/65] edit config --- .../resources/config_template.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index 50c49c8..69ceea6 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -1,6 +1,8 @@ title = "this is the configuration file of the plugin Arduino" com_port = "COM24" +ip_address = "172.17.50.236" +ip_port = 31336 [presets] preset_for_colorsynthesizer = "ArduinoLED" @@ -19,4 +21,9 @@ rows = 2 [servo] pin = 2 pos_1 = 55 -pos_2 = 80 \ No newline at end of file +pos_2 = 80 + +[FanHeater] +[FanHeater.pins] +heater_pin = 9 +heater_fan_pin = 8 From e274a02f9a0f71dc2840c9ec50084359f3c89433 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:05:43 +0200 Subject: [PATCH 06/65] chore: remove files for clean reimplementation --- .../daq_move_plugins/daq_move_FanHeater.py | 224 ++++++++++++++++++ .../hardware/esp32_telemetrix.py | 0 tests/test_esp32.py | 67 ++++++ 3 files changed, 291 insertions(+) create mode 100644 src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py create mode 100644 src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py create mode 100644 tests/test_esp32.py diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py new file mode 100644 index 0000000..97df80e --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -0,0 +1,224 @@ +""" +PyMoDAQ DAQ_Move plugin – Fan (and Heater) PWM control via ESP32 + XY-MOS MOSFET. + +Hardware +-------- +- Arduino Nano ESP32 running Telemetrix4Esp32WIFI firmware +- 2× XY-MOS 15A MOSFET modules driven from ESP32 GPIO pins +- Fan → D8 (GPIO 17), MOSFET channel 0 +- Heat → D9 (GPIO 18), MOSFET channel 1 +- Communication: Wi-Fi TCP + +Note on Windows +--------------- +The *threaded* BLE API of telemetrix-esp32 is NOT compatible with Windows. +This plugin uses the asyncio API exclusively (via esp32_telemetrix.ESP32). + +Plugin axes +----------- +- "Fan" : PWM duty cycle 0-255 (maps to 0-100 % fan speed via MOSFET) +- "Heater" : PWM duty cycle 0-255 (maps to 0-100 % heater power via MOSFET) + +Both axes share a single ESP32 controller (master/slave pattern). +""" + +from typing import Optional + +from pymodaq.control_modules.move_utility_classes import ( + DAQ_Move_base, + comon_parameters_fun, + main, + DataActuatorType, + DataActuator, +) +from pymodaq_utils.utils import ThreadCommand +from pymodaq_gui.parameter import Parameter + +# Import the synchronous ESP32 wrapper (place esp32_telemetrix.py inside +# src/pymodaq_plugins_arduino/hardware/) +from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ESP32 + +# --------------------------------------------------------------------------- +# Pin mapping (Arduino Nano ESP32 silk-screen → GPIO number) +# D8 = GPIO 17 ← Fan MOSFET TRIG +# D9 = GPIO 18 ← Heater MOSFET TRIG +# --------------------------------------------------------------------------- +_FAN_PIN = 17 +_HEATER_PIN = 18 + +# Each PWM channel on the ESP32 must be unique (0-15) +_PIN_CHANNEL = { + _FAN_PIN: 0, + _HEATER_PIN: 1, +} + +# Default IP / port – can be overridden from the PyMoDAQ settings panel +_DEFAULT_IP = "172.17.50.234" +_DEFAULT_PORT = 31336 + + +class DAQ_Move_Fan(DAQ_Move_base): + """ + PyMoDAQ actuator plugin controlling a fan and a heater via PWM through + XY-MOS MOSFET modules driven by an Arduino Nano ESP32 (Wi-Fi / Telemetrix). + + Axis "Fan" → GPIO 17 (D8) → MOSFET → 12 V fan + Axis "Heater" → GPIO 18 (D9) → MOSFET → resistive load / heater + + The actuator value is a PWM duty-cycle in the range **0-255**: + - 0 → 0 % (fully off) + - 255 → 100 % (fully on) + """ + + _controller_units = "" + is_multiaxes = True + + # Map human-readable axis names to the corresponding GPIO pin numbers. + # axis_value (used in move_abs / get_actuator_value) will be the GPIO int. + _axis_names = { + "Fan": _FAN_PIN, + "Heater": _HEATER_PIN, + } + + _epsilon = 1 # minimum meaningful step (1 PWM count) + data_actuator_type = DataActuatorType["DataActuator"] + + params = [ + { + "title": "ESP32 IP address:", + "name": "esp32_ip", + "type": "str", + "value": _DEFAULT_IP, + }, + { + "title": "ESP32 TCP port:", + "name": "esp32_port", + "type": "int", + "value": _DEFAULT_PORT, + }, + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + + # ------------------------------------------------------------------ + # Lifecycle + # ------------------------------------------------------------------ + + def ini_attributes(self): + self.controller: Optional[ESP32] = None + + def ini_stage(self, controller=None): + """ + Initialise the connection to the ESP32. + + In the multi-axes / master-slave pattern PyMoDAQ creates one plugin + instance per axis but only the *master* actually opens the hardware + connection; slaves re-use the same controller object. + """ + self.controller = self.ini_stage_init( + old_controller=controller, + new_controller=None, + ) + + if self.is_master: + ip = self.settings["esp32_ip"] + port = self.settings["esp32_port"] + self.controller = ESP32(ip_address=ip, ip_port=port) + self._configure_pins() + + info = ( + f"ESP32 Fan controller initialised — " + f"IP={self.settings['esp32_ip']}:{self.settings['esp32_port']}" + ) + initialized = True + return info, initialized + + def _configure_pins(self): + """Set every axis pin to PWM (analog output) mode.""" + for axis_name, pin in self._axis_names.items(): + self.controller.set_pin_mode_analog_output( + pin, + channel=_PIN_CHANNEL[pin], + frequency=5000.0, # 5 kHz – inaudible, good for MOSFETs + resolution=8, # 8-bit → 0-255 + ) + + def close(self): + """Turn off all outputs and close the connection.""" + if self.is_master: + self.controller.set_pins_output_to(0) + self.controller.shutdown() + + # ------------------------------------------------------------------ + # Settings + # ------------------------------------------------------------------ + + def commit_settings(self, param: Parameter): + """React to changes in the settings panel (none require live updates here).""" + pass + + # ------------------------------------------------------------------ + # Actuator interface + # ------------------------------------------------------------------ + + def get_actuator_value(self) -> DataActuator: + """ + Return the last PWM value sent to the current axis pin. + + Because the XY-MOS modules have no feedback, we return the memorised + value (what was last written). + """ + pin = self.axis_value # axis_value holds the GPIO pin number + raw = self.controller.get_output_pin_value(pin) + pos = DataActuator(data=float(raw)) + pos = self.get_position_with_scaling(pos) + return pos + + def move_abs(self, value: DataActuator): + """ + Set the PWM duty cycle to an absolute value. + + Parameters + ---------- + value: + Target PWM duty cycle in [0, 255]. + """ + value = self.check_bound(value) + self.target_value = value + value = self.set_position_with_scaling(value) + + pin = self.axis_value + channel = _PIN_CHANNEL[pin] + self.controller.analog_write_and_memorize(pin, int(value.value()), channel=channel) + self.emit_status(ThreadCommand("Update_Status", [f"Pin {pin} -> {int(value.value())}"])) + + def move_rel(self, value: DataActuator): + """ + Change the PWM duty cycle by a relative amount. + + Parameters + ---------- + value: + Relative change (positive = increase, negative = decrease). + """ + value = self.check_bound(self.current_position + value) - self.current_position + self.target_value = value + self.current_position + value = self.set_position_relative_with_scaling(value) + + pin = self.axis_value + channel = _PIN_CHANNEL[pin] + self.controller.analog_write_and_memorize(pin, int(self.target_value.value()), channel=channel) + + def move_home(self): + """Set the current axis to 0 (fan / heater off).""" + pin = self.axis_value + channel = _PIN_CHANNEL[pin] + self.controller.analog_write_and_memorize(pin, 0, channel=channel) + + def stop_motion(self): + """Immediately stop the current axis (set PWM to 0).""" + pin = self.axis_value + channel = _PIN_CHANNEL[pin] + self.controller.analog_write_and_memorize(pin, 0, channel=channel) + + +if __name__ == "__main__": + main(__file__) \ No newline at end of file diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_esp32.py b/tests/test_esp32.py new file mode 100644 index 0000000..a000bf1 --- /dev/null +++ b/tests/test_esp32.py @@ -0,0 +1,67 @@ +""" +Test minimal - ESP32 PWM sur GPIO17 (D8) via telemetrix-aio-esp32 +Lancer avec : python test_esp32_pwm.py + +Ce script teste directement sans PyMoDAQ pour isoler le problème hardware. +""" + +import asyncio +import time + +from telemetrix_aio_esp32 import telemetrix_aio_esp32 + +ESP32_IP = "172.17.50.238" +ESP32_PORT = 31336 +FAN_PIN = 17 # D8 sur Arduino Nano ESP32 +FAN_CH = 0 # canal PWM (0-15, unique par pin) + + +async def main(): + print(f"Connexion a {ESP32_IP}:{ESP32_PORT} ...") + + board = telemetrix_aio_esp32.TelemetrixAioEsp32( + transport_is_wifi=True, + transport_address=ESP32_IP, + ip_port=ESP32_PORT, + autostart=False, + shutdown_on_exception=True, + restart_on_shutdown=False, + ) + + await board.start_aio() + print("Connecte. Firmware OK.") + + # Attente stabilisation + await asyncio.sleep(1) + + # --- Configurer le pin en PWM --- + print(f"Configuration GPIO{FAN_PIN} en analog output (PWM), canal {FAN_CH} ...") + await board.set_pin_mode_analog_output( + FAN_PIN, + channel=FAN_CH, + frequency=5000.0, + resolution=8, + ) + await asyncio.sleep(0.5) + + # --- Test 1 : valeur maximale (255) --- + print("PWM = 255 (100%) pendant 5 secondes ...") + await board.analog_write(FAN_CH, 255) + await asyncio.sleep(5) + + # --- Test 2 : valeur moyenne (128) --- + print("PWM = 128 (50%) pendant 5 secondes ...") + await board.analog_write(FAN_CH, 128) + await asyncio.sleep(5) + + # --- Test 3 : extinction --- + print("PWM = 0 (off) ...") + await board.analog_write(FAN_CH, 0) + await asyncio.sleep(1) + + print("Shutdown.") + await board.shutdown() + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file From a48adf09a0d640e771195034d96c51be5377506f Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:13:10 +0200 Subject: [PATCH 07/65] resolve merge conflicts --- .../daq_move_plugins/daq_move_FanHeater.py | 99 ++++++++++++++++++- .../resources/config_template.toml | 7 ++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 56e6802..3bdb527 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -1 +1,98 @@ -from typing \ No newline at end of file +<<<<<<< Updated upstream +from typing +======= +from typing import Optional + +from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, main, + DataActuatorType, DataActuator) +from pymodaq_utils.utils import ThreadCommand +from pymodaq_gui.parameter import Parameter + +from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi +from pymodaq_plugins_arduino.utils import Config + +config = Config() + + +class DAQ_Move_FanHeater(DAQ_Move_base): + """Plugin PyMoDAQ pour le contrôle du chauffage et du ventilateur + via XY-MOS PWM sur ESP32 en WiFi (Telemetrix). + + Broches ESP32 : + Chauffage → GPIO18 + Ventilateur → GPIO17 + """ + + _controller_units = '%' + is_multiaxes = True + _axis_names = { + 'Heater': config('esp32', 'pins', 'heater_pin'), + 'Fan': config('esp32', 'pins', 'fan_pin'), + } + _epsilon = 0.1 + data_actuator_type = DataActuatorType['DataActuator'] + + params = [ + {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', + 'value': config('esp32', 'ip_address')} + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + + def ini_attributes(self): + self.controller: Optional[ArduinoWifi] = None + + def get_actuator_value(self): + pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) + pos = self.get_position_with_scaling(pos) + return pos + + def close(self): + if self.is_master: + self.controller.set_pins_output_to(0) + self.controller.shutdown() + + def commit_settings(self, param: Parameter): + pass + + def ini_stage(self, controller=None): + self.controller = self.ini_stage_init( + old_controller=controller, + new_controller=None) + + if self.is_master: + self.controller = ArduinoWifi( + ip_address=self.settings['ip_address'] + ) + self.set_pins() + + info = "Heater and Fan ready" + initialized = True + return info, initialized + + def set_pins(self): + for pin in self._axis_names.values(): + self.controller.set_pin_mode_analog_output(pin) + + def move_abs(self, value: DataActuator): + value = self.check_bound(value) + self.target_value = value + value = self.set_position_with_scaling(value) + pwm_value = int(value.value() * 255 / 100) + self.controller.analog_write_and_memorize(self.axis_value, pwm_value) + + def move_rel(self, value: DataActuator): + value = self.check_bound(self.current_position + value) - self.current_position + self.target_value = value + self.current_position + value = self.set_position_relative_with_scaling(value) + pwm_value = int(self.target_value.value() * 255 / 100) + self.controller.analog_write_and_memorize(self.axis_value, pwm_value) + + def move_home(self): + self.controller.analog_write_and_memorize(self.axis_value, 0) + + def stop_motion(self): + pass + + +if __name__ == '__main__': + main(__file__) +>>>>>>> Stashed changes diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index 69ceea6..b2b984f 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -4,6 +4,13 @@ com_port = "COM24" ip_address = "172.17.50.236" ip_port = 31336 +[esp32] +ip_address = "172.17.50.237" # remplace par l'IP de ton ESP32 + +[esp32.pins] +heater_pin = 18 +fan_pin = 17 + [presets] preset_for_colorsynthesizer = "ArduinoLED" From 7ab2efd6c46396776e96fab222914f5e0eaf2f2b Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:14:48 +0200 Subject: [PATCH 08/65] add ArduinoWifi class base --- .../hardware/esp32_telemetrix.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py new file mode 100644 index 0000000..c638515 --- /dev/null +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -0,0 +1,11 @@ +import asyncio +import threading +from threading import Lock +from telemetrix_aio_esp32 import telemetrix_aio_esp32 + +lock = Lock() + +class ArduinoWifi: + def __init__(self, ip_address): + self.pin_values_output = {} + self.analog_pin_values_input = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0} \ No newline at end of file From 01544767ae24881f278b6c646fd057b92358b3a2 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:17:03 +0200 Subject: [PATCH 09/65] feat : add asyncio event loop in dedicated thread --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index c638515..0a6c6ad 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -8,4 +8,8 @@ class ArduinoWifi: def __init__(self, ip_address): self.pin_values_output = {} - self.analog_pin_values_input = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0} \ No newline at end of file + self.analog_pin_values_input = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0} + + self._loop = asyncio.new_event_loop() + self._thread = threading.Thread(target=self._loop.run_forever, daemon=True) + self._thread.start() \ No newline at end of file From e771dc46b0397c4b7a8273604354b92cbb09bddf Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:19:23 +0200 Subject: [PATCH 10/65] feat : add board initialization via asyncio --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index 0a6c6ad..5c2edf5 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -12,4 +12,11 @@ def __init__(self, ip_address): self._loop = asyncio.new_event_loop() self._thread = threading.Thread(target=self._loop.run_forever, daemon=True) - self._thread.start() \ No newline at end of file + self._thread.start() + + async def _init_board(self, ip_address): + self._board = telemetrix_aio_esp32.TelemetrixAioEsp32( + transport_address=ip_address, + autostart=True, + loop=self._loop + ) \ No newline at end of file From c58f4a21628d35d4b6a489a49d62232b106f3a6b Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:20:08 +0200 Subject: [PATCH 11/65] fix: disable autostart to avoid event loop conflict --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index 5c2edf5..2df570a 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -17,6 +17,7 @@ def __init__(self, ip_address): async def _init_board(self, ip_address): self._board = telemetrix_aio_esp32.TelemetrixAioEsp32( transport_address=ip_address, - autostart=True, + autostart=False, loop=self._loop - ) \ No newline at end of file + ) + await self._board.start_aio() \ No newline at end of file From 75254e62b5b45f5acfd6a81b1cc6a12049ddbc03 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:21:01 +0200 Subject: [PATCH 12/65] feat: add _run helper + round_value utility --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index 2df570a..04be3b0 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -20,4 +20,11 @@ async def _init_board(self, ip_address): autostart=False, loop=self._loop ) - await self._board.start_aio() \ No newline at end of file + await self._board.start_aio() + + def _run(self, coro): + return asyncio.run_coroutine_threadsafe(coro, self._loop).result(timeout=5) + + @staticmethod + def round_value(value): + return max(0, min(255, int(value))) \ No newline at end of file From fec161a4c0e1fee1e4100e98b78748c93ce7f698 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:22:52 +0200 Subject: [PATCH 13/65] feat: add set_pin_mode_analog_output --- .../hardware/esp32_telemetrix.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index 04be3b0..b31914d 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -5,6 +5,11 @@ lock = Lock() +PIN_TO_CHANNEL = { + 17: 0, + 18: 1, +} + class ArduinoWifi: def __init__(self, ip_address): self.pin_values_output = {} @@ -27,4 +32,7 @@ def _run(self, coro): @staticmethod def round_value(value): - return max(0, min(255, int(value))) \ No newline at end of file + return max(0, min(255, int(value))) + + def set_pin_mode_analog_output(self, pin): + self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=pin)) \ No newline at end of file From 3a86296220db3d3e2e81617557648083fbbc2d77 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:23:14 +0200 Subject: [PATCH 14/65] fix: correct channel mapping for set_pin_mode_analog_output --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index b31914d..ef4863c 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -35,4 +35,5 @@ def round_value(value): return max(0, min(255, int(value))) def set_pin_mode_analog_output(self, pin): - self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=pin)) \ No newline at end of file + channel = PIN_TO_CHANNEL.get(pin, 0) + self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) \ No newline at end of file From 9730059a5c954702b0bd80dce95c989ec5982bdb Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:24:56 +0200 Subject: [PATCH 15/65] feat: add analog_write --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index ef4863c..e5bb395 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -36,4 +36,8 @@ def round_value(value): def set_pin_mode_analog_output(self, pin): channel = PIN_TO_CHANNEL.get(pin, 0) - self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) \ No newline at end of file + self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) + + def analog_write(self, pin, value): + channel = PIN_TO_CHANNEL.get(pin, 0) + self._run(self._board.analog_write(channel=channel, value=value)) \ No newline at end of file From ea88ccc6c1adb48c820c7f92ed9f737f67cc8a6c Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:25:27 +0200 Subject: [PATCH 16/65] fix: use pin directly for ledcWrite on ESP32 --- src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index e5bb395..c07eca1 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -39,5 +39,4 @@ def set_pin_mode_analog_output(self, pin): self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) def analog_write(self, pin, value): - channel = PIN_TO_CHANNEL.get(pin, 0) - self._run(self._board.analog_write(channel=channel, value=value)) \ No newline at end of file + self._run(self._board.analog_write(channel=pin, value=value)) \ No newline at end of file From 501f9d255c331244cbd1424196b5b7d773f2de00 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:25:53 +0200 Subject: [PATCH 17/65] feat: add analog_write_and_memorize, set_pins_output_to, get_output_pin_value, shutdown --- .../hardware/esp32_telemetrix.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index c07eca1..3b5ae96 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -39,4 +39,24 @@ def set_pin_mode_analog_output(self, pin): self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) def analog_write(self, pin, value): - self._run(self._board.analog_write(channel=pin, value=value)) \ No newline at end of file + self._run(self._board.analog_write(channel=pin, value=value)) + + def analog_write_and_memorize(self, pin, value): + lock.acquire() + value = self.round_value(value) + self.analog_write(pin, value) + self.pin_values_output[pin] = value + lock.release() + + def set_pins_output_to(self, value: int): + lock.acquire() + for pin in self.pin_values_output: + self.analog_write(pin, int(value)) + lock.release() + + def get_output_pin_value(self, pin: int): + return self.pin_values_output.get(pin, 0) + + def shutdown(self): + self._run(self._board.shutdown()) + self._loop.call_soon_threadsafe(self._loop.stop) \ No newline at end of file From d4e64a8750d2a8ae138d9319438bc9ede245cf1d Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 11:27:10 +0200 Subject: [PATCH 18/65] fix: improve board initialization and add debug logs --- .../hardware/esp32_telemetrix.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index 3b5ae96..417027f 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -6,10 +6,11 @@ lock = Lock() PIN_TO_CHANNEL = { - 17: 0, - 18: 1, + 17: 0, # Fan + 18: 1, # Heater } + class ArduinoWifi: def __init__(self, ip_address): self.pin_values_output = {} @@ -19,11 +20,18 @@ def __init__(self, ip_address): self._thread = threading.Thread(target=self._loop.run_forever, daemon=True) self._thread.start() + future = asyncio.run_coroutine_threadsafe( + self._init_board(ip_address), self._loop + ) + future.result(timeout=10) + async def _init_board(self, ip_address): self._board = telemetrix_aio_esp32.TelemetrixAioEsp32( transport_address=ip_address, autostart=False, - loop=self._loop + loop=self._loop, + restart_on_shutdown=False, + shutdown_on_exception=False ) await self._board.start_aio() @@ -36,10 +44,15 @@ def round_value(value): def set_pin_mode_analog_output(self, pin): channel = PIN_TO_CHANNEL.get(pin, 0) + print(f"DEBUG set_pin_mode_analog_output → pin={pin}, channel={channel}") self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) + print(f"DEBUG set_pin_mode_analog_output → done") def analog_write(self, pin, value): + # Core ESP32 v3.x : ledcWrite(pin, value) — pin GPIO directement + print(f"DEBUG analog_write → pin={pin}, value={value}") self._run(self._board.analog_write(channel=pin, value=value)) + print(f"DEBUG analog_write → done") def analog_write_and_memorize(self, pin, value): lock.acquire() From f572955dd2871904b50204fb9aa34c2e72547561 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:01:11 +0200 Subject: [PATCH 19/65] add : DAQ_Move_FanHeater class base --- .../daq_move_plugins/daq_move_FanHeater.py | 73 +------------------ 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 3bdb527..2a5299d 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -1,28 +1,19 @@ -<<<<<<< Updated upstream -from typing -======= from typing import Optional - from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, main, DataActuatorType, DataActuator) -from pymodaq_utils.utils import ThreadCommand from pymodaq_gui.parameter import Parameter - from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi from pymodaq_plugins_arduino.utils import Config config = Config() - class DAQ_Move_FanHeater(DAQ_Move_base): """Plugin PyMoDAQ pour le contrôle du chauffage et du ventilateur via XY-MOS PWM sur ESP32 en WiFi (Telemetrix). - Broches ESP32 : Chauffage → GPIO18 Ventilateur → GPIO17 """ - _controller_units = '%' is_multiaxes = True _axis_names = { @@ -32,67 +23,5 @@ class DAQ_Move_FanHeater(DAQ_Move_base): _epsilon = 0.1 data_actuator_type = DataActuatorType['DataActuator'] - params = [ - {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', - 'value': config('esp32', 'ip_address')} - ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) - - def ini_attributes(self): - self.controller: Optional[ArduinoWifi] = None - - def get_actuator_value(self): - pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) - pos = self.get_position_with_scaling(pos) - return pos - - def close(self): - if self.is_master: - self.controller.set_pins_output_to(0) - self.controller.shutdown() - - def commit_settings(self, param: Parameter): - pass - - def ini_stage(self, controller=None): - self.controller = self.ini_stage_init( - old_controller=controller, - new_controller=None) - - if self.is_master: - self.controller = ArduinoWifi( - ip_address=self.settings['ip_address'] - ) - self.set_pins() - - info = "Heater and Fan ready" - initialized = True - return info, initialized - - def set_pins(self): - for pin in self._axis_names.values(): - self.controller.set_pin_mode_analog_output(pin) - - def move_abs(self, value: DataActuator): - value = self.check_bound(value) - self.target_value = value - value = self.set_position_with_scaling(value) - pwm_value = int(value.value() * 255 / 100) - self.controller.analog_write_and_memorize(self.axis_value, pwm_value) - - def move_rel(self, value: DataActuator): - value = self.check_bound(self.current_position + value) - self.current_position - self.target_value = value + self.current_position - value = self.set_position_relative_with_scaling(value) - pwm_value = int(self.target_value.value() * 255 / 100) - self.controller.analog_write_and_memorize(self.axis_value, pwm_value) - - def move_home(self): - self.controller.analog_write_and_memorize(self.axis_value, 0) - - def stop_motion(self): - pass - - if __name__ == '__main__': - main(__file__) ->>>>>>> Stashed changes + main(__file__) \ No newline at end of file From 20ab984cd1a9fdbdc88722e23863e6c39a865980 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:03:33 +0200 Subject: [PATCH 20/65] add : params and ini_attributes --- .../daq_move_plugins/daq_move_FanHeater.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 2a5299d..0cfe6f1 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -23,5 +23,17 @@ class DAQ_Move_FanHeater(DAQ_Move_base): _epsilon = 0.1 data_actuator_type = DataActuatorType['DataActuator'] + params = [ + {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', + 'value': config('esp32', 'ip_address')} + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + + def ini_attributes(self): + self.controller: Optional[ArduinoWifi] = None + + if __name__ == '__main__': - main(__file__) \ No newline at end of file + main(__file__) + + + From 18df8827d0e95eaa84fa935fb9b234c210b3d705 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:05:46 +0200 Subject: [PATCH 21/65] add : ini_stage and set_pins --- .../daq_move_plugins/daq_move_FanHeater.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 0cfe6f1..997ee7d 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -31,6 +31,39 @@ class DAQ_Move_FanHeater(DAQ_Move_base): def ini_attributes(self): self.controller: Optional[ArduinoWifi] = None + def ini_stage(self, controller=None): + self.controller = self.ini_stage_init( + old_controller=controller, + new_controller=None) + if self.is_master: + self.controller = ArduinoWifi( + ip_address=self.settings['ip_address'] + ) + self.set_pins() + info = "Heater and Fan ready" + initialized = True + return info, initialized + + def set_pins(self): + for pin in self._axis_names.values(): + self.controller.set_pin_mode_analog_output(pin) + + def ini_stage(self, controller=None): + self.controller = self.ini_stage_init( + old_controller=controller, + new_controller=None) + if self.is_master: + self.controller = ArduinoWifi( + ip_address=self.settings['ip_address'] + ) + self.set_pins() + info = "Heater and Fan ready" + initialized = True + return info, initialized + + def set_pins(self): + for pin in self._axis_names.values(): + self.controller.set_pin_mode_analog_output(pin) if __name__ == '__main__': main(__file__) From 7ceac827a60549afbc035a1032190de3400da48c Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:06:18 +0200 Subject: [PATCH 22/65] feat : add get_actuator_value, close, commit_settings --- .../daq_move_plugins/daq_move_FanHeater.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 997ee7d..2b4c65a 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -65,6 +65,19 @@ def set_pins(self): for pin in self._axis_names.values(): self.controller.set_pin_mode_analog_output(pin) + def get_actuator_value(self): + pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) + pos = self.get_position_with_scaling(pos) + return pos + + def close(self): + if self.is_master: + self.controller.set_pins_output_to(0) + self.controller.shutdown() + + def commit_settings(self, param: Parameter): + pass + if __name__ == '__main__': main(__file__) From 133a73af774585d55196a697fcd19daace0ec513 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:06:49 +0200 Subject: [PATCH 23/65] feat : add move_abs, move_rel, move_home, stop_motion --- .../daq_move_plugins/daq_move_FanHeater.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 2b4c65a..759249e 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -78,6 +78,26 @@ def close(self): def commit_settings(self, param: Parameter): pass + def move_abs(self, value: DataActuator): + value = self.check_bound(value) + self.target_value = value + value = self.set_position_with_scaling(value) + pwm_value = int(value.value() * 255 / 100) + self.controller.analog_write_and_memorize(self.axis_value, pwm_value) + + def move_rel(self, value: DataActuator): + value = self.check_bound(self.current_position + value) - self.current_position + self.target_value = value + self.current_position + value = self.set_position_relative_with_scaling(value) + pwm_value = int(self.target_value.value() * 255 / 100) + self.controller.analog_write_and_memorize(self.axis_value, pwm_value) + + def move_home(self): + self.controller.analog_write_and_memorize(self.axis_value, 0) + + def stop_motion(self): + pass + if __name__ == '__main__': main(__file__) From 6b367e413d7c73a83b7946725daf5e345b2780ed Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:10:32 +0200 Subject: [PATCH 24/65] feat : add get_actuator_value, close, commit_settings --- .../daq_move_plugins/daq_move_FanHeater.py | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 759249e..52fb762 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -1,19 +1,25 @@ from typing import Optional + from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, main, DataActuatorType, DataActuator) +from pymodaq_utils.utils import ThreadCommand from pymodaq_gui.parameter import Parameter + from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi from pymodaq_plugins_arduino.utils import Config config = Config() + class DAQ_Move_FanHeater(DAQ_Move_base): """Plugin PyMoDAQ pour le contrôle du chauffage et du ventilateur via XY-MOS PWM sur ESP32 en WiFi (Telemetrix). + Broches ESP32 : Chauffage → GPIO18 Ventilateur → GPIO17 """ + _controller_units = '%' is_multiaxes = True _axis_names = { @@ -24,39 +30,37 @@ class DAQ_Move_FanHeater(DAQ_Move_base): data_actuator_type = DataActuatorType['DataActuator'] params = [ - {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', - 'value': config('esp32', 'ip_address')} - ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', + 'value': config('esp32', 'ip_address')} + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) def ini_attributes(self): self.controller: Optional[ArduinoWifi] = None - def ini_stage(self, controller=None): - self.controller = self.ini_stage_init( - old_controller=controller, - new_controller=None) + def get_actuator_value(self): + pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) + pos = self.get_position_with_scaling(pos) + return pos + + def close(self): if self.is_master: - self.controller = ArduinoWifi( - ip_address=self.settings['ip_address'] - ) - self.set_pins() - info = "Heater and Fan ready" - initialized = True - return info, initialized + self.controller.set_pins_output_to(0) + self.controller.shutdown() - def set_pins(self): - for pin in self._axis_names.values(): - self.controller.set_pin_mode_analog_output(pin) + def commit_settings(self, param: Parameter): + pass def ini_stage(self, controller=None): self.controller = self.ini_stage_init( old_controller=controller, new_controller=None) + if self.is_master: self.controller = ArduinoWifi( ip_address=self.settings['ip_address'] ) self.set_pins() + info = "Heater and Fan ready" initialized = True return info, initialized @@ -65,19 +69,6 @@ def set_pins(self): for pin in self._axis_names.values(): self.controller.set_pin_mode_analog_output(pin) - def get_actuator_value(self): - pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) - pos = self.get_position_with_scaling(pos) - return pos - - def close(self): - if self.is_master: - self.controller.set_pins_output_to(0) - self.controller.shutdown() - - def commit_settings(self, param: Parameter): - pass - def move_abs(self, value: DataActuator): value = self.check_bound(value) self.target_value = value @@ -98,8 +89,6 @@ def move_home(self): def stop_motion(self): pass -if __name__ == '__main__': - main(__file__) - - +if __name__ == '__main__': + main(__file__) \ No newline at end of file From fced7763638bb1181450f66060a4d13f11853b87 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 12 May 2026 12:50:19 +0200 Subject: [PATCH 25/65] fix: remove debug prints and clean up code --- .../daq_move_plugins/daq_move_FanHeater.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 52fb762..443247c 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -20,7 +20,7 @@ class DAQ_Move_FanHeater(DAQ_Move_base): Ventilateur → GPIO17 """ - _controller_units = '%' + _controller_units = '' is_multiaxes = True _axis_names = { 'Heater': config('esp32', 'pins', 'heater_pin'), @@ -73,14 +73,14 @@ def move_abs(self, value: DataActuator): value = self.check_bound(value) self.target_value = value value = self.set_position_with_scaling(value) - pwm_value = int(value.value() * 255 / 100) + pwm_value = int(value.value()) self.controller.analog_write_and_memorize(self.axis_value, pwm_value) def move_rel(self, value: DataActuator): value = self.check_bound(self.current_position + value) - self.current_position self.target_value = value + self.current_position value = self.set_position_relative_with_scaling(value) - pwm_value = int(self.target_value.value() * 255 / 100) + pwm_value = int(self.target_value.value()) self.controller.analog_write_and_memorize(self.axis_value, pwm_value) def move_home(self): From f530fa25486dc63dc959a1954669c8917ede710e Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 09:31:02 +0200 Subject: [PATCH 26/65] fix: remove files --- .../hardware/arduino_telemetrix_esp32_wifi.py | 106 ------------------ .../resources/config_template.toml | 2 +- 2 files changed, 1 insertion(+), 107 deletions(-) delete mode 100644 src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py deleted file mode 100644 index 9a0313b..0000000 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix_esp32_wifi.py +++ /dev/null @@ -1,106 +0,0 @@ -import numbers -from typing import Optional -from threading import Lock -import asyncio - -from telemetrix_aio_esp32 import telemetrix_aio_esp32 - -lock = Lock() - - -class ArduinoWiFi(telemetrix_aio_esp32.TelemetrixAioEsp32): - """Arduino Nano ESP32 WiFi connection wrapper. - - Child class of TelemetrixAioEsp32. Provides a synchronous interface - compatible with PyMoDAQ plugins, mirroring the Arduino API. - - Attributes - ---------- - ip_address : str - pin_values_output : dict - analog_pin_values_input : dict - """ - - COM_PORTS = [] # No serial port for WiFi — kept for UI compatibility - - def __init__(self, ip_address: Optional[str] = None, ip_port: int = 31336, *args, **kwargs): - telemetrix_aio_esp32.TelemetrixAioEsp32.__init__( - self, transport_address=ip_address, ip_port=ip_port, - autostart=False, *args, **kwargs) - self.pin_values_output = {} - self.analog_pin_values_input = {0: 0, - 1: 0, - 2: 0, - 3: 0, - 4: 0, - 5: 0} # Initialized dictionary for 6 analog channels - - - def _run(self, coro): - """Run a coroutine from a synchronous context.""" - loop = asyncio.get_event_loop() - return loop.run_until_complete(coro) - - - @staticmethod - def round_value(value): - return max(0, min(255, int(value))) - - def set_pin_mode_analog_output(self, pin: int): - """Configure a pin as PWM analog output.""" - self._run(super().set_pin_mode_analog_output(pin)) - - def set_pins_output_to(self, value: int): - lock.acquire() - for pin in self.pin_values_output: - self._run(self.analog_write(pin, int(value))) - lock.release() - - def analog_write_and_memorize(self, pin: int, value: int): - lock.acquire() - value = self.round_value(value) - self._run(self.analog_write(pin, value)) - self.pin_values_output[pin] = value - lock.release() - - def get_output_pin_value(self, pin: int) -> numbers.Number: - value = self.pin_values_output.get(pin, 0) - return value - - - def read_analog_pin(self, data): - """ - Used as a callback function to read the value of the analog inputs. - Data[0]: pin_type (not used here) - Data[1]: pin_number: i.e. 0 is A0 etc. - Data[2]: pin_value: an integer between 0 and 1023 - Data[3]: raw_time_stamp (not used here) - :param data: a list in which are loaded the acquisition parameter analog input - :return: a dictionary with the following structure {pin_number(int):pin_value(int)} - """ - self.analog_pin_values_input[data[1]] = data[2] - - def set_analog_input(self, pin: int): - """ - Activate the analog pin, make an acquisition, write in the callback, stop the analog reporting. - :param pin: pin number, 1 is A1 etc. - :return: acquisition parameters in the declared callback - """ - lock.acquire() - self._run(self.set_pin_mode_analog_input(pin, differential=0, callback=self.read_analog_pin)) - lock.release() - - - async def shutdown(self): - """Terminate the WiFi connection.""" - await super().shutdown() - - -if __name__ == '__main__': - async def test(): - tele = ArduinoWiFi(ip_address='172.17.50.236', ip_port=31336) - await tele.start_aio() - print("Connexion OK !") - await tele.shutdown() - - asyncio.run(test()) \ No newline at end of file diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index b2b984f..38887ac 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -5,7 +5,7 @@ ip_address = "172.17.50.236" ip_port = 31336 [esp32] -ip_address = "172.17.50.237" # remplace par l'IP de ton ESP32 +ip_address = "172.17.50.53" # remplace par l'IP de ton ESP32 [esp32.pins] heater_pin = 18 From 9d0a1d92a33e53e2de89186b0ea450758b799051 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 11:49:01 +0200 Subject: [PATCH 27/65] add MAX31865 class --- .../hardware/sensors/__init__.py | 0 .../hardware/sensors/max31965/__init__.py | 0 .../hardware/sensors/max31965/spi_max31865.py | 29 +++++++++++++++++++ .../resources/config_template.toml | 2 +- 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/pymodaq_plugins_arduino/hardware/sensors/__init__.py create mode 100644 src/pymodaq_plugins_arduino/hardware/sensors/max31965/__init__.py create mode 100644 src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/__init__.py b/src/pymodaq_plugins_arduino/hardware/sensors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/__init__.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py new file mode 100644 index 0000000..7e32387 --- /dev/null +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py @@ -0,0 +1,29 @@ +from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi + +# Broches SPI Nano ESP32 (By GPIO number) +CS_PIN = 21 # D10 = GPIO21 +CS = [21] + +# Registres MAX31865 +MAX31865_CONFIG_REG = 0x00 +MAX31865_CONFIG_BIAS = 0x80 +MAX31865_CONFIG_MODEAUTO = 0x40 +MAX31865_RTDMSB_REG = 0x01 + +# Constantes PT100 +RTD_NOMINAL = 100.0 # résistance nominale PT100 +RTD_REFERENCE = 430.0 # résistance de référence sur le module +RTD_A = 3.9083e-3 +RTD_B = -5.775e-7 + + +class MAX31865(ArduinoWifi): + """Driver pour le capteur PT100 via MAX31865 SPI. + + Broches SPI Nano ESP32 : + SCK → D13 = GPIO48 + MISO → D12 = GPIO47 + MOSI → D11 = GPIO38 + CS → D10 = GPIO21 + """ + pass \ No newline at end of file diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index 38887ac..c702f00 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -5,7 +5,7 @@ ip_address = "172.17.50.236" ip_port = 31336 [esp32] -ip_address = "172.17.50.53" # remplace par l'IP de ton ESP32 +ip_address = "172.17.50.53" [esp32.pins] heater_pin = 18 From fc6d95eed00057b15940234a30f8499a3283ba27 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 11:52:39 +0200 Subject: [PATCH 28/65] add MAX31865 class pattern injection --- .../hardware/sensors/max31965/spi_max31865.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py index 7e32387..5934a06 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py @@ -1,7 +1,7 @@ from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi # Broches SPI Nano ESP32 (By GPIO number) -CS_PIN = 21 # D10 = GPIO21 +CS_PIN = 21 CS = [21] # Registres MAX31865 @@ -11,13 +11,13 @@ MAX31865_RTDMSB_REG = 0x01 # Constantes PT100 -RTD_NOMINAL = 100.0 # résistance nominale PT100 -RTD_REFERENCE = 430.0 # résistance de référence sur le module +RTD_NOMINAL = 100.0 +RTD_REFERENCE = 430.0 RTD_A = 3.9083e-3 RTD_B = -5.775e-7 -class MAX31865(ArduinoWifi): +class MAX31865: """Driver pour le capteur PT100 via MAX31865 SPI. Broches SPI Nano ESP32 : @@ -26,4 +26,7 @@ class MAX31865(ArduinoWifi): MOSI → D11 = GPIO38 CS → D10 = GPIO21 """ - pass \ No newline at end of file + + def __init__(self, controller: ArduinoWifi): + self._board = controller._board + self._run = controller._run \ No newline at end of file From 70fc8a229689f69994aee33d56b8be842ee831c4 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 11:53:28 +0200 Subject: [PATCH 29/65] Add SPI initialization and MAX31865 config --- .../hardware/sensors/max31965/spi_max31865.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py index 5934a06..6ff2904 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py @@ -29,4 +29,14 @@ class MAX31865: def __init__(self, controller: ArduinoWifi): self._board = controller._board - self._run = controller._run \ No newline at end of file + self._run = controller._run + + def ini_max31865(self): + """Initialise le bus SPI et configure le MAX31865 en mode automatique.""" + self._run(self._board.set_pin_mode_spi(CS)) + + # Configuration : bias ON + mode auto conversion + config = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO + self._run(self._board.spi_cs_control(CS_PIN, 0)) + self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config])) + self._run(self._board.spi_cs_control(CS_PIN, 1)) \ No newline at end of file From 1072c022e25b13c212c1d151b0dde10b05059ecb Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 11:54:12 +0200 Subject: [PATCH 30/65] Add read_rtd_resistance method via SPI --- .../hardware/sensors/max31965/spi_max31865.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py index 6ff2904..16c44c0 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py @@ -39,4 +39,25 @@ def ini_max31865(self): config = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO self._run(self._board.spi_cs_control(CS_PIN, 0)) self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config])) - self._run(self._board.spi_cs_control(CS_PIN, 1)) \ No newline at end of file + self._run(self._board.spi_cs_control(CS_PIN, 1)) + + def read_rtd_resistance(self) -> float: + """Lit les registres RTD du MAX31865 et retourne la résistance en ohms.""" + data = [] + + async def spi_callback(report): + data.extend(report[3:]) + + self._run(self._board.spi_cs_control(CS_PIN, 0)) + self._run(self._board.spi_read_blocking( + MAX31865_RTDMSB_REG, + 2, + call_back=spi_callback + )) + self._run(self._board.spi_cs_control(CS_PIN, 1)) + + msb = data[0] + lsb = data[1] + rtd_raw = ((msb << 8) | lsb) >> 1 # retire le bit de fault + resistance = (rtd_raw / 32768.0) * RTD_REFERENCE + return resistance \ No newline at end of file From d5104bdbefe4de16bc8f267bed2d98bb2d2a4131 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 11:54:54 +0200 Subject: [PATCH 31/65] Add temperature conversion and get_temperature method --- .../hardware/sensors/max31965/spi_max31865.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py index 16c44c0..a569e28 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py @@ -60,4 +60,21 @@ async def spi_callback(report): lsb = data[1] rtd_raw = ((msb << 8) | lsb) >> 1 # retire le bit de fault resistance = (rtd_raw / 32768.0) * RTD_REFERENCE - return resistance \ No newline at end of file + return resistance + + def resistance_to_temperature(self, resistance: float) -> float: + """Convertit la résistance PT100 en température (°C) + via l'équation de Callendar-Van Dusen.""" + z1 = -RTD_A + z2 = RTD_A ** 2 - (4 * RTD_B) + z3 = (4 * RTD_B) / RTD_NOMINAL + z4 = 2 * RTD_B + + temp = z2 + (z3 * resistance) + temp = (temp ** 0.5 + z1) / z4 + return temp + + def get_temperature(self) -> float: + """Retourne directement la température en °C.""" + resistance = self.read_rtd_resistance() + return self.resistance_to_temperature(resistance) \ No newline at end of file From cb25e876d0b130bece349783dc7b84c997ee3776 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 28 May 2026 12:04:02 +0200 Subject: [PATCH 32/65] Add DAQ_0DViewer_PT100 class and delete useless files and folders --- src/pymodaq_plugins_arduino/app/__init__.py | 0 .../plugins_0D/daq_0Dviewer_pt100.py | 24 +++++++++++++++++++ .../daq_viewer_plugins/plugins_1D/__init__.py | 13 ---------- .../daq_viewer_plugins/plugins_2D/__init__.py | 14 ----------- .../daq_viewer_plugins/plugins_ND/__init__.py | 13 ---------- .../exporters/__init__.py | 6 ----- .../models/__init__.py | 6 ----- 7 files changed, 24 insertions(+), 52 deletions(-) delete mode 100644 src/pymodaq_plugins_arduino/app/__init__.py create mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py delete mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py delete mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py delete mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py delete mode 100644 src/pymodaq_plugins_arduino/exporters/__init__.py delete mode 100644 src/pymodaq_plugins_arduino/models/__init__.py diff --git a/src/pymodaq_plugins_arduino/app/__init__.py b/src/pymodaq_plugins_arduino/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py new file mode 100644 index 0000000..baeac0f --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py @@ -0,0 +1,24 @@ +from typing import Optional + +import numpy as np +from pymodaq.utils.data import DataFromPlugins, DataToExport +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main +from pymodaq.utils.parameter import Parameter + +from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi +from pymodaq_plugins_arduino.hardware.sensors.max31865.esp32_telemetrix_max31865 import MAX31865 +from pymodaq_plugins_arduino.utils import Config + +config = Config() + + +class DAQ_0DViewer_PT100(DAQ_Viewer_base): + """Plugin PyMoDAQ pour la lecture de température via MAX31865 et sonde PT100. + + Broches SPI Nano ESP32 : + SCK → D13 = GPIO48 + MISO → D12 = GPIO47 + MOSI → D11 = GPIO38 + CS → D10 = GPIO21 + """ + _controller_units = '°C' \ No newline at end of file diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py deleted file mode 100644 index 3fafded..0000000 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import importlib -from pathlib import Path -from ... import set_logger -logger = set_logger('viewer1D_plugins', add_to_console=False) - -for path in Path(__file__).parent.iterdir(): - try: - if '__init__' not in str(path): - importlib.import_module('.' + path.stem, __package__) - except Exception as e: - logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e))) - pass - diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py deleted file mode 100644 index cbcf921..0000000 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -import importlib -from pathlib import Path -from ... import set_logger -logger = set_logger('viewer2D_plugins', add_to_console=False) - -for path in Path(__file__).parent.iterdir(): - try: - if '__init__' not in str(path): - importlib.import_module('.' + path.stem, __package__) - except Exception as e: - logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e))) - pass - - diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py deleted file mode 100644 index 527b2d8..0000000 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import importlib -from pathlib import Path -from ... import set_logger -logger = set_logger('viewerND_plugins', add_to_console=False) - -for path in Path(__file__).parent.iterdir(): - try: - if '__init__' not in str(path): - importlib.import_module('.' + path.stem, __package__) - except Exception as e: - logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e))) - pass - diff --git a/src/pymodaq_plugins_arduino/exporters/__init__.py b/src/pymodaq_plugins_arduino/exporters/__init__.py deleted file mode 100644 index 180d4dd..0000000 --- a/src/pymodaq_plugins_arduino/exporters/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created the 01/06/2023 - -@author: Sebastien Weber -""" diff --git a/src/pymodaq_plugins_arduino/models/__init__.py b/src/pymodaq_plugins_arduino/models/__init__.py deleted file mode 100644 index 180d4dd..0000000 --- a/src/pymodaq_plugins_arduino/models/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created the 01/06/2023 - -@author: Sebastien Weber -""" From 30e4f76e1cf34654e87640051bb3785fe0437090 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:20:34 +0200 Subject: [PATCH 33/65] Add params and attribute --- .../plugins_0D/daq_0Dviewer_pt100.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py index baeac0f..db23e82 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py @@ -21,4 +21,13 @@ class DAQ_0DViewer_PT100(DAQ_Viewer_base): MOSI → D11 = GPIO38 CS → D10 = GPIO21 """ - _controller_units = '°C' \ No newline at end of file + _controller_units = '°C' + + params = comon_parameters + [ + {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', + 'value': config('esp32', 'ip_address')}, + ] + + def ini_attributes(self): + self.controller: Optional[ArduinoWifi] = None + self.max31865: Optional[MAX31865] = None \ No newline at end of file From 80bd5fe517c053f6cc773597c12f27771303adad Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:21:59 +0200 Subject: [PATCH 34/65] Add ini_detector and close methods --- .../plugins_0D/daq_0Dviewer_pt100.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py index db23e82..40c7a76 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py @@ -30,4 +30,25 @@ class DAQ_0DViewer_PT100(DAQ_Viewer_base): def ini_attributes(self): self.controller: Optional[ArduinoWifi] = None - self.max31865: Optional[MAX31865] = None \ No newline at end of file + self.max31865: Optional[MAX31865] = None + + def ini_detector(self, controller=None): + """Initialisation de la communication WiFi avec l'ESP32.""" + self.ini_detector_init(slave_controller=controller) + + if self.is_master: + self.controller = ArduinoWifi( + ip_address=self.settings['ip_address'] + ) + + self.max31865 = MAX31865(controller=self.controller) + self.max31865.ini_max31865() + + info = "PT100 ready" + initialized = True + return info, initialized + + def close(self): + """Termine la communication avec l'ESP32.""" + if self.is_master: + self.controller.shutdown() \ No newline at end of file From 5b3d0b853cc8d4254abb8ca791c4d7992fe02ddb Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:24:59 +0200 Subject: [PATCH 35/65] Add grab_data method and temperature acquisition --- .../plugins_0D/daq_0Dviewer_pt100.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py index 40c7a76..7abb7cb 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py @@ -51,4 +51,18 @@ def ini_detector(self, controller=None): def close(self): """Termine la communication avec l'ESP32.""" if self.is_master: - self.controller.shutdown() \ No newline at end of file + self.controller.shutdown() + + def grab_data(self, Naverage=1, **kwargs): + """Lecture de la température via MAX31865.""" + temperature = self.max31865.get_temperature() + + self.dte_signal.emit(DataToExport( + name='PT100', + data=[DataFromPlugins( + name='Temperature', + data=[np.array([temperature])], + dim='Data0D', + labels=['Temperature (°C)'] + )] + )) \ No newline at end of file From 8db22c65c611815e1352bf4113252e8568f90041 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:25:42 +0200 Subject: [PATCH 36/65] Add stop method --- .../plugins_0D/daq_0Dviewer_pt100.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py index 7abb7cb..f009d35 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py @@ -65,4 +65,15 @@ def grab_data(self, Naverage=1, **kwargs): dim='Data0D', labels=['Temperature (°C)'] )] - )) \ No newline at end of file + )) + + def commit_settings(self, param: Parameter): + """Applique les changements de paramètres.""" + pass + + def stop(self): + """Arrête l'acquisition.""" + pass + + if __name__ == '__main__': + main(__file__) \ No newline at end of file From 8302ea18d48b88cc4e468094691f07271c503fd2 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:35:39 +0200 Subject: [PATCH 37/65] Fix stop and main --- .../plugins_0D/daq_0Dviewer_pt100.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py index f009d35..984efa6 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py @@ -33,30 +33,23 @@ def ini_attributes(self): self.max31865: Optional[MAX31865] = None def ini_detector(self, controller=None): - """Initialisation de la communication WiFi avec l'ESP32.""" self.ini_detector_init(slave_controller=controller) - if self.is_master: self.controller = ArduinoWifi( ip_address=self.settings['ip_address'] ) - self.max31865 = MAX31865(controller=self.controller) self.max31865.ini_max31865() - info = "PT100 ready" initialized = True return info, initialized def close(self): - """Termine la communication avec l'ESP32.""" if self.is_master: self.controller.shutdown() def grab_data(self, Naverage=1, **kwargs): - """Lecture de la température via MAX31865.""" temperature = self.max31865.get_temperature() - self.dte_signal.emit(DataToExport( name='PT100', data=[DataFromPlugins( @@ -67,13 +60,12 @@ def grab_data(self, Naverage=1, **kwargs): )] )) - def commit_settings(self, param: Parameter): - """Applique les changements de paramètres.""" - pass + def commit_settings(self, param: Parameter): + pass + + def stop(self): + pass - def stop(self): - """Arrête l'acquisition.""" - pass - if __name__ == '__main__': - main(__file__) \ No newline at end of file +if __name__ == '__main__': + main(__file__) \ No newline at end of file From 1cbf7070e7d4073c6dccb5c60646110609a80e3b Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:46:08 +0200 Subject: [PATCH 38/65] Fix change name file --- .../plugins_0D/{daq_0Dviewer_pt100.py => daq_0Dviewer_PT100.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/{daq_0Dviewer_pt100.py => daq_0Dviewer_PT100.py} (100%) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py similarity index 100% rename from src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_pt100.py rename to src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py From 9d75aa86e54e3c2b5a0d64173152c8bb5efacfd5 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:51:17 +0200 Subject: [PATCH 39/65] Fix change name file --- .../plugins_0D/{daq_0Dviewer_PT100.py => daq_0Dviewer_Pt100.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/{daq_0Dviewer_PT100.py => daq_0Dviewer_Pt100.py} (100%) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Pt100.py similarity index 100% rename from src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py rename to src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Pt100.py From ddb422bc83d949672e77c4fa7b5b010e4c3a0eec Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:57:20 +0200 Subject: [PATCH 40/65] fix: rename max31965 to max31865 rename file --- .../plugins_0D/{daq_0Dviewer_Pt100.py => daq_0Dviewer_PT100.py} | 0 .../hardware/sensors/{max31965 => max31865}/__init__.py | 0 .../hardware/sensors/{max31965 => max31865}/spi_max31865.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/{daq_0Dviewer_Pt100.py => daq_0Dviewer_PT100.py} (100%) rename src/pymodaq_plugins_arduino/hardware/sensors/{max31965 => max31865}/__init__.py (100%) rename src/pymodaq_plugins_arduino/hardware/sensors/{max31965 => max31865}/spi_max31865.py (100%) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Pt100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py similarity index 100% rename from src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Pt100.py rename to src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/__init__.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/__init__.py similarity index 100% rename from src/pymodaq_plugins_arduino/hardware/sensors/max31965/__init__.py rename to src/pymodaq_plugins_arduino/hardware/sensors/max31865/__init__.py diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py similarity index 100% rename from src/pymodaq_plugins_arduino/hardware/sensors/max31965/spi_max31865.py rename to src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py From 79660adb191225e3b9c6bde7b99e6d441abdc1c5 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 09:59:51 +0200 Subject: [PATCH 41/65] Fix update import path to spi_max31865 --- .../daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py index 984efa6..8710ddc 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py @@ -6,7 +6,7 @@ from pymodaq.utils.parameter import Parameter from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi -from pymodaq_plugins_arduino.hardware.sensors.max31865.esp32_telemetrix_max31865 import MAX31865 +from pymodaq_plugins_arduino.hardware.sensors.max31865.spi_max31865 import MAX31865 from pymodaq_plugins_arduino.utils import Config config = Config() From da8a67d5bce31edd486759eadf43a8ebe75682c9 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Mon, 1 Jun 2026 10:14:23 +0200 Subject: [PATCH 42/65] Fix use method for wait SPI callback reponse --- .../hardware/sensors/max31865/spi_max31865.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index a569e28..5a09ca7 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -43,22 +43,29 @@ def ini_max31865(self): def read_rtd_resistance(self) -> float: """Lit les registres RTD du MAX31865 et retourne la résistance en ohms.""" + import asyncio data = [] + event = asyncio.Event() async def spi_callback(report): data.extend(report[3:]) + event.set() - self._run(self._board.spi_cs_control(CS_PIN, 0)) - self._run(self._board.spi_read_blocking( - MAX31865_RTDMSB_REG, - 2, - call_back=spi_callback - )) - self._run(self._board.spi_cs_control(CS_PIN, 1)) + async def read(): + await self._board.spi_cs_control(CS_PIN, 0) + await self._board.spi_read_blocking( + MAX31865_RTDMSB_REG, + 2, + call_back=spi_callback + ) + await self._board.spi_cs_control(CS_PIN, 1) + await asyncio.wait_for(event.wait(), timeout=5) + + self._run(read()) msb = data[0] lsb = data[1] - rtd_raw = ((msb << 8) | lsb) >> 1 # retire le bit de fault + rtd_raw = ((msb << 8) | lsb) >> 1 resistance = (rtd_raw / 32768.0) * RTD_REFERENCE return resistance From f2692e6af14afef48489f8e6bb21f2de0753f82c Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 2 Jun 2026 09:37:09 +0200 Subject: [PATCH 43/65] Feat SPI pins configurable from config_template.toml --- .../hardware/sensors/max31865/spi_max31865.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 5a09ca7..17c01c9 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -1,8 +1,11 @@ from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi -# Broches SPI Nano ESP32 (By GPIO number) -CS_PIN = 21 -CS = [21] +# Lecture des broches via fichier conf +CS_PIN = config('max31865', 'cs_pin') +SCK_PIN = config('max31865', 'sck_pin') +MISO_PIN = config('max31865', 'miso_pin') +MOSI_PIN = config('max31865', 'mosi_pin') + # Registres MAX31865 MAX31865_CONFIG_REG = 0x00 @@ -20,11 +23,7 @@ class MAX31865: """Driver pour le capteur PT100 via MAX31865 SPI. - Broches SPI Nano ESP32 : - SCK → D13 = GPIO48 - MISO → D12 = GPIO47 - MOSI → D11 = GPIO38 - CS → D10 = GPIO21 + Les broches SPI sont lues depuis config_template.toml """ def __init__(self, controller: ArduinoWifi): @@ -32,13 +31,22 @@ def __init__(self, controller: ArduinoWifi): self._run = controller._run def ini_max31865(self): - """Initialise le bus SPI et configure le MAX31865 en mode automatique.""" - self._run(self._board.set_pin_mode_spi(CS)) + """Initialise le bus SPI avec les broches du fichier de config et + configure le MAX31865 en mode automatique.""" + + # Initialisation SPI avec les broches configurables + # Le firmware reçoit : [sck, miso, mosi, nb_cs, cs_pin1, ...] + self._run(self._board.set_pin_mode_spi( + CS, + sck=SCK_PIN, + miso=MISO_PIN, + mosi=MOSI_PIN + )) # Configuration : bias ON + mode auto conversion - config = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO + config_byte = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO self._run(self._board.spi_cs_control(CS_PIN, 0)) - self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config])) + self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config_byte])) self._run(self._board.spi_cs_control(CS_PIN, 1)) def read_rtd_resistance(self) -> float: @@ -58,8 +66,9 @@ async def read(): 2, call_back=spi_callback ) - await self._board.spi_cs_control(CS_PIN, 1) + # On attend la réponse AVANT de relâcher le CS await asyncio.wait_for(event.wait(), timeout=5) + await self._board.spi_cs_control(CS_PIN, 1) self._run(read()) @@ -84,4 +93,4 @@ def resistance_to_temperature(self, resistance: float) -> float: def get_temperature(self) -> float: """Retourne directement la température en °C.""" resistance = self.read_rtd_resistance() - return self.resistance_to_temperature(resistance) \ No newline at end of file + return self.resistance_to_temperature(resistance) From 606e3b4de35da81f34c6281be67cff7d9fd1307c Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 2 Jun 2026 09:37:38 +0200 Subject: [PATCH 44/65] Add max31865 pins config --- .../resources/config_template.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index c702f00..e21097d 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -34,3 +34,12 @@ pos_2 = 80 [FanHeater.pins] heater_pin = 9 heater_fan_pin = 8 + +[max31865] +#Broches SPI pour le capteur MAX31865 / PT100 +#Vameirs par défaut : Nano ESP32 en mode GPIO Legacy +sck_pin = 48 +miso_pin = 47 +mosi_pin = 38 +cs_pin = 21 + From 7a7780f947869b9065e039b7bc15665909443622 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 2 Jun 2026 09:38:56 +0200 Subject: [PATCH 45/65] Feat SPI pins in Pymodaq dashboard --- .../plugins_0D/daq_0Dviewer_PT100.py | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py index 8710ddc..efef28c 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py @@ -15,17 +15,30 @@ class DAQ_0DViewer_PT100(DAQ_Viewer_base): """Plugin PyMoDAQ pour la lecture de température via MAX31865 et sonde PT100. - Broches SPI Nano ESP32 : - SCK → D13 = GPIO48 - MISO → D12 = GPIO47 - MOSI → D11 = GPIO38 - CS → D10 = GPIO21 + Les broches SPI sont configurables depuis l'interface PyMoDAQ + ou depuis le fichier config_template.toml : """ _controller_units = '°C' params = comon_parameters + [ - {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', - 'value': config('esp32', 'ip_address')}, + {'title': 'Connexion', 'name': 'connection', 'type': 'group', 'children': [ + {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', + 'value': config('esp32', 'ip_address')}, + ]}, + {'title': 'SPI Pins (GPIO)', 'name': 'spi_pins', 'type': 'group', 'children': [ + {'title': 'SCK pin:', 'name': 'sck_pin', 'type': 'int', + 'value': config('max31865', 'sck_pin'), + 'tip': 'Nano ESP32 legacy : D13 = GPIO48'}, + {'title': 'MISO pin:', 'name': 'miso_pin', 'type': 'int', + 'value': config('max31865', 'miso_pin'), + 'tip': 'Nano ESP32 legacy : D12 = GPIO47'}, + {'title': 'MOSI pin:', 'name': 'mosi_pin', 'type': 'int', + 'value': config('max31865', 'mosi_pin'), + 'tip': 'Nano ESP32 legacy : D11 = GPIO38'}, + {'title': 'CS pin:', 'name': 'cs_pin', 'type': 'int', + 'value': config('max31865', 'cs_pin'), + 'tip': 'Nano ESP32 legacy : D10 = GPIO21'}, + ]}, ] def ini_attributes(self): @@ -36,9 +49,15 @@ def ini_detector(self, controller=None): self.ini_detector_init(slave_controller=controller) if self.is_master: self.controller = ArduinoWifi( - ip_address=self.settings['ip_address'] + ip_address=self.settings['connection', 'ip_address'] ) - self.max31865 = MAX31865(controller=self.controller) + self.max31865 = MAX31865( + controller=self.controller, + sck_pin=self.settings['spi_pins', 'sck_pin'], + miso_pin=self.settings['spi_pins', 'miso_pin'], + mosi_pin=self.settings['spi_pins', 'mosi_pin'], + cs_pin=self.settings['spi_pins', 'cs_pin'], + ) self.max31865.ini_max31865() info = "PT100 ready" initialized = True From fbc0756b6a9824749fa70027ead97adafadfdb04 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 2 Jun 2026 09:57:00 +0200 Subject: [PATCH 46/65] Feat Custom SPI pins via SPI_INIT Command --- .../hardware/sensors/max31865/spi_max31865.py | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 17c01c9..902e0ac 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -31,23 +31,21 @@ def __init__(self, controller: ArduinoWifi): self._run = controller._run def ini_max31865(self): - """Initialise le bus SPI avec les broches du fichier de config et - configure le MAX31865 en mode automatique.""" - - # Initialisation SPI avec les broches configurables - # Le firmware reçoit : [sck, miso, mosi, nb_cs, cs_pin1, ...] - self._run(self._board.set_pin_mode_spi( - CS, - sck=SCK_PIN, - miso=MISO_PIN, - mosi=MOSI_PIN - )) - - # Configuration : bias ON + mode auto conversion + """Initialise le bus SPI avec les broches configurables.""" + + async def _init(): + # Envoyer directement SPI_INIT avec [sck, miso, mosi, nb_cs, cs_pin] + command = [22, self.sck_pin, self.miso_pin, self.mosi_pin, 1, self.cs_pin] + await self._board.transport.write(bytes([len(command)] + command)) + await asyncio.sleep(0.1) + + self._run(_init()) + + # Configuration MAX31865 config_byte = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO - self._run(self._board.spi_cs_control(CS_PIN, 0)) + self._run(self._board.spi_cs_control(self.cs_pin, 0)) self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config_byte])) - self._run(self._board.spi_cs_control(CS_PIN, 1)) + self._run(self._board.spi_cs_control(self.cs_pin, 1)) def read_rtd_resistance(self) -> float: """Lit les registres RTD du MAX31865 et retourne la résistance en ohms.""" From 5e44f2e3de7fff0d6cefa413f50285baefb3524b Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 2 Jun 2026 11:39:46 +0200 Subject: [PATCH 47/65] Fix SPI protocol --- .../hardware/sensors/max31865/spi_max31865.py | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 902e0ac..4e338a0 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -1,11 +1,8 @@ +import asyncio from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi +from pymodaq_plugins_arduino.utils import Config -# Lecture des broches via fichier conf -CS_PIN = config('max31865', 'cs_pin') -SCK_PIN = config('max31865', 'sck_pin') -MISO_PIN = config('max31865', 'miso_pin') -MOSI_PIN = config('max31865', 'mosi_pin') - +config = Config() # Registres MAX31865 MAX31865_CONFIG_REG = 0x00 @@ -22,34 +19,29 @@ class MAX31865: """Driver pour le capteur PT100 via MAX31865 SPI. - - Les broches SPI sont lues depuis config_template.toml + Les broches SPI sont lues depuis config_template.toml ou passées en paramètres. """ - def __init__(self, controller: ArduinoWifi): + def __init__(self, controller: ArduinoWifi, + cs_pin=None, sck_pin=None, miso_pin=None, mosi_pin=None): self._board = controller._board self._run = controller._run + self.cs_pin = cs_pin or config('max31865', 'cs_pin') + self.sck_pin = sck_pin or config('max31865', 'sck_pin') + self.miso_pin = miso_pin or config('max31865', 'miso_pin') + self.mosi_pin = mosi_pin or config('max31865', 'mosi_pin') def ini_max31865(self): - """Initialise le bus SPI avec les broches configurables.""" - - async def _init(): - # Envoyer directement SPI_INIT avec [sck, miso, mosi, nb_cs, cs_pin] - command = [22, self.sck_pin, self.miso_pin, self.mosi_pin, 1, self.cs_pin] - await self._board.transport.write(bytes([len(command)] + command)) - await asyncio.sleep(0.1) + # D'abord init SPI via Telemetrix (pour qu'il soit "activé") + self._run(self._board.set_pin_mode_spi([self.cs_pin])) - self._run(_init()) - - # Configuration MAX31865 + # Puis config MAX31865 config_byte = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO self._run(self._board.spi_cs_control(self.cs_pin, 0)) self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config_byte])) self._run(self._board.spi_cs_control(self.cs_pin, 1)) def read_rtd_resistance(self) -> float: - """Lit les registres RTD du MAX31865 et retourne la résistance en ohms.""" - import asyncio data = [] event = asyncio.Event() @@ -58,15 +50,14 @@ async def spi_callback(report): event.set() async def read(): - await self._board.spi_cs_control(CS_PIN, 0) + await self._board.spi_cs_control(self.cs_pin, 0) await self._board.spi_read_blocking( MAX31865_RTDMSB_REG, 2, call_back=spi_callback ) - # On attend la réponse AVANT de relâcher le CS await asyncio.wait_for(event.wait(), timeout=5) - await self._board.spi_cs_control(CS_PIN, 1) + await self._board.spi_cs_control(self.cs_pin, 1) self._run(read()) @@ -77,18 +68,14 @@ async def read(): return resistance def resistance_to_temperature(self, resistance: float) -> float: - """Convertit la résistance PT100 en température (°C) - via l'équation de Callendar-Van Dusen.""" z1 = -RTD_A z2 = RTD_A ** 2 - (4 * RTD_B) z3 = (4 * RTD_B) / RTD_NOMINAL z4 = 2 * RTD_B - temp = z2 + (z3 * resistance) temp = (temp ** 0.5 + z1) / z4 return temp def get_temperature(self) -> float: - """Retourne directement la température en °C.""" resistance = self.read_rtd_resistance() - return self.resistance_to_temperature(resistance) + return self.resistance_to_temperature(resistance) \ No newline at end of file From 29bdf0c2d3a6002b80807c8d6000f186b1f20383 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Thu, 4 Jun 2026 11:17:03 +0200 Subject: [PATCH 48/65] Fix all structures and edit comments --- .../daq_move_plugins/daq_move_FanHeater.py | 87 +++++++++++++++---- .../plugins_0D/daq_0Dviewer_PT100.py | 83 ++++++++++++++---- .../hardware/esp32_telemetrix.py | 68 +++++++++++---- .../hardware/sensors/max31865/spi_max31865.py | 77 ++++++++++------ 4 files changed, 243 insertions(+), 72 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 443247c..0e187f4 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -12,48 +12,88 @@ class DAQ_Move_FanHeater(DAQ_Move_base): - """Plugin PyMoDAQ pour le contrôle du chauffage et du ventilateur - via XY-MOS PWM sur ESP32 en WiFi (Telemetrix). + """Instrument plugin class for the heater and fan actuators. - Broches ESP32 : - Chauffage → GPIO18 - Ventilateur → GPIO17 + This object inherits all functionalities to communicate with PyMoDAQ's DAQ_Move module through + inheritance via DAQ_Move_base. It makes a bridge between the DAQ_Move module and the Python + wrapper of a particular instrument. + + Both actuators are driven by a XY-MOS PWM board connected to an ESP32 over WiFi using the + Telemetrix AIO protocol. The PWM duty cycle ranges from 0 to 255 (8-bit resolution). + + Heater → GPIO18 + Fan → GPIO17 + + Attributes: + ----------- + controller: object + The particular object that allows communication with the hardware, in general a Python + wrapper around the hardware library. """ _controller_units = '' is_multiaxes = True _axis_names = { 'Heater': config('esp32', 'pins', 'heater_pin'), - 'Fan': config('esp32', 'pins', 'fan_pin'), + 'Fan': config('esp32', 'pins', 'fan_pin'), } _epsilon = 0.1 data_actuator_type = DataActuatorType['DataActuator'] params = [ {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', - 'value': config('esp32', 'ip_address')} + 'value': config('esp32', 'ip_address')}, ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) def ini_attributes(self): self.controller: Optional[ArduinoWifi] = None def get_actuator_value(self): + """Get the current value from the hardware with scaling conversion. + + Returns + ------- + float: The position obtained after scaling conversion. + """ pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) pos = self.get_position_with_scaling(pos) return pos def close(self): + """Terminate the communication protocol""" if self.is_master: self.controller.set_pins_output_to(0) self.controller.shutdown() def commit_settings(self, param: Parameter): + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been changed by the user + """ pass def ini_stage(self, controller=None): + """Actuator communication initialization + + Parameters + ---------- + controller: (object) + custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller + (Master case) + + Returns + ------- + info: str + initialized: bool + False if initialization failed otherwise True + """ self.controller = self.ini_stage_init( old_controller=controller, - new_controller=None) + new_controller=None, + ) if self.is_master: self.controller = ArduinoWifi( @@ -66,29 +106,46 @@ def ini_stage(self, controller=None): return info, initialized def set_pins(self): + """Configure every axis pin as a PWM output and reset it to 0.""" for pin in self._axis_names.values(): self.controller.set_pin_mode_analog_output(pin) + self.controller.analog_write_and_memorize(pin, 0) def move_abs(self, value: DataActuator): - value = self.check_bound(value) + """Move the actuator to the absolute target defined by value + + Parameters + ---------- + value: (float) value of the absolute target positioning + """ + value = self.check_bound(value) # apply user-defined bounds self.target_value = value - value = self.set_position_with_scaling(value) - pwm_value = int(value.value()) - self.controller.analog_write_and_memorize(self.axis_value, pwm_value) + value = self.set_position_with_scaling(value) # apply scaling if the user specified one + + self.controller.analog_write_and_memorize(self.axis_value, int(value.value())) def move_rel(self, value: DataActuator): + """Move the actuator to the relative target actuator value defined by value + + Parameters + ---------- + value: (float) value of the relative target positioning + """ value = self.check_bound(self.current_position + value) - self.current_position self.target_value = value + self.current_position value = self.set_position_relative_with_scaling(value) - pwm_value = int(self.target_value.value()) - self.controller.analog_write_and_memorize(self.axis_value, pwm_value) + + # PWM value is set in duty-cycle counts (0–255) + self.controller.analog_write_and_memorize(self.axis_value, int(self.target_value.value())) def move_home(self): + """Call the reference method of the controller""" self.controller.analog_write_and_memorize(self.axis_value, 0) def stop_motion(self): + """Stop the actuator and emits move_done signal""" pass if __name__ == '__main__': - main(__file__) \ No newline at end of file + main(__file__) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py index efef28c..624681f 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py @@ -13,31 +13,48 @@ class DAQ_0DViewer_PT100(DAQ_Viewer_base): - """Plugin PyMoDAQ pour la lecture de température via MAX31865 et sonde PT100. + """Instrument plugin class for a 0D viewer. - Les broches SPI sont configurables depuis l'interface PyMoDAQ - ou depuis le fichier config_template.toml : + This object inherits all functionalities to communicate with PyMoDAQ's DAQ_Viewer module through + inheritance via DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python + wrapper of a particular instrument. + + This plugin reads temperature from a PT100 resistance temperature detector (RTD) wired to a + MAX31865 amplifier/ADC board. Communication with the MAX31865 is performed over bit-banged SPI + by an ESP32 running the Telemetrix AIO WiFi firmware. + + The four SPI pin numbers (SCK, MISO, MOSI, CS) are configurable from the PyMoDAQ parameter tree + or from *config_template.toml*. + + Attributes: + ----------- + controller: object + The particular object that allows communication with the hardware, in general a Python + wrapper around the hardware library. + max31865: MAX31865 + The driver object for the MAX31865 chip. """ + _controller_units = '°C' params = comon_parameters + [ - {'title': 'Connexion', 'name': 'connection', 'type': 'group', 'children': [ + {'title': 'Connection', 'name': 'connection', 'type': 'group', 'children': [ {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', 'value': config('esp32', 'ip_address')}, ]}, {'title': 'SPI Pins (GPIO)', 'name': 'spi_pins', 'type': 'group', 'children': [ - {'title': 'SCK pin:', 'name': 'sck_pin', 'type': 'int', + {'title': 'SCK pin:', 'name': 'sck_pin', 'type': 'int', 'value': config('max31865', 'sck_pin'), - 'tip': 'Nano ESP32 legacy : D13 = GPIO48'}, + 'tip': 'Nano ESP32 legacy: D13 = GPIO48'}, {'title': 'MISO pin:', 'name': 'miso_pin', 'type': 'int', 'value': config('max31865', 'miso_pin'), - 'tip': 'Nano ESP32 legacy : D12 = GPIO47'}, + 'tip': 'Nano ESP32 legacy: D12 = GPIO47'}, {'title': 'MOSI pin:', 'name': 'mosi_pin', 'type': 'int', 'value': config('max31865', 'mosi_pin'), - 'tip': 'Nano ESP32 legacy : D11 = GPIO38'}, - {'title': 'CS pin:', 'name': 'cs_pin', 'type': 'int', + 'tip': 'Nano ESP32 legacy: D11 = GPIO38'}, + {'title': 'CS pin:', 'name': 'cs_pin', 'type': 'int', 'value': config('max31865', 'cs_pin'), - 'tip': 'Nano ESP32 legacy : D10 = GPIO21'}, + 'tip': 'Nano ESP32 legacy: D10 = GPIO21'}, ]}, ] @@ -45,12 +62,38 @@ def ini_attributes(self): self.controller: Optional[ArduinoWifi] = None self.max31865: Optional[MAX31865] = None + def commit_settings(self, param: Parameter): + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been changed by the user + """ + pass + def ini_detector(self, controller=None): + """Detector communication initialization + + Parameters + ---------- + controller: (object) + custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by + controller (Master case) + + Returns + ------- + info: str + initialized: bool + False if initialization failed otherwise True + """ self.ini_detector_init(slave_controller=controller) + if self.is_master: self.controller = ArduinoWifi( ip_address=self.settings['connection', 'ip_address'] ) + self.max31865 = MAX31865( controller=self.controller, sck_pin=self.settings['spi_pins', 'sck_pin'], @@ -59,15 +102,27 @@ def ini_detector(self, controller=None): cs_pin=self.settings['spi_pins', 'cs_pin'], ) self.max31865.ini_max31865() + info = "PT100 ready" initialized = True return info, initialized def close(self): + """Terminate the communication protocol""" if self.is_master: self.controller.shutdown() def grab_data(self, Naverage=1, **kwargs): + """Start a grab from the detector + + Parameters + ---------- + Naverage: int + Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging + should be set to True in class preamble and you should code this implementation) + kwargs: dict + others optional arguments + """ temperature = self.max31865.get_temperature() self.dte_signal.emit(DataToExport( name='PT100', @@ -75,16 +130,14 @@ def grab_data(self, Naverage=1, **kwargs): name='Temperature', data=[np.array([temperature])], dim='Data0D', - labels=['Temperature (°C)'] + labels=['Temperature (°C)'], )] )) - def commit_settings(self, param: Parameter): - pass - def stop(self): + """Stop the current grab hardware wise if necessary""" pass if __name__ == '__main__': - main(__file__) \ No newline at end of file + main(__file__) diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index 417027f..fe6c702 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -1,18 +1,36 @@ import asyncio +import numbers import threading from threading import Lock + from telemetrix_aio_esp32 import telemetrix_aio_esp32 lock = Lock() +# Maps GPIO pin numbers to ESP32 LEDC hardware channels. +# LEDC channels are required when configuring a pin as PWM output. PIN_TO_CHANNEL = { - 17: 0, # Fan - 18: 1, # Heater + 17: 0, # Fan → LEDC channel 0 + 18: 1, # Heater → LEDC channel 1 } class ArduinoWifi: - def __init__(self, ip_address): + """WiFi wrapper for the ESP32 board using the Telemetrix AIO protocol. + + This object exposes a subset of the Telemetrix API over a WiFi connection + and mirrors the interface of the :class:`Arduino` (USB/telemetrix) class so + that higher-level plugins can target either board with minimal changes. + + Attributes: + ----------- + pin_values_output: dict + Keeps track of the last value written to each output pin. + analog_pin_values_input: dict + Keeps track of the last value read from each analog input channel. + """ + + def __init__(self, ip_address: str): self.pin_values_output = {} self.analog_pin_values_input = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0} @@ -25,36 +43,48 @@ def __init__(self, ip_address): ) future.result(timeout=10) - async def _init_board(self, ip_address): + async def _init_board(self, ip_address: str): self._board = telemetrix_aio_esp32.TelemetrixAioEsp32( transport_address=ip_address, autostart=False, loop=self._loop, restart_on_shutdown=False, - shutdown_on_exception=False + shutdown_on_exception=False, ) await self._board.start_aio() def _run(self, coro): + """Submit a coroutine to the board event-loop and block until done.""" return asyncio.run_coroutine_threadsafe(coro, self._loop).result(timeout=5) @staticmethod - def round_value(value): + def round_value(value) -> int: + """Clamp *value* to the valid PWM range [0, 255].""" return max(0, min(255, int(value))) - def set_pin_mode_analog_output(self, pin): + def set_pin_mode_analog_output(self, pin: int): + """Configure *pin* as a PWM output (LEDC) on the ESP32. + + The LEDC channel is resolved from :data:`PIN_TO_CHANNEL`; channel 0 + is used as fallback for unmapped pins. + """ channel = PIN_TO_CHANNEL.get(pin, 0) - print(f"DEBUG set_pin_mode_analog_output → pin={pin}, channel={channel}") self._run(self._board.set_pin_mode_analog_output(pin_number=pin, channel=channel)) - print(f"DEBUG set_pin_mode_analog_output → done") - def analog_write(self, pin, value): - # Core ESP32 v3.x : ledcWrite(pin, value) — pin GPIO directement - print(f"DEBUG analog_write → pin={pin}, value={value}") + def analog_write(self, pin: int, value: int): + """Write a raw PWM duty cycle to *pin*. + + ESP32 Core v3.x uses ``ledcWrite(pin, value)`` where the GPIO number is + passed directly as the channel identifier — hence ``channel=pin`` here. + """ self._run(self._board.analog_write(channel=pin, value=value)) - print(f"DEBUG analog_write → done") - def analog_write_and_memorize(self, pin, value): + def analog_write_and_memorize(self, pin: int, value): + """Write *value* to *pin* and record it in :attr:`pin_values_output`. + + The value is clamped to [0, 255] before being sent. + Thread-safe. + """ lock.acquire() value = self.round_value(value) self.analog_write(pin, value) @@ -62,14 +92,20 @@ def analog_write_and_memorize(self, pin, value): lock.release() def set_pins_output_to(self, value: int): + """Write *value* to every pin that has been initialised as an output. + + Thread-safe. + """ lock.acquire() for pin in self.pin_values_output: self.analog_write(pin, int(value)) lock.release() - def get_output_pin_value(self, pin: int): + def get_output_pin_value(self, pin: int) -> numbers.Number: + """Return the last value written to *pin*, or 0 if never written.""" return self.pin_values_output.get(pin, 0) def shutdown(self): + """Gracefully stop the Telemetrix session and the event-loop thread.""" self._run(self._board.shutdown()) - self._loop.call_soon_threadsafe(self._loop.stop) \ No newline at end of file + self._loop.call_soon_threadsafe(self._loop.stop) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 4e338a0..9b93222 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -1,48 +1,71 @@ import asyncio + from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi from pymodaq_plugins_arduino.utils import Config config = Config() -# Registres MAX31865 -MAX31865_CONFIG_REG = 0x00 -MAX31865_CONFIG_BIAS = 0x80 -MAX31865_CONFIG_MODEAUTO = 0x40 -MAX31865_RTDMSB_REG = 0x01 +# ── MAX31865 register map ──────────────────────────────────────────────────── +MAX31865_CONFIG_REG = 0x00 # Configuration register (write address = reg | 0x80) +MAX31865_CONFIG_BIAS = 0x80 # Bias voltage ON +MAX31865_CONFIG_MODEAUTO = 0x40 # Auto (continuous) conversion mode +MAX31865_RTDMSB_REG = 0x01 # RTD resistance data MSB (read-only) -# Constantes PT100 -RTD_NOMINAL = 100.0 -RTD_REFERENCE = 430.0 -RTD_A = 3.9083e-3 -RTD_B = -5.775e-7 +# ── PT100 Callendar-Van Dusen coefficients ─────────────────────────────────── +RTD_NOMINAL = 100.0 # PT100 nominal resistance at 0 °C (Ω) +RTD_REFERENCE = 430.0 # Reference resistor mounted on the MAX31865 board (Ω) +RTD_A = 3.9083e-3 # CVD coefficient A +RTD_B = -5.775e-7 # CVD coefficient B class MAX31865: - """Driver pour le capteur PT100 via MAX31865 SPI. - Les broches SPI sont lues depuis config_template.toml ou passées en paramètres. + """Software driver for the MAX31865 RTD-to-digital converter. + + Communicates with the chip over bit-banged SPI via the Telemetrix AIO + firmware running on the ESP32. The four SPI pins can be passed at + construction time or read from *config_template.toml*. + + Attributes: + ----------- + cs_pin, sck_pin, miso_pin, mosi_pin: int + GPIO numbers of the four SPI lines. """ def __init__(self, controller: ArduinoWifi, - cs_pin=None, sck_pin=None, miso_pin=None, mosi_pin=None): + cs_pin: int = None, sck_pin: int = None, + miso_pin: int = None, mosi_pin: int = None): + # Borrow the board handle and the synchronous _run helper from the controller self._board = controller._board - self._run = controller._run + self._run = controller._run + self.cs_pin = cs_pin or config('max31865', 'cs_pin') self.sck_pin = sck_pin or config('max31865', 'sck_pin') self.miso_pin = miso_pin or config('max31865', 'miso_pin') self.mosi_pin = mosi_pin or config('max31865', 'mosi_pin') def ini_max31865(self): - # D'abord init SPI via Telemetrix (pour qu'il soit "activé") + """Initialise the SPI bus and put the MAX31865 in auto-conversion mode. + + Sequence: + 1. Register the CS pin with Telemetrix (``set_pin_mode_spi``). + 2. Write the configuration byte that enables the bias voltage and + selects continuous conversion. + """ self._run(self._board.set_pin_mode_spi([self.cs_pin])) - # Puis config MAX31865 config_byte = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO self._run(self._board.spi_cs_control(self.cs_pin, 0)) self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config_byte])) self._run(self._board.spi_cs_control(self.cs_pin, 1)) def read_rtd_resistance(self) -> float: - data = [] + """Read the raw RTD register and return the equivalent resistance in Ω. + + The MAX31865 stores the 15-bit ADC result in registers 0x01 (MSB) and + 0x02 (LSB). Bit 0 of the LSB is the fault flag and is discarded by + shifting right one position before computing the resistance. + """ + data = [] event = asyncio.Event() async def spi_callback(report): @@ -54,28 +77,30 @@ async def read(): await self._board.spi_read_blocking( MAX31865_RTDMSB_REG, 2, - call_back=spi_callback + call_back=spi_callback, ) await asyncio.wait_for(event.wait(), timeout=5) await self._board.spi_cs_control(self.cs_pin, 1) self._run(read()) - msb = data[0] - lsb = data[1] - rtd_raw = ((msb << 8) | lsb) >> 1 + rtd_raw = ((data[0] << 8) | data[1]) >> 1 # discard fault bit (LSB) resistance = (rtd_raw / 32768.0) * RTD_REFERENCE return resistance def resistance_to_temperature(self, resistance: float) -> float: + """Convert a resistance (Ω) to temperature (°C) via the Callendar-Van Dusen equation. + + This approximation is valid for T > 0 °C. + """ z1 = -RTD_A - z2 = RTD_A ** 2 - (4 * RTD_B) + z2 = RTD_A ** 2 - (4 * RTD_B) z3 = (4 * RTD_B) / RTD_NOMINAL - z4 = 2 * RTD_B - temp = z2 + (z3 * resistance) - temp = (temp ** 0.5 + z1) / z4 + z4 = 2 * RTD_B + temp = (((z2 + z3 * resistance) ** 0.5) + z1) / z4 return temp def get_temperature(self) -> float: + """Return the current probe temperature in °C.""" resistance = self.read_rtd_resistance() - return self.resistance_to_temperature(resistance) \ No newline at end of file + return self.resistance_to_temperature(resistance) From 7e9fe224ffd445ec89d9bf5709afc7d5a2bd7033 Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Sat, 6 Jun 2026 13:33:43 +0200 Subject: [PATCH 49/65] Add instance on gitignore --- .gitignore | 9 +++++++++ .pymodaq_dev | 1 + 2 files changed, 10 insertions(+) create mode 160000 .pymodaq_dev diff --git a/.gitignore b/.gitignore index d97e2b0..3c247ce 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,12 @@ venv.bak/ *yacctab.py *lextab.py + +# Environments Pixi +pixi.lock +pixi.toml +setup_dev.py + +# VSCode Settings +.vscode/launch.json +.vscode/settings.json diff --git a/.pymodaq_dev b/.pymodaq_dev new file mode 160000 index 0000000..52a8c42 --- /dev/null +++ b/.pymodaq_dev @@ -0,0 +1 @@ +Subproject commit 52a8c42390b6b51dc11891832e8d09efed3f6c3d From f1b4567bba10dd2b6d5825e8c88d493a695d8dba Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Sat, 6 Jun 2026 16:04:56 +0200 Subject: [PATCH 50/65] Add env with Pixi --- .gitignore | 5 - pixi.lock | 1062 ++++++++++++++++++++++++++++++++++++++++++++++++++ pixi.toml | 24 ++ setup_dev.py | 89 +++++ 4 files changed, 1175 insertions(+), 5 deletions(-) create mode 100644 pixi.lock create mode 100644 pixi.toml create mode 100644 setup_dev.py diff --git a/.gitignore b/.gitignore index 3c247ce..d832fba 100644 --- a/.gitignore +++ b/.gitignore @@ -114,11 +114,6 @@ venv.bak/ *yacctab.py *lextab.py -# Environments Pixi -pixi.lock -pixi.toml -setup_dev.py - # VSCode Settings .vscode/launch.json .vscode/settings.json diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..e751289 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,1062 @@ +version: 7 +platforms: +- name: win-64 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.1.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/c-blosc2-3.1.2-h2af8807_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/git-2.54.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-8_h6c93730_netlib.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-8_hc41557d_netlib.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-8_h018ca30_netlib.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numexpr-2.14.1-py311h670de69_102.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-1.26.4-py311h0b4df5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pytables-3.11.1-py311h155d883_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_38.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_38.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_38.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/01/8c/15a2de09cc3c30793336cf79798948768611463ddfb2b2669f51569228fb/adafruit_circuitpython_requests-4.1.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/fd/5bd5da5d7997725ba3f1995c16aa1c3362937f8ff68ad4cadfd3415eebcb/winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/05/a5/216d66df6bdcee58eb3877fabc1544337e23f850bf9f93838db7f5698371/winrt_windows_foundation-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/e8/0856886ebbb02dc47a91ec99fbade9da871a07e072d20503c3d89ecf712f/adafruit_circuitpython_ble-10.1.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/cd/0731490946e037e954ef83719f07c7672cf32bc90dd9c75201c40b827664/pyftdi-0.57.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/9d/28e9d12f36e13c5f2acba3098187b0e931290ecd1d8df924391b5ad2db19/Adafruit_PureIO-1.1.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/98/6c9c21b5e75ff5927a130da9eaf5ab628dfa1f93b64c181f0193706cbd6c/winrt_windows_storage_streams-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/26/54/05aceb9cd80073805b3ed8522e3196e8cb22f70e741873fa51406c31f4e7/bleak-3.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/29/c4/32572c051f1554d73633a802447321bff2a2332ef4210d47de807afd26c7/adafruit_platformdetect-3.88.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/58/049db1d95fdfc0c8451dc6db17442ed4e6b2aba361c425c0bb8dc8c98c4a/winrt_windows_foundation_collections-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/42/0d/66a4e0fbd7b35107f7dee04fed890f77b83d1da9dd1f7474af2ed21700ea/adafruit_circuitpython_busdevice-5.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/d6/c40e8ae38a6e2bce9e837b64688f55746bfdad1aa557eb733fb5e90edd7c/pyqt6_sip-13.11.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6f/85/dd9f03d78d87460e109e0121cd6201c5802bdd655656bf2780e964870fea/pyqt6-6.11.0-cp310-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/70/0b/06ccf917ce30d78d75e452d0afc89236b1d384b776c5c33d75c8cc55fbe7/pyvisa_py-0.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/6b/0f13486003aea3eb349c2946b7ec9753e7558b78e35d22c938062a96959c/binho_host_adapter-0.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/db/929ab0085ec89e46bd3a58c74b451dd770c3285dfa0cbd4f4aa4730da004/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/97/05/b5fa1fcc4cc68bda4bd317cb60a826eef753d9ab8efcd2d28fbcc7d60dd2/adafruit_blinka_bleio-4.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/1a/d172d6f1c2fae53535e7f23835025cf39e3002749a0304f18a38e8ed490d/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ac/66/d05f6e6c0517654734e7f87fa1f0fbc965add9f27cc36b524d96331ab3d8/winrt_runtime-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b6/d5/5761a8b6dcc56957018970dd443059c8ee8a79de7b07f0b4d143f8e7dc15/winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bb/97/17dac675981730d29b2d583de2aa0a9c2963d6c63f6bdc7eebf2711914ef/adafruit_blinka-9.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/a1/578a03ba2bce0809b4e30974b47958963c9efe67b9fe74e7dbcdbbd45318/adafruit_circuitpython_typing-1.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/0b/6e3121375aeda38fa76669f6caca2794e4a10fc7396ae88a3bf0a3b5a09b/telemetrix-1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/1d/616b3770ebf191f8d9aa1a5ae919d4885e3037341ea1d367e0045235c815/telemetrix_esp32-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/49/ba69e3180585dbc6f3336a09fef7cba4558a6a1e7d500500f62c1478418e/winrt_windows_devices_radios-3.2.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e4/43/929d17e5dbe0773e3a3c728b12cf1a777ada32639764b09365a1b56703c0/adafruit_circuitpython_connectionmanager-3.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/a9/776da4f397003cca093659524c3526590522f81b642936838428c11274e6/pyvisa-1.16.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fa/f1/70e83c23bf897c7f5025aa100482f482038ef70232dc27b407659d941fbf/pyqt6_qt6-6.11.1-py3-none-win_amd64.whl +packages: +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-h4c7d964_0.conda + sha256: 86981d764e4ea1883409d30447ff9da46127426d31a63df08315aaded768e652 + md5: c9b86eece2f944541b86441c94117ab3 + depends: + - __win + license: ISC + purls: [] + size: 130182 + timestamp: 1779289939595 +- conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + sha256: d38542a151a90417065c1a234866f97fd1ea82a81de75ecb725955ab78f88b4b + md5: 9a66894dfd07c4510beb6b3f9672ccc0 + constrains: + - mkl <0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 3843 + timestamp: 1582593857545 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 + md5: 4c06a92e74452cfa53623a81592e8934 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + size: 91574 + timestamp: 1777103621679 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.1.2-pyh8b19718_0.conda + sha256: 29b7d75bf81ad11645a8e320b369abdc90a92b93f2a9178e853d9dddf82e5106 + md5: 511fbc2c63d2c73650ad1755e4d357ba + depends: + - python >=3.10,<3.13.0a0 + - setuptools + - wheel + license: MIT + license_family: MIT + purls: + - pkg:pypi/pip?source=compressed-mapping + size: 1203173 + timestamp: 1780262795392 +- conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda + sha256: 6d8f03c13d085a569fde931892cded813474acbef2e03381a1a87f420c7da035 + md5: 46830ee16925d5ed250850503b5dc3a8 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/py-cpuinfo?source=hash-mapping + size: 25766 + timestamp: 1733236452235 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + build_number: 8 + sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 + md5: 8fcb6b0e2161850556231336dae58358 + constrains: + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7003 + timestamp: 1752805919375 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 + md5: 8e194e7b992f99a5015edbd4ebd38efd + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=hash-mapping + size: 639697 + timestamp: 1773074868565 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 + depends: + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda + sha256: 9e156ffaefb8463437144326ada4b85d1de17961b9997ac5f1cbbaf747bd8bed + md5: d0e3b2f0030cf4fca58bde71d246e94c + depends: + - packaging >=24.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wheel?source=hash-mapping + size: 33491 + timestamp: 1776878563806 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda + sha256: ffa66e862ddcd8a825c3d44e83404daec7b8d36b7313650e09aa39443c312f5e + md5: 9f25944ccae498b7afbc81ce24f4c37a + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 127435 + timestamp: 1777489461908 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda + sha256: 5f61082caea9fbdd6ba02702935e9dea9997459a7e6c06fd47f21b81aac882fb + md5: 7cc4953d504d4e8f3d6f4facb8549465 + depends: + - aws-c-common >=0.12.6,<0.12.7.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 53613 + timestamp: 1764593604081 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda + sha256: 0627691c34eb3d9fcd18c71346d9f16f83e8e58f9983e792138a2cccf387d18a + md5: b1465f33b05b9af02ad0887c01837831 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 236441 + timestamp: 1763586152571 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda + sha256: f98fbb797d28de3ae41dbd42590549ee0a2a4e61772f9cc6d1a4fa45d47637de + md5: 0385f2340be1776b513258adaf70e208 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 23087 + timestamp: 1767790877990 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda + sha256: cf939d4a0849bc41421b4c380b2bbbc0beb1fd9b375bb9627b98d9415ec9ea69 + md5: 88626be3c14ac87c09629dcbf65e6279 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-compression >=0.3.2,<0.3.3.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 208426 + timestamp: 1774488477105 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_2.conda + sha256: 7cf5aca930fc12f4e27bd4645d20224d608c2c650443e5633faea3bf8b0a7736 + md5: 86eb8e8959c2d6053a50ad31ef6e5b5d + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 182313 + timestamp: 1779133038517 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda + sha256: 8d9c747d71c493e6d5e5a125a267c6ac51baba1e4b89c01c2a4084239267b8e1 + md5: 2c4cd5a0bb004c9975a4d7257a55c34a + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-cal >=0.9.13,<0.9.14.0a0 + - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 143057 + timestamp: 1777824834454 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda + sha256: c86c30edba7457e04d905c959328142603b62d7d1888aed893b2e21cca9c302c + md5: 3c97faee5be6fd0069410cf2bca71c85 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 56509 + timestamp: 1764610148907 +- conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda + sha256: 505b2365bbf3c197c9c2e007ba8262bcdaaddc970f84ce67cf73868ca2990989 + md5: 96e950e5007fb691322db578736aba52 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + size: 116853 + timestamp: 1771063509650 +- conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda + sha256: 9303a7a0e03cf118eab3691013f6d6cbd1cbac66efbc70d89b20f5d0145257c0 + md5: 357d7be4146d5fec543bfaa96a8a40de + depends: + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - snappy >=1.2.1,<1.3.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - zstd >=1.5.6,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 49840 + timestamp: 1733513605730 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 + md5: 4cb8e6b48f67de0b018719cdf1136306 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 56115 + timestamp: 1771350256444 +- conda: https://conda.anaconda.org/conda-forge/win-64/c-blosc2-3.1.2-h2af8807_0.conda + sha256: c23851edfb0eb2a14fdd3018b868607f485289672fe8dbf3e793b37ba48577f6 + md5: 834b5862d5a42c44c93bb26292964e5a + depends: + - lz4-c >=1.10.0,<1.11.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zlib-ng >=2.3.3,<2.4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 252277 + timestamp: 1780000126700 +- conda: https://conda.anaconda.org/conda-forge/win-64/git-2.54.0-h57928b3_0.conda + sha256: d9077b6b2e9aac60c2ea868b0ed018b68131c0f88394471c8f1c6ff8c0a40f4d + md5: 7e64dae740e7d370ec8c8a999f5f5978 + license: GPL-2.0-or-later and LGPL-2.1-or-later + purls: [] + size: 122873375 + timestamp: 1778072461017 +- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda + sha256: 2f2d49ccf163a4bdf556662fb2949bdf408940e2db67a2d15be2d8be247b6e43 + md5: d5850b9e97b9a577441067628fb8d573 + depends: + - aws-c-auth >=0.10.1,<0.10.2.0a0 + - aws-c-common >=0.12.6,<0.12.7.0a0 + - aws-c-http >=0.10.13,<0.10.14.0a0 + - aws-c-io >=0.26.3,<0.26.4.0a0 + - aws-c-s3 >=0.12.2,<0.12.3.0a0 + - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.20.0,<9.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2599543 + timestamp: 1777861984545 +- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 + md5: 4432f52dc0c8eb6a7a6abc00a037d93c + depends: + - openssl >=3.5.5,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 751055 + timestamp: 1769769688841 +- conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda + sha256: e54c08964262c73671d9e80e400333e59c617e0b454476ad68933c0c458156c8 + md5: 43b6385cfad52a7083f2c41984eb4e91 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 34463 + timestamp: 1769221960556 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-8_h6c93730_netlib.conda + build_number: 8 + sha256: cc0df341dbc9af74abdbe658caeea5b5d5f362e25283f856d0457f4ae7a36f7e + md5: e2591f7c5a702a478532441e83648878 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - blas * netlib + track_features: + - blas_netlib + - blas_netlib_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 152280 + timestamp: 1779861305744 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-8_hc41557d_netlib.conda + build_number: 8 + sha256: 2132d8b764c1df063778880dc2b0086cb655754b189e43d44a99cf760479a09f + md5: ccbf2d1416567af1b8d098b6f3ba2f32 + depends: + - libblas 3.11.0.* + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + track_features: + - blas_netlib + - blas_netlib_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 45490 + timestamp: 1779861325576 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda + sha256: f4ce5aa835a698532feaa368e804365a7e45a9edebe006a8e1c80505d893c24e + md5: 7bee27a8f0a295117ccb864f30d2d87e + depends: + - krb5 >=1.22.2,<1.23.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: curl + license_family: MIT + purls: [] + size: 393114 + timestamp: 1777461635732 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda + sha256: a65e518c20d1482182bc0f1f6dd5d992f25ca44c3b32307be39ae8310db8f060 + md5: 23eb9474a16d4b9f6f27429989e82002 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + size: 71280 + timestamp: 1779278786150 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 + md5: 720b39f5ec0610457b725eb3f396219a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 45831 + timestamp: 1769456418774 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-8_h018ca30_netlib.conda + build_number: 8 + sha256: 2c1c411a1196f1d09ac1a7265f2b976deedb3791d0930dedcdc58b991d613c26 + md5: cae9364b4d1f1af0c9b9595b01fcd6be + depends: + - libblas 3.11.0.* + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + track_features: + - blas_netlib + - blas_netlib_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 2131555 + timestamp: 1779861344704 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + sha256: d636d1a25234063642f9c531a7bb58d84c1c496411280a36ea000bd122f078f1 + md5: 8f83619ab1588b98dd99c90b0bfc5c6d + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 106486 + timestamp: 1775825663227 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda + sha256: e70562450332ca8954bc16f3455468cca5ef3695c7d7187ecc87f8fc3c70e9eb + md5: 7fea434a17c323256acc510a041b80d7 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1304178 + timestamp: 1777986510497 +- conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + sha256: cbdf93898f2e27cefca5f3fe46519335d1fab25c4ea2a11b11502ff63e602c09 + md5: 9dce2f112bfd3400f4f432b3d0ac07b2 + depends: + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 292785 + timestamp: 1745608759342 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 58347 + timestamp: 1774072851498 +- conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + sha256: 632cf3bdaf7a7aeb846de310b6044d90917728c73c77f138f08aa9438fc4d6b5 + md5: 0b69331897a92fac3d8923549d48d092 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 139891 + timestamp: 1733741168264 +- conda: https://conda.anaconda.org/conda-forge/win-64/numexpr-2.14.1-py311h670de69_102.conda + sha256: 712f97bec6ce2bdd56eabbe015ea5cac49de094a4501f390485083c4814ff864 + md5: 8bcaf94571260915e56c7bc6b2c50537 + depends: + - nomkl + - numpy >=1.23,<3 + - numpy >=1.23.0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/numexpr?source=hash-mapping + size: 209566 + timestamp: 1778498793173 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-1.26.4-py311h0b4df5a_0.conda + sha256: 14116e72107de3089cc58119a5ce5905c22abf9a715c9fe41f8ac14db0992326 + md5: 7b240edd44fd7a0991aa409b07cee776 + depends: + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 7104093 + timestamp: 1707226459646 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + sha256: feb5815125c60f2be4a411e532db1ed1cd2d7261a6a43c54cb6ae90724e2e154 + md5: 05c7d624cff49dbd8db1ad5ba537a8a3 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9410183 + timestamp: 1775589779763 +- conda: https://conda.anaconda.org/conda-forge/win-64/pytables-3.11.1-py311h155d883_3.conda + sha256: c6f00f87555399be6400b626b3ae78a429e0b82e3316ab72031ade448e1091f8 + md5: d9207d905434270434ce10a96cedd70d + depends: + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - c-blosc2 >=3.1.2,<3.2.0a0 + - hdf5 >=2.1.0,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - numexpr + - numpy >=1.20.0 + - numpy >=1.23,<3 + - packaging + - py-cpuinfo + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - typing-extensions >=4.4.0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/tables?source=hash-mapping + size: 1514244 + timestamp: 1780064690372 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 + md5: d09dbf470b41bca48cbe6a78ba1e009b + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 18416208 + timestamp: 1772728847666 +- conda: https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda + sha256: d2deda1350abf8c05978b73cf7fe9147dd5c7f2f9b312692d1b98e52efad53c3 + md5: 3075846de68f942150069d4289aaad63 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 67417 + timestamp: 1762948090450 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: TCL + license_family: BSD + purls: [] + size: 3526350 + timestamp: 1769460339384 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_38.conda + sha256: 61b68e5a4fc71a17f8d64b12e013a2f971ad980bd08e9c389d5e68efe1a67de0 + md5: 774568633f3b26d7a4a6dd4f9ea6d3e1 + depends: + - vc14_runtime >=14.51.36231 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 20187 + timestamp: 1780005880049 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_38.conda + sha256: 957c7c65583c7107a5e76f39756c6361fcb7b0dc101ac7c0aea86e7ca09fe49c + md5: 2cdcd8ea1010920911bb2eacb4c61227 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.51.36231 h1b9f54f_38 + constrains: + - vs2015_runtime 14.51.36231.* *_38 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 740997 + timestamp: 1780005875753 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_38.conda + sha256: c645fdc1f0f47718431d973386e946754a10200e7ba2c32032560913a970cacd + md5: 63ee70d69d7540e821940dac5d4d9ba2 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.51.36231.* *_38 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 123561 + timestamp: 1780005858779 +- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda + sha256: 71332532332d13b5dbe57074ddcf82ae711bdc132affa5a2982a29ffa06dc234 + md5: 46a21c0a4e65f1a135251fc7c8663f83 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Zlib + license_family: Other + purls: [] + size: 124542 + timestamp: 1770167984883 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 + md5: 053b84beec00b71ea8ff7a4f84b55207 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 388453 + timestamp: 1764777142545 +- pypi: https://files.pythonhosted.org/packages/01/8c/15a2de09cc3c30793336cf79798948768611463ddfb2b2669f51569228fb/adafruit_circuitpython_requests-4.1.17-py3-none-any.whl + name: adafruit-circuitpython-requests + version: 4.1.17 + sha256: 4c205188a052f52b3bb8ab4af97798d7d56ae3701857d31f03b164f029fae44f + requires_dist: + - adafruit-blinka + - adafruit-circuitpython-connectionmanager + - requests ; extra == 'optional' +- pypi: https://files.pythonhosted.org/packages/03/fd/5bd5da5d7997725ba3f1995c16aa1c3362937f8ff68ad4cadfd3415eebcb/winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-devices-enumeration + version: 3.2.1 + sha256: 2a725d04b4cb43aa0e2af035f73a60d16a6c0ff165fcb6b763383e4e33a975fd + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-applicationmodel-background[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-security-credentials[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-ui[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-ui-popups[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/05/a5/216d66df6bdcee58eb3877fabc1544337e23f850bf9f93838db7f5698371/winrt_windows_foundation-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-foundation + version: 3.2.1 + sha256: f3762be2f6e0f2aedf83a0742fd727290b397ffe3463d963d29211e4ebb53a7e + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl + name: pyserial + version: '3.5' + sha256: c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0 + requires_dist: + - hidapi ; extra == 'cp2110' +- pypi: https://files.pythonhosted.org/packages/15/e8/0856886ebbb02dc47a91ec99fbade9da871a07e072d20503c3d89ecf712f/adafruit_circuitpython_ble-10.1.3-py3-none-any.whl + name: adafruit-circuitpython-ble + version: 10.1.3 + sha256: 7945174784a6c975a425669818ced06f136fb20fc5d050220ed0c6eff37a927a + requires_dist: + - adafruit-blinka + - adafruit-blinka-bleio + - adafruit-circuitpython-typing + - typing-extensions +- pypi: https://files.pythonhosted.org/packages/16/cd/0731490946e037e954ef83719f07c7672cf32bc90dd9c75201c40b827664/pyftdi-0.57.1-py3-none-any.whl + name: pyftdi + version: 0.57.1 + sha256: efd3f5a7d43202dc883ff261a7b1cb4dcbbe65b19628f8603a8b1183a7bc2841 + requires_dist: + - pyusb>=1.0.0,!=1.2.0 + - pyserial>=3.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/19/9d/28e9d12f36e13c5f2acba3098187b0e931290ecd1d8df924391b5ad2db19/Adafruit_PureIO-1.1.11-py3-none-any.whl + name: adafruit-pureio + version: 1.1.11 + sha256: 281ab2099372cc0decc26326918996cbf21b8eed694ec4764d51eefa029d324e + requires_python: '>=3.5.0' +- pypi: https://files.pythonhosted.org/packages/23/98/6c9c21b5e75ff5927a130da9eaf5ab628dfa1f93b64c181f0193706cbd6c/winrt_windows_storage_streams-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-storage-streams + version: 3.2.1 + sha256: b02fa251a7eef6081eca1a5f64ecf349cfd1ac0ac0c5a5a30be52897d060bed5 + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-storage[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-system[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/26/54/05aceb9cd80073805b3ed8522e3196e8cb22f70e741873fa51406c31f4e7/bleak-3.0.2-py3-none-any.whl + name: bleak + version: 3.0.2 + sha256: 39092feb9e83f1df5ad2f88e837723c7211c982ce9e9cda6235104bc2ebe0d0d + requires_dist: + - async-timeout>=3.0.0 ; python_full_version < '3.11' + - typing-extensions>=4.7.0 ; python_full_version < '3.12' + - pyobjc-core>=10.3 ; sys_platform == 'darwin' + - pyobjc-framework-corebluetooth>=10.3 ; sys_platform == 'darwin' + - pyobjc-framework-libdispatch>=10.3 ; sys_platform == 'darwin' + - winrt-runtime>=3.1 ; sys_platform == 'win32' + - winrt-windows-devices-bluetooth>=3.1 ; sys_platform == 'win32' + - winrt-windows-devices-bluetooth-advertisement>=3.1 ; sys_platform == 'win32' + - winrt-windows-devices-bluetooth-genericattributeprofile>=3.1 ; sys_platform == 'win32' + - winrt-windows-devices-enumeration>=3.1 ; sys_platform == 'win32' + - winrt-windows-devices-radios>=3.1 ; sys_platform == 'win32' + - winrt-windows-foundation>=3.1 ; sys_platform == 'win32' + - winrt-windows-foundation-collections>=3.1 ; sys_platform == 'win32' + - winrt-windows-storage-streams>=3.1 ; sys_platform == 'win32' + - dbus-fast>=1.83.0 ; sys_platform == 'linux' + - bleak-pythonista>=0.1.1 ; extra == 'pythonista' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl + name: pyusb + version: 1.3.1 + sha256: bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430 + requires_python: '>=3.9.0' +- pypi: https://files.pythonhosted.org/packages/29/c4/32572c051f1554d73633a802447321bff2a2332ef4210d47de807afd26c7/adafruit_platformdetect-3.88.0-py3-none-any.whl + name: adafruit-platformdetect + version: 3.88.0 + sha256: 69e694d80d551c6cb8e39f731e6ee0de1f135e64cffee0e2a665b1f9579c10d7 +- pypi: https://files.pythonhosted.org/packages/32/58/049db1d95fdfc0c8451dc6db17442ed4e6b2aba361c425c0bb8dc8c98c4a/winrt_windows_foundation_collections-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-foundation-collections + version: 3.2.1 + sha256: c646a5d442dd6540ade50890081ca118b41f073356e19032d0a5d7d0d38fbc89 + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/42/0d/66a4e0fbd7b35107f7dee04fed890f77b83d1da9dd1f7474af2ed21700ea/adafruit_circuitpython_busdevice-5.2.17-py3-none-any.whl + name: adafruit-circuitpython-busdevice + version: 5.2.17 + sha256: 5a834fbe0b88b07d20494bec566815da154aa4b1b668e2e665277b34b3578e44 + requires_dist: + - adafruit-blinka>=7.0.0 + - adafruit-circuitpython-typing +- pypi: https://files.pythonhosted.org/packages/4a/d6/c40e8ae38a6e2bce9e837b64688f55746bfdad1aa557eb733fb5e90edd7c/pyqt6_sip-13.11.1-cp311-cp311-win_amd64.whl + name: pyqt6-sip + version: 13.11.1 + sha256: 98db8ed37cf08130e1ee74b8ff47a6bfb8c3cdfe826310597a630a50e47feedc + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6f/85/dd9f03d78d87460e109e0121cd6201c5802bdd655656bf2780e964870fea/pyqt6-6.11.0-cp310-abi3-win_amd64.whl + name: pyqt6 + version: 6.11.0 + sha256: bd11b459c54dca068e988a42cf838303334f0d441b9d16d92ae6719fcb5ac6ba + requires_dist: + - pyqt6-sip>=13.8,<14 + - pyqt6-qt6>=6.11.0,<6.12.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/70/0b/06ccf917ce30d78d75e452d0afc89236b1d384b776c5c33d75c8cc55fbe7/pyvisa_py-0.8.1-py3-none-any.whl + name: pyvisa-py + version: 0.8.1 + sha256: 31208a2933c1793b4e829ba5f07d265b83f280668df440ee7ee6ac505dea4ee9 + requires_dist: + - pyvisa>=1.15.0 + - typing-extensions + - gpib-ctypes>=0.3.0 ; extra == 'gpib-ctypes' + - pyserial>=3.0 ; extra == 'serial' + - pyusb ; extra == 'usb' + - pyusb ; extra == 'usb-full' + - libusb-package ; extra == 'usb-full' + - psutil ; extra == 'psutil' + - zeroconf ; extra == 'hislip-discovery' + - pyvicp ; extra == 'vicp' + - zeroconf ; extra == 'vicp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7b/6b/0f13486003aea3eb349c2946b7ec9753e7558b78e35d22c938062a96959c/binho_host_adapter-0.1.6-py3-none-any.whl + name: binho-host-adapter + version: 0.1.6 + sha256: f71ca176c1e2fc1a5dce128beb286da217555c6c7c805f2ed282a6f3507ec277 + requires_dist: + - pyserial +- pypi: https://files.pythonhosted.org/packages/90/db/929ab0085ec89e46bd3a58c74b451dd770c3285dfa0cbd4f4aa4730da004/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-devices-bluetooth-genericattributeprofile + version: 3.2.1 + sha256: 8179638a6c721b0bbf04ba251ef98d5e02d9a17f0cce377398e42c4fbb441415 + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-devices-bluetooth[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-devices-enumeration[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/97/05/b5fa1fcc4cc68bda4bd317cb60a826eef753d9ab8efcd2d28fbcc7d60dd2/adafruit_blinka_bleio-4.1.2-py3-none-any.whl + name: adafruit-blinka-bleio + version: 4.1.2 + sha256: f4e21ed7072d05e86261c69b43c72b050c3d8b1b3297b6c71b36292ffbfc3275 + requires_dist: + - adafruit-blinka + - bleak +- pypi: https://files.pythonhosted.org/packages/ac/1a/d172d6f1c2fae53535e7f23835025cf39e3002749a0304f18a38e8ed490d/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-devices-bluetooth-advertisement + version: 3.2.1 + sha256: 78e99dd48b4d89b71b7778c5085fdba64e754dd3ebc54fd09c200fe5222c6e09 + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-devices-bluetooth[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ac/66/d05f6e6c0517654734e7f87fa1f0fbc965add9f27cc36b524d96331ab3d8/winrt_runtime-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-runtime + version: 3.2.1 + sha256: c0a9046ae416808420a358c51705af8ae100acd40bc578be57ddfdd51cbb0f9c + requires_dist: + - typing-extensions>=4.12.2 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b6/d5/5761a8b6dcc56957018970dd443059c8ee8a79de7b07f0b4d143f8e7dc15/winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-devices-bluetooth + version: 3.2.1 + sha256: 44277a3f2cc5ac32ce9b4b2d96c5c5f601d394ac5f02cc71bcd551f738660e2d + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-devices-bluetooth-genericattributeprofile[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-devices-bluetooth-rfcomm[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-devices-enumeration[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-devices-radios[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-networking[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/bb/97/17dac675981730d29b2d583de2aa0a9c2963d6c63f6bdc7eebf2711914ef/adafruit_blinka-9.1.0-py3-none-any.whl + name: adafruit-blinka + version: 9.1.0 + sha256: 6f617d4ebb7c2e14dfe1259c63f21df4a77d1b12d5efd92cf71ae4bf34d21b91 + requires_dist: + - adafruit-platformdetect>=3.70.1 + - adafruit-pureio>=1.1.7 + - binho-host-adapter>=0.1.6 + - pyftdi>=0.40.0 + - adafruit-circuitpython-typing + - sysv-ipc>=1.1.0 ; platform_machine != 'mips' and sys_platform == 'linux' + - toml>=0.10.2 ; python_full_version < '3.11' + requires_python: '>=3.7.0' +- pypi: https://files.pythonhosted.org/packages/cc/a1/578a03ba2bce0809b4e30974b47958963c9efe67b9fe74e7dbcdbbd45318/adafruit_circuitpython_typing-1.12.3-py3-none-any.whl + name: adafruit-circuitpython-typing + version: 1.12.3 + sha256: f6d0a02150e1e4efb5a2c2945b88d948809fdb465875f39947108b8467c986d9 + requires_dist: + - adafruit-blinka + - adafruit-circuitpython-busdevice + - adafruit-circuitpython-requests + - typing-extensions~=4.0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ce/0b/6e3121375aeda38fa76669f6caca2794e4a10fc7396ae88a3bf0a3b5a09b/telemetrix-1.46-py3-none-any.whl + name: telemetrix + version: '1.46' + sha256: 38c62b58211f44ce63909ede884b19c521babfc2cce67d9107f9644de5d4d0dc + requires_dist: + - pyserial + - bleak + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/dc/1d/616b3770ebf191f8d9aa1a5ae919d4885e3037341ea1d367e0045235c815/telemetrix_esp32-2.0.0-py3-none-any.whl + name: telemetrix-esp32 + version: 2.0.0 + sha256: f861ef12bae6d1a3b7211d906e5540f656c4944cd1645f420991dd914eacdfaa + requires_dist: + - pyserial + - bleak + - adafruit-blinka-bleio + - adafruit-circuitpython-ble + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/de/49/ba69e3180585dbc6f3336a09fef7cba4558a6a1e7d500500f62c1478418e/winrt_windows_devices_radios-3.2.1-cp311-cp311-win_amd64.whl + name: winrt-windows-devices-radios + version: 3.2.1 + sha256: f87745486d313ba1e7562ca97f25ad436ec01ad4b3b9ea349fb6b6f25cb41104 + requires_dist: + - winrt-runtime~=3.2.1.0 + - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' + - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e4/43/929d17e5dbe0773e3a3c728b12cf1a777ada32639764b09365a1b56703c0/adafruit_circuitpython_connectionmanager-3.1.8-py3-none-any.whl + name: adafruit-circuitpython-connectionmanager + version: 3.1.8 + sha256: f93e27874a840f728b5cdbb1bcf0aee4e75ed1c0ba46b4562606ac3ac3ea2cca + requires_dist: + - adafruit-blinka +- pypi: https://files.pythonhosted.org/packages/e4/a9/776da4f397003cca093659524c3526590522f81b642936838428c11274e6/pyvisa-1.16.2-py3-none-any.whl + name: pyvisa + version: 1.16.2 + sha256: 54f034adafd3e8d1858d57cdafec64e920444f4b84b31c9fd17487fbad0a197a + requires_dist: + - typing-extensions>=4.0.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fa/f1/70e83c23bf897c7f5025aa100482f482038ef70232dc27b407659d941fbf/pyqt6_qt6-6.11.1-py3-none-win_amd64.whl + name: pyqt6-qt6 + version: 6.11.1 + sha256: 7486c80512e823f2d3087e67f854f0556b345f4368040a853c8dc4d30fd3fe69 diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..6d2207f --- /dev/null +++ b/pixi.toml @@ -0,0 +1,24 @@ +[workspace] +name = "pymodaq-plugins-arduino" +version = "0.0.1" +description = "Dev environment for pymodaq-plugins-arduino" +channels = ["conda-forge"] +platforms = ["win-64"] + +[dependencies] +python = "3.11.*" +pytables = "*" +pip = "*" +git = "*" +numpy = ">=1.26,<2.0.0" + +[pypi-dependencies] +PyQt6 = "*" +telemetrix = "*" +telemetrix-esp32 = "*" +pyvisa = "*" +pyvisa-py = "*" + +[tasks] +setup = { cmd = "python setup_dev.py", description = "Clone PyMoDAQ 5.0.x et installe tout en mode éditable" } +test = { cmd = "pytest tests/" } diff --git a/setup_dev.py b/setup_dev.py new file mode 100644 index 0000000..69702ee --- /dev/null +++ b/setup_dev.py @@ -0,0 +1,89 @@ +""" +setup_dev.py — Appelé par `pixi run setup` +Clone PyMoDAQ branche 5.0.x et installe les packages en mode éditable. +Idempotent : peut être relancé sans problème. +""" + +import subprocess +import sys +from pathlib import Path + +PYMODAQ_REPO = "https://github.com/PyMoDAQ/PyMoDAQ" +PYMODAQ_BRANCH = "5.0.x" +PYMODAQ_DIR = Path(__file__).parent / ".pymodaq_dev" + +PYMODAQ_PACKAGES = [ + "pymodaq_utils", + "pymodaq_data", + "pymodaq_gui", + "pymodaq", +] + + +def run(cmd: list[str], **kwargs) -> None: + print(f" > {' '.join(cmd)}") + subprocess.run(cmd, check=True, **kwargs) + + +def clone_or_update() -> None: + if PYMODAQ_DIR.exists(): + print(f"[PyMoDAQ] Mise à jour de la branche '{PYMODAQ_BRANCH}'...") + run(["git", "pull"], cwd=PYMODAQ_DIR) + else: + print(f"[PyMoDAQ] Clonage de la branche '{PYMODAQ_BRANCH}'...") + run(["git", "clone", "-b", PYMODAQ_BRANCH, PYMODAQ_REPO, str(PYMODAQ_DIR)]) + + +def install_packages() -> None: + packages_dir = PYMODAQ_DIR / "packages" + if packages_dir.exists(): + print("[PyMoDAQ] Installation des packages en mode éditable...") + for pkg in PYMODAQ_PACKAGES: + pkg_path = packages_dir / pkg + if pkg_path.exists(): + print(f" pip install -e {pkg}") + run([sys.executable, "-m", "pip", "install", "-e", str(pkg_path)]) + else: + print(f" (ignoré : {pkg} non trouvé)") + else: + print("[PyMoDAQ] Installation depuis la racine...") + run([sys.executable, "-m", "pip", "install", "-e", str(PYMODAQ_DIR)]) + + # Installer ce plugin en mode éditable + plugin_dir = Path(__file__).parent + print(f" pip install -e . (plugin Arduino)") + run([sys.executable, "-m", "pip", "install", "-e", str(plugin_dir)]) + + +def verify() -> None: + print("\n[Vérification]") + import importlib.util + for module, label in [ + ("pymodaq_utils", "pymodaq_utils"), + ("pymodaq", "pymodaq"), + ("pymodaq_plugins_arduino", "pymodaq_plugins_arduino"), + ("PyQt6", "PyQt6"), + ("tables", "tables (HDF5)"), + ]: + spec = importlib.util.find_spec(module) + if spec is not None: + print(f" ✓ {label} ({spec.origin})") + else: + print(f" ✗ {label} — NON TROUVÉ") + + +if __name__ == "__main__": + print("=" * 55) + print(" Setup PyMoDAQ Arduino Plugin — mode contributeur") + print(" Branche PyMoDAQ : 5.0.x") + print("=" * 55) + + clone_or_update() + install_packages() + verify() + + print() + print(" Tout est prêt !") + print(" Vérifier la version : pixi run python -c \"import pymodaq; print(pymodaq.__version__)\"") + print(" Lancer les tests : pixi run test") + print("=" * 55) From f6ef10c8552ef1c912bb64c6bb711a573da9634a Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Mon, 8 Jun 2026 11:02:51 +0200 Subject: [PATCH 51/65] Edit README --- README.rst | 57 +++++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/README.rst b/README.rst index ecfa47e..ca80028 100644 --- a/README.rst +++ b/README.rst @@ -1,34 +1,16 @@ pymodaq_plugins_arduino ####################### -.. the following must be adapted to your developed package, links to pypi, github description... - -.. image:: https://img.shields.io/pypi/v/pymodaq_plugins_arduino.svg - :target: https://pypi.org/project/pymodaq_plugins_arduino/ - :alt: Latest Version - -.. image:: https://readthedocs.org/projects/pymodaq/badge/?version=latest - :target: https://pymodaq.readthedocs.io/en/stable/?badge=latest - :alt: Documentation Status - -.. image:: https://github.com/PyMoDAQ/pymodaq_plugins_arduino/workflows/Upload%20Python%20Package/badge.svg - :target: https://github.com/PyMoDAQ/pymodaq_plugins_arduino - :alt: Publication Status - -.. image:: https://github.com/PyMoDAQ/pymodaq_plugins_arduino/actions/workflows/Test.yml/badge.svg - :target: https://github.com/PyMoDAQ/pymodaq_plugins_arduino/actions/workflows/Test.yml - - -This package regroups a list of instrument created around an arduino board. Some instruments use the -Telemetrix library to use python together with the arduino board. - +This package regroups a list of instruments created around an Arduino or ESP32 board. Some +instruments use the Telemetrix library to use Python together with the Arduino board. Others use the +Telemetrix AIO ESP32 library to communicate with an ESP32 board over WiFi. Authors ======= * Sebastien J. Weber (sebastien.weber@cemes.fr) * Jérémie Margueritat - +* Mohamed El Mokhtari (mohamed.elmokhtari26@gmail.com) Instruments =========== @@ -40,25 +22,34 @@ Actuators * **LED**: control of a multicolor LED using three PWM digital outputs and the Telemetrix library. Allows the control of the three color channel independently + * **LEDwithLCD**: same as **LED** actuator but displaying the red, green, blue values on a standard 16x2 liquid crystal display -* **Analog**: data acquisition from analog inputs +* **Analog**: data acquisition from analog inputs +* **FanHeater**: control of a heater and a fan using two PWM outputs and the Telemetrix AIO ESP32 + library. Allows the control of the heater and fan independently over WiFi. The PWM duty cycle + ranges from 0 to 255 (8-bit resolution) Extensions ========== -* **ColorSynthesizer**: DashBoard extension using RBG LED actuators. Allows to quicly select a RGB value and apply those +* **ColorSynthesizer**: DashBoard extension using RBG LED actuators. Allows to quickly select a RGB value and apply those to the actuators +Viewers +======= + +* **PT100**: reads temperature from a PT100 resistance temperature detector (RTD) wired to a + MAX31865 amplifier/ADC board. Communication with the MAX31865 is performed over bit-banged SPI + by an ESP32 running the Telemetrix AIO WiFi firmware. Installation instructions ========================= * PyMoDAQ version > 4.1.0 - LED actuator ++++++++++++ @@ -82,5 +73,19 @@ The **Analog** 0D viewer uses the telemetrix library. The corresponding sketch s on the arduino board. This allows to acquire data from the analog inputs on an Arduino board from python objects on the connected computer. See https://mryslab.github.io/telemetrix/ -Here are `detailed installation instructions `_. +FanHeater actuator +++++++++++++++++++ + +The **FanHeater** actuator uses the telemetrix-aio-esp32 library. The corresponding firmware should +therefore be uploaded on the ESP32 board. This allows to control a heater and a fan connected to +the ESP32 over WiFi from python objects on the connected computer. +See https://mryslab.github.io/telemetrix-esp32/ + +PT100 0D viewer ++++++++++++++++ + +The **PT100** 0D viewer uses the telemetrix-aio-esp32 library. The corresponding firmware should +therefore be uploaded on the ESP32 board. This allows to acquire temperature data from a PT100 +sensor wired to a MAX31865 board connected to the ESP32 over WiFi. +Here are `detailed installation instructions `_. \ No newline at end of file From ae0d72abcbbf74f2edc3c7d398c1526ea77646b4 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 9 Jun 2026 09:58:47 +0200 Subject: [PATCH 52/65] Fix GPIO pins for sensors --- .../hardware/sensors/max31865/spi_max31865.py | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 9b93222..0214779 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -5,6 +5,8 @@ config = Config() +SPI_INIT = 22 + # ── MAX31865 register map ──────────────────────────────────────────────────── MAX31865_CONFIG_REG = 0x00 # Configuration register (write address = reg | 0x80) MAX31865_CONFIG_BIAS = 0x80 # Bias voltage ON @@ -47,32 +49,57 @@ def ini_max31865(self): """Initialise the SPI bus and put the MAX31865 in auto-conversion mode. Sequence: - 1. Register the CS pin with Telemetrix (``set_pin_mode_spi``). - 2. Write the configuration byte that enables the bias voltage and - selects continuous conversion. + 1. Send SPI_INIT manually with all four pin numbers. + (set_pin_mode_spi([cs]) only forwards the CS pin; the firmware's + init_spi() also needs SCK, MISO and MOSI.) + 2. Force the internal Telemetrix flags that gate spi_cs_control(). + (Bypassing set_pin_mode_spi leaves spi_enabled=False.) + 3. Write the configuration byte: bias voltage ON + auto conversion. """ - self._run(self._board.set_pin_mode_spi([self.cs_pin])) + self._run(self._manual_spi_init()) + # Telemetrix gates spi_cs_control() behind these two flags; set them + # manually because we bypassed the normal set_pin_mode_spi path. + self._board.spi_enabled = True + if self.cs_pin not in self._board.cs_pins_enabled: + self._board.cs_pins_enabled.append(self.cs_pin) config_byte = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO self._run(self._board.spi_cs_control(self.cs_pin, 0)) self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config_byte])) self._run(self._board.spi_cs_control(self.cs_pin, 1)) + async def _manual_spi_init(self): + """Send SPI_INIT (cmd 22) with all four pin numbers explicitly. + + Payload expected by firmware init_spi(): + [sck_pin, miso_pin, mosi_pin, num_cs=1, cs_pin] + """ + await self._board._send_command([SPI_INIT, + self.sck_pin, self.miso_pin, self.mosi_pin, + 1, self.cs_pin]) + def read_rtd_resistance(self) -> float: """Read the raw RTD register and return the equivalent resistance in Ω. The MAX31865 stores the 15-bit ADC result in registers 0x01 (MSB) and 0x02 (LSB). Bit 0 of the LSB is the fault flag and is discarded by shifting right one position before computing the resistance. - """ - data = [] - event = asyncio.Event() - async def spi_callback(report): - data.extend(report[3:]) - event.set() + Note: requires the firmware fix — read_blocking_spi must NOT OR the + register address with 0x80 (MAX31865 convention: bit7=0 = read). + """ + data = [] async def read(): + # asyncio.Event must be created inside the running event-loop thread + event = asyncio.Event() + + async def spi_callback(report): + # report layout from firmware: [len, SPI_REPORT, reg, num_bytes, byte0, byte1, ...] + # report[3:] = [byte0, byte1, ...] + data.extend(report[3:]) + event.set() + await self._board.spi_cs_control(self.cs_pin, 0) await self._board.spi_read_blocking( MAX31865_RTDMSB_REG, From feadd099b201bcb199ad985ee236dbbda2d028f0 Mon Sep 17 00:00:00 2001 From: elmokhtarim Date: Tue, 9 Jun 2026 11:11:50 +0200 Subject: [PATCH 53/65] Add Feature ADS1115.py --- .claude/settings.local.json | 9 + .../plugins_0D/daq_0Dviewer_ADS1115.py | 164 +++++++++++++++++ .../hardware/sensors/ads1115/__init__.py | 0 .../hardware/sensors/ads1115/i2c_ads1115.py | 174 ++++++++++++++++++ .../resources/config_template.toml | 10 +- 5 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 .claude/settings.local.json create mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py create mode 100644 src/pymodaq_plugins_arduino/hardware/sensors/ads1115/__init__.py create mode 100644 src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..558f5cb --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(Get-ChildItem -Path \"D:\\\\elmokhtarim.SNIRW\\\\pymodaq-plugins-arduino\" -Recurse -Directory)", + "Bash(Select-Object -ExpandProperty FullName)", + "Bash(Sort-Object)" + ] + } +} diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py new file mode 100644 index 0000000..975169e --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py @@ -0,0 +1,164 @@ +from typing import Optional + +import numpy as np +from pymodaq.utils.data import DataFromPlugins, DataToExport +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main +from pymodaq.utils.parameter import Parameter + +from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi +from pymodaq_plugins_arduino.hardware.sensors.ads1115.i2c_ads1115 import ADS1115, GAIN_CONFIG, DATA_RATE_CONFIG +from pymodaq_plugins_arduino.utils import Config + +config = Config() + + +class DAQ_0DViewer_ADS1115(DAQ_Viewer_base): + """Instrument plugin class for a 0D viewer. + + This object inherits all functionalities to communicate with PyMoDAQ's DAQ_Viewer module through + inheritance via DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python + wrapper of a particular instrument. + + This plugin reads analog voltages from an ADS1115 (16-bit) or ADS1015 (12-bit) I2C ADC. + Communication is performed over I2C by an ESP32 running the Telemetrix AIO WiFi firmware. + + Up to 4 single-ended channels (AIN0–AIN3) can be acquired simultaneously. The number of + active channels, the PGA gain, and the data rate are configurable from the parameter tree. + + Attributes: + ----------- + controller: ArduinoWifi + Connection to the ESP32 board. + ads: ADS1115 + Low-level driver for the ADS1115/ADS1015 chip. + """ + + _controller_units = 'V' + + params = comon_parameters + [ + {'title': 'Connection', 'name': 'connection', 'type': 'group', 'children': [ + {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', + 'value': config('esp32', 'ip_address')}, + ]}, + {'title': 'I2C Settings', 'name': 'i2c', 'type': 'group', 'children': [ + {'title': 'I2C Address (hex):', 'name': 'i2c_address', 'type': 'int', + 'value': config('ads1115', 'i2c_address'), + 'tip': '0x48=ADDR→GND 0x49=ADDR→VDD 0x4A=ADDR→SDA 0x4B=ADDR→SCL'}, + {'title': 'SDA pin:', 'name': 'sda_pin', 'type': 'int', + 'value': config('ads1115', 'sda_pin'), + 'tip': 'Nano ESP32 : A4 = GPIO18'}, + {'title': 'SCL pin:', 'name': 'scl_pin', 'type': 'int', + 'value': config('ads1115', 'scl_pin'), + 'tip': 'Nano ESP32 : A5 = GPIO19'}, + ]}, + {'title': 'ADC Settings', 'name': 'adc', 'type': 'group', 'children': [ + {'title': 'Chip type:', 'name': 'chip_type', 'type': 'list', + 'limits': ['ADS1115 (16-bit)', 'ADS1015 (12-bit)'], + 'value': 'ADS1115 (16-bit)'}, + {'title': 'Gain (PGA):', 'name': 'gain', 'type': 'list', + 'limits': list(GAIN_CONFIG.keys()), + 'value': config('ads1115', 'gain'), + 'tip': 'Gain × → full-scale range: 2/3×→±6.144V 1×→±4.096V 2×→±2.048V 4×→±1.024V 8×→±0.512V 16×→±0.256V'}, + {'title': 'Data rate (SPS):', 'name': 'data_rate', 'type': 'list', + 'limits': list(DATA_RATE_CONFIG.keys()), + 'value': 128}, + {'title': 'Active channels:', 'name': 'num_channels', 'type': 'int', + 'value': 1, 'min': 1, 'max': 4, + 'tip': 'Number of single-ended channels to read (AIN0 … AIN(n-1))'}, + ]}, + ] + + def ini_attributes(self): + self.controller: Optional[ArduinoWifi] = None + self.ads: Optional[ADS1115] = None + + def commit_settings(self, param: Parameter): + """Apply the consequences of a change of value in the detector settings. + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been changed by the user + """ + if param.name() in ('gain', 'data_rate', 'chip_type'): + if self.ads is not None: + self.ads.gain = self.settings['adc', 'gain'] + self.ads.data_rate = self.settings['adc', 'data_rate'] + self.ads.is_ads1015 = self.settings['adc', 'chip_type'] == 'ADS1015 (12-bit)' + + def ini_detector(self, controller=None): + """Detector communication initialization. + + Parameters + ---------- + controller: object + Custom object of a PyMoDAQ plugin (Slave case). None if this is the Master. + + Returns + ------- + info: str + initialized: bool + False if initialization failed otherwise True. + """ + self.ini_detector_init(slave_controller=controller) + + if self.is_master: + self.controller = ArduinoWifi( + ip_address=self.settings['connection', 'ip_address'] + ) + + self.ads = ADS1115( + controller=self.controller, + i2c_address=self.settings['i2c', 'i2c_address'], + sda_pin=self.settings['i2c', 'sda_pin'], + scl_pin=self.settings['i2c', 'scl_pin'], + gain=self.settings['adc', 'gain'], + data_rate=self.settings['adc', 'data_rate'], + is_ads1015=self.settings['adc', 'chip_type'] == 'ADS1015 (12-bit)', + ) + self.ads.ini_ads1115() + + info = "ADS1115 ready" + initialized = True + return info, initialized + + def close(self): + """Terminate the communication protocol.""" + if self.is_master: + self.controller.shutdown() + + def grab_data(self, Naverage=1, **kwargs): + """Start a grab from the detector. + + Parameters + ---------- + Naverage: int + Number of hardware averaging iterations. + kwargs: dict + Other optional arguments. + """ + num_channels = self.settings['adc', 'num_channels'] + + voltages = [] + labels = [] + for ch in range(num_channels): + voltages.append(np.array([self.ads.get_voltage(ch)])) + labels.append(f'AIN{ch} (V)') + + self.dte_signal.emit(DataToExport( + name='ADS1115', + data=[DataFromPlugins( + name='Voltage', + data=voltages, + dim='Data0D', + labels=labels, + )] + )) + + def stop(self): + """Stop the current grab hardware wise if necessary.""" + pass + + +if __name__ == '__main__': + main(__file__) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/__init__.py b/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py b/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py new file mode 100644 index 0000000..88a25f5 --- /dev/null +++ b/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py @@ -0,0 +1,174 @@ +import asyncio +import time + +from pymodaq_plugins_arduino.hardware.esp32_telemetrix import ArduinoWifi +from pymodaq_plugins_arduino.utils import Config + +config = Config() + +# ADS1115/ADS1015 register addresses +ADS_REG_CONVERSION = 0x00 +ADS_REG_CONFIG = 0x01 + +# MUX bits for single-ended channels (bits 14:12 of config register) +_MUX_SINGLE = { + 0: 0x4000, # AIN0 vs GND + 1: 0x5000, # AIN1 vs GND + 2: 0x6000, # AIN2 vs GND + 3: 0x7000, # AIN3 vs GND +} + +# PGA (Gain) settings: gain_label -> (config_bits, full-scale range in V) +GAIN_CONFIG = { + '2/3': (0x0000, 6.144), + '1': (0x0200, 4.096), + '2': (0x0400, 2.048), + '4': (0x0600, 1.024), + '8': (0x0800, 0.512), + '16': (0x0A00, 0.256), +} + +# Data rate bits (bits 7:5 of config register) +DATA_RATE_CONFIG = { + 8: 0x0000, + 16: 0x0020, + 32: 0x0040, + 64: 0x0060, + 128: 0x0080, + 250: 0x00A0, + 475: 0x00C0, + 860: 0x00E0, +} + + +class ADS1115: + """Software driver for the ADS1115 / ADS1015 I2C ADC. + + Communicates with the chip over I2C via the Telemetrix AIO firmware + running on the ESP32. Pins and address are configurable at construction + time or read from *config_template.toml*. + + Attributes: + ----------- + i2c_address: int + 7-bit I2C address of the chip (0x48–0x4B depending on ADDR pin). + sda_pin, scl_pin: int + GPIO numbers for the I2C bus. + gain: str + PGA gain label (one of '2/3', '1', '2', '4', '8', '16'). + data_rate: int + Samples per second (8, 16, 32, 64, 128, 250, 475, 860). + is_ads1015: bool + True for ADS1015 (12-bit); False for ADS1115 (16-bit, default). + """ + + def __init__(self, controller: ArduinoWifi, + i2c_address: int = None, + sda_pin: int = None, + scl_pin: int = None, + gain: str = '1', + data_rate: int = 128, + is_ads1015: bool = False): + self._board = controller._board + self._run = controller._run + + self.i2c_address = i2c_address if i2c_address is not None else config('ads1115', 'i2c_address') + self.sda_pin = sda_pin if sda_pin is not None else config('ads1115', 'sda_pin') + self.scl_pin = scl_pin if scl_pin is not None else config('ads1115', 'scl_pin') + self.gain = gain + self.data_rate = data_rate + self.is_ads1015 = is_ads1015 + + def ini_ads1115(self): + """Initialise the I2C bus on the ESP32.""" + self._run(self._board.set_pin_mode_i2c( + i2c_port=0, + sda_gpio=self.sda_pin, + scl_gpio=self.scl_pin, + )) + + def read_channel(self, channel: int) -> float: + """Trigger a single-ended conversion on *channel* and return the voltage in V. + + Parameters + ---------- + channel: int + Analog input channel, 0–3 (AIN0–AIN3 vs GND). + + Returns + ------- + float + Measured voltage in volts. + """ + if channel not in _MUX_SINGLE: + raise ValueError(f"Channel must be 0–3, got {channel}") + + gain_bits, fsr = GAIN_CONFIG[self.gain] + dr_bits = DATA_RATE_CONFIG.get(self.data_rate, 0x0080) + + # Build 16-bit config register: + # OS=1 (start single-shot), MUX, PGA, MODE=1 (single-shot), + # DR, COMP_MODE/POL/LAT=0, COMP_QUE=11 (disabled) + config_reg = ( + 0x8000 | + _MUX_SINGLE[channel] | + gain_bits | + 0x0100 | # single-shot mode + dr_bits | + 0x0003 # disable comparator + ) + + msb = (config_reg >> 8) & 0xFF + lsb = config_reg & 0xFF + + # Write config register to start conversion + self._run(self._board.i2c_write( + self.i2c_address, + [ADS_REG_CONFIG, msb, lsb], + )) + + # Allow time for conversion: 1/data_rate + margin + time.sleep(1.0 / self.data_rate + 0.002) + + # Read 2 bytes from conversion register + data = [] + + async def _read(): + event = asyncio.Event() + + async def _callback(report): + # Firmware sends: [len, I2C_READ_REPORT, num_bytes, address, register, byte0, ...] + # Python lib strips len → report = [I2C_READ_REPORT, num_bytes, address, register, byte0, ...] + # Data bytes start at index 4. + data.extend(report[4:4 + report[1]]) + event.set() + + await self._board.i2c_read( + self.i2c_address, + ADS_REG_CONVERSION, + 2, + _callback, + ) + await asyncio.wait_for(event.wait(), timeout=5) + + self._run(_read()) + + raw = (data[0] << 8) | data[1] + + # ADS1015 result occupies the upper 12 bits → shift right by 4 + if self.is_ads1015: + raw >>= 4 + full_scale = 2048.0 + else: + full_scale = 32768.0 + + # Convert unsigned raw to signed (two's complement) + half = int(full_scale) + if raw >= half: + raw -= 2 * half + + return (raw / full_scale) * fsr + + def get_voltage(self, channel: int) -> float: + """Return the voltage (V) measured on *channel* (0–3).""" + return self.read_channel(channel) diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index e21097d..6303254 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -37,9 +37,17 @@ heater_fan_pin = 8 [max31865] #Broches SPI pour le capteur MAX31865 / PT100 -#Vameirs par défaut : Nano ESP32 en mode GPIO Legacy +#Valeurs par défaut : Nano ESP32 en mode GPIO Legacy sck_pin = 48 miso_pin = 47 mosi_pin = 38 cs_pin = 21 +[ads1115] +#Broches I2C et adresse pour le capteur GY-ADS1115 / ADS1015 +#Adresse I2C : 0x48 (ADDR→GND), 0x49 (ADDR→VDD), 0x4A (ADDR→SDA), 0x4B (ADDR→SCL) +i2c_address = 0x48 +sda_pin = 18 # A4 sur Nano ESP32 +scl_pin = 19 # A5 sur Nano ESP32 +gain = "1" + From 2b5fca24d07657b569dd5eceb6c7281bb6c66eb2 Mon Sep 17 00:00:00 2001 From: Mohamed EL Mokhtari Date: Thu, 11 Jun 2026 09:48:47 +0200 Subject: [PATCH 54/65] Fix improve SPI --- .claude/settings.local.json | 8 +++++++- .pymodaq_dev | 1 - .../hardware/sensors/max31865/spi_max31865.py | 11 ++++++++++- .../resources/config_template.toml | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) delete mode 160000 .pymodaq_dev diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 558f5cb..04330bc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,7 +3,13 @@ "allow": [ "Bash(Get-ChildItem -Path \"D:\\\\elmokhtarim.SNIRW\\\\pymodaq-plugins-arduino\" -Recurse -Directory)", "Bash(Select-Object -ExpandProperty FullName)", - "Bash(Sort-Object)" + "Bash(Sort-Object)", + "WebSearch", + "PowerShell(python -c \"import telemetrix_aio_esp32, os; print\\(os.path.dirname\\(telemetrix_aio_esp32.__file__\\)\\)\")", + "PowerShell($paths = @\\(\"$env:USERPROFILE\\\\miniconda3\\\\envs\", \"$env:USERPROFILE\\\\anaconda3\\\\envs\", \"$env:USERPROFILE\\\\.conda\\\\envs\", \"$env:LOCALAPPDATA\\\\miniconda3\\\\envs\", \"C:\\\\ProgramData\\\\miniconda3\\\\envs\", \"C:\\\\ProgramData\\\\anaconda3\\\\envs\"\\); foreach \\($p in $paths\\) { if \\(Test-Path $p\\) { Get-ChildItem $p -Directory | Select-Object -ExpandProperty FullName } })", + "PowerShell($cli = Get-Command arduino-cli -ErrorAction SilentlyContinue; if \\($cli\\) { $cli.Source; arduino-cli version } else { \"arduino-cli absent\" }; $ide = Get-ChildItem \"$env:LOCALAPPDATA\\\\Programs\\\\Arduino IDE\",\"C:\\\\Program Files\\\\Arduino IDE\" -ErrorAction SilentlyContinue -Filter \"Arduino IDE.exe\" -Recurse | Select-Object -First 1; if \\($ide\\) { $ide.FullName } else { \"Arduino IDE introuvable aux emplacements standards\" })", + "WebFetch(domain:htmlpreview.github.io)", + "WebFetch(domain:raw.githubusercontent.com)" ] } } diff --git a/.pymodaq_dev b/.pymodaq_dev deleted file mode 160000 index 52a8c42..0000000 --- a/.pymodaq_dev +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 52a8c42390b6b51dc11891832e8d09efed3f6c3d diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 0214779..0434f4c 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -54,7 +54,12 @@ def ini_max31865(self): init_spi() also needs SCK, MISO and MOSI.) 2. Force the internal Telemetrix flags that gate spi_cs_control(). (Bypassing set_pin_mode_spi leaves spi_enabled=False.) - 3. Write the configuration byte: bias voltage ON + auto conversion. + 3. Set the SPI format: the MAX31865 requires SPI mode 1 or 3 + (CPHA=1); the firmware default is mode 0, which the chip ignores. + 4. Write the configuration byte: bias voltage ON + auto conversion. + + Requires firmware >= 3.1.1 (read address sent as-is + SPI format + actually applied through SPI.beginTransaction()). """ self._run(self._manual_spi_init()) # Telemetrix gates spi_cs_control() behind these two flags; set them @@ -63,6 +68,10 @@ def ini_max31865(self): if self.cs_pin not in self._board.cs_pins_enabled: self._board.cs_pins_enabled.append(self.cs_pin) + # 1 MHz (divisor 16 of the Arduino 16 MHz convention), MSB first, + # SPI mode 1 (AVR constant 0x04) as required by the MAX31865. + self._run(self._board.spi_set_format(16, 1, 0x04)) + config_byte = MAX31865_CONFIG_BIAS | MAX31865_CONFIG_MODEAUTO self._run(self._board.spi_cs_control(self.cs_pin, 0)) self._run(self._board.spi_write_blocking([MAX31865_CONFIG_REG | 0x80, config_byte])) diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index 6303254..b88b9a5 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -1,7 +1,7 @@ title = "this is the configuration file of the plugin Arduino" com_port = "COM24" -ip_address = "172.17.50.236" +ip_address = "172.17.50.53" ip_port = 31336 [esp32] From 81cf13d241530bc6e2e2e9c6c982c1ecf819e9e3 Mon Sep 17 00:00:00 2001 From: Mohamed EL Mokhtari Date: Thu, 11 Jun 2026 10:07:25 +0200 Subject: [PATCH 55/65] Fix name MOVE --- ...viewer_PT100.py => daq_0Dviewer_Temperature_MAX31865.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/{daq_0Dviewer_PT100.py => daq_0Dviewer_Temperature_MAX31865.py} (97%) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py similarity index 97% rename from src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py rename to src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py index 624681f..98e05d1 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_PT100.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py @@ -12,7 +12,7 @@ config = Config() -class DAQ_0DViewer_PT100(DAQ_Viewer_base): +class DAQ_0DViewer_Temperature_MAX31865(DAQ_Viewer_base): """Instrument plugin class for a 0D viewer. This object inherits all functionalities to communicate with PyMoDAQ's DAQ_Viewer module through @@ -103,7 +103,7 @@ def ini_detector(self, controller=None): ) self.max31865.ini_max31865() - info = "PT100 ready" + info = "Temperature MAX31865 ready" initialized = True return info, initialized @@ -125,7 +125,7 @@ def grab_data(self, Naverage=1, **kwargs): """ temperature = self.max31865.get_temperature() self.dte_signal.emit(DataToExport( - name='PT100', + name='Temperature MAX31865', data=[DataFromPlugins( name='Temperature', data=[np.array([temperature])], From eb0e24f5c42ce6a0edfe7df035f5cbbd8509f4a4 Mon Sep 17 00:00:00 2001 From: Mohamed EL Mokhtari Date: Thu, 11 Jun 2026 11:06:21 +0200 Subject: [PATCH 56/65] Fix I2C and SPI protocol communication --- .claude/settings.local.json | 4 ++- .../plugins_0D/daq_0Dviewer_ADS1115.py | 6 ++--- .../daq_0Dviewer_Temperature_MAX31865.py | 2 +- .../hardware/sensors/ads1115/i2c_ads1115.py | 25 ++++++++++++++----- .../resources/config_template.toml | 4 +-- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 04330bc..3d407b4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,9 @@ "PowerShell($paths = @\\(\"$env:USERPROFILE\\\\miniconda3\\\\envs\", \"$env:USERPROFILE\\\\anaconda3\\\\envs\", \"$env:USERPROFILE\\\\.conda\\\\envs\", \"$env:LOCALAPPDATA\\\\miniconda3\\\\envs\", \"C:\\\\ProgramData\\\\miniconda3\\\\envs\", \"C:\\\\ProgramData\\\\anaconda3\\\\envs\"\\); foreach \\($p in $paths\\) { if \\(Test-Path $p\\) { Get-ChildItem $p -Directory | Select-Object -ExpandProperty FullName } })", "PowerShell($cli = Get-Command arduino-cli -ErrorAction SilentlyContinue; if \\($cli\\) { $cli.Source; arduino-cli version } else { \"arduino-cli absent\" }; $ide = Get-ChildItem \"$env:LOCALAPPDATA\\\\Programs\\\\Arduino IDE\",\"C:\\\\Program Files\\\\Arduino IDE\" -ErrorAction SilentlyContinue -Filter \"Arduino IDE.exe\" -Recurse | Select-Object -First 1; if \\($ide\\) { $ide.FullName } else { \"Arduino IDE introuvable aux emplacements standards\" })", "WebFetch(domain:htmlpreview.github.io)", - "WebFetch(domain:raw.githubusercontent.com)" + "WebFetch(domain:raw.githubusercontent.com)", + "WebFetch(domain:docs.arduino.cc)", + "PowerShell($f = Get-ChildItem \"$env:LOCALAPPDATA\\\\Arduino15\\\\packages\" -Recurse -Filter \"pins_arduino.h\" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match \"nora\" } | Select-Object -First 1; $f.FullName)" ] } } diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py index 975169e..a146693 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py @@ -46,10 +46,10 @@ class DAQ_0DViewer_ADS1115(DAQ_Viewer_base): 'tip': '0x48=ADDR→GND 0x49=ADDR→VDD 0x4A=ADDR→SDA 0x4B=ADDR→SCL'}, {'title': 'SDA pin:', 'name': 'sda_pin', 'type': 'int', 'value': config('ads1115', 'sda_pin'), - 'tip': 'Nano ESP32 : A4 = GPIO18'}, + 'tip': 'Nano ESP32 : A4 = GPIO11 (SDA par défaut)'}, {'title': 'SCL pin:', 'name': 'scl_pin', 'type': 'int', 'value': config('ads1115', 'scl_pin'), - 'tip': 'Nano ESP32 : A5 = GPIO19'}, + 'tip': 'Nano ESP32 : A5 = GPIO12 (SCL par défaut)'}, ]}, {'title': 'ADC Settings', 'name': 'adc', 'type': 'group', 'children': [ {'title': 'Chip type:', 'name': 'chip_type', 'type': 'list', @@ -124,7 +124,7 @@ def ini_detector(self, controller=None): def close(self): """Terminate the communication protocol.""" - if self.is_master: + if self.is_master and self.controller is not None: self.controller.shutdown() def grab_data(self, Naverage=1, **kwargs): diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py index 98e05d1..5c5af14 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py @@ -109,7 +109,7 @@ def ini_detector(self, controller=None): def close(self): """Terminate the communication protocol""" - if self.is_master: + if self.is_master and self.controller is not None: self.controller.shutdown() def grab_data(self, Naverage=1, **kwargs): diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py b/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py index 88a25f5..5ae00f1 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/ads1115/i2c_ads1115.py @@ -6,6 +6,8 @@ config = Config() +I2C_BEGIN = 9 + # ADS1115/ADS1015 register addresses ADS_REG_CONVERSION = 0x00 ADS_REG_CONFIG = 0x01 @@ -80,12 +82,23 @@ def __init__(self, controller: ArduinoWifi, self.is_ads1015 = is_ads1015 def ini_ads1115(self): - """Initialise the I2C bus on the ESP32.""" - self._run(self._board.set_pin_mode_i2c( - i2c_port=0, - sda_gpio=self.sda_pin, - scl_gpio=self.scl_pin, - )) + """Initialise the I2C bus on the ESP32. + + set_pin_mode_i2c() in telemetrix_aio_esp32 2.0.0 takes no pin + arguments, so the I2C_BEGIN command is sent manually with the + SDA/SCL pins as payload. Firmware >= 3.1.2 applies them through + Wire.begin(sda, scl); older firmwares ignore the payload and use + the board defaults (A4 = GPIO11 / A5 = GPIO12 on the Nano ESP32, + which match the config defaults). + """ + async def _begin(): + await self._board._send_command( + [I2C_BEGIN, self.sda_pin, self.scl_pin]) + + self._run(_begin()) + # The lib gates i2c_read/i2c_write behind this flag; set it manually + # because we bypassed set_pin_mode_i2c(). + self._board.i2c_active = True def read_channel(self, channel: int) -> float: """Trigger a single-ended conversion on *channel* and return the voltage in V. diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index b88b9a5..582afb5 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -47,7 +47,7 @@ cs_pin = 21 #Broches I2C et adresse pour le capteur GY-ADS1115 / ADS1015 #Adresse I2C : 0x48 (ADDR→GND), 0x49 (ADDR→VDD), 0x4A (ADDR→SDA), 0x4B (ADDR→SCL) i2c_address = 0x48 -sda_pin = 18 # A4 sur Nano ESP32 -scl_pin = 19 # A5 sur Nano ESP32 +sda_pin = 11 # A4 sur Nano ESP32 (GPIO11, SDA par defaut) +scl_pin = 12 # A5 sur Nano ESP32 (GPIO12, SCL par defaut) gain = "1" From 8a9267346ac390786c572e80f36a72dbdecdbd6e Mon Sep 17 00:00:00 2001 From: Mohamed EL Mokhtari Date: Thu, 11 Jun 2026 11:14:43 +0200 Subject: [PATCH 57/65] Fix name of DAQ_VIEWER --- ..._0Dviewer_ADS1115.py => daq_0Dviewer_Voltage_ADS1115.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/{daq_0Dviewer_ADS1115.py => daq_0Dviewer_Voltage_ADS1115.py} (98%) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py similarity index 98% rename from src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py rename to src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py index a146693..3d1e24f 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_ADS1115.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py @@ -12,7 +12,7 @@ config = Config() -class DAQ_0DViewer_ADS1115(DAQ_Viewer_base): +class DAQ_0DViewer_Voltage_ADS1115(DAQ_Viewer_base): """Instrument plugin class for a 0D viewer. This object inherits all functionalities to communicate with PyMoDAQ's DAQ_Viewer module through @@ -118,7 +118,7 @@ def ini_detector(self, controller=None): ) self.ads.ini_ads1115() - info = "ADS1115 ready" + info = "Voltage ADS1115 ready" initialized = True return info, initialized @@ -146,7 +146,7 @@ def grab_data(self, Naverage=1, **kwargs): labels.append(f'AIN{ch} (V)') self.dte_signal.emit(DataToExport( - name='ADS1115', + name='Voltage ADS1115', data=[DataFromPlugins( name='Voltage', data=voltages, From 181f537df60cd196a54b83f3289ec0490c041b70 Mon Sep 17 00:00:00 2001 From: Mohamed EL Mokhtari Date: Thu, 11 Jun 2026 11:32:25 +0200 Subject: [PATCH 58/65] Fix SPI --- .../hardware/sensors/max31865/spi_max31865.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py index 0434f4c..0f127e9 100644 --- a/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py +++ b/src/pymodaq_plugins_arduino/hardware/sensors/max31865/spi_max31865.py @@ -15,7 +15,9 @@ # ── PT100 Callendar-Van Dusen coefficients ─────────────────────────────────── RTD_NOMINAL = 100.0 # PT100 nominal resistance at 0 °C (Ω) -RTD_REFERENCE = 430.0 # Reference resistor mounted on the MAX31865 board (Ω) +RTD_REFERENCE = 430.0 # Default reference resistor (Ω) — Adafruit boards use + # 430 Ω, but clones ship with 350/390/400 Ω: check the + # SMD resistor marked Rref next to the chip. RTD_A = 3.9083e-3 # CVD coefficient A RTD_B = -5.775e-7 # CVD coefficient B @@ -35,7 +37,8 @@ class MAX31865: def __init__(self, controller: ArduinoWifi, cs_pin: int = None, sck_pin: int = None, - miso_pin: int = None, mosi_pin: int = None): + miso_pin: int = None, mosi_pin: int = None, + ref_resistor: float = None): # Borrow the board handle and the synchronous _run helper from the controller self._board = controller._board self._run = controller._run @@ -44,6 +47,7 @@ def __init__(self, controller: ArduinoWifi, self.sck_pin = sck_pin or config('max31865', 'sck_pin') self.miso_pin = miso_pin or config('max31865', 'miso_pin') self.mosi_pin = mosi_pin or config('max31865', 'mosi_pin') + self.ref_resistor = ref_resistor or RTD_REFERENCE def ini_max31865(self): """Initialise the SPI bus and put the MAX31865 in auto-conversion mode. @@ -121,7 +125,7 @@ async def spi_callback(report): self._run(read()) rtd_raw = ((data[0] << 8) | data[1]) >> 1 # discard fault bit (LSB) - resistance = (rtd_raw / 32768.0) * RTD_REFERENCE + resistance = (rtd_raw / 32768.0) * self.ref_resistor return resistance def resistance_to_temperature(self, resistance: float) -> float: From 8b5411398a5d6eca901391c892ec9ecbc0bdfcb2 Mon Sep 17 00:00:00 2001 From: VIL-CIEL Date: Fri, 26 Jun 2026 21:28:24 +0200 Subject: [PATCH 59/65] =?UTF-8?q?chore:=20prepare=20branch=20for=20upstrea?= =?UTF-8?q?m=20PR=20=E2=80=94=20remove=20local/dev=20artifacts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove .claude/settings.local.json (local Claude Code config from a dev machine) - remove pixi.toml, pixi.lock, setup_dev.py (local dev tooling, unused by PyMoDAQ) - restore upstream CI workflows (.github/workflows/*) and .gitignore - restore template package dirs removed during development (daq_viewer_plugins/plugins_1D, plugins_2D, plugins_ND, exporters, models) Net change vs upstream/5.0.x is limited to the new features. --- .claude/settings.local.json | 17 - .github/workflows/Testbase.yml | 4 +- .github/workflows/python-publish.yml | 4 +- .github/workflows/updater.yml | 2 +- .gitignore | 4 - pixi.lock | 1062 ----------------- pixi.toml | 24 - setup_dev.py | 89 -- .../daq_viewer_plugins/plugins_1D/__init__.py | 13 + .../daq_viewer_plugins/plugins_2D/__init__.py | 14 + .../daq_viewer_plugins/plugins_ND/__init__.py | 13 + .../exporters/__init__.py | 6 + .../models/__init__.py | 6 + 13 files changed, 57 insertions(+), 1201 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 pixi.lock delete mode 100644 pixi.toml delete mode 100644 setup_dev.py create mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py create mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py create mode 100644 src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py create mode 100644 src/pymodaq_plugins_arduino/exporters/__init__.py create mode 100644 src/pymodaq_plugins_arduino/models/__init__.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 3d407b4..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(Get-ChildItem -Path \"D:\\\\elmokhtarim.SNIRW\\\\pymodaq-plugins-arduino\" -Recurse -Directory)", - "Bash(Select-Object -ExpandProperty FullName)", - "Bash(Sort-Object)", - "WebSearch", - "PowerShell(python -c \"import telemetrix_aio_esp32, os; print\\(os.path.dirname\\(telemetrix_aio_esp32.__file__\\)\\)\")", - "PowerShell($paths = @\\(\"$env:USERPROFILE\\\\miniconda3\\\\envs\", \"$env:USERPROFILE\\\\anaconda3\\\\envs\", \"$env:USERPROFILE\\\\.conda\\\\envs\", \"$env:LOCALAPPDATA\\\\miniconda3\\\\envs\", \"C:\\\\ProgramData\\\\miniconda3\\\\envs\", \"C:\\\\ProgramData\\\\anaconda3\\\\envs\"\\); foreach \\($p in $paths\\) { if \\(Test-Path $p\\) { Get-ChildItem $p -Directory | Select-Object -ExpandProperty FullName } })", - "PowerShell($cli = Get-Command arduino-cli -ErrorAction SilentlyContinue; if \\($cli\\) { $cli.Source; arduino-cli version } else { \"arduino-cli absent\" }; $ide = Get-ChildItem \"$env:LOCALAPPDATA\\\\Programs\\\\Arduino IDE\",\"C:\\\\Program Files\\\\Arduino IDE\" -ErrorAction SilentlyContinue -Filter \"Arduino IDE.exe\" -Recurse | Select-Object -First 1; if \\($ide\\) { $ide.FullName } else { \"Arduino IDE introuvable aux emplacements standards\" })", - "WebFetch(domain:htmlpreview.github.io)", - "WebFetch(domain:raw.githubusercontent.com)", - "WebFetch(domain:docs.arduino.cc)", - "PowerShell($f = Get-ChildItem \"$env:LOCALAPPDATA\\\\Arduino15\\\\packages\" -Recurse -Filter \"pins_arduino.h\" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match \"nora\" } | Select-Object -First 1; $f.FullName)" - ] - } -} diff --git a/.github/workflows/Testbase.yml b/.github/workflows/Testbase.yml index b5bfd9d..989a2a0 100644 --- a/.github/workflows/Testbase.yml +++ b/.github/workflows/Testbase.yml @@ -18,9 +18,9 @@ jobs: QT_DEBUG_PLUGINS: 1 steps: - name: Set up Python ${{ inputs.python }} - uses: actions/checkout@v6.0.1 + uses: actions/checkout@v4 - name: Install dependencies - uses: actions/setup-python@v6.1.0 + uses: actions/setup-python@v4 with: python-version: ${{ inputs.python }} - name: Install package diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 6bfb6f7..69806c5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6.1.0 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 1c948f9..f2d0fcd 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v4.2.2 with: # [Required] Access token with `workflow` scope. token: ${{ secrets.WORKFLOW_SECRET }} diff --git a/.gitignore b/.gitignore index d832fba..d97e2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -113,7 +113,3 @@ venv.bak/ *yacctab.py *lextab.py - -# VSCode Settings -.vscode/launch.json -.vscode/settings.json diff --git a/pixi.lock b/pixi.lock deleted file mode 100644 index e751289..0000000 --- a/pixi.lock +++ /dev/null @@ -1,1062 +0,0 @@ -version: 7 -platforms: -- name: win-64 -environments: - default: - channels: - - url: https://conda.anaconda.org/conda-forge/ - indexes: - - https://pypi.org/simple - packages: - win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.1.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/c-blosc2-3.1.2-h2af8807_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/git-2.54.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-8_h6c93730_netlib.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-8_hc41557d_netlib.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-8_h018ca30_netlib.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numexpr-2.14.1-py311h670de69_102.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-1.26.4-py311h0b4df5a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pytables-3.11.1-py311h155d883_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_38.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_38.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_38.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: https://files.pythonhosted.org/packages/01/8c/15a2de09cc3c30793336cf79798948768611463ddfb2b2669f51569228fb/adafruit_circuitpython_requests-4.1.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/fd/5bd5da5d7997725ba3f1995c16aa1c3362937f8ff68ad4cadfd3415eebcb/winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/05/a5/216d66df6bdcee58eb3877fabc1544337e23f850bf9f93838db7f5698371/winrt_windows_foundation-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/e8/0856886ebbb02dc47a91ec99fbade9da871a07e072d20503c3d89ecf712f/adafruit_circuitpython_ble-10.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/cd/0731490946e037e954ef83719f07c7672cf32bc90dd9c75201c40b827664/pyftdi-0.57.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/19/9d/28e9d12f36e13c5f2acba3098187b0e931290ecd1d8df924391b5ad2db19/Adafruit_PureIO-1.1.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/23/98/6c9c21b5e75ff5927a130da9eaf5ab628dfa1f93b64c181f0193706cbd6c/winrt_windows_storage_streams-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/26/54/05aceb9cd80073805b3ed8522e3196e8cb22f70e741873fa51406c31f4e7/bleak-3.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/29/c4/32572c051f1554d73633a802447321bff2a2332ef4210d47de807afd26c7/adafruit_platformdetect-3.88.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/32/58/049db1d95fdfc0c8451dc6db17442ed4e6b2aba361c425c0bb8dc8c98c4a/winrt_windows_foundation_collections-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/42/0d/66a4e0fbd7b35107f7dee04fed890f77b83d1da9dd1f7474af2ed21700ea/adafruit_circuitpython_busdevice-5.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/d6/c40e8ae38a6e2bce9e837b64688f55746bfdad1aa557eb733fb5e90edd7c/pyqt6_sip-13.11.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/6f/85/dd9f03d78d87460e109e0121cd6201c5802bdd655656bf2780e964870fea/pyqt6-6.11.0-cp310-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/70/0b/06ccf917ce30d78d75e452d0afc89236b1d384b776c5c33d75c8cc55fbe7/pyvisa_py-0.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7b/6b/0f13486003aea3eb349c2946b7ec9753e7558b78e35d22c938062a96959c/binho_host_adapter-0.1.6-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/db/929ab0085ec89e46bd3a58c74b451dd770c3285dfa0cbd4f4aa4730da004/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/97/05/b5fa1fcc4cc68bda4bd317cb60a826eef753d9ab8efcd2d28fbcc7d60dd2/adafruit_blinka_bleio-4.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/1a/d172d6f1c2fae53535e7f23835025cf39e3002749a0304f18a38e8ed490d/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ac/66/d05f6e6c0517654734e7f87fa1f0fbc965add9f27cc36b524d96331ab3d8/winrt_runtime-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b6/d5/5761a8b6dcc56957018970dd443059c8ee8a79de7b07f0b4d143f8e7dc15/winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/bb/97/17dac675981730d29b2d583de2aa0a9c2963d6c63f6bdc7eebf2711914ef/adafruit_blinka-9.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cc/a1/578a03ba2bce0809b4e30974b47958963c9efe67b9fe74e7dbcdbbd45318/adafruit_circuitpython_typing-1.12.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ce/0b/6e3121375aeda38fa76669f6caca2794e4a10fc7396ae88a3bf0a3b5a09b/telemetrix-1.46-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/1d/616b3770ebf191f8d9aa1a5ae919d4885e3037341ea1d367e0045235c815/telemetrix_esp32-2.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/49/ba69e3180585dbc6f3336a09fef7cba4558a6a1e7d500500f62c1478418e/winrt_windows_devices_radios-3.2.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e4/43/929d17e5dbe0773e3a3c728b12cf1a777ada32639764b09365a1b56703c0/adafruit_circuitpython_connectionmanager-3.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/a9/776da4f397003cca093659524c3526590522f81b642936838428c11274e6/pyvisa-1.16.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/f1/70e83c23bf897c7f5025aa100482f482038ef70232dc27b407659d941fbf/pyqt6_qt6-6.11.1-py3-none-win_amd64.whl -packages: -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-h4c7d964_0.conda - sha256: 86981d764e4ea1883409d30447ff9da46127426d31a63df08315aaded768e652 - md5: c9b86eece2f944541b86441c94117ab3 - depends: - - __win - license: ISC - purls: [] - size: 130182 - timestamp: 1779289939595 -- conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 - sha256: d38542a151a90417065c1a234866f97fd1ea82a81de75ecb725955ab78f88b4b - md5: 9a66894dfd07c4510beb6b3f9672ccc0 - constrains: - - mkl <0.a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 3843 - timestamp: 1582593857545 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 - md5: 4c06a92e74452cfa53623a81592e8934 - depends: - - python >=3.8 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/packaging?source=hash-mapping - size: 91574 - timestamp: 1777103621679 -- conda: https://conda.anaconda.org/conda-forge/noarch/pip-26.1.2-pyh8b19718_0.conda - sha256: 29b7d75bf81ad11645a8e320b369abdc90a92b93f2a9178e853d9dddf82e5106 - md5: 511fbc2c63d2c73650ad1755e4d357ba - depends: - - python >=3.10,<3.13.0a0 - - setuptools - - wheel - license: MIT - license_family: MIT - purls: - - pkg:pypi/pip?source=compressed-mapping - size: 1203173 - timestamp: 1780262795392 -- conda: https://conda.anaconda.org/conda-forge/noarch/py-cpuinfo-9.0.0-pyhd8ed1ab_1.conda - sha256: 6d8f03c13d085a569fde931892cded813474acbef2e03381a1a87f420c7da035 - md5: 46830ee16925d5ed250850503b5dc3a8 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/py-cpuinfo?source=hash-mapping - size: 25766 - timestamp: 1733236452235 -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - build_number: 8 - sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 - md5: 8fcb6b0e2161850556231336dae58358 - constrains: - - python 3.11.* *_cpython - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 7003 - timestamp: 1752805919375 -- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 - md5: 8e194e7b992f99a5015edbd4ebd38efd - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/setuptools?source=hash-mapping - size: 639697 - timestamp: 1773074868565 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - purls: [] - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d - depends: - - python >=3.10 - - python - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/typing-extensions?source=hash-mapping - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c - md5: ad659d0a2b3e47e38d829aa8cad2d610 - license: LicenseRef-Public-Domain - purls: [] - size: 119135 - timestamp: 1767016325805 -- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.47.0-pyhd8ed1ab_0.conda - sha256: 9e156ffaefb8463437144326ada4b85d1de17961b9997ac5f1cbbaf747bd8bed - md5: d0e3b2f0030cf4fca58bde71d246e94c - depends: - - packaging >=24.0 - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/wheel?source=hash-mapping - size: 33491 - timestamp: 1776878563806 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.10.1-h8b39d88_3.conda - sha256: ffa66e862ddcd8a825c3d44e83404daec7b8d36b7313650e09aa39443c312f5e - md5: 9f25944ccae498b7afbc81ce24f4c37a - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - - aws-c-http >=0.10.13,<0.10.14.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 127435 - timestamp: 1777489461908 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda - sha256: 5f61082caea9fbdd6ba02702935e9dea9997459a7e6c06fd47f21b81aac882fb - md5: 7cc4953d504d4e8f3d6f4facb8549465 - depends: - - aws-c-common >=0.12.6,<0.12.7.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 53613 - timestamp: 1764593604081 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda - sha256: 0627691c34eb3d9fcd18c71346d9f16f83e8e58f9983e792138a2cccf387d18a - md5: b1465f33b05b9af02ad0887c01837831 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 236441 - timestamp: 1763586152571 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda - sha256: f98fbb797d28de3ae41dbd42590549ee0a2a4e61772f9cc6d1a4fa45d47637de - md5: 0385f2340be1776b513258adaf70e208 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 23087 - timestamp: 1767790877990 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.13-h612f3e8_0.conda - sha256: cf939d4a0849bc41421b4c380b2bbbc0beb1fd9b375bb9627b98d9415ec9ea69 - md5: 88626be3c14ac87c09629dcbf65e6279 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-compression >=0.3.2,<0.3.3.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 208426 - timestamp: 1774488477105 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.3-h0d5b9f9_2.conda - sha256: 7cf5aca930fc12f4e27bd4645d20224d608c2c650443e5633faea3bf8b0a7736 - md5: 86eb8e8959c2d6053a50ad31ef6e5b5d - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 182313 - timestamp: 1779133038517 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.12.2-h61b906f_1.conda - sha256: 8d9c747d71c493e6d5e5a125a267c6ac51baba1e4b89c01c2a4084239267b8e1 - md5: 2c4cd5a0bb004c9975a4d7257a55c34a - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-checksums >=0.2.10,<0.2.11.0a0 - - aws-c-cal >=0.9.13,<0.9.14.0a0 - - aws-c-auth >=0.10.1,<0.10.2.0a0 - - aws-c-http >=0.10.13,<0.10.14.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 143057 - timestamp: 1777824834454 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda - sha256: c86c30edba7457e04d905c959328142603b62d7d1888aed893b2e21cca9c302c - md5: 3c97faee5be6fd0069410cf2bca71c85 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 56509 - timestamp: 1764610148907 -- conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - sha256: 505b2365bbf3c197c9c2e007ba8262bcdaaddc970f84ce67cf73868ca2990989 - md5: 96e950e5007fb691322db578736aba52 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - license: Apache-2.0 - license_family: APACHE - purls: [] - size: 116853 - timestamp: 1771063509650 -- conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda - sha256: 9303a7a0e03cf118eab3691013f6d6cbd1cbac66efbc70d89b20f5d0145257c0 - md5: 357d7be4146d5fec543bfaa96a8a40de - depends: - - libzlib >=1.3.1,<2.0a0 - - lz4-c >=1.10.0,<1.11.0a0 - - snappy >=1.2.1,<1.3.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - zstd >=1.5.6,<1.6.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 49840 - timestamp: 1733513605730 -- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 - md5: 4cb8e6b48f67de0b018719cdf1136306 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: bzip2-1.0.6 - license_family: BSD - purls: [] - size: 56115 - timestamp: 1771350256444 -- conda: https://conda.anaconda.org/conda-forge/win-64/c-blosc2-3.1.2-h2af8807_0.conda - sha256: c23851edfb0eb2a14fdd3018b868607f485289672fe8dbf3e793b37ba48577f6 - md5: 834b5862d5a42c44c93bb26292964e5a - depends: - - lz4-c >=1.10.0,<1.11.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - zlib-ng >=2.3.3,<2.4.0a0 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 252277 - timestamp: 1780000126700 -- conda: https://conda.anaconda.org/conda-forge/win-64/git-2.54.0-h57928b3_0.conda - sha256: d9077b6b2e9aac60c2ea868b0ed018b68131c0f88394471c8f1c6ff8c0a40f4d - md5: 7e64dae740e7d370ec8c8a999f5f5978 - license: GPL-2.0-or-later and LGPL-2.1-or-later - purls: [] - size: 122873375 - timestamp: 1778072461017 -- conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-2.1.0-nompi_h0a39f1e_105.conda - sha256: 2f2d49ccf163a4bdf556662fb2949bdf408940e2db67a2d15be2d8be247b6e43 - md5: d5850b9e97b9a577441067628fb8d573 - depends: - - aws-c-auth >=0.10.1,<0.10.2.0a0 - - aws-c-common >=0.12.6,<0.12.7.0a0 - - aws-c-http >=0.10.13,<0.10.14.0a0 - - aws-c-io >=0.26.3,<0.26.4.0a0 - - aws-c-s3 >=0.12.2,<0.12.3.0a0 - - aws-c-sdkutils >=0.2.4,<0.2.5.0a0 - - libaec >=1.1.5,<2.0a0 - - libcurl >=8.20.0,<9.0a0 - - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.6,<4.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 2599543 - timestamp: 1777861984545 -- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 - md5: 4432f52dc0c8eb6a7a6abc00a037d93c - depends: - - openssl >=3.5.5,<4.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: [] - size: 751055 - timestamp: 1769769688841 -- conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - sha256: e54c08964262c73671d9e80e400333e59c617e0b454476ad68933c0c458156c8 - md5: 43b6385cfad52a7083f2c41984eb4e91 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 34463 - timestamp: 1769221960556 -- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-8_h6c93730_netlib.conda - build_number: 8 - sha256: cc0df341dbc9af74abdbe658caeea5b5d5f362e25283f856d0457f4ae7a36f7e - md5: e2591f7c5a702a478532441e83648878 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - blas * netlib - track_features: - - blas_netlib - - blas_netlib_2 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 152280 - timestamp: 1779861305744 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-8_hc41557d_netlib.conda - build_number: 8 - sha256: 2132d8b764c1df063778880dc2b0086cb655754b189e43d44a99cf760479a09f - md5: ccbf2d1416567af1b8d098b6f3ba2f32 - depends: - - libblas 3.11.0.* - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - track_features: - - blas_netlib - - blas_netlib_2 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 45490 - timestamp: 1779861325576 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.20.0-h8206538_0.conda - sha256: f4ce5aa835a698532feaa368e804365a7e45a9edebe006a8e1c80505d893c24e - md5: 7bee27a8f0a295117ccb864f30d2d87e - depends: - - krb5 >=1.22.2,<1.23.0a0 - - libssh2 >=1.11.1,<2.0a0 - - libzlib >=1.3.2,<2.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: curl - license_family: MIT - purls: [] - size: 393114 - timestamp: 1777461635732 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda - sha256: a65e518c20d1482182bc0f1f6dd5d992f25ca44c3b32307be39ae8310db8f060 - md5: 23eb9474a16d4b9f6f27429989e82002 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - expat 2.8.1.* - license: MIT - license_family: MIT - purls: [] - size: 71280 - timestamp: 1779278786150 -- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 - md5: 720b39f5ec0610457b725eb3f396219a - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: [] - size: 45831 - timestamp: 1769456418774 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-8_h018ca30_netlib.conda - build_number: 8 - sha256: 2c1c411a1196f1d09ac1a7265f2b976deedb3791d0930dedcdc58b991d613c26 - md5: cae9364b4d1f1af0c9b9595b01fcd6be - depends: - - libblas 3.11.0.* - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - track_features: - - blas_netlib - - blas_netlib_2 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 2131555 - timestamp: 1779861344704 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - sha256: d636d1a25234063642f9c531a7bb58d84c1c496411280a36ea000bd122f078f1 - md5: 8f83619ab1588b98dd99c90b0bfc5c6d - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - xz 5.8.3.* - license: 0BSD - purls: [] - size: 106486 - timestamp: 1775825663227 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - sha256: e70562450332ca8954bc16f3455468cca5ef3695c7d7187ecc87f8fc3c70e9eb - md5: 7fea434a17c323256acc510a041b80d7 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: blessing - purls: [] - size: 1304178 - timestamp: 1777986510497 -- conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - sha256: cbdf93898f2e27cefca5f3fe46519335d1fab25c4ea2a11b11502ff63e602c09 - md5: 9dce2f112bfd3400f4f432b3d0ac07b2 - depends: - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 292785 - timestamp: 1745608759342 -- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 - md5: dbabbd6234dea34040e631f87676292f - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - zlib 1.3.2 *_2 - license: Zlib - license_family: Other - purls: [] - size: 58347 - timestamp: 1774072851498 -- conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - sha256: 632cf3bdaf7a7aeb846de310b6044d90917728c73c77f138f08aa9438fc4d6b5 - md5: 0b69331897a92fac3d8923549d48d092 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 139891 - timestamp: 1733741168264 -- conda: https://conda.anaconda.org/conda-forge/win-64/numexpr-2.14.1-py311h670de69_102.conda - sha256: 712f97bec6ce2bdd56eabbe015ea5cac49de094a4501f390485083c4814ff864 - md5: 8bcaf94571260915e56c7bc6b2c50537 - depends: - - nomkl - - numpy >=1.23,<3 - - numpy >=1.23.0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/numexpr?source=hash-mapping - size: 209566 - timestamp: 1778498793173 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-1.26.4-py311h0b4df5a_0.conda - sha256: 14116e72107de3089cc58119a5ce5905c22abf9a715c9fe41f8ac14db0992326 - md5: 7b240edd44fd7a0991aa409b07cee776 - depends: - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - liblapack >=3.9.0,<4.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - constrains: - - numpy-base <0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/numpy?source=hash-mapping - size: 7104093 - timestamp: 1707226459646 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - sha256: feb5815125c60f2be4a411e532db1ed1cd2d7261a6a43c54cb6ae90724e2e154 - md5: 05c7d624cff49dbd8db1ad5ba537a8a3 - depends: - - ca-certificates - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 9410183 - timestamp: 1775589779763 -- conda: https://conda.anaconda.org/conda-forge/win-64/pytables-3.11.1-py311h155d883_3.conda - sha256: c6f00f87555399be6400b626b3ae78a429e0b82e3316ab72031ade448e1091f8 - md5: d9207d905434270434ce10a96cedd70d - depends: - - blosc >=1.21.6,<2.0a0 - - bzip2 >=1.0.8,<2.0a0 - - c-blosc2 >=3.1.2,<3.2.0a0 - - hdf5 >=2.1.0,<3.0a0 - - libzlib >=1.3.2,<2.0a0 - - numexpr - - numpy >=1.20.0 - - numpy >=1.23,<3 - - packaging - - py-cpuinfo - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - typing-extensions >=4.4.0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/tables?source=hash-mapping - size: 1514244 - timestamp: 1780064690372 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 - md5: d09dbf470b41bca48cbe6a78ba1e009b - depends: - - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - python_abi 3.11.* *_cp311 - license: Python-2.0 - purls: [] - size: 18416208 - timestamp: 1772728847666 -- conda: https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda - sha256: d2deda1350abf8c05978b73cf7fe9147dd5c7f2f9b312692d1b98e52efad53c3 - md5: 3075846de68f942150069d4289aaad63 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 67417 - timestamp: 1762948090450 -- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 - md5: 0481bfd9814bf525bd4b3ee4b51494c4 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: TCL - license_family: BSD - purls: [] - size: 3526350 - timestamp: 1769460339384 -- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 - md5: 71b24316859acd00bdb8b38f5e2ce328 - constrains: - - vc14_runtime >=14.29.30037 - - vs2015_runtime >=14.29.30037 - license: LicenseRef-MicrosoftWindowsSDK10 - purls: [] - size: 694692 - timestamp: 1756385147981 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_38.conda - sha256: 61b68e5a4fc71a17f8d64b12e013a2f971ad980bd08e9c389d5e68efe1a67de0 - md5: 774568633f3b26d7a4a6dd4f9ea6d3e1 - depends: - - vc14_runtime >=14.51.36231 - track_features: - - vc14 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 20187 - timestamp: 1780005880049 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_38.conda - sha256: 957c7c65583c7107a5e76f39756c6361fcb7b0dc101ac7c0aea86e7ca09fe49c - md5: 2cdcd8ea1010920911bb2eacb4c61227 - depends: - - ucrt >=10.0.20348.0 - - vcomp14 14.51.36231 h1b9f54f_38 - constrains: - - vs2015_runtime 14.51.36231.* *_38 - license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime - license_family: Proprietary - purls: [] - size: 740997 - timestamp: 1780005875753 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_38.conda - sha256: c645fdc1f0f47718431d973386e946754a10200e7ba2c32032560913a970cacd - md5: 63ee70d69d7540e821940dac5d4d9ba2 - depends: - - ucrt >=10.0.20348.0 - constrains: - - vs2015_runtime 14.51.36231.* *_38 - license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime - license_family: Proprietary - purls: [] - size: 123561 - timestamp: 1780005858779 -- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - sha256: 71332532332d13b5dbe57074ddcf82ae711bdc132affa5a2982a29ffa06dc234 - md5: 46a21c0a4e65f1a135251fc7c8663f83 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Zlib - license_family: Other - purls: [] - size: 124542 - timestamp: 1770167984883 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 - md5: 053b84beec00b71ea8ff7a4f84b55207 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 388453 - timestamp: 1764777142545 -- pypi: https://files.pythonhosted.org/packages/01/8c/15a2de09cc3c30793336cf79798948768611463ddfb2b2669f51569228fb/adafruit_circuitpython_requests-4.1.17-py3-none-any.whl - name: adafruit-circuitpython-requests - version: 4.1.17 - sha256: 4c205188a052f52b3bb8ab4af97798d7d56ae3701857d31f03b164f029fae44f - requires_dist: - - adafruit-blinka - - adafruit-circuitpython-connectionmanager - - requests ; extra == 'optional' -- pypi: https://files.pythonhosted.org/packages/03/fd/5bd5da5d7997725ba3f1995c16aa1c3362937f8ff68ad4cadfd3415eebcb/winrt_windows_devices_enumeration-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-devices-enumeration - version: 3.2.1 - sha256: 2a725d04b4cb43aa0e2af035f73a60d16a6c0ff165fcb6b763383e4e33a975fd - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-applicationmodel-background[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-security-credentials[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-ui[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-ui-popups[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/05/a5/216d66df6bdcee58eb3877fabc1544337e23f850bf9f93838db7f5698371/winrt_windows_foundation-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-foundation - version: 3.2.1 - sha256: f3762be2f6e0f2aedf83a0742fd727290b397ffe3463d963d29211e4ebb53a7e - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl - name: pyserial - version: '3.5' - sha256: c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0 - requires_dist: - - hidapi ; extra == 'cp2110' -- pypi: https://files.pythonhosted.org/packages/15/e8/0856886ebbb02dc47a91ec99fbade9da871a07e072d20503c3d89ecf712f/adafruit_circuitpython_ble-10.1.3-py3-none-any.whl - name: adafruit-circuitpython-ble - version: 10.1.3 - sha256: 7945174784a6c975a425669818ced06f136fb20fc5d050220ed0c6eff37a927a - requires_dist: - - adafruit-blinka - - adafruit-blinka-bleio - - adafruit-circuitpython-typing - - typing-extensions -- pypi: https://files.pythonhosted.org/packages/16/cd/0731490946e037e954ef83719f07c7672cf32bc90dd9c75201c40b827664/pyftdi-0.57.1-py3-none-any.whl - name: pyftdi - version: 0.57.1 - sha256: efd3f5a7d43202dc883ff261a7b1cb4dcbbe65b19628f8603a8b1183a7bc2841 - requires_dist: - - pyusb>=1.0.0,!=1.2.0 - - pyserial>=3.0 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/19/9d/28e9d12f36e13c5f2acba3098187b0e931290ecd1d8df924391b5ad2db19/Adafruit_PureIO-1.1.11-py3-none-any.whl - name: adafruit-pureio - version: 1.1.11 - sha256: 281ab2099372cc0decc26326918996cbf21b8eed694ec4764d51eefa029d324e - requires_python: '>=3.5.0' -- pypi: https://files.pythonhosted.org/packages/23/98/6c9c21b5e75ff5927a130da9eaf5ab628dfa1f93b64c181f0193706cbd6c/winrt_windows_storage_streams-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-storage-streams - version: 3.2.1 - sha256: b02fa251a7eef6081eca1a5f64ecf349cfd1ac0ac0c5a5a30be52897d060bed5 - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-storage[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-system[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/26/54/05aceb9cd80073805b3ed8522e3196e8cb22f70e741873fa51406c31f4e7/bleak-3.0.2-py3-none-any.whl - name: bleak - version: 3.0.2 - sha256: 39092feb9e83f1df5ad2f88e837723c7211c982ce9e9cda6235104bc2ebe0d0d - requires_dist: - - async-timeout>=3.0.0 ; python_full_version < '3.11' - - typing-extensions>=4.7.0 ; python_full_version < '3.12' - - pyobjc-core>=10.3 ; sys_platform == 'darwin' - - pyobjc-framework-corebluetooth>=10.3 ; sys_platform == 'darwin' - - pyobjc-framework-libdispatch>=10.3 ; sys_platform == 'darwin' - - winrt-runtime>=3.1 ; sys_platform == 'win32' - - winrt-windows-devices-bluetooth>=3.1 ; sys_platform == 'win32' - - winrt-windows-devices-bluetooth-advertisement>=3.1 ; sys_platform == 'win32' - - winrt-windows-devices-bluetooth-genericattributeprofile>=3.1 ; sys_platform == 'win32' - - winrt-windows-devices-enumeration>=3.1 ; sys_platform == 'win32' - - winrt-windows-devices-radios>=3.1 ; sys_platform == 'win32' - - winrt-windows-foundation>=3.1 ; sys_platform == 'win32' - - winrt-windows-foundation-collections>=3.1 ; sys_platform == 'win32' - - winrt-windows-storage-streams>=3.1 ; sys_platform == 'win32' - - dbus-fast>=1.83.0 ; sys_platform == 'linux' - - bleak-pythonista>=0.1.1 ; extra == 'pythonista' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl - name: pyusb - version: 1.3.1 - sha256: bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430 - requires_python: '>=3.9.0' -- pypi: https://files.pythonhosted.org/packages/29/c4/32572c051f1554d73633a802447321bff2a2332ef4210d47de807afd26c7/adafruit_platformdetect-3.88.0-py3-none-any.whl - name: adafruit-platformdetect - version: 3.88.0 - sha256: 69e694d80d551c6cb8e39f731e6ee0de1f135e64cffee0e2a665b1f9579c10d7 -- pypi: https://files.pythonhosted.org/packages/32/58/049db1d95fdfc0c8451dc6db17442ed4e6b2aba361c425c0bb8dc8c98c4a/winrt_windows_foundation_collections-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-foundation-collections - version: 3.2.1 - sha256: c646a5d442dd6540ade50890081ca118b41f073356e19032d0a5d7d0d38fbc89 - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/42/0d/66a4e0fbd7b35107f7dee04fed890f77b83d1da9dd1f7474af2ed21700ea/adafruit_circuitpython_busdevice-5.2.17-py3-none-any.whl - name: adafruit-circuitpython-busdevice - version: 5.2.17 - sha256: 5a834fbe0b88b07d20494bec566815da154aa4b1b668e2e665277b34b3578e44 - requires_dist: - - adafruit-blinka>=7.0.0 - - adafruit-circuitpython-typing -- pypi: https://files.pythonhosted.org/packages/4a/d6/c40e8ae38a6e2bce9e837b64688f55746bfdad1aa557eb733fb5e90edd7c/pyqt6_sip-13.11.1-cp311-cp311-win_amd64.whl - name: pyqt6-sip - version: 13.11.1 - sha256: 98db8ed37cf08130e1ee74b8ff47a6bfb8c3cdfe826310597a630a50e47feedc - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/6f/85/dd9f03d78d87460e109e0121cd6201c5802bdd655656bf2780e964870fea/pyqt6-6.11.0-cp310-abi3-win_amd64.whl - name: pyqt6 - version: 6.11.0 - sha256: bd11b459c54dca068e988a42cf838303334f0d441b9d16d92ae6719fcb5ac6ba - requires_dist: - - pyqt6-sip>=13.8,<14 - - pyqt6-qt6>=6.11.0,<6.12.0 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/70/0b/06ccf917ce30d78d75e452d0afc89236b1d384b776c5c33d75c8cc55fbe7/pyvisa_py-0.8.1-py3-none-any.whl - name: pyvisa-py - version: 0.8.1 - sha256: 31208a2933c1793b4e829ba5f07d265b83f280668df440ee7ee6ac505dea4ee9 - requires_dist: - - pyvisa>=1.15.0 - - typing-extensions - - gpib-ctypes>=0.3.0 ; extra == 'gpib-ctypes' - - pyserial>=3.0 ; extra == 'serial' - - pyusb ; extra == 'usb' - - pyusb ; extra == 'usb-full' - - libusb-package ; extra == 'usb-full' - - psutil ; extra == 'psutil' - - zeroconf ; extra == 'hislip-discovery' - - pyvicp ; extra == 'vicp' - - zeroconf ; extra == 'vicp' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/7b/6b/0f13486003aea3eb349c2946b7ec9753e7558b78e35d22c938062a96959c/binho_host_adapter-0.1.6-py3-none-any.whl - name: binho-host-adapter - version: 0.1.6 - sha256: f71ca176c1e2fc1a5dce128beb286da217555c6c7c805f2ed282a6f3507ec277 - requires_dist: - - pyserial -- pypi: https://files.pythonhosted.org/packages/90/db/929ab0085ec89e46bd3a58c74b451dd770c3285dfa0cbd4f4aa4730da004/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-devices-bluetooth-genericattributeprofile - version: 3.2.1 - sha256: 8179638a6c721b0bbf04ba251ef98d5e02d9a17f0cce377398e42c4fbb441415 - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-devices-bluetooth[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-devices-enumeration[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/97/05/b5fa1fcc4cc68bda4bd317cb60a826eef753d9ab8efcd2d28fbcc7d60dd2/adafruit_blinka_bleio-4.1.2-py3-none-any.whl - name: adafruit-blinka-bleio - version: 4.1.2 - sha256: f4e21ed7072d05e86261c69b43c72b050c3d8b1b3297b6c71b36292ffbfc3275 - requires_dist: - - adafruit-blinka - - bleak -- pypi: https://files.pythonhosted.org/packages/ac/1a/d172d6f1c2fae53535e7f23835025cf39e3002749a0304f18a38e8ed490d/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-devices-bluetooth-advertisement - version: 3.2.1 - sha256: 78e99dd48b4d89b71b7778c5085fdba64e754dd3ebc54fd09c200fe5222c6e09 - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-devices-bluetooth[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ac/66/d05f6e6c0517654734e7f87fa1f0fbc965add9f27cc36b524d96331ab3d8/winrt_runtime-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-runtime - version: 3.2.1 - sha256: c0a9046ae416808420a358c51705af8ae100acd40bc578be57ddfdd51cbb0f9c - requires_dist: - - typing-extensions>=4.12.2 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b6/d5/5761a8b6dcc56957018970dd443059c8ee8a79de7b07f0b4d143f8e7dc15/winrt_windows_devices_bluetooth-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-devices-bluetooth - version: 3.2.1 - sha256: 44277a3f2cc5ac32ce9b4b2d96c5c5f601d394ac5f02cc71bcd551f738660e2d - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-devices-bluetooth-genericattributeprofile[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-devices-bluetooth-rfcomm[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-devices-enumeration[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-devices-radios[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-networking[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-storage-streams[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/bb/97/17dac675981730d29b2d583de2aa0a9c2963d6c63f6bdc7eebf2711914ef/adafruit_blinka-9.1.0-py3-none-any.whl - name: adafruit-blinka - version: 9.1.0 - sha256: 6f617d4ebb7c2e14dfe1259c63f21df4a77d1b12d5efd92cf71ae4bf34d21b91 - requires_dist: - - adafruit-platformdetect>=3.70.1 - - adafruit-pureio>=1.1.7 - - binho-host-adapter>=0.1.6 - - pyftdi>=0.40.0 - - adafruit-circuitpython-typing - - sysv-ipc>=1.1.0 ; platform_machine != 'mips' and sys_platform == 'linux' - - toml>=0.10.2 ; python_full_version < '3.11' - requires_python: '>=3.7.0' -- pypi: https://files.pythonhosted.org/packages/cc/a1/578a03ba2bce0809b4e30974b47958963c9efe67b9fe74e7dbcdbbd45318/adafruit_circuitpython_typing-1.12.3-py3-none-any.whl - name: adafruit-circuitpython-typing - version: 1.12.3 - sha256: f6d0a02150e1e4efb5a2c2945b88d948809fdb465875f39947108b8467c986d9 - requires_dist: - - adafruit-blinka - - adafruit-circuitpython-busdevice - - adafruit-circuitpython-requests - - typing-extensions~=4.0 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/ce/0b/6e3121375aeda38fa76669f6caca2794e4a10fc7396ae88a3bf0a3b5a09b/telemetrix-1.46-py3-none-any.whl - name: telemetrix - version: '1.46' - sha256: 38c62b58211f44ce63909ede884b19c521babfc2cce67d9107f9644de5d4d0dc - requires_dist: - - pyserial - - bleak - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/dc/1d/616b3770ebf191f8d9aa1a5ae919d4885e3037341ea1d367e0045235c815/telemetrix_esp32-2.0.0-py3-none-any.whl - name: telemetrix-esp32 - version: 2.0.0 - sha256: f861ef12bae6d1a3b7211d906e5540f656c4944cd1645f420991dd914eacdfaa - requires_dist: - - pyserial - - bleak - - adafruit-blinka-bleio - - adafruit-circuitpython-ble - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/de/49/ba69e3180585dbc6f3336a09fef7cba4558a6a1e7d500500f62c1478418e/winrt_windows_devices_radios-3.2.1-cp311-cp311-win_amd64.whl - name: winrt-windows-devices-radios - version: 3.2.1 - sha256: f87745486d313ba1e7562ca97f25ad436ec01ad4b3b9ea349fb6b6f25cb41104 - requires_dist: - - winrt-runtime~=3.2.1.0 - - winrt-windows-foundation[all]~=3.2.1.0 ; extra == 'all' - - winrt-windows-foundation-collections[all]~=3.2.1.0 ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e4/43/929d17e5dbe0773e3a3c728b12cf1a777ada32639764b09365a1b56703c0/adafruit_circuitpython_connectionmanager-3.1.8-py3-none-any.whl - name: adafruit-circuitpython-connectionmanager - version: 3.1.8 - sha256: f93e27874a840f728b5cdbb1bcf0aee4e75ed1c0ba46b4562606ac3ac3ea2cca - requires_dist: - - adafruit-blinka -- pypi: https://files.pythonhosted.org/packages/e4/a9/776da4f397003cca093659524c3526590522f81b642936838428c11274e6/pyvisa-1.16.2-py3-none-any.whl - name: pyvisa - version: 1.16.2 - sha256: 54f034adafd3e8d1858d57cdafec64e920444f4b84b31c9fd17487fbad0a197a - requires_dist: - - typing-extensions>=4.0.0 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fa/f1/70e83c23bf897c7f5025aa100482f482038ef70232dc27b407659d941fbf/pyqt6_qt6-6.11.1-py3-none-win_amd64.whl - name: pyqt6-qt6 - version: 6.11.1 - sha256: 7486c80512e823f2d3087e67f854f0556b345f4368040a853c8dc4d30fd3fe69 diff --git a/pixi.toml b/pixi.toml deleted file mode 100644 index 6d2207f..0000000 --- a/pixi.toml +++ /dev/null @@ -1,24 +0,0 @@ -[workspace] -name = "pymodaq-plugins-arduino" -version = "0.0.1" -description = "Dev environment for pymodaq-plugins-arduino" -channels = ["conda-forge"] -platforms = ["win-64"] - -[dependencies] -python = "3.11.*" -pytables = "*" -pip = "*" -git = "*" -numpy = ">=1.26,<2.0.0" - -[pypi-dependencies] -PyQt6 = "*" -telemetrix = "*" -telemetrix-esp32 = "*" -pyvisa = "*" -pyvisa-py = "*" - -[tasks] -setup = { cmd = "python setup_dev.py", description = "Clone PyMoDAQ 5.0.x et installe tout en mode éditable" } -test = { cmd = "pytest tests/" } diff --git a/setup_dev.py b/setup_dev.py deleted file mode 100644 index 69702ee..0000000 --- a/setup_dev.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -setup_dev.py — Appelé par `pixi run setup` -Clone PyMoDAQ branche 5.0.x et installe les packages en mode éditable. -Idempotent : peut être relancé sans problème. -""" - -import subprocess -import sys -from pathlib import Path - -PYMODAQ_REPO = "https://github.com/PyMoDAQ/PyMoDAQ" -PYMODAQ_BRANCH = "5.0.x" -PYMODAQ_DIR = Path(__file__).parent / ".pymodaq_dev" - -PYMODAQ_PACKAGES = [ - "pymodaq_utils", - "pymodaq_data", - "pymodaq_gui", - "pymodaq", -] - - -def run(cmd: list[str], **kwargs) -> None: - print(f" > {' '.join(cmd)}") - subprocess.run(cmd, check=True, **kwargs) - - -def clone_or_update() -> None: - if PYMODAQ_DIR.exists(): - print(f"[PyMoDAQ] Mise à jour de la branche '{PYMODAQ_BRANCH}'...") - run(["git", "pull"], cwd=PYMODAQ_DIR) - else: - print(f"[PyMoDAQ] Clonage de la branche '{PYMODAQ_BRANCH}'...") - run(["git", "clone", "-b", PYMODAQ_BRANCH, PYMODAQ_REPO, str(PYMODAQ_DIR)]) - - -def install_packages() -> None: - packages_dir = PYMODAQ_DIR / "packages" - if packages_dir.exists(): - print("[PyMoDAQ] Installation des packages en mode éditable...") - for pkg in PYMODAQ_PACKAGES: - pkg_path = packages_dir / pkg - if pkg_path.exists(): - print(f" pip install -e {pkg}") - run([sys.executable, "-m", "pip", "install", "-e", str(pkg_path)]) - else: - print(f" (ignoré : {pkg} non trouvé)") - else: - print("[PyMoDAQ] Installation depuis la racine...") - run([sys.executable, "-m", "pip", "install", "-e", str(PYMODAQ_DIR)]) - - # Installer ce plugin en mode éditable - plugin_dir = Path(__file__).parent - print(f" pip install -e . (plugin Arduino)") - run([sys.executable, "-m", "pip", "install", "-e", str(plugin_dir)]) - - -def verify() -> None: - print("\n[Vérification]") - import importlib.util - for module, label in [ - ("pymodaq_utils", "pymodaq_utils"), - ("pymodaq", "pymodaq"), - ("pymodaq_plugins_arduino", "pymodaq_plugins_arduino"), - ("PyQt6", "PyQt6"), - ("tables", "tables (HDF5)"), - ]: - spec = importlib.util.find_spec(module) - if spec is not None: - print(f" ✓ {label} ({spec.origin})") - else: - print(f" ✗ {label} — NON TROUVÉ") - - -if __name__ == "__main__": - print("=" * 55) - print(" Setup PyMoDAQ Arduino Plugin — mode contributeur") - print(" Branche PyMoDAQ : 5.0.x") - print("=" * 55) - - clone_or_update() - install_packages() - verify() - - print() - print(" Tout est prêt !") - print(" Vérifier la version : pixi run python -c \"import pymodaq; print(pymodaq.__version__)\"") - print(" Lancer les tests : pixi run test") - print("=" * 55) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py new file mode 100644 index 0000000..3fafded --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_1D/__init__.py @@ -0,0 +1,13 @@ +import importlib +from pathlib import Path +from ... import set_logger +logger = set_logger('viewer1D_plugins', add_to_console=False) + +for path in Path(__file__).parent.iterdir(): + try: + if '__init__' not in str(path): + importlib.import_module('.' + path.stem, __package__) + except Exception as e: + logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e))) + pass + diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py new file mode 100644 index 0000000..cbcf921 --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_2D/__init__.py @@ -0,0 +1,14 @@ +import importlib +from pathlib import Path +from ... import set_logger +logger = set_logger('viewer2D_plugins', add_to_console=False) + +for path in Path(__file__).parent.iterdir(): + try: + if '__init__' not in str(path): + importlib.import_module('.' + path.stem, __package__) + except Exception as e: + logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e))) + pass + + diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py new file mode 100644 index 0000000..527b2d8 --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_ND/__init__.py @@ -0,0 +1,13 @@ +import importlib +from pathlib import Path +from ... import set_logger +logger = set_logger('viewerND_plugins', add_to_console=False) + +for path in Path(__file__).parent.iterdir(): + try: + if '__init__' not in str(path): + importlib.import_module('.' + path.stem, __package__) + except Exception as e: + logger.warning("{:} plugin couldn't be loaded due to some missing packages or errors: {:}".format(path.stem, str(e))) + pass + diff --git a/src/pymodaq_plugins_arduino/exporters/__init__.py b/src/pymodaq_plugins_arduino/exporters/__init__.py new file mode 100644 index 0000000..180d4dd --- /dev/null +++ b/src/pymodaq_plugins_arduino/exporters/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +""" +Created the 01/06/2023 + +@author: Sebastien Weber +""" diff --git a/src/pymodaq_plugins_arduino/models/__init__.py b/src/pymodaq_plugins_arduino/models/__init__.py new file mode 100644 index 0000000..180d4dd --- /dev/null +++ b/src/pymodaq_plugins_arduino/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +""" +Created the 01/06/2023 + +@author: Sebastien Weber +""" From 89156a20cd31d8db69633dcfed429ae080ceb32f Mon Sep 17 00:00:00 2001 From: VIL-CIEL Date: Fri, 26 Jun 2026 21:55:31 +0200 Subject: [PATCH 60/65] fix: make ESP32 plugin import-safe without telemetrix at load time - esp32_telemetrix: import telemetrix_aio_esp32 lazily inside _init_board instead of at module level, so plugin discovery and the test suite work even when telemetrix_aio_esp32 (or its bleak dependency) is missing or incompatible (the CI runner ships a bleak version without `discover`). - remove tests/test_esp32.py: a manual hardware diagnostic script (no pytest tests) that broke test collection by importing telemetrix at module top. Fixes CI failures: test_move_inst_plugins_name, test_move_has_mandatory_methods and the tests/test_esp32.py collection error. --- .../hardware/esp32_telemetrix.py | 8 ++- tests/test_esp32.py | 67 ------------------- 2 files changed, 6 insertions(+), 69 deletions(-) delete mode 100644 tests/test_esp32.py diff --git a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py index fe6c702..ad3bbbe 100644 --- a/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/esp32_telemetrix.py @@ -3,8 +3,6 @@ import threading from threading import Lock -from telemetrix_aio_esp32 import telemetrix_aio_esp32 - lock = Lock() # Maps GPIO pin numbers to ESP32 LEDC hardware channels. @@ -44,6 +42,12 @@ def __init__(self, ip_address: str): future.result(timeout=10) async def _init_board(self, ip_address: str): + # Lazy import: telemetrix_aio_esp32 (and its optional bleak dependency) is + # only needed when actually connecting to the board. Importing it at module + # level would make the whole plugin fail to load (and break plugin discovery + # and the test suite) on environments where the dependency is missing or + # incompatible. + from telemetrix_aio_esp32 import telemetrix_aio_esp32 self._board = telemetrix_aio_esp32.TelemetrixAioEsp32( transport_address=ip_address, autostart=False, diff --git a/tests/test_esp32.py b/tests/test_esp32.py deleted file mode 100644 index a000bf1..0000000 --- a/tests/test_esp32.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Test minimal - ESP32 PWM sur GPIO17 (D8) via telemetrix-aio-esp32 -Lancer avec : python test_esp32_pwm.py - -Ce script teste directement sans PyMoDAQ pour isoler le problème hardware. -""" - -import asyncio -import time - -from telemetrix_aio_esp32 import telemetrix_aio_esp32 - -ESP32_IP = "172.17.50.238" -ESP32_PORT = 31336 -FAN_PIN = 17 # D8 sur Arduino Nano ESP32 -FAN_CH = 0 # canal PWM (0-15, unique par pin) - - -async def main(): - print(f"Connexion a {ESP32_IP}:{ESP32_PORT} ...") - - board = telemetrix_aio_esp32.TelemetrixAioEsp32( - transport_is_wifi=True, - transport_address=ESP32_IP, - ip_port=ESP32_PORT, - autostart=False, - shutdown_on_exception=True, - restart_on_shutdown=False, - ) - - await board.start_aio() - print("Connecte. Firmware OK.") - - # Attente stabilisation - await asyncio.sleep(1) - - # --- Configurer le pin en PWM --- - print(f"Configuration GPIO{FAN_PIN} en analog output (PWM), canal {FAN_CH} ...") - await board.set_pin_mode_analog_output( - FAN_PIN, - channel=FAN_CH, - frequency=5000.0, - resolution=8, - ) - await asyncio.sleep(0.5) - - # --- Test 1 : valeur maximale (255) --- - print("PWM = 255 (100%) pendant 5 secondes ...") - await board.analog_write(FAN_CH, 255) - await asyncio.sleep(5) - - # --- Test 2 : valeur moyenne (128) --- - print("PWM = 128 (50%) pendant 5 secondes ...") - await board.analog_write(FAN_CH, 128) - await asyncio.sleep(5) - - # --- Test 3 : extinction --- - print("PWM = 0 (off) ...") - await board.analog_write(FAN_CH, 0) - await asyncio.sleep(1) - - print("Shutdown.") - await board.shutdown() - - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file From 85310740085c099bad5c65ada0103ac29a8467c8 Mon Sep 17 00:00:00 2001 From: VIL-CIEL Date: Fri, 26 Jun 2026 22:00:50 +0200 Subject: [PATCH 61/65] fix: pin bleak<1.0 so telemetrix-esp32 stays importable telemetrix-esp32 imports `from bleak import discover`, removed in bleak 1.0. Pinning bleak<1.0 keeps the dependency importable (the working local version is 0.22.3, which still provides `discover`). --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c145afa..ed13264 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,9 @@ dependencies = [ "pymodaq>=5.0.0", 'telemetrix', 'telemetrix-esp32', + # telemetrix-esp32 imports `from bleak import discover`, which was removed in + # bleak 1.0. Pin bleak below 1.0 so telemetrix-esp32 stays importable. + 'bleak<1.0', 'pyvisa', 'pyvisa-py', From 3a18a9c4f7810f5fae25493582035c84f17b31e1 Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Sat, 27 Jun 2026 17:32:33 +0200 Subject: [PATCH 62/65] Fix readme --- README.rst | 60 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index ca80028..d217cb6 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,24 @@ pymodaq_plugins_arduino ####################### +.. the following must be adapted to your developed package, links to pypi, github description... + +.. image:: https://img.shields.io/pypi/v/pymodaq_plugins_arduino.svg + :target: https://pypi.org/project/pymodaq_plugins_arduino/ + :alt: Latest Version + +.. image:: https://readthedocs.org/projects/pymodaq/badge/?version=latest + :target: https://pymodaq.readthedocs.io/en/stable/?badge=latest + :alt: Documentation Status + +.. image:: https://github.com/PyMoDAQ/pymodaq_plugins_arduino/workflows/Upload%20Python%20Package/badge.svg + :target: https://github.com/PyMoDAQ/pymodaq_plugins_arduino + :alt: Publication Status + +.. image:: https://github.com/PyMoDAQ/pymodaq_plugins_arduino/actions/workflows/Test.yml/badge.svg + :target: https://github.com/PyMoDAQ/pymodaq_plugins_arduino/actions/workflows/Test.yml + + This package regroups a list of instruments created around an Arduino or ESP32 board. Some instruments use the Telemetrix library to use Python together with the Arduino board. Others use the Telemetrix AIO ESP32 library to communicate with an ESP32 board over WiFi. @@ -11,6 +29,7 @@ Authors * Sebastien J. Weber (sebastien.weber@cemes.fr) * Jérémie Margueritat * Mohamed El Mokhtari (mohamed.elmokhtari26@gmail.com) +* Fabien Villedieu (fabien.villedieu.pro@gmail.com) Instruments =========== @@ -26,6 +45,9 @@ Actuators * **LEDwithLCD**: same as **LED** actuator but displaying the red, green, blue values on a standard 16x2 liquid crystal display +* **Servo**: control of a servo motor position (in degrees) on a digital pin using the Telemetrix + library + * **Analog**: data acquisition from analog inputs * **FanHeater**: control of a heater and a fan using two PWM outputs and the Telemetrix AIO ESP32 @@ -41,14 +63,19 @@ Extensions Viewers ======= -* **PT100**: reads temperature from a PT100 resistance temperature detector (RTD) wired to a +* **Voltage_ADS1115**: reads up to four single-ended analog voltages (AIN0–AIN3) from an ADS1115 + (16-bit) or ADS1015 (12-bit) I2C ADC. The PGA gain, data rate and number of active channels are + configurable. Communication is performed over I2C by an ESP32 running the Telemetrix AIO WiFi + firmware. + +* **Temperature MAX31865**: reads temperature from a PT100 resistance temperature detector (RTD) wired to a MAX31865 amplifier/ADC board. Communication with the MAX31865 is performed over bit-banged SPI by an ESP32 running the Telemetrix AIO WiFi firmware. Installation instructions ========================= -* PyMoDAQ version > 4.1.0 +* PyMoDAQ version > 5.0.0 LED actuator ++++++++++++ @@ -66,6 +93,13 @@ I2C backpack. The functionalities used to drive the LCD are adapted from a micro (https://github.com/brainelectronics/micropython-i2c-lcd) itself adapted from https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library +Servo actuator +++++++++++++++ + +The **Servo** actuator uses the telemetrix library. The corresponding sketch should therefore be +uploaded on the arduino board. This allows to drive a servo motor connected to a digital pin of the +Arduino board, the position being commanded in degrees. See https://mryslab.github.io/telemetrix/ + Analog 0D viewer ++++++++++++++++ @@ -81,11 +115,27 @@ therefore be uploaded on the ESP32 board. This allows to control a heater and a the ESP32 over WiFi from python objects on the connected computer. See https://mryslab.github.io/telemetrix-esp32/ -PT100 0D viewer +Voltage_ADS1115 0D viewer ++++++++++++++++++++++++++ + +The **Voltage_ADS1115** 0D viewer uses the telemetrix-aio-esp32 library. The corresponding firmware +should therefore be uploaded on the ESP32 board. This allows to acquire analog voltages from an +ADS1115/ADS1015 I2C ADC connected to the ESP32 over WiFi. +See https://mryslab.github.io/telemetrix-esp32/ + +Temperature MAX31865 0D viewer +++++++++++++++ -The **PT100** 0D viewer uses the telemetrix-aio-esp32 library. The corresponding firmware should +The **Temperature MAX31865** 0D viewer uses the telemetrix-aio-esp32 library. The corresponding firmware should therefore be uploaded on the ESP32 board. This allows to acquire temperature data from a PT100 sensor wired to a MAX31865 board connected to the ESP32 over WiFi. -Here are `detailed installation instructions `_. \ No newline at end of file +Here are `detailed installation instructions `_. + +Wiki +==== + +Additional documentation for this fork is available on the dedicated Wiki: +https://wiki-plugins-dap-pymodaq.github.io/ + + From b406db164eeb2be607ebcc800a3a912ece039d91 Mon Sep 17 00:00:00 2001 From: ELM_CIEL Date: Mon, 29 Jun 2026 13:47:07 +0200 Subject: [PATCH 63/65] Fix review feedback --- .../daq_move_plugins/daq_move_FanHeater.py | 18 ++++++++---------- .../daq_0Dviewer_Temperature_MAX31865.py | 3 ++- .../plugins_0D/daq_0Dviewer_Voltage_ADS1115.py | 3 ++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 0e187f4..52c1887 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -21,8 +21,8 @@ class DAQ_Move_FanHeater(DAQ_Move_base): Both actuators are driven by a XY-MOS PWM board connected to an ESP32 over WiFi using the Telemetrix AIO protocol. The PWM duty cycle ranges from 0 to 255 (8-bit resolution). - Heater → GPIO18 - Fan → GPIO17 + Heater + Fan Attributes: ----------- @@ -55,7 +55,7 @@ def get_actuator_value(self): ------- float: The position obtained after scaling conversion. """ - pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value)) + pos = DataActuator(data=self.controller.get_output_pin_value(self.axis_value), units=self.axis_unit) pos = self.get_position_with_scaling(pos) return pos @@ -90,16 +90,14 @@ def ini_stage(self, controller=None): initialized: bool False if initialization failed otherwise True """ - self.controller = self.ini_stage_init( - old_controller=controller, - new_controller=None, - ) if self.is_master: self.controller = ArduinoWifi( ip_address=self.settings['ip_address'] ) self.set_pins() + else: + self.controller = controller info = "Heater and Fan ready" initialized = True @@ -122,7 +120,7 @@ def move_abs(self, value: DataActuator): self.target_value = value value = self.set_position_with_scaling(value) # apply scaling if the user specified one - self.controller.analog_write_and_memorize(self.axis_value, int(value.value())) + self.controller.analog_write_and_memorize(self.axis_value, int(value.value(self.axis_unit))) def move_rel(self, value: DataActuator): """Move the actuator to the relative target actuator value defined by value @@ -136,8 +134,8 @@ def move_rel(self, value: DataActuator): value = self.set_position_relative_with_scaling(value) # PWM value is set in duty-cycle counts (0–255) - self.controller.analog_write_and_memorize(self.axis_value, int(self.target_value.value())) - + self.controller.analog_write_and_memorize(self.axis_value, int(self.target_value.value(self.axis_unit))) + def move_home(self): """Call the reference method of the controller""" self.controller.analog_write_and_memorize(self.axis_value, 0) diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py index 5c5af14..3c32415 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Temperature_MAX31865.py @@ -87,12 +87,13 @@ def ini_detector(self, controller=None): initialized: bool False if initialization failed otherwise True """ - self.ini_detector_init(slave_controller=controller) if self.is_master: self.controller = ArduinoWifi( ip_address=self.settings['connection', 'ip_address'] ) + else: + self.controller = controller self.max31865 = MAX31865( controller=self.controller, diff --git a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py index 3d1e24f..26d808f 100644 --- a/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py +++ b/src/pymodaq_plugins_arduino/daq_viewer_plugins/plugins_0D/daq_0Dviewer_Voltage_ADS1115.py @@ -100,12 +100,13 @@ def ini_detector(self, controller=None): initialized: bool False if initialization failed otherwise True. """ - self.ini_detector_init(slave_controller=controller) if self.is_master: self.controller = ArduinoWifi( ip_address=self.settings['connection', 'ip_address'] ) + else: + self.controller = controller self.ads = ADS1115( controller=self.controller, From 1af638d4add431028ae0642a0b28d9441a866dad Mon Sep 17 00:00:00 2001 From: Mohamed El Mokhtari Date: Tue, 30 Jun 2026 14:15:55 +0200 Subject: [PATCH 64/65] Fix commentary Units --- .../daq_move_plugins/daq_move_FanHeater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py index 52c1887..a31912c 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_FanHeater.py @@ -31,7 +31,7 @@ class DAQ_Move_FanHeater(DAQ_Move_base): wrapper around the hardware library. """ - _controller_units = '' + _controller_units = '' # raw PWM level (0-255) unit depends on wired device is_multiaxes = True _axis_names = { 'Heater': config('esp32', 'pins', 'heater_pin'), From 502c00b804a3c7595c9958f84564795645d37afd Mon Sep 17 00:00:00 2001 From: ELM_CIEL Date: Tue, 30 Jun 2026 22:53:20 +0200 Subject: [PATCH 65/65] Fix drop old bleak --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ed13264..18e2b58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,7 @@ description = 'Set of instrument plugins implemented using an Arduino Board' dependencies = [ "pymodaq>=5.0.0", 'telemetrix', - 'telemetrix-esp32', - # telemetrix-esp32 imports `from bleak import discover`, which was removed in - # bleak 1.0. Pin bleak below 1.0 so telemetrix-esp32 stays importable. - 'bleak<1.0', + 'telemetrix-esp32>=2.1.1', 'pyvisa', 'pyvisa-py',