OrsayDev / OrsayLaser

Instrumentation for Laser / Blanker / Gain usage in Nionswift
0 stars 0 forks source link

Hardware Communication [nionswift env] #12

Closed yvesauad closed 4 years ago

yvesauad commented 4 years ago

I was able pretty easily to read powermeter using PYVISA module. NI VISA is another module that allows us to acess national insturment VISA architecture.

Just so you dont forget how to do it:

import pyvisa
pyvisa.ResourceManager()
rm = pyvisa.ResourceManager()
tl = rm.open_resource('USB0::4883::32882::1907040::0::INSTR')
#print(rm.list_resources())
print(tl.query('*IDN?'))
tl.write('CONF:POW')
tl.write('INIT')
print(tl.query('FETCH?'))
print(tl.query('CONFIGURE?'))
print(tl.query('SENS:CORR:WAV?'))

This very simple program recovers and prints the power in the terminal. Last line tell us the setted wavelength. Implement this in nionswift shouldn't be an issue.

i) test how to do this (maybe a simple PIP in terminal, but check python version, as pyVISA runs in a pretty old python version (2.7+) ii) test laser. I know laser also uses NI-VISA architecture. Maybe these simple lines will work right away and everything is done

PS: You need to have permission in the USB. A simple chmod in /home/@USER/dev or something like this should do the trick. Test peripheric identification with lsusb and test serial communication querying *IDN?. If you have a response like Thorlabs,PM100USB,1907040,1.6.0 then communication is working

ps2: In Linux, a simple sudo python is enough to temporary set super user permission so you dont need anything else. Handy to debug

If all this works, as it had worked with power meter, then its almost over :)

yvesauad commented 4 years ago

Please note that there is a non-proprietary version of NI VISA. Check this for windows and test everything on windows as well. Again, should work with no probs

yvesauad commented 4 years ago

I have tried a lot to make my laser work using ctypes. I had a few issues but i believe i am in the right track. For doc:

#include <iostream>
#include <stdio.h>
#include <crtdbg.h>
#include "SirahLaserObject.h" 

void main(void)
{
    LASER_OBJECT *p;
    double wl;

    !LaserCreate("Sirah Laser.ini", &p);
    !LaserGet(p, LASER_FUND_WAVELENGTH, &wl);
    //!LaserGoto(p, LASER_FUND_WAVELENGTH, wl, true);
    !LaserDestroy(p);

    printf("%f", wl);
}

This works using visual studio 2017. In order to make it work, we have included header .h in the same directory, as well as the available .dll. Note that this is not enough. Need to go to Project > Properties > Path Editors > General > Suplementary libraries and add the path of the sirah .lib archive

You also need to go to the tab below general (entrée) and add the name together with a bunch other such as kernel32.lib and shell32.lib.

Finally, you need to properly initialize object with Sirah Laser.ini. I've put in the same folder but doesn matter as long as you provide right path.

laser_necessary_files.zip

yvesauad commented 4 years ago
from ctypes import cdll

mylib = cdll.LoadLibrary('C:\\Users\Lumiere\Downloads\yves\\31_05_2020\\test_c\SirahLaserObject.dll')

I got an annoying error for a long time when trying to import my .dll using ctypes (did nothing with .dll..i was just trying to import it) Error: [WinError 193] %1 is not a valid Win32 application

The solution of this error was reinstalling python 3.8 using 32-bit version. Python 64 bit is simple not compatible or the available workarounds doesn't worth doing.

There is no error message. I will try tomorrow to implement ctypes and read laser wavelength inside python. I don't believe change everything to python 32 bit is a big deal. Let's hope for the best

yvesauad commented 4 years ago

Let's remember we have a second way of doing this by using pyserial module. I was already able to

import serial

ser = serial.Serial()
ser.baudrate=19200
ser.port='COM12'
ser.timeout=2
ser.open()

mes = [60, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62]
b= bytearray(mes)

check = 0
for i in range(len(mes)-1):
    check = check + mes[i]
b[11] = check

ser.write(b)
s=ser.read(100)
print(s)
ser.close()

using ser.is_open shows that we succesfully open and close our serial communication through COM12 (our laser). I have tryind to send these arrat of bytes but i didn't receive a response message. Message head is always 3c (60 in decimal) and ending is 3e (62 decimal). For STATUS kind of messages, only second byte defines instruction. In this case, second byte (x04) tells the stepper motor to stop and answers back with 0/1 depending if it was succesfull or not. Well, I am not super far away from this. CRC seems to be working as well (baud rate, parity, stop bits etc is eveyrhing by default well setted)

visit: https://pyserial.readthedocs.io/en/latest/pyserial_api.html for more info.

marceltence commented 4 years ago

Hi Yves,

Yes, it is a 32 bit library. That's why I made a separate 32 bit program with a TCP/IP interface so that it can relay commands from a 64 bit program.

You can talk to this program instead, called "Credo.exe" if I remember well. I should send you the API.

The laser is quite old now, it is not probably worth to work a lot as new ones will not have this 32 bit problem.

Marcel.

Le 01/06/2020 à 20:08, yvesauad a écrit :

from ctypes import cdll mylib = cdll.LoadLibrary('C:\Users\Lumiere\Downloads\yves\31_05_2020\test_c\SirahLaserObject.dll')

I got an annoying error for a long time when trying to import my .dll using ctypes (did nothing with .dll..i was just trying to import it) Error: [WinError 193] %1 is not a valid Win32 application

The solution of this error was reinstalling python 3.8 using 32-bit version. Python 64 bit is simple not compatible or the available workarounds doesn't worth doing.

There is no error message. I will try tomorrow to implement ctypes and read laser wavelength inside python. I don't believe change everything to python 32 bit is a big deal. Let's hope for the best

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/yvesauad/yvorsay-instrument/issues/12#issuecomment-637020329, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABGQQWKKWTS43SVQSFBQWTTRUPVAHANCNFSM4NPIQM2A.

yvesauad commented 4 years ago

@marceltence thank you so much. So i have tried with ctypes as you have seen but really looks like will be a complicated solution. Not only it is 32 bit but also .dll is a black box, we have a .lib and also the header .h (not saying the laser init) that we all need to in some way load using ctypes.

justo for me to to understand, do this credo.exe was compilled in C? So in fact you did a plain C program (which i was finally able to do as well) to read and set the wavelength and returned the output? I agree with you that this would be 200% easier. Going through all the process loading in python seems pretty crazy right?

marceltence commented 4 years ago

Yves,

CTypes is great to use C dll. That's how the scan and cameras are implemented. You can have a look at orsaycamera.py or orsayscan.py. There is a tool that makes easier the definition of all function access.

credo.exe is program that I wrote in C# because it is easier to write TCP server code.

As you can see it also include the PowerMeter readout, that we made together.

/using System;// //using System.Globalization;// //using System.Runtime.InteropServices;// //using System.IO;// //using System.Net.Sockets;// //using System.Net;// //using System.Threading;// // //namespace Credo// //{// //    class CredoServer// //    {// //        private const string sirah = "SirahLaserObject.dll";// //        const short LASER_FUND_WAVELENGTH = 0;// //        const short LASER_FUND_WAVENUMBER = 1;// //        const short LASER_FUND_FREQUENCY = 2;// // //        private Object locker = new Object();// // //        CultureInfo ci;// // //        [DllImport(sirah, EntryPoint = "LaserCreate", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]// //        private static extern Int16 LaserCreate(string configurationfile, ref int laser);// // //        [DllImport(sirah, EntryPoint = "LaserDestroy", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]// //        private static extern Int16 LaserDestroy(int laser);// // //        [DllImport(sirah, EntryPoint = "LaserInit", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]// //        private static extern Int16 LaserInit(int laser);// // //        [DllImport(sirah, EntryPoint = "LaserGet", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]// //        private static extern Int16 LaserGet(int laser, short mode, ref double wl);// // //        [DllImport(sirah, EntryPoint = "LaserGoto", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]// //        private static extern Int16 LaserGoto(int laser, short mode, double wl);// // //        [DllImport(sirah, EntryPoint = "LaserGetLastError", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]// //        private static extern Int16 LaserGetLastError(int laser, string error);// // //        private int laser = 0;// //        private TcpListener listener;// // //        public delegate void WaveLengthChangedDelegate(double wl);// //        public WaveLengthChangedDelegate onWaveLengthChanged;// // //        public PM100 pm100;// // //        public CredoServer()// //        {// //            int port = 13000;// //            IPAddress localAddr = IPAddress.Parse("127.0.0.1");// //            Start(localAddr, port);// //            ci = new CultureInfo("en-US");// // //            pm100 = new PM100();// //        }// // //        ~CredoServer()// //        {// //        }// // //        private double wavelength = 710.0;// // //        public async void Start(IPAddress adr, int port)// //        {// //            // Connect to the laser first// //            int laserok = LaserCreate("Sirah Laser.ini", ref laser);// //            if ((laserok == 0) && (laser != 0))// //            {// //                GetWL(0, out wavelength);// //            }// //            listener = new TcpListener(adr, port);// //            listener.Start();// // //            while (true)// //            {// //                try// //                {// //                    var tcpClient = await listener.AcceptTcpClientAsync();// //                    HandleConnectionAsync(tcpClient);// //                }// //                catch (Exception exp)// //                {// //System.Diagnostics.Debug.WriteLine(exp.ToString());// //                }// // //            }// // //        }// //        private async void HandleConnectionAsync(TcpClient tcpClient)// //        {// //            string clientInfo = tcpClient.Client.RemoteEndPoint.ToString();// //            Byte[] bytes = new Byte[256];// // //            bool alive = true;// // //            while (alive)// //            {// //                try// //                {// //                    using (var networkStream = tcpClient.GetStream())// //                    using (var reader = new StreamReader(networkStream))// //                    using (var writer = new StreamWriter(networkStream))// //                    {// //                        writer.AutoFlush = true;// //                        while (true)// //                        {// //                            double wl;// //                            var dataFromServer = await reader.ReadLineAsync();// //                            if (string.IsNullOrEmpty(dataFromServer))// //                            {// //                                break;// //                            }// //System.Diagnostics.Debug.WriteLine(dataFromServer);// //                            if (dataFromServer.Substring(0, 3) == "?WL")// //                            {// //                                if (GetWL(0, out wl))// //                                {// //                                    dataFromServer = "WL:" + wl.ToString(ci);// //                                }// //                            }// //                            else if (dataFromServer.Substring(0, 3) == "WL:")// //                            {// //                                try// //                                {// //                                    wl = double.Parse(dataFromServer.Substring(3));// //SetWL(LASER_FUND_WAVELENGTH, wl);// //                                }// //                                catch (Exception ex)// //                                {// //System.Diagnostics.Debug.WriteLine("Failed to set wavelength: " + ex.ToString());// //                                }// //                                if (GetWL(LASER_FUND_WAVELENGTH, out wl))// //                                {// //                                    dataFromServer = "WL:" + wl.ToString(ci);// //                                }// //                            }// //                            else if (dataFromServer.Substring(0, 3) == "?PW")// //                            {// //                                dataFromServer = "PW:" + pm100.Power.ToString(ci);// //                            }// //                            else if (dataFromServer.Substring(0, 3) == "?pw")// //                            {// //                                dataFromServer = "PW:" + pm100.MeasurePower().ToString(ci);// //                            }// //                            await writer.WriteLineAsync(dataFromServer);// //                        }// //                    }// //                }// //                catch (Exception exp)// //                {// //System.Diagnostics.Debug.WriteLine(exp.Message);// //                }// //                finally// //                {// //System.Diagnostics.Debug.WriteLine(string.Format("Closing the client connection - {0}",// //                                clientInfo));// //                    tcpClient.Close();// //                    alive = false;// //                }// // //            }// //        }// // //        public void Close()// //        {// //            if (laser != 0)// //            {// //                int res = LaserDestroy(laser);// //            }// //        }// // //        public bool SetWL(short mode, double wl)// //        {// //            short res = LaserGoto(laser, mode, wl);// //            wavelength = wl;// //            if (pm100 != null)// //            {// //                pm100.SetWaveLength(wl);// //            }// //            return true;// //        }// // //        public bool GetWL(short mode, out double wl)// //        {// //            short res = LaserGet(laser, mode, ref wavelength);// //            wl = wavelength;// //            if (onWaveLengthChanged!= null)// //            {// //                onWaveLengthChanged(wl);// //            }// //            return true;// //        }// //    }// //}// // /

Basically you send  a string through TCP to localhost if on the same computer on port 13000, and you read the answer.

"?WL"  returns the wavelength

"WL:500" set the wavelength(don't remember which units to put here).

"?PW" returns the latest known power, periodically measured by credo itself.

"?pw" explicitly reads the power.

For DM, there is a mechanism to check whether we change the wavelength in credo itself. No such thing for TCP side.

This can be done, but makes the client(swift side) a bit more complicated.

This a mechanism quite simple to accommodate 64 applications, when 64 bit dll does not exist.

Marcel.

Le 02/06/2020 à 10:43, yvesauad a écrit :

@marceltence https://github.com/marceltence thank you so much. So i have tried with ctypes as you have seen but really looks like will be a complicated solution. Not only it is 32 bit but also .dll is a black box, we have a .lib and also the header .h (not saying the laser init) that we all need to in some way load using ctypes.

justo for me to to understand, do this credo.exe was compilled in C? So in fact you did a plain C program (which i was finally able to do as well) to read and set the wavelength and returned the output? I agree with you that this would be 200% easier. Going through all the process loading in python seems pretty crazy right?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/yvesauad/yvorsay-instrument/issues/12#issuecomment-637388531, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABGQQWLRWHXUQYVN6KHU4NTRUS3RRANCNFSM4NPIQM2A.

yvesauad commented 4 years ago

thank you so much Marcel, this is a beautiful workaround! I know you guys have sent me a lot of examples and its amazing that i have so much resource to study. Sorry that sometimes i question already answered questions...even when i look these codes most of the time i get it, like, 5%. This one you sent me sounded me surprinsily doable, maybe because i was lurking around you at the time you were doing the power meter part. thanks again!!! :+1:

i will report here another workaround that worked directly using python with no C. I finally got talking using directly the serial port. It is easier than i thought although they can't directly do all functions available in DLL.

yvesauad commented 4 years ago

The challenge here is send the train of strings correctly. Line terminator changes from machine to machine and in this case it seems to be NULL.

Complete code and a few outputs:

import serial
import time

ser = serial.Serial()
ser.baudrate=19200
ser.port='COM12'
ser.timeout=1
print(ser)
ser.open()

mes = [60, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 62]
ba= bytearray(mes)

print("send :")
print(ba)
ser.write(ba) #WE WILL CHANGE HERE TO CHECK DIFFERENT OUTPUTS
print()
print("answer: ")
s=ser.read(14)
print(s)

ser.close()

If ser.write=('hello_world\n'.encode()) We have an answer like this: b'[\x01\xc1\x02\nl\x13\x00\x1e\x00\x00\x00\x00]' Following the manual, first bit is the error code, being the first bit an 01 means transmission error. Not surprising as we have sent gibberish information to the hardware.

if mes = [60, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62], our answer is: b'[\x0f\xc1\x02\nl\x13\x00\x1e\x00\x00\x00\x00]' first bit is 15, a checksum error. Before last pixel should be the sum of all bits excepting the last one so the correct message is mes = [60, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 62].

Good message doest not produce by default status message so nothing is printed at the terminal. You can change this setting using "auto-prompt enable".

Note that answer byte #06 tells us the position of the grating. I've checked using sirah control and error messages that they are consistent, altought i have no idea of precision. We have 2 bytes (255*255 = 65025 values) for describing something that must do a step of 0.001. I will check this right now and do a table.

If this solution is easy, we can stick to this ATM

kociak commented 4 years ago

Hi All Yves, you have example of TCP/IP communication using sockets in the MönchActuator. The case of the Mönch is a bit complicated, but in fact you can just use a simple socket and it works like a charm

yvesauad commented 4 years ago

again, just for the sake of doc:

send_mes = [60, 7, 1, 106, 8, 19, 0, 0, 0, 0, 0, 201, 62]
bs = bytearray(send_mes)

ser.open()
ser.write(bs)
s = ser.read(14)
ser.close()

This three line code puts the laser at 600nm. I have tried moving it from several starting points and it works. good news: the absolute motor positions, given in this case by 106 (0x6A), 8 (0x08), 19 (0x13) and 0 (0x00) are very very consistent.

I was mistaken and we have 4 bytes (256 256 256 * 256) steps which puts precision <0.001 nm.

we can set a async communication in which packets go and back like crazy, but we know they are valid because of header and checksum.

if i understood correctly TCP/IP, that would be nice to implement like this. that would be full python :)

Regarding precision: When laser is at 600, bytes are [106, 08, 18, 0] (LSB -> MSB). When in 599.8: [22, 10, 19, 0]. When in 599.79 [44, 10, 19, 0] 22 steps in LSB between 0.01nm -> ~0.0004nm / bit pretty nice

yvesauad commented 4 years ago

check laser_example for a calibration (3 order polynomial using numpy) and a hello_hardware. Laser working with <pm resolution.