简体   繁体   English

Python 解释的 C++ 字节数组 (struct)

[英]C++ byte array (struct) interpreted by Python

I am trying to pass a C++ struct from my arduino to my raspberry pi.我正在尝试将 C++ 结构从我的 arduino 传递到我的树莓派。 I have a struct that looks like this:我有一个看起来像这样的结构:

struct node_status
{
  char *node_type = "incubator";
  char *sub_type;          // set the type of incubator
  int sub_type_id;
  bool sleep = false;         // set to sleep
  int check_in_time = 1000;   // set check in time
  bool LOCK = false;      // set if admin control is true/false
} nodeStatus;

I tried using the python module named struct我尝试使用名为 struct 的 python 模块

from struct import *

                print("Rcvd Node Status msg from 0{:o}".format(header.from_node))
                print("node_type: {}".format(unpack("10s",payload[0]))) #node_type
                node_type = unpack("10s",payload[0])
                print("sub_type: {}".format(unpack("10s",payload[1]), header.from_node))    #sub_type
                sub_type = unpack("10s",payload[1])
                print("sub_type_id: {}".format(unpack("b",payload[2])))
                sub_type_id = unpack("b",payload[2])
                print("sleep: {}".format(unpack("?",payload)[3]))   #sleep
                sleep = unpack("?",payload[3])
                print("check_in_time: {}".format(unpack("l",payload[4])))   #check_in_time
                check_in_time = unpack("l",payload[4])
                print("Lock: {}".format(unpack("?",payload[5])))    #LOCK
                Lock = unpack("?",payload[5])

but I am not having much luck.但我运气不佳。 I was even looking at just using ctypes module but seem to not be going anywhere..我什至只考虑使用 ctypes 模块,但似乎没有去任何地方..

from ctypes import *

class interpret_nodes_status(Structure):
    _fields_ = [('node_type',c_char_p),
                ('sub_type',c_char_p),
                ('sub_type_id',c_int),
                ('sleep',c_bool),
                (check_in_time',c_int),
                ('LOCK',c_bool)]

nodestatus = translate_nodes_status(payload)

but that just gives me an error但这只是给了我一个错误

TypeError: bytes or integer address expected instead of bytearray instance

What can I do?我能做什么? WHERE am I going wrong with this?我哪里出错了?

EDIT:编辑:

I am using the RF24Mesh Library from我正在使用来自的 RF24Mesh 库

https://github.com/nRF24/RF24Mesh https://github.com/nRF24/RF24Mesh

The way I send the message is this?我发送消息的方式是这样的?

  RF24NetworkHeader header();

  if (!mesh.write(&nodeStatus, /*type*/ 126, sizeof(nodeStatus), /*to node*/ 000))
  { // Send the data
    if ( !mesh.checkConnection() )
    {
      Serial.println("Renewing Address");
      mesh.renewAddress();

    }
  }
  else
  {
    Serial.println("node status msg Sent");
    return;
  }

}

Your C program is just sending the struct, but the struct doesn't contain any of the string data.您的 C 程序只是发送结构,但该结构不包含任何字符串数据。 It only includes pointers (addresses) which are not usable by any other process (different address spaces).它只包括不能被任何其他进程(不同地址空间)使用的指针(地址)。

You would need to determine a way to send all the required data, which would likely mean sending the length of each string and its data.您需要确定一种发送所有必需数据的方法,这可能意味着发送每个字符串的长度及其数据。

One way to do that would be to use a maximum length and just store the strings in your struct:一种方法是使用最大长度并将字符串存储在结构中:

struct node_status
{
  char node_type[48];
  char sub_type[48];          // set the type of incubator
  int sub_type_id;
  bool sleep = false;         // set to sleep
  int check_in_time = 1000;   // set check in time
  bool LOCK = false;      // set if admin control is true/false
} nodeStatus;

You would then need to copy strings into those buffers instead of assigning them, and check for buffer overflow.然后您需要将字符串复制到这些缓冲区中而不是分配它们,并检查缓冲区溢出。 If the strings are ever entered by users, this has security implications.如果用户曾经输入过这些字符串,就会有安全隐患。

Another approach is to pack the data into a single block just when you send it.另一种方法是在发送数据时将数据打包到一个块中。 You could use multiple writes, as well, but I don't know this mesh library or how you would set the type parameter to do that.您也可以使用多次写入,但我不知道这个网格库或您将如何设置type参数来做到这一点。 Using a buffer is something like:使用缓冲区类似于:

// be sure to check for null on your strings, too.
int lennodetype = strlen(nodeStatus.node_type);
int lensubtype = strlen(nodeStatus.sub_type);
int bufsize = sizeof(nodeStatus) + lennodetype + lensubtype;
byte* buffer = new byte[bufsize];

int offset = 0;
memcpy(buffer+offset, &lennodetype, sizeof(int));
offset += sizeof(int);
memcpy(buffer+offset, nodeStatus.node_type, lennodetype * sizeof(char));
offset += lennodetype * sizeof(char);
memcpy(buffer+offset, &lensubtype, sizeof(int));
offset += sizeof(int);
memcpy(buffer+offset, nodeStatus.sub_type, lensubtype * sizeof(char));
offset += lensubtype * sizeof(char);
// this still copies the pointers, which aren't needed, but simplifies the code
// and 8 unused bytes shouldn't matter too much. You could adjust this line to 
// eliminate it if you wanted.
memcpy(buffer+offset, &nodeStatus, sizeof(nodeStatus));

if (!mesh.write(buffer, 
/*type*/ 126, 
bufsize, 
/*to node*/ 000))
  { // Send the data
    if ( !mesh.checkConnection() )
    {
      Serial.println("Renewing Address");
      mesh.renewAddress();

    }
  }
  else
  {
    Serial.println("node status msg Sent");
  }
delete [] buffer;

Now that the data is actually SENT (a prerequisite for reading the data) the data you need should all be in the payload array.既然数据实际上已发送(读取数据的先决条件),您需要的数据应该都在payload数组中。 You will need to unpack it, but you can't just pass unpack a single byte, it needs the array:您需要解压它,但不能只传递解压单个字节,它需要数组:

len = struct.unpack("@4i", payload)
offset = 4
node_type = struct.unpack_from("{}s".format(len), payload, offset)
offset += len
len = struct.unpack_from("@4i", payload, offset)
offset += 4
sub_type = struct.unpack_from("{}s".format(len), payload, offset)
offset += len
...

I might be wrong but I think you don't need to use ctypes, just use the data that you received in python directly -- it might be already packed as a tuple.我可能是错的,但我认为您不需要使用 ctypes,只需直接使用您在 python 中收到的数据——它可能已经打包为一个元组。 I wanted to add this as a comment but I don't have enough reputation我想将此添加为评论,但我没有足够的声誉

I upvoted Garr Godfrey's answer as it is a good one indeed.我赞成 Garr Godfrey 的回答,因为它确实很好。 However, it will increase the struct's size.但是,它会增加结构的大小。 This neither a good nor bad thing, however if for some reason you would like to keep the solution based on char* pointers instead of arrays (eg you don't know the maximum length of the strings), it can be achieved the following way (my code makes assumption of int's size being 4 bytes, little endian, bool's size=1bytes, char size=1byte):这既不是好事也不是坏事,但是如果出于某种原因您想保留基于char*指针而不是数组的解决方案(例如,您不知道字符串的最大长度),则可以通过以下方式实现(我的代码假设 int 的大小为 4 个字节,小端,bool 的大小 = 1 字节,字符大小 = 1 字节):

//_Static_assert(sizeof(int)==4u, "Int size has to be 4 bytes");
//the above one is C11, the one below is C++: 
//feel free to ifdef that if you need it
static_assert(sizeof(int)==4u, "Int size has to be 4 bytes");

struct node_status
{
  char* node_type;
  char* sub_type;          // set the type of incubator
  int sub_type_id;
  bool sleep;         // set to sleep
  int check_in_time;   // set check in time
  bool LOCK;      // set if admin control is true/false
};  


size_t serialize_node_status(const struct node_status* st, char* buffer)
{                                        
    //this bases on the assumption buffer is large enough
    //and string pointers are not null
    size_t offset=0u;                    
    size_t l = 0;                        
                                         
    l = strlen(st->node_type)+1;         
    memcpy(buffer+offset, st->node_type, l); 
    offset += l;                         
                                         
    l = strlen(st->sub_type)+1;          
    memcpy(buffer+offset, st->sub_type, l); 
    offset += l;                         
                                         
    l = sizeof(st->sub_type_id);         
    memcpy(buffer+offset, &st->sub_type_id, l); 
    offset += l;                         
                                         
    l = sizeof(st->sleep);               
    memcpy(buffer+offset, &st->sleep, l);
    offset += l;                         
                                         
    l = sizeof(st->check_in_time);       
    memcpy(buffer+offset, &st->check_in_time, l);                                                                                                                            
    offset += l;                         
                                                                         
    l = sizeof(st->LOCK);                
    memcpy(buffer+offset, &st->LOCK, l); 
    offset += l;                         
                                         
    return offset;    

// sending:
char buf[100] = {0}; //pick the needed size or allocate it dynamically
    
struct node_status nodeStatus = {"abcz", "x", 20, true, 999, false};
size_t serialized_bytes = serialize_node_status(&nodeStatus, buf);

mesh.write(buf, /*type*/ 126, serialized_bytes, /*to node*/ 000);                   

Side note: assigning string literals directly to char pointers is not valid C++.旁注:将字符串文字直接分配给 char 指针是无效的 C++。 So the string types either should be const char* , eg const char* node_type or the file should be compiled as C (where you can get away with it).所以字符串类型要么应该是const char* ,例如const char* node_type要么应该将文件编译为 C (在那里你可以摆脱它)。 Arduino often tends to have its own compilation options set, so it is likely to work due to compiler extension (or just inhibited warning). Arduino 通常倾向于设置自己的编译选项,因此很可能由于编译器扩展(或只是禁止警告)而工作。 Thus, not being sure what exactly is going to be used, I wrote a C11-compatible version.因此,不确定到底要使用什么,我写了一个 C11 兼容版本。

And then on Python's end:然后在 Python 的末尾:


INT_SIZE=4

class node_status:
    def __init__(self,
            nt: str,
            st: str,
            stid: int,
            sl: bool,
            cit: int,
            lck: bool):
        self.node_type = nt
        self.sub_type = st
        self.sub_type_id = stid
        self.sleep = sl
        self.check_in_time  = cit 
        self.LOCK = lck 
 
    def __str__(self):
        s=f'node_type={self.node_type} sub_type={self.sub_type}'
        s+=f' sub_type_id={self.sub_type_id} sleep={self.sleep}'
        s+=f' check_in_time={self.check_in_time} LOCK={self.LOCK}'
        return s;
 
    @classmethod 
    def from_bytes(cls, b: bytes):
        offset = b.index(0x00)+1
        nt = str(b[:offset], 'utf-8')
        b=b[offset:]
        offset = b.index(0x00)+1
        st = str(b[:offset], 'utf-8')
        b=b[offset:]
 
        stid = int.from_bytes(b[:INT_SIZE], 'little')
        b = b[INT_SIZE:]
 
        sl = bool(b[0])
        b = b[1:]
 
        cit = int.from_bytes(b[:INT_SIZE], 'little')
        b = b[INT_SIZE:]
 
        lck = bool(b[0])
        b = b[1:]
 
        assert(len(b) == 0)
 
        return cls(nt, st, stid, sl, cit, lck)             


#and the deserialization goes like this:
    fromMesh1 = bytes([0x61,0x62,0x63,0x0,0x78,0x79,0x7A,0x0,0x14,0x0,0x0,0x0,0x1,0xE7,0x3,0x0,0x0,0x1])
        
    fromMesh2 = bytes([0x61,0x62,0x63,0x0,0x78,0x79,0x7A,0x0,0x14,0x0,0x0,0x0,0x1,0xE7,0x3,0x0,0x0,0x0])
    fromMesh3 = bytes([0x61,0x62,0x63,0x7A,0x0,0x78,0x0,0x14,0x0,0x0,0x0,0x1,0xE7,0x3,0x0,0x0,0x0])
    print(node_status.from_bytes(fromMesh1))
    print(node_status.from_bytes(fromMesh2))
    print(node_status.from_bytes(fromMesh3))                                                                                                                  

These are all good answers but not what was required.这些都是很好的答案,但不是必需的。 I suppose a more in depth knowledge of the RF24Mesh library was needed.我想需要对 RF24Mesh 库有更深入的了解。 I have been able to find the answer with the help of some RF24 pro's.在一些 RF24 专业人士的帮助下,我已经能够找到答案。 Here is my solution:这是我的解决方案:

I had to change the struct to specific sizes using char name[10] on the C++ arduino side.我不得不在 C++ arduino 端使用char name[10]将结构更改为特定大小。

struct node_status
{
  char node_type[10] = "incubator";
  char sub_type[10] = "chicken";   // set the type of incubator
  int sub_type_id = 1;
  bool sleep = false;         // set to sleep
  int check_in_time = 1000;   // set check in time
  bool LOCK = false;      // set if admin control is true/false
} nodeStatus;

Unfortunately, it looks like read() returns the payload with a length of what you passed to the read() function.不幸的是,看起来read()返回的有效负载的长度与您传递给read()函数的长度相同。 This is unintuitive and should be improved.这是不直观的,应该改进。 Not to mention, the parameter specifying the length of the payload to return should be optional.更不用说,指定要返回的有效负载长度的参数应该是可选的。

Until they get a fix for this, I will have to slice the payload to only the length that struct.pack() needs (which can be determined based on the format specifier string).在他们解决这个问题之前,我将不得不将有效负载切成struct.pack()需要的长度(可以根据格式说明符字符串确定)。 So, basically所以,基本上

# get the max sized payload despite what was actually received
head, payload = network.read(144)

# unpack 30 bytes
(
    node_type,
    sub_type,
    sub_type_id,
    sleep,
    check_in_time,
    LOCK,
) = struct.unpack("<10s10si?i?", payload[:30])

I finally got it to work using this method.我终于使用这种方法让它工作了。 I want to be fair about giving the points and would like to have your opinion on who should get them that was closest to this method.我想公平地给出分数,并希望您对谁应该获得最接近这种方法的分数提出意见。 Please comment below.请在下方评论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM