I'm using swig to connect a C ++ function with my Python libraries. I managed to compile everything and create the .so file. But after having done the import of my C ++ function in python I have some errors on the argv ... maybe I'm wrong to pass the values to it from the outside or maybe when I compiled and created the .i swig file I got something wrong. The C ++ function creates a TCP socket and sends a Hex code to a device that replies with another Hex code, from a terminal with the C ++ code i write:
connect_PE_func 192.168.1.170 600000060
and it works perfectly. and it works perfectly. I would expect a similar syntax once I set my c ++ function via swig in python, type:
answer= connect_PE_func.connect_PE_func("192.168.1.170", 600000060,2)
but I get this error:
test=connect_PE_func.connect_pe_func(["192.168.2.170"],["2600000026"])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-f58f79b27fe5> in <module>
----> 1 test=connect_PE_func.connect_pe_func(["192.168.2.170"],["2600000026"])
~/mqtt_atenapy/C_connect_PE_dev/test_cpython/connect_PE_func.py in connect_pe_func(argv, argc)
64
65 def connect_pe_func(argv, argc):
---> 66 return _connect_PE_func.connect_pe_func(argv, argc)
67
68
TypeError: in method 'connect_pe_func', argument 1 of type 'char *[]'
I attach the code to the files .c .h e .i which I used with swig to get the .so.
thanks.
connect_PE.func.c :
#include <stdio.h>
#include <errno.h>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <unistd.h>
#include <sstream>
#include "connect_PE_func.h"
using namespace std;
// to compile gcc connect_PE_func.cpp -lstdc++ -c
char* connect_pe_func(char *argv[],int argc)
{
int sockfd, n;
int connected = 0;
struct sockaddr_in servaddr;
std::string serveraddr = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(serveraddr.c_str());
servaddr.sin_port = htons(9761);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
std::string pref_hex;
std::string hex("0x");
std::string test = argv[2];
size_t numbytes = test.size() / 2;
uint8_t command[numbytes];
for (size_t w = 0, x = 0; w < numbytes; ++w, x += 2)
{
pref_hex = hex + test.substr(x, 2);
cout << pref_hex;
command[w] = stoi(pref_hex, nullptr, 16);
//cout << test.substr(x, 2);
//cout << "\n";
//cout << command[w];
//cout << "\n";
}
//uint8_t command[] = {0x26, 0x00, 0x00, 0x00, 0x26};
int bytes_to_send = sizeof(command);
send(sockfd, command, bytes_to_send, 0);
uint8_t output_command[numbytes];
recv(sockfd, output_command, bytes_to_send, 0);
char test_out[10];
for (size_t w = 0, x = 0; w < numbytes; ++w, x += 2)
{
test_out[x] = (char)output_command[w];
//cout << unsigned(test_out[x]);
}
return test_out;
}
connect_PE_func.h:
// file: connect_PE_func.h
char* connect_pe_func(char *argv[], int argc);
connect_PE_func.i:
/* file: connect_PE_func.i */
%module connect_PE_func
%{
/* Everything in this block will be copied in the wrapper file. We include the C header file necessary to compile the interface
*/
#include "connect_PE_func.h"
// extern char *connect_pe_func(int argc, char *argv[]);
%}
/* list functions to be interfaced: */
char* connect_pe_func(char *argv[], int argc);
You've misplaced a comma func(["192.168.2.170"] , ["2600000026"])
My guess is that you need to pass func(["192.168.2.170", "2600000026"], 2)
Related
I am trying to use PyBind11 to create a C++ Wrapper to access my USB device on Ubuntu 22.04. I have working c++ program that does this .
#include <iostream>
#include "FUTEK_USB_DLL.h"
#include <string.h>
#include <stdio.h>
#include <ctime>
#include <ratio>
#include <chrono>
#include <unistd.h>
#include <fstream>
#include <cmath>
using namespace std;
int main()
{
_FUTEK_USB_DLL::FUTEK_USB_DLL dll;
PVOID deviceHandle ;
BYTE channelNumber = 0;
string sn = dll.Get_Device_Serial_Number(0);
char *serialNumber = new char[sn.length() + 1];
strcpy(serialNumber, sn.c_str());
dll.Open_Device_Connection(serialNumber);
deviceHandle = dll.DeviceHandle;
string boardType = dll.Get_Type_of_Board(deviceHandle);
string firmVersion = dll.Get_Firmware_Version(deviceHandle);
string samplingRate = dll.Get_ADC_Sampling_Rate(deviceHandle, channelNumber);
double offsetValue = stod(dll.Get_Offset_Value(deviceHandle, channelNumber));
double fullScaleLoad = stod(dll.Get_Fullscale_Load(deviceHandle, channelNumber));
double offsetLoad = stod(dll.Get_Offset_Load(deviceHandle, channelNumber));
double fullScaleValue = stod(dll.Get_Fullscale_Value(deviceHandle, channelNumber));
double decimalPoint = stod(dll.Get_Decimal_Point(deviceHandle, channelNumber));
cout << "Get_Offset_Value: " << offsetValue << endl;
cout << "Get_Fullscale_Load: " << fullScaleLoad << endl;
cout << "Get_Offset_Load: " << offsetLoad << endl;
cout << "Get_Fullscale_Value: " << fullScaleValue << endl;
cout << "Get_Decimal_Point: " << decimalPoint << endl;
while(true)
{
double normalDataRequest = stod(dll.Normal_Data_Request(deviceHandle, channelNumber));
//cout << "Normal_Data_Request: " << normalDataRequest << endl;
float torque = (normalDataRequest - offsetValue)*((fullScaleLoad - offsetLoad)/(fullScaleValue-offsetValue)) / pow(10, decimalPoint);
cout << "torque: " << torque << endl;
usleep((unsigned int)500000);
}
dll.Close_Device_Connection(serialNumber);
}
This code uses two external shared libraries that I link when compiling.
all: main.cpp
g++ -D _GLIBCXX_USE_CXX11_ABI=0 -std=c++11 -Wall main.cpp -o main -lFUTEK_USB-x86_64 -lftd2xx
I am now trying to use PyBind11 to be able to call this code from Python. I am new to PyBind11, but I did get the "hello world" example to compile and work from being called from python. My issue is I am not sure how to link these two dependencies (FUTEK_USB and lftd2xx) in my module using CMake. Here is my CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
project(pybind_test)
find_library(futekLib FUTEK_USB-x86_64)
if (${futekLib} STREQUAL futekLib-NOTFOUND)
message(FATAL_ERROR "No FutekLib, sad day")
else()
message(STATUS "futeklib Found! as ${futekLib}")
endif()
find_library(ftd2xxLib ftd2xx)
if (${ftd2xxLib} STREQUAL ftd2xxLib-NOTFOUND)
message(FATAL_ERROR "No ftd2xxLib, sad day")
else()
message(STATUS "ftd2xxLib Found! as ${ftd2xxLib}")
endif()
add_subdirectory(pybind11)
include_directories(thirdparty)
pybind11_add_module(bind_test main.cpp)
target_link_libraries(bind_test PUBLIC ${futekLib} ${ftd2xxLib})
By adding those message statements to my CMakeLists.txt I can confirm that it does find those two libraries. When i build this python module and then try to import it, I keep getting this undefined reference
<ipython-input-1-a9d1c7c9c951> in <module>
----> 1 import bind_test
ImportError: /home/dyno/Downloads/futek_usb_linux_x86_64/FUTEK_USB-x86_64/bind_test.cpython-310-x86_64-linux-gnu.so: undefined symbol: _ZN14_FUTEK_USB_DLL13FUTEK_USB_DLL21Get_ADC_Sampling_RateB5cxx11EPvh
Here is my PyBind11 file im trying to run
#include <pybind11/pybind11.h>
#include <iostream>
#include "FUTEK_USB_DLL.h"
#include <string.h>
#include <stdio.h>
#include <ctime>
#include <ratio>
#include <chrono>
#include <unistd.h>
#include <fstream>
namespace py = pybind11;
using namespace std;
std::string get_torque(){
_FUTEK_USB_DLL::FUTEK_USB_DLL dll;
PVOID deviceHandle ;
BYTE channelNumber = 0;
string sn = dll.Get_Device_Serial_Number(0);
char *serialNumber = new char[sn.length() + 1];
strcpy(serialNumber, sn.c_str());
dll.Open_Device_Connection(serialNumber);
deviceHandle = dll.DeviceHandle;
string boardType = dll.Get_Type_of_Board(deviceHandle);
string firmVersion = dll.Get_Firmware_Version(deviceHandle);
string samplingRate = dll.Get_ADC_Sampling_Rate(deviceHandle, channelNumber);
string adc = dll.Normal_Data_Request(deviceHandle, channelNumber);
return adc;
}
PYBIND11_MODULE(bind_test, handle){
handle.doc()="TEST";
handle.def("python_name", &get_torque);
}
Any help would be greatly appreciated as I am very stuck on this issue. Here is the repo with the full code https://github.com/RavenLabsNH/FutekTorqueTransducer
I'm trying to call in my C ++ library from python with types but I can't pass as IP arguments and HEX code, can anyone help me?
import sys
import ctypes
lib = ctypes.CDLL('./hello.so')
LP_c_char = ctypes.POINTER(ctypes.c_char)
LP_LP_c_char = ctypes.POINTER(LP_c_char)
lib.connect_pe_func.argtypes = (ctypes.c_int, LP_LP_c_char)
argc = 2
argv = ["192.168.2.170","2600000026"]
for i, arg in enumerate(sys.argv):
enc_arg = arg.encode('utf-8')
argv[i] = ctypes.create_string_buffer(enc_arg)
lib.connect_pe_func(argc, argv)
this is the error message, how do I insert IP and Hex code into the argv vector without having this error?
---------------------------------------------------------------------------
ArgumentError Traceback (most recent call last)
<ipython-input-21-f59eabe02690> in <module>
17 argv[i] = ctypes.create_string_buffer(enc_arg)
18
---> 19 lib.connect_pe_func(argc, argv)
ArgumentError: argument 2: <class 'TypeError'>: expected LP_LP_c_char instance instead of list
for completeness I also insert the C ++ code that I compiled and made into a hello.so library
#include <stdio.h>
#include <errno.h>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <unistd.h>
#include <sstream>
#include "connect_PE_func.h"
using namespace std;
extern "C" char * connect_pe_func(int argc, char *argv[])
{
int sockfd, n;
int connected = 0;
struct sockaddr_in servaddr;
std::string serveraddr = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(serveraddr.c_str());
servaddr.sin_port = htons(9761);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
std::string pref_hex;
std::string hex("0x");
std::string test = argv[2];
size_t numbytes = test.size() / 2;
uint8_t command[numbytes];
for (size_t w = 0, x = 0; w < numbytes; ++w, x += 2)
{
pref_hex = hex + test.substr(x, 2);
cout << pref_hex;
command[w] = stoi(pref_hex, nullptr, 16);
}
int bytes_to_send = sizeof(command);
send(sockfd, command, bytes_to_send, 0);
uint8_t output_command[numbytes];
recv(sockfd, output_command, bytes_to_send, 0);
char test_out[10];
for (size_t w = 0, x = 0; w < numbytes; ++w, x += 2)
{
test_out[x] = (char)output_command[w];
}
return test_out;
};
extern "C" char * hello_world(char * name){
char * output = (char *) calloc(sizeof(name)+7, sizeof(char));
strcat(output, "Hello ");
strcat(output, name);
strcat(output, "\0");
//output[sizeof(output)-1] = '/0';
return output;
};
Python lists can't be passed as pointer of pointers.
To create and populate a pointer of pointers from Python, you need:
to create a pointer array: p = (LP_c_char*len(argv))() (allocates the space for the pointers)
to cast it into a pointer of pointers: na = ctypes.cast(p, LP_LP_c_char) (makes it compatible with the decayed pointer of pointers form)
or without the redefinitions:
p = (ctypes.POINTER(ctypes.c_char)*len(argv))()
na = ctypes.cast(p, ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))
Fixed code:
import sys
import ctypes
lib = ctypes.CDLL('./hello.so')
LP_c_char = ctypes.POINTER(ctypes.c_char)
LP_LP_c_char = ctypes.POINTER(LP_c_char)
lib.connect_pe_func.argtypes = (ctypes.c_int, LP_LP_c_char)
argv = ["192.168.2.170","2600000026"]
argc = len(argv)
p = (LP_c_char*len(argv))()
for i, arg in enumerate(argv): # not sys.argv, but argv!!!
enc_arg = arg.encode('utf-8')
p[i] = ctypes.create_string_buffer(enc_arg)
na = ctypes.cast(p, LP_LP_c_char)
lib.connect_pe_func(argc, na)
To test this, I've created a very simple c++ code (instead of yours)
#include <stdio.h>
extern "C" char * connect_pe_func(int argc, char *argv[])
{
for (int i=0;i<argc;i++)
{
puts(argv[i]);
}
return 0;
}
built with: g++ -shared -o hello.so test.cpp
running the python module proves that arguments are passed all right:
192.168.2.170
2600000026
Inspired by Pointers and arrays in Python ctypes
I have a function in c++ that returns vector. I am using BOOST_PYTHON_MODULE to call it from python. I want to send a pointer as input to the c++ function. I was trying to send the pointer as string. I know this is the worst method but some people are using it and it is working fine. For my case it isn't working. I am new in c++.
Working case:
#include <iostream>
#include <string.h>
#include <sstream>
using namespace std;
int main(){
string s1 = "0x7fff3e8aee1c";
stringstream ss;
ss << s1;
cout << "ss : " << ss << endl;
long long unsigned int i;
ss >> hex >> i;
cout << "i : " << i << endl;
int *i_ptr=reinterpret_cast<int *>(i);
cout << "pointer: " << i_ptr << endl;
return 0;
}
My case:
#include "openrave-core.h"
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "valid_grasp_generator/GraspSnapshot.h"
#include <iostream>
#include <vector>
#include <boost/python.hpp>
#include <sstream>
using namespace OpenRAVE;
using namespace std;
boost::python::list get_penetration_depth(string env)
{
vector<double> vec(9,0);
stringstream ss(env);
long long unsigned int i;
ss >> hex >> i;
EnvironmentBasePtr penv = reinterpret_cast<int *>(i);
//code for getting penetration value from the openrave
typename std::vector<double>::iterator iter;
boost::python::list list;
for (iter = vec.begin(); iter != vec.end(); ++iter) {
list.append(*iter);
}
return list;
}
BOOST_PYTHON_MODULE(libdepth_penetration){
using namespace boost::python;
def("get_penetration", get_penetration_depth);
}
Error during catkin_make:
/home/saurabh/catkin_ws/src/valid_grasp_generator/src/depth_penetration.cpp: In function ‘boost::python::list get_penetration_depth(std::string)’:
/home/saurabh/catkin_ws/src/valid_grasp_generator/src/depth_penetration.cpp:37:56: error: conversion from ‘int*’ to non-scalar type ‘OpenRAVE::EnvironmentBasePtr {aka boost::shared_ptr<OpenRAVE::EnvironmentBase>}’ requested
EnvironmentBasePtr penv = reinterpret_cast<int *>(i);
^
make[2]: *** [valid_grasp_generator/CMakeFiles/depth_penetration.dir/src/depth_penetration.cpp.o] Error 1
make[1]: *** [valid_grasp_generator/CMakeFiles/depth_penetration.dir/all] Error 2
make: *** [all] Error 2
Invoking "make -j8 -l8" failed
In your case, you try a wrong conversion between an int to a shared_ptr:
EnvironmentBasePtr penv = reinterpret_cast<int *>(i); // wrong conversion
// where EnvironmentBasePtr is boost::shared_ptr<EnvironmentBase>
For this conversion I suggest using a constructor of shared_ptr and a null deleter. I see 3 possible solutions.
You can define your own null deleter void deleter(void* ptr) {} then :
boost::shared_ptr<EnvironmentBase> penv(reinterpret_cast<EnvironmentBase *>(i),&deleter);
If your boost is 1.55 or less, you can include <boost/serialization/shared_ptr.hpp> then :
boost::shared_ptr<EnvironmentBase> penv(reinterpret_cast<EnvironmentBase *>(i),boost::serialization::null_deleter());
If your boost is 1.56 or more, you can include <boost/core/null_deleter.hpp> then :
boost::shared_ptr<EnvironmentBase> penv(reinterpret_cast<EnvironmentBase *>(i),boost:null_deleter());
I have a C++ class
class EventHandler {
virtual long readFromDaemon(void *buf, size_t count) = 0;
};
and a Python program that uses it
class Handler(EventHandler):
def readFromDaemon(self, buf, count):
...
C++ code calls EventHandler::readFromDaemon().
SWIG converts arguments and calls Python's readFromDaemon():
long SwigDirector_EventHandler::readFromDaemon(void *buf, size_t count) {
...
swig::SwigVar_PyObject obj0;
obj0 = SWIG_NewPointerObj(SWIG_as_voidptr(buf), SWIGTYPE_p_void, 0 );
swig::SwigVar_PyObject obj1;
obj1 = SWIG_From_size_t(static_cast< size_t >(count));
...
swig::SwigVar_PyObject swig_method_name = SWIG_Python_str_FromChar((char *)"readFromDaemon");
swig::SwigVar_PyObject result = PyObject_CallMethodObjArgs(swig_get_self(), (PyObject *) swig_method_name ,(PyObject *)obj0,(PyObject *)obj1, NULL);
I want to convert (void *buf, size_t count) to PyBytesObject
def readFromDaemon(self, buf):
# buf is bytes
But I didn't found a way how to achieve it.
%typemap(in) (void *buf, size_t count) { ... } does not help.
Here's an example of reading and writing to C++ data buffers. the C++ functions in my example are:
void read(void* buf, size_t count);
void write(const void* buf, size_t count);
SWIG interface file:
%module x
%include <exception.i>
// Handle an input-only (const) buffer.
// A single Python input is translated into a buffer and a size.
%typemap(in) (const void *buf, size_t count) (Py_ssize_t tmp) %{
if(PyBytes_AsStringAndSize($input,(char**)&$1,&tmp) == -1)
return NULL;
$2 = tmp;
%}
// Handle an output-only (non-const) buffer.
// The single integer Python input is allocated in a temporary
// buffer and the pointer and its size are created as C++ parameters.
%typemap(in,numinputs=1) (void *buf, size_t count) (long tmp) %{
if(!PyLong_Check($input))
SWIG_exception(SWIG_TypeError,"expected integer");
tmp = PyLong_AsLong($input);
if(tmp < 1 || tmp > 65535)
SWIG_exception(SWIG_ValueError,"expected value 1-65535");
$2 = tmp;
$1 = new char[$2];
%}
// The pair of output arguments are translated into a single
// Python bytes object and appended to any existing return value.
%typemap(argout) (void *buf, size_t count) (PyObject* po) %{
po = PyBytes_FromStringAndSize((char*)$1,$2);
$result = SWIG_Python_AppendOutput($result,po);
%}
// Free the temporary buffer created in the "in" typemap.
%typemap(freearg) (void* buf, size_t count) %{
delete $1;
%}
// SWIG will wrap these two functions prototypes.
void read(void* buf, size_t count);
void write(const void* buf, size_t count);
// Implementation
%{
#include <iostream>
void write(const void* buf, size_t count)
{
char* tmp = (char*)buf;
for(size_t i = 0; i < count; i++)
std::cout << i << ": " << (int)tmp[i] << std::endl;
}
void read(const void* buf, size_t count)
{
char* tmp = (char*)buf;
for(size_t i = 0; i < count; i++)
tmp[i] = (char)i;
}
%}
Demo:
>>> import x
>>> x.read(10)
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t'
>>> x.read(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: expected value 1-65535
>>> x.read(1000000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: expected value 1-65535
>>> x.write(b'abcdefg')
0: 97
1: 98
2: 99
3: 100
4: 101
5: 102
6: 103
>>> x.write(12)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: expected bytes, int found
I found a decision in another topic here.
%typemap(directorin) (void *buf, size_t count) {
$input = PyBytes_FromStringAndSize(static_cast<char*>($1), $2);
}
But there is another problem. The memory buffer is copied from C++ to Python object. How to copy bytes back from python object to C++? There is memoryview since python 2.7, but what to do with 2.6 and olders versions?
This is C++ code that calls EventHandler::readFromDaemon, for clarity
std::vector<char> buf(maxlen);
long cnt = m_evt_handler->readFromDaemon(&buf[0], buf.size());
Here is wrapper code now
long SwigDirector_EventHandler::readFromDaemon(void *buf, size_t count) {
long c_result;
swig::SwigVar_PyObject obj0;
{
obj0 = PyBytes_FromStringAndSize(static_cast<char*>(buf), count);
}
...
I have the following C file that I am compiling to a shared object. I then load the .so shared object via ctypes in python. I can call the function from ctypes, and the function prints the correct temp and humidity, however I can't seem to get the struct back from the main code. How can I get the struct back from the C function and how can I retrieve the fields from it within python.
#!/bin/python
from ctypes import *
class HMTEMP(Structure):
_fields_ = [ ("temp", c_double) , ("humidity", c_double) ]
dhtlib = 'libdht4py.so'
hlibc = CDLL(dhtlib)
HMTEMP = hlibc.readDHT()
print HMTEMP.temp
#define BCM2708_PERI_BASE 0x20000000
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <bcm2835.h>
#include <unistd.h>
#define MAXTIMINGS 100
struct DHStruct {
double temp;
double humidity;
} ;
struct DHStruct readDHT();
int bits[250], data[100];
int bitidx = 0;
struct DHStruct readDHT() {
bcm2835_init() ;
int type = 11 ;
int pin = 4 ;
struct DHStruct dhts;
int counter = 0;
int laststate = HIGH;
int j=0;
// Set GPIO pin to output
bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_write(pin, HIGH);
usleep(500000); // 500 ms
bcm2835_gpio_write(pin, LOW);
usleep(20000);
bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_INPT);
data[0] = data[1] = data[2] = data[3] = data[4] = 0;
// wait for pin to drop?
while (bcm2835_gpio_lev(pin) == 1) {
usleep(1);
} //while
// read data!
for (int i=0; i< MAXTIMINGS; i++) {
counter = 0;
while ( bcm2835_gpio_lev(pin) == laststate) {
counter++;
//nanosleep(1); // overclocking might change this?
if (counter == 1000)
break;
}//while
laststate = bcm2835_gpio_lev(pin);
if (counter == 1000) break;
bits[bitidx++] = counter;
if ((i>3) && (i%2 == 0)) {
// shove each bit into the storage bytes
data[j/8] <<= 1;
if (counter > 200)
data[j/8] |= 1;
j++;
}//if
} //for
dhts.temp = data[2] ;
dhts.humidity = data[0] ;
printf("Temp = %5.2f *C, Hum = %5.2f \%\n", dhts.temp , dhts.humidity );
return dhts;
}//function
Ok I got it - and using ctypes was very fast. The python code:
#!/bin/python
from ctypes import *
# define the struct and it's fields
class DHStruct(Structure):
_fields_ = [("temp",c_double),("humidity",c_double)]
#reference the library
dhtlib = CDLL("libdht4py.so")
# set the return type as the object above
dhtlib.readDHT.restype = POINTER(DHStruct)
# dereference the pointer using ctype's -contents and access the struct fields.
print ( dhtlib.readDHT().contents.temp , dhtlib.readDHT().contents.humidity )
The C code : the key was to convert the function to return a pointer.
#define BCM2708_PERI_BASE 0x20000000
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <bcm2835.h>
#include <unistd.h>
#define MAXTIMINGS 100
//define the struct
struct DHStruct {
double temp;
double humidity;
} ;
struct DHStruct *readDHT(); // define the function prototype to return the pointer
int bits[250], data[100];
int bitidx = 0;
//make sure to return a POINTER!!
struct DHStruct *readDHT() {
bcm2835_init() ;
int type = 11 ;
int pin = 4 ;
struct DHStruct *dhts; // here is the key - define the pointer to the struct
int counter = 0;
int laststate = HIGH;
int j=0;
// Set GPIO pin to output
bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_write(pin, HIGH);
usleep(500000); // 500 ms
bcm2835_gpio_write(pin, LOW);
usleep(20000);
bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_INPT);
data[0] = data[1] = data[2] = data[3] = data[4] = 0;
// wait for pin to drop?
while (bcm2835_gpio_lev(pin) == 1) {
usleep(1);
} //while
// read data!
for (int i=0; i< MAXTIMINGS; i++) {
counter = 0;
while ( bcm2835_gpio_lev(pin) == laststate) {
counter++;
//nanosleep(1); // overclocking might change this?
if (counter == 1000)
break;
}//while
laststate = bcm2835_gpio_lev(pin);
if (counter == 1000) break;
bits[bitidx++] = counter;
if ((i>3) && (i%2 == 0)) {
// shove each bit into the storage bytes
data[j/8] <<= 1;
if (counter > 200)
data[j/8] |= 1;
j++;
}//if
} //for
dhts->temp = data[2] ;
dhts->humidity = data[0] ;
//for debug printf("Temp = %5.2f *C, Hum = %5.2f \%\n", dhts->temp , dhts->humidity );
return dhts;
}//function
To combine C/C++ and Python I would recommend to use Cython.
With Cython you are able to pass objects (eg. numpy arrays) to C/C++, fill it with your data and get it back to your python-code.
Here is a minmal example:
The C-skript: (c_example.c)
#include <stdlib.h>
#include <math.h>
void c_claculate(double *x, int N) {
int i;
for (i = 0; i<N;i++) {
x[i]+=i*i;
}
}
The python-skript: (example.py)
from numpy import *
from example import *
data=zeros(10)
calculate(data)
print data
The .pyx file: (example.pyx)
import cython
import numpy
cimport numpy
# declare the interface to the C code
cdef extern void c_claculate(double *x, int N)
# Cython interface to C function
def calculate(numpy.ndarray[double, ndim=1, mode='c'] x not None):
cdef int N = x.shape[0]
c_claculate(&x[0],N)
return x
and the setup file: (setup.py)
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy
setup(
cmdclass = {'build_ext': build_ext},
ext_modules = [
Extension("example",
sources=["example.pyx", "c_example.c"],
include_dirs=[numpy.get_include()]
)
],
)
Now you can compile the skript by running
python setup.py build_ext -fi
and then execute the python skript.
Cython should be available via pip on your PI.