When it comes to embedded systems, it is often debugged by serial communication. If you make your own IoT device, there is no shell, so a simple serial monitor comes into play.
In that case, will the standard be TeraTerm? Macros are also substantial.
However, when I try to process the serial result or convert the result to csv, I want to use Python or something (in my case, it's just a matter of familiarity, not which one is better). I think it's probably the best way to use pyserial.
But,
It feels strange to be caught in Java (what?). In another case, I was playing with python and C language, so I tried to realize serial communication this time by applying it. Create the serial communication part with DLL and access it from ctypes. This is enough for slow message exchange such as built-in.
I hope it will be helpful as to how to use Windows-specific controls from Python.
Check below. Since it is a prototype, I would like to forgive you.
--Confirmed on Windows10 (64bit) and Windows7 (64bit). --Python is 2.7 series 32bit, and DLL is also built and used on x86. --I made a DLL in VisualStudio2015 / 2017 Community. --It is assumed that serial only handles character strings. Binaries cannot be used as they are (especially on the Python side) --The maximum communication size at one time is 1024 bytes. So with the image of doing things differently. --This time, only the COM port and BPS can be set, and the rest are the usual fixed values. Because it's basically a 3-line cross, isn't it? (If you need to change it, I think it's easy, so please modify it appropriately.)
The tutorial is below http://starship.python.net/crew/theller/ctypes/tutorial.html
I think that DLLs using Visual C ++ can be coded in a normal way. On the other hand, the python side needs some ingenuity, and it is necessary to define functions using ctypes. However, I think it's faster to look at the tutorial above and the actual code, so I'll expose the code immediately.
Below is the code on the DLL side.
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
#define DLL_API __declspec(dllexport)
#define WCHAR_PORT_NUMBER_BUF_LENGTH (16 * 2)
#define SERIAL_BUFFER_SIZE (1024)
extern "C" DLL_API INT32 __stdcall SERIAL_open(char *port, INT32 baud);
extern "C" DLL_API char * __stdcall SERIAL_read(void *len);
extern "C" DLL_API INT32 __stdcall SERIAL_write(char *text, INT32 len);
extern "C" DLL_API void __stdcall SERIAL_close(void);
static HANDLE SERIAL_handle = NULL;
static char SERIAL_recv[SERIAL_BUFFER_SIZE + 1];
DLL_API INT32 __stdcall SERIAL_open(char *port, INT32 baud)
{
TCHAR tcPort[WCHAR_PORT_NUMBER_BUF_LENGTH];
DCB comDcb;
BOOL success;
INT32 ret = 0;
COMMTIMEOUTS comTimeouts;
memset(tcPort, 0, 32);
MultiByteToWideChar(CP_OEMCP,
MB_PRECOMPOSED,
port,
strlen(port),
tcPort,
WCHAR_PORT_NUMBER_BUF_LENGTH / 2);
SERIAL_handle = CreateFile(tcPort, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (SERIAL_handle == INVALID_HANDLE_VALUE) {
return -1;
}
success = SetupComm(SERIAL_handle, SERIAL_BUFFER_SIZE, SERIAL_BUFFER_SIZE);
if (success == FALSE)
{
ret = -2;
goto error;
}
memset(&comDcb, 0, sizeof(DCB));
comDcb.DCBlength = sizeof(DCB);
comDcb.BaudRate = baud;
comDcb.fParity = FALSE;
comDcb.Parity = NOPARITY;
comDcb.ByteSize = 8;
comDcb.StopBits = ONESTOPBIT;
success = SetCommState(SERIAL_handle, &comDcb);
if (success == FALSE) {
ret = -3;
goto error;
}
memset(&comTimeouts, 0, sizeof(COMMTIMEOUTS));
comTimeouts.ReadIntervalTimeout = 500;
comTimeouts.ReadTotalTimeoutMultiplier = 0;
comTimeouts.ReadTotalTimeoutConstant = 500;
comTimeouts.WriteTotalTimeoutMultiplier = 0;
comTimeouts.WriteTotalTimeoutConstant = 500;
success = SetCommTimeouts(SERIAL_handle, &comTimeouts);
if (success == FALSE)
{
ret = -4;
goto error;
}
success = PurgeComm(SERIAL_handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
if (success == FALSE)
{
ret = -5;
goto error;
}
return 0;
error:
CloseHandle(SERIAL_handle);
return ret;
}
DLL_API char * __stdcall SERIAL_read(void *len)
{
BOOL success;
DWORD recvLen = 0;
INT32 *lenToPython = (INT32 *)len;
success = ReadFile(SERIAL_handle, SERIAL_recv, SERIAL_BUFFER_SIZE, &recvLen, NULL);
if (success == FALSE || recvLen == 0) {
*lenToPython = 0;
return "";
}
*lenToPython = (INT32)recvLen;
SERIAL_recv[recvLen] = '\0';
return SERIAL_recv;
}
DLL_API INT32 __stdcall SERIAL_write(char *text, INT32 len)
{
BOOL success;
DWORD dummy = 0;
success = WriteFile(SERIAL_handle, text, strlen(text), &dummy, NULL);
if (success == TRUE)
{
return 0;
}
else {
return -1;
}
}
DLL_API void __stdcall SERIAL_close(void)
{
if (SERIAL_handle == NULL) {
return;
}
CloseHandle(SERIAL_handle);
}
The following four functions are prepared. It's extremely simple.
//Port open, port="COMxx", Baud is bps. The return value is 0 for success and negative for failure
INT32 SERIAL_open(char *port, INT32 baud)
//Read data. When called, len contains the number of bytes read and char contains the received data.
//With a timeout of 500msec,*len=It can be 0.
char * SERIAL_read(void *len)
//Data writing. The text string is len bytes (as you can see from the code, the maximum is 1024 bytes)
INT32 SERIAL_write(char *text, INT32 len)
//Port close
void SERIAL_close(void)
Build this code as a DLL in Visual Studio. The points to note in that case are as follows.
--Win32 project in Visual C ++. Create a project with the DLL. --And then copy the above code into the empty cpp generated by that project. --Use dllmain.cpp as it is. --By the way, please match the number of bits with the Python environment (I confirmed it with 32 bits) --As you can see in the code, I defined each function with stdcall. --I don't want to think about def, so I use __declspec (dllexport).
The actual code is supposed to have written serial communication according to the textbook using Win32 API (rather, I'm sorry it's a degraded version). .. If you build with, you can create a dll normally. I will write the Python side as if using only this dll.
The following sites were very helpful when creating the program. http://www.geocities.co.jp/SiliconValley-SanJose/5309/serial.html http://www.ys-labo.com/BCB/2007/070512%20RS232C%20zenpan.html
It's the Python side. The code of the class that performs serial communication using the above DLL is shown below. By the way, it is the code when the file name of the DLL is serial_if.dll.
#!/usr/bin/env python
from ctypes import *
'''
Describe serial DLL's functions.
'''
dll = windll.serial_if
dll.SERIAL_open.argtype = (c_char_p, c_int)
dll.SERIAL_open.restype = c_int
dll.SERIAL_read.argtype = c_void_p
dll.SERIAL_read.restype = c_char_p
dll.SERIAL_write.argtype = (c_char_p, c_int)
dll.SERIAL_write.restype = c_int
'''
Serial Device Driver
'''
class SerialDriver:
def open(self, port, baud):
int_ret = dll.SERIAL_open(port, baud);
if int_ret == 0:
return True
else:
return False
def read(self):
read_len = c_int(0)
text = dll.SERIAL_read(byref(read_len))
return text, read_len.value
def write(self, text):
write_ret = dll.SERIAL_write(text, len(text))
if write_ret == 0:
return True
else:
return False
def close(self):
dll.SERIAL_close();
Describe serial DLL's functions. Written in the comment is the description for handling the DLL.
This time, we have adopted the description that uses DLL using windll. At that time, first set the name of the DLL to be used as follows.
dll = windll.serial_if
# windll.(DLL file name)Specify the corresponding DLL with. The above example is serial_if.Means dll
Next, define the arguments and return value of each function exported by DLL using the description of ctypes. I will write it in comparison with the C function below.
# INT32 SERIAL_open(char *port, INT32 baud);
dll.SERIAL_open.argtype = (c_char_p, c_int)
dll.SERIAL_open.restype = c_int
# char * SERIAL_read(void *len);
dll.SERIAL_read.argtype = c_void_p
dll.SERIAL_read.restype = c_char_p
# INT32 SERIAL_write(char *text, INT32 len);
dll.SERIAL_write.argtype = (c_char_p, c_int)
dll.SERIAL_write.restype = c_int
# void SERIAL_close(void);
#No definition required if neither argument nor return value
If you have done both C and Python, you can understand the basic writing method by comparing the above. Tutorial will be helpful for dealing with fine types.
After that, you can call the defined function normally. Please note the following. Be especially careful when getting a value from C with a pointer.
The above SerialDriver class calls a DLL function to perform serial communication. Those who use this class are implemented so that they do not need to be aware of ctypes.
For the matter of value, the following site of stackoverflow was helpful. http://stackoverflow.com/questions/2330587/how-to-convert-ctypes-c-long-to-pythons-int
The following is a program that uses the SerialDriver class to perform actual serial communication and logs the data received serially. By the way, this is based on the assumption that the SerialDriver class is written with the file name serial_lib.py (as you can see by importing the code below).
#!/usr/bin/env python
from serial_lib import SerialDriver
from datetime import datetime
import sys
import time
def serial_read(serial, keyword):
text = ""
text_len = 0
while text.find(keyword) < 0:
read_text, read_len = serial.read()
if read_len > 0:
text_len += read_len
text += read_text
return text
def serial_test():
filename = datetime.now().strftime('%Y%m%d%H%M%S') + ".txt"
f = open(filename, "w")
serial = SerialDriver()
ret = serial.open("COM3", 115200)
print "python:SERIAL_open=" , ret
if ret == False:
sys.exit()
text = serial_read(serial,"teraterm command1")
print text
f.write(text)
text = "python response1\r\n"
serial.write(text)
text = serial_read(serial,"teraterm command2")
print text
f.write(text)
text = "python response2\r\n"
serial.write(text)
f.close()
serial.close()
if __name__ == "__main__":
serial_test()
print "python: complete"
#EOF
Also, I wrote this file as serial_test.py. That is the premise of the following explanations.
As you can see from the code, it consists of the following functions.
--serial_test main processing --serial_read Serial reception processing
As you can see by looking at the DLL code, the reception result may be 0 bytes because it times out in 500 msec. Therefore, the process of continuing to read until a specific keyword is received is performed here (so, the mechanism is such that it will not come out unless that keyword comes. I have not taken any measures for this w)
The main process (serial_test function) is processed according to the following flow.
As an aside, if it is COM10 or higher, it is useless in the above and must be described as \\. \ COM10 "(MS-like specifications as shown below). https://support.microsoft.com/ja-jp/help/115831/howto-specify-serial-ports-larger-than-com9
The other party of the above serial_test function was experimentally performed with the following TeraTerm macro.
timeout=30
sendln 'teraterm command1'
wait 'python response1'
sendln 'teraterm command2'
The contents are as stated below.
It feels like communicating with each other.
The test was run as follows.
--The DLL, the SerialDeriver class, and the test application that uses it (serial_test.py in the author's environment example) should be placed in the same folder. --Connect to the other side with a serial cable (cross). --Start TeraTerm on the other side, connect with 115200BPS, 8bit, no-parity, stop1bit and execute the above macro. ――If you start the test app within 30 seconds, they will communicate with each other. --In addition, the log file of the received data is also left.
By applying this, it is possible to perform serial communication and data analysis with Python. You will be able to check the function using serial and convert it to CSV. You should be able to do machine learning with this by taking sensor data serial.
I used it below. Thank you for providing the wonderful software.
that's all.
Recommended Posts