繁体   English   中英

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

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

我正在尝试将 C++ 结构从我的 arduino 传递到我的树莓派。 我有一个看起来像这样的结构:

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;

我尝试使用名为 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])

但我运气不佳。 我什至只考虑使用 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)

但这只是给了我一个错误

TypeError: bytes or integer address expected instead of bytearray instance

我能做什么? 我哪里出错了?

编辑:

我正在使用来自的 RF24Mesh 库

https://github.com/nRF24/RF24Mesh

我发送消息的方式是这样的?

  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;
  }

}

您的 C 程序只是发送结构,但该结构不包含任何字符串数据。 它只包括不能被任何其他进程(不同地址空间)使用的指针(地址)。

您需要确定一种发送所有必需数据的方法,这可能意味着发送每个字符串的长度及其数据。

一种方法是使用最大长度并将字符串存储在结构中:

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;

然后您需要将字符串复制到这些缓冲区中而不是分配它们,并检查缓冲区溢出。 如果用户曾经输入过这些字符串,就会有安全隐患。

另一种方法是在发送数据时将数据打包到一个块中。 您也可以使用多次写入,但我不知道这个网格库或您将如何设置type参数来做到这一点。 使用缓冲区类似于:

// 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;

既然数据实际上已发送(读取数据的先决条件),您需要的数据应该都在payload数组中。 您需要解压它,但不能只传递解压单个字节,它需要数组:

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
...

我可能是错的,但我认为您不需要使用 ctypes,只需直接使用您在 python 中收到的数据——它可能已经打包为一个元组。 我想将此添加为评论,但我没有足够的声誉

我赞成 Garr Godfrey 的回答,因为它确实很好。 但是,它会增加结构的大小。 这既不是好事也不是坏事,但是如果出于某种原因您想保留基于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);                   

旁注:将字符串文字直接分配给 char 指针是无效的 C++。 所以字符串类型要么应该是const char* ,例如const char* node_type要么应该将文件编译为 C (在那里你可以摆脱它)。 Arduino 通常倾向于设置自己的编译选项,因此很可能由于编译器扩展(或只是禁止警告)而工作。 因此,不确定到底要使用什么,我写了一个 C11 兼容版本。

然后在 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))                                                                                                                  

这些都是很好的答案,但不是必需的。 我想需要对 RF24Mesh 库有更深入的了解。 在一些 RF24 专业人士的帮助下,我已经能够找到答案。 这是我的解决方案:

我不得不在 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;

不幸的是,看起来read()返回的有效负载的长度与您传递给read()函数的长度相同。 这是不直观的,应该改进。 更不用说,指定要返回的有效负载长度的参数应该是可选的。

在他们解决这个问题之前,我将不得不将有效负载切成struct.pack()需要的长度(可以根据格式说明符字符串确定)。 所以,基本上

# 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])

我终于使用这种方法让它工作了。 我想公平地给出分数,并希望您对谁应该获得最接近这种方法的分数提出意见。 请在下方评论。

暂无
暂无

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

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