Skip to content

Commit

Permalink
Merge pull request #2 from jerlfan/main
Browse files Browse the repository at this point in the history
Adding analog input read functionality
  • Loading branch information
seb5g committed Nov 10, 2024
1 parent 4998d9f commit d5891ee
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 40 deletions.
53 changes: 15 additions & 38 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,7 @@ Authors
=======

* Sebastien J. Weber ([email protected])


.. if needed use this field
Contributors
============
* First Contributor
* Other Contributors
.. if needed use this field
Depending on the plugin type, delete/complete the fields below
* Jérémie Margueritat


Instruments
Expand All @@ -54,34 +42,15 @@ Actuators
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

.. if needed use this field
Viewer0D
++++++++
* **yyy**: control of yyy 0D detector
* **xxx**: control of xxx 0D detector

Viewer1D
++++++++

* **yyy**: control of yyy 1D detector
* **xxx**: control of xxx 1D detector
Extensions
==========

Viewer2D
++++++++
* **yyy**: control of yyy 2D detector
* **xxx**: control of xxx 2D detector
PID Models
==========
Extensions
==========
* **ColorSynthesizer**: DashBoard extension using RBG LED actuators. Allows to quicly select a RGB value and apply those
to the actuators


Installation instructions
Expand All @@ -104,4 +73,12 @@ The **LEDwithLCD** actuator uses the telemetrix library. The corresponding sketc
on the arduino board. It then uses the telemetrix I2C communication protocol to control a LCD equipped with a
I2C backpack. The functionalities used to drive the LCD are adapted from a micropython code
(https://github.com/brainelectronics/micropython-i2c-lcd) itself adapted from
https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library

Analog 0D viewer
++++++++++++++++

The **Analog** 0D viewer uses the telemetrix library. The corresponding sketch should therefore be uploaded
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/

Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
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 typing import Optional

from pymodaq_plugins_arduino.hardware.arduino_telemetrix import Arduino
from pymodaq_plugins_arduino.utils import Config


config = Config()
class DAQ_0DViewer_Analog(DAQ_Viewer_base):
""" Instrument plugin class for a OD 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 plugins is intended to work with Arduino UNO type board. It should be working for others but haven't been
tested yet (10/2024). This plugin use the Telemetrix implementation developed here:
(https://mryslab.github.io/telemetrix/).
It has been tested with an Arduino Uno with PyMoDAQ Version was 4.4.2 on Windows 10 Pro (Ver 22h2)
This plugin needs to upload Telemetrix4Arduino to your Arduino-Core board (see Telemetrix installation)
Attributes:
-----------
controller: object
The particular object that allow the communication with the hardware, in general a python wrapper around the
hardware library.
"""
_controller_units=''
params = comon_parameters + [{'title': 'Ports:', 'name': 'com_port', 'type': 'list',
'value': config('com_port'), 'limits': Arduino.COM_PORTS},
{'title': 'Separated viewers', 'name': 'sep_viewers', 'type': 'bool', 'value': False},
{'name':'AI0', 'type':'group','children':[
{'title': 'Activate', 'name': 'ch', 'type': 'led_push', 'value':False, 'label':'On/Off',
'tip':'click to change status, Green: On, Red: Off'},
{'title': 'Units:', 'name': 'ai_ch0_units', 'type': 'list',
'limits': ['Integer bit','Volts','pH Units', 'Absorbance', 'Transmitance'],
'value': 'Volts'},
]},{'name':'AI1', 'type':'group','children':[
{'title': 'Activate', 'name': 'ch', 'type': 'led_push', 'value':False, 'label':'On/Off',
'tip':'click to change status, Green: On, Red: Off'},
{'title': 'Units:', 'name': 'ai_ch1_units', 'type': 'list',
'limits': ['Integer bit','Volts','pH Units', 'Absorbance', 'Transmitance'],
'value': 'Volts'},
]},{'name':'AI2', 'type':'group','children':[
{'title': 'Activate', 'name': 'ch', 'type': 'led_push', 'value':False, 'label':'On/Off',
'tip':'click to change status, Green: On, Red: Off'},
{'title': 'Units:', 'name': 'ai_ch2_units', 'type': 'list',
'limits': ['Integer bit','Volts','pH Units', 'Absorbance', 'Transmitance'],
'value': 'Volts'},
]},{'name':'AI3', 'type':'group','children':[
{'title': 'Activate', 'name': 'ch', 'type': 'led_push', 'value':False, 'label':'On/Off',
'tip':'click to change status, Green: On, Red: Off'},
{'title': 'Units:', 'name': 'ai_ch3_units', 'type': 'list',
'limits': ['Integer bit','Volts','pH Units', 'Absorbance', 'Transmitance'],
'value': 'Volts'},
]},{'name':'AI4', 'type':'group','children':[
{'title': 'Activate', 'name': 'ch', 'type': 'led_push', 'value':False, 'label':'On/Off',
'tip':'click to change status, Green: On, Red: Off'},
{'title': 'Units:', 'name': 'ai_ch4_units', 'type': 'list',
'limits': ['Integer bit','Volts','pH Units', 'Absorbance', 'Transmitance'],
'value': 'Volts'},
]},{'name':'AI5', 'type':'group','children':[
{'title': 'Activate', 'name': 'ch', 'type': 'led_push', 'value':False, 'label':'On/Off',
'tip':'click to change status, Green: On, Red: Off'},
{'title': 'Units:', 'name': 'ai_ch5_units', 'type': 'list',
'limits': ['Integer bit','Volts','pH Units', 'Absorbance', 'Transmitance'],
'value': 'Volts'},
]}

]

def ini_attributes(self):
self.controller: Optional[Arduino] = None
pass

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() == "ai0":
if param.value():
self.controller.set_analog_input(0)
else:
self.controller.disable_analog_reporting(0)
if param.name() == "ai1":
if param.value():
self.controller.set_analog_input(1)
else:
self.controller.disable_analog_reporting(1)
if param.name() == "ai2":
if param.value():
self.controller.set_analog_input(2)
else:
self.controller.disable_analog_reporting(2)
if param.name() == "ai3":
if param.value():
self.controller.set_analog_input(3)
else:
self.controller.disable_analog_reporting(3)
if param.name() == "ai4":
if param.value():
self.controller.set_analog_input(4)
else:
self.controller.disable_analog_reporting(4)
if param.name() == "ai5":
if param.value():
self.controller.set_analog_input(5)
else:
self.controller.disable_analog_reporting(5)


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 = Arduino(com_port=self.settings['com_port']) # instantiate you driver with whatever arguments are needed

info = "Analog 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 optionals arguments
"""
data_tot=[]
channel_available=[]

for param in self.settings.children():
if 'AI' in param.name():
if param['ch']:
channel_available.append(int(param.name()[2:3]))
self.controller.set_analog_input(int(param.name()[2:3]))
data_tot.append(np.array([self.controller.analog_pin_values_input[int(param.name()[2:3])]]))

if self.settings.child('sep_viewers').value():
dat = DataToExport('Analog0D',
data=[DataFromPlugins(name=f'AI{channel_available[ind]}', data=[data],
dim='Data0D',
labels=[f'AI{channel_available[ind]} data '])
for ind, data in enumerate(data_tot)])
self.dte_signal.emit(dat)
else:
self.dte_signal.emit(DataToExport(name='Analog Input',
data=[DataFromPlugins(name='AI', data=data_tot,
dim='Data0D',
labels=[f'AI{channel_available[ind]} data '
for ind, data in enumerate(data_tot)])]))


def stop(self):
"""Stop the current grab hardware wise if necessary"""
self.controller.disable_all_reporting()


if __name__ == '__main__':
main(__file__)
38 changes: 36 additions & 2 deletions src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import numbers
from threading import Lock


from pyvisa import ResourceManager
from telemetrix import telemetrix

Expand All @@ -22,6 +21,12 @@ class Arduino(telemetrix.Telemetrix):
def __init__(self, *args, **kwargs):
super().__init__(*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

@staticmethod
def round_value(value):
Expand All @@ -40,6 +45,35 @@ def analog_write_and_memorize(self, pin, value):
self.pin_values_output[pin] = value
lock.release()

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 (with an Arduino UNO)
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)}
With an arduino up to 6 analog input might be interrogated at the same time
"""
self.analog_pin_values_input[data[1]] = data[2] # data are integer from 0 to 1023 in case Arduino UNO

def set_analog_input(self, pin):
"""
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
The differential parameter:
When comparing the previous value and the current value, if the
difference exceeds the differential. This value needs to be equaled
or exceeded for a callback report to be generated.
"""
lock.acquire()
self.set_pin_mode_analog_input(pin, differential=0, callback=self.read_analog_pin)
self.set_analog_scan_interval(1)
self.disable_analog_reporting(pin)
lock.release()

def get_output_pin_value(self, pin: int) -> numbers.Number:
value = self.pin_values_output.get(pin, 0)
return value
Expand All @@ -65,7 +99,7 @@ def servo_move_degree(self, pin: int, value: float):

if __name__ == '__main__':
import time
tele = Arduino('COM23')
tele = Arduino('COM6')
tele.set_pin_mode_servo(5, 100, 3000)
time.sleep(.2)

Expand Down

0 comments on commit d5891ee

Please sign in to comment.