Slow serial communication with Arduino and Python - python

In a project of mine, I have to take a picture from a camera connected to a Sony Spresense Arduino board which is linked to my computer via an USB port. I am not very experienced in Arduino and serial communication, not at all in fact, so I am asking for some help.
I want to read the data from the camera which is sent in hexadecimal from the Arduino to my computer via a Serial.begin at a given baud rate, processed by a Python program in order to collect the hex code, correct it (there have been some print errors which are now solved), and convert it to a JPEG image.
I am able to do it, but the serial communication part of my program where Python collects the data from the Arduino is significantly slow, it takes 34 seconds to obtain an image that weighs "only" 100 ko (I don't know if it's a lot to handle for an Arduino), at a baud rate of 115200. If I try to increase this number, the hex code collected shows some errors and the image can not be converted. I can also change the pixel resolution of the image, but I'd like to be able to work with HD pictures.
In detail, here is the code from the Arduino, I have found no other way than this to get the data from the camera (there is no designed function in the Spresense library for serial communication). The relevant part is at the end :
#include <SDHCI.h>
#include <stdio.h> /* for sprintf */
#include <Camera.h>
#define BAUDRATE (115200)
/**
* Print error message
*/
void printError(enum CamErr err)
{
Serial.print("Error: ");
switch (err)
{
case CAM_ERR_NO_DEVICE:
Serial.println("No Device");
break;
case CAM_ERR_ILLEGAL_DEVERR:
Serial.println("Illegal device error");
break;
case CAM_ERR_ALREADY_INITIALIZED:
Serial.println("Already initialized");
break;
case CAM_ERR_NOT_INITIALIZED:
Serial.println("Not initialized");
break;
case CAM_ERR_NOT_STILL_INITIALIZED:
Serial.println("Still picture not initialized");
break;
case CAM_ERR_CANT_CREATE_THREAD:
Serial.println("Failed to create thread");
break;
case CAM_ERR_INVALID_PARAM:
Serial.println("Invalid parameter");
break;
case CAM_ERR_NO_MEMORY:
Serial.println("No memory");
break;
case CAM_ERR_USR_INUSED:
Serial.println("Buffer already in use");
break;
case CAM_ERR_NOT_PERMITTED:
Serial.println("Operation not permitted");
break;
default:
break;
}
}
void CamCB(CamImage img)
{
/* Check the img instance is available or not. */
if (img.isAvailable())
{
/* If you want RGB565 data, convert image data format to RGB565 */
img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565);
}
else
{
Serial.print("Failed to get video stream image\n");
}
}
/**
* #brief Initialize camera
*/
void setup()
{
CamErr err;
/* Open serial communications and wait for port to open */
Serial.begin(BAUDRATE);
while (!Serial)
{
; /* wait for serial port to connect. Needed for native USB port only */
}
/* begin() without parameters means that
* number of buffers = 1, 30FPS, QVGA, YUV 4:2:2 format */
Serial.println("Prepare camera");
err = theCamera.begin();
if (err != CAM_ERR_SUCCESS)
{
printError(err);
}
/* Start video stream.
* If received video stream data from camera device,
* camera library call CamCB.
*/
Serial.println("Start streaming");
err = theCamera.startStreaming(true, CamCB);
if (err != CAM_ERR_SUCCESS)
{
printError(err);
}
/* Auto white balance configuration */
// Serial.println("Set Auto white balance parameter");
err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_DAYLIGHT);
if (err != CAM_ERR_SUCCESS)
{
printError(err);
}
/* Set parameters about still picture.
* In the following case, QUADVGA and JPEG.
*/
Serial.println("Set still picture format");
// err = theCamera.setStillPictureImageFormat(
// CAM_IMGSIZE_QUADVGA_H,
// CAM_IMGSIZE_QUADVGA_V,
// CAM_IMAGE_PIX_FMT_JPG);
//err = theCamera.setStillPictureImageFormat(320, 240, CAM_IMAGE_PIX_FMT_JPG);
err = theCamera.setStillPictureImageFormat(CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);
if (err != CAM_ERR_SUCCESS)
{
printError(err);
}
/**
* #brief Take picture with format JPEG per second
*/
/******** Affichage serie ********/
/* une ligne de vide et l'image */
Serial.println(" ");
CamImage img = theCamera.takePicture();
/* Check availability of the img instance. */
/* If any error was occured, the img is not available. */
if (img.isAvailable())
{
/*Indicateur de debut img : FFD8FF (Magic number of the jpeg format) */
for(int i=0;i<img.getImgSize();i++)
{
Serial.print(*(img.getImgBuff()+i),HEX); //img.getImgBuff() gets the data address of the picture, so the * before.
Serial.print(";");
}
/*End indicator : FFD9FF (Magic number of jpeg format) */
}
}
void loop()
{
// put your main code here, to run repeatedly:
sleep(1);
}
And here is the Python code :
## Serial collecting the data of the picture taken by the camera
import serial
from serial import Serial
import binascii
import string
from PIL import Image
import time
start_time = time.time()
ser = serial.Serial('COM3', baudrate=115200, timeout=1)
# writing the data in a text file
data = open("data.txt", "w")
data.write(str(ser.readlines()))
## Correcting the data
data = open("data.txt", "r")
string = str(data.readlines())
# spliting the string into a list
# I chose to use the ";" to split the different hex couples in the Arduino program
tab=string.split(";")
tab[0] = 'FF'
tab.pop(-1)
N = len(tab)
# correcting the arguments that are not couples :
# Indeed, when Arduino encounter a couple starting with a 0,
# it omits it, so I have to add it back manually
for i in range(N):
if len(tab[i]) == 1:
tab[i] = '0' + tab[i]
newdata = open("newdata.txt", "w")
# writing the new data in a text file
for s in tab:
newdata.write(s)
newdata.close()
data.close()
## Converting the hex data into a JPEG file
file = open("newdata.txt", "r")
data= file.read()
# conversion
data = binascii.a2b_hex(data)
# creation of the JPEG file
with open('imagenew.jpg', 'wb') as image_file:
image_file.write(data)
file.close()
img = Image.open('imagenew.jpg')
img.show()
print("--- %s seconds ---" % (time.time() - start_time))
If you have any idea or any advice in order to speed up this process I am taking it. I heard that there are some flow control and buffer stories here and there, but I am quite lost when I try to find something relevant to my situation. Thanks in advance.

Related

Serial communication between python + arduino and parsing string

I'm trying to send a set of [x, y] coordinates from python to arduino in order to control servo motors on a robot drivetrain. I'm taking the approach of sending a string in the format "x,y". On the arduino side, I'm trying to parse this string to get the x and y substrings. However the indexOf function doesn't return the proper index. Here is my code:
arduino = serial.Serial(port='COM3', baudrate=9600, timeout=.1)
def write_read(x):
arduino.write(bytes(x, 'utf-8'))
time.sleep(0.05)
data = arduino.readline()
return data
for coordinate in coordinates:
c = str(coordinate[0]) + ", " + str(coordinate[1])
print(write_read(c))
#include <Servo.h>
void setup() {
Serial.begin(9600);
Serial.setTimeout(1);
}
void loop() {
while (!Serial.available());
String coordinates = "" + Serial.readString();
// Serial.print(coordinates); //this prints normally as the string value
int i = coordinates.indexOf(',');
Serial.print(i);
}
When I send something like "-9.45, -16.3". The output is b'-1-1-1-1-10-1-1-1-1-1-1'. I'm not sure what's going on here or how to get the actual index (in this case it would be 5).

Python I2C communication TTP229

I need to read 2 different bytes from TTP229 (16 keys or 8 keys touch pad detector).
I use I2C In Python. TTP229 datasheet PDF.
I can't read the second byte, but I can get the first byte.
Python code:
import smbus
bus = smbus.SMBus(1)
adressTTP229 = 0x57 #0xAF>>1
byte1 = bus.read_byte(adressTTP229)
byte2 = bus.read_byte(adressTTP229)
byte1 is always equal to byte2.
This Arduino code, works ok:
#include <Wire.h>
#define ttp229 (0xAF>>1)
void setup() {
Serial.begin(9600); // start serial for output
Wire.begin();
}
void loop() {
delay(50);
bool isNewData = false;
Wire.requestFrom(ttp229,2,true);
while (Wire.available()) {
uint16_t b1 = Wire.read(); // receive a first byte
uint16_t b2 = Wire.read(); // receive a second byte
if (b1==b2 && b2==0) {break;}
//...
}
}
How do I use Arduino's requestFrom() function in Python?
try:
import smbus, time
bus = smbus.SMBus(1)
while True:
print bus.read_word(0xAF)
time.sleep(0.1)
dont change address, bus doe the conversion, and if you read byte you will always get the same first byte. you want to read a word = 2 bytes at once
not tested, but might work, have it ordered and will test

Python Netlink Multicast Communication in Kernels above 4

I was trying to reproduce the example from a previous SO post on a kernel above 4 (4.1):
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <net/net_namespace.h>
/* Protocol family, consistent in both kernel prog and user prog. */
#define MYPROTO NETLINK_USERSOCK
/* Multicast group, consistent in both kernel prog and user prog. */
#define MYGRP 31
static struct sock *nl_sk = NULL;
static void send_to_user(void)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
char *msg = "Hello from kernel";
int msg_size = strlen(msg) + 1;
int res;
pr_info("Creating skb.\n");
skb = nlmsg_new(NLMSG_ALIGN(msg_size + 1), GFP_KERNEL);
if (!skb) {
pr_err("Allocation failure.\n");
return;
}
nlh = nlmsg_put(skb, 0, 1, NLMSG_DONE, msg_size + 1, 0);
strcpy(nlmsg_data(nlh), msg);
pr_info("Sending skb.\n");
res = nlmsg_multicast(nl_sk, skb, 0, MYGRP, GFP_KERNEL);
if (res < 0)
pr_info("nlmsg_multicast() error: %d\n", res);
else
pr_info("Success.\n");
}
static int __init hello_init(void)
{
pr_info("Inserting hello module.\n");
nl_sk = netlink_kernel_create(&init_net, MYPROTO, NULL);
if (!nl_sk) {
pr_err("Error creating socket.\n");
return -10;
}
send_to_user();
netlink_kernel_release(nl_sk);
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Exiting hello module.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
However, compilation works fine, but when I insert the module, it returns:
nlmsg_multicast() error: -3
I dont even know, where I can lookup the error codes to learn, what -3 means in this context (I searched here, but was unable to find anything useful, regarding the error code).
Just to be sure, I post the userland code (Python) also:
EDITED due to a comment: (but still not working)
#!/usr/bin/env python
import socket
import os
import time
sock = socket.socket(socket.AF_NETLINK, socket.SOCK_DGRAM, socket.NETLINK_USERSOCK)
# 270 is SOL_NETLINK and 1 is NETLINK_ADD_MEMBERSHIP
sock.setsockopt(270, 1, 31)
while 1:
try:
print sock.recvfrom(1024)
except socket.error, e:
print 'Exception'
You forgot to bind the socket. :-)
I'm not very fluent with Python, so use this only as a starting point (between the socket and the setsockopt):
sock.bind((0, 0))
That prints me a bunch of garbage, among which I can see
Hello from kernel
By the way: When nlmsg_multicast() throws ESRCH, it's usually (or maybe always) because there were no clients listening.
First open the client, then try to send the message from the kernel.
Otherwise you can always ignore that error code it that makes sense for your use case.

Read continuous data from serial port with pyserial

I have strings being continuously sent from my arduino.
For example, I am sending an integer (0-1023) line by line, so it should be:
"51\r\n233\r\n37\r\n166\r\n"
And infinitely long as it is continuously streaming.
I am currently using pyserial's function readline() to read the data, but constantly see broken/missing bytes. For example, instead of "37\r\n" followed with "11\r\n", it will get "3\r11\r\n" or even "3711\r\n"!
Here's my full Python-end code:
import serial
import time
if __name__ == '__main__':
ser = serial.Serial('COM3', baudrate=1000000)
data = []
time0 = time.time()
while (time.time() - time0 < 5): # Read data for 5 seconds
data.append(ser.readline())
ser.close()
For those interested, the (probably relevant) arduino code is simply:
#define FASTADC 1 // Flag for prescale 16
// Code pasted for modifying ADCSRA
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
void setup() {
Serial.begin(1000000);
#if FASTADC
// set prescale to 16
sbi(ADCSRA,ADPS2) ;
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;
#endif
}
void loop() {
int val;
val = analogRead(A0);
Serial.println(val);
}
As you can see from the code, the baud rate, parity, stop bit were both set to 1,000,000; None; 1.

Check to see if system volume is muted?

I am working on a project that plays audio for part of the program. I would like to be able to display a message if the user's system volume is muted. I am using Python on Windows.
Use the Windows Mixer API. I've found this article for you, and attached the relevant code:
(From MS KB 181550: "Monitor Audio Volume Levels")
This is in C, but it can be "translated" to Python as well.
I hope that helps.
#include <windows.h>
#include <mmsystem.h>
MMRESULT rc; // Return code.
HMIXER hMixer; // Mixer handle used in mixer API calls.
MIXERCONTROL mxc; // Holds the mixer control data.
MIXERLINE mxl; // Holds the mixer line data.
MIXERLINECONTROLS mxlc; // Obtains the mixer control.
// Open the mixer. This opens the mixer with a deviceID of 0. If you
// have a single sound card/mixer, then this will open it. If you have
// multiple sound cards/mixers, the deviceIDs will be 0, 1, 2, and
// so on.
rc = mixerOpen(&hMixer, 0,0,0,0);
if (MMSYSERR_NOERROR != rc) {
// Couldn't open the mixer.
}
// Initialize MIXERLINE structure.
ZeroMemory(&mxl,sizeof(mxl));
mxl.cbStruct = sizeof(mxl);
// Specify the line you want to get. You are getting the input line
// here. If you want to get the output line, you need to use
// MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT.
mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
rc = mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl,
MIXER_GETLINEINFOF_COMPONENTTYPE);
if (MMSYSERR_NOERROR == rc) {
// Couldn't get the mixer line.
}
// Get the control.
ZeroMemory(&mxlc, sizeof(mxlc));
mxlc.cbStruct = sizeof(mxlc);
mxlc.dwLineID = mxl.dwLineID;
mxlc.dwControlType = MIXERCONTROL_CONTROLTYPE_PEAKMETER;
mxlc.cControls = 1;
mxlc.cbmxctrl = sizeof(mxc);
mxlc.pamxctrl = &mxc;
ZeroMemory(&mxc, sizeof(mxc));
mxc.cbStruct = sizeof(mxc);
rc = mixerGetLineControls((HMIXEROBJ)hMixer,&mxlc,
MIXER_GETLINECONTROLSF_ONEBYTYPE);
if (MMSYSERR_NOERROR != rc) {
// Couldn't get the control.
}
// After successfully getting the peakmeter control, the volume range
// will be specified by mxc.Bounds.lMinimum to mxc.Bounds.lMaximum.
MIXERCONTROLDETAILS mxcd; // Gets the control values.
MIXERCONTROLDETAILS_SIGNED volStruct; // Gets the control values.
long volume; // Holds the final volume value.
// Initialize the MIXERCONTROLDETAILS structure
ZeroMemory(&mxcd, sizeof(mxcd));
mxcd.cbStruct = sizeof(mxcd);
mxcd.cbDetails = sizeof(volStruct);
mxcd.dwControlID = mxc.dwControlID;
mxcd.paDetails = &volStruct;
mxcd.cChannels = 1;
// Get the current value of the peakmeter control. Typically, you
// would set a timer in your program to query the volume every 10th
// of a second or so.
rc = mixerGetControlDetails((HMIXEROBJ)hMixer, &mxcd,
MIXER_GETCONTROLDETAILSF_VALUE);
if (MMSYSERR_NOERROR == rc) {
// Couldn't get the current volume.
}
volume = volStruct.lValue;
// Get the absolute value of the volume.
if (volume < 0)
volume = -volume;

Categories

Resources