midstar / pycstruct

A python library for reading and writing binary data similar to what is done in C language structs
MIT License
22 stars 4 forks source link

Question - StructDef #35

Open hecko opened 3 years ago

hecko commented 3 years ago

I can see that I can get get_field_type of a specific field, however is there a method available to list the available fields?

Im trying to create a dynamic documentation tool to document C header file with struct defs, but im struggling with the use of this module. Am I missing something - is the pycstruct really not suitable for this kind of thing?

Marcel

midstar commented 3 years ago

Hi Marcel,

StructDef class has the method _elementnames() that can be used to get all field names. It is a hidden method (starts with ), but it can be used. However the method might be removed or changed in future releases without this is indicated by the pycstruct version number.

pycstruct was not intended for documenting C header files but it could be used for it. Also, checkout following projects that might suit you needs even more:

Best regards, Joel

hecko commented 3 years ago

Well, I gave it a go with pycstruct anyways and yeah - the pasring is slightly inconsistent in output:

For example:

#define MEASUREMENT_BUFFER_SIZE 5

typedef struct __attribute__((packed)) {
  uint8_t packet_type : 4;       /**< \sa MEASUREMENT_PACKET_TYPES */
  uint8_t packet_version : 4;    /**< packet version \sa PACKET_VERSIONS */
  uint8_t measurement_type : 4;  /**< \sa MEASUREMENT_TYPES */
  uint8_t measurement_unit : 4;  /**< \sa MEASUREMENT_UNIT_TYPES */
  uint16_t location_id;          /**< Location ID */
  uint32_t sensor_id;            /**< Sensor ID */
  uint8_t manufacturer_id;       /**< Manufacturer ID */
  uint16_t measurement_interval; /**< Measurement interval in seconds. */
  uint16_t measurement_amount;   /**< Number of measurements in this packet */
  uint8_t packet_number;         /**< Packets of this type sent since boot */
  float sensor_measurement_range_min;
  float sensor_measurement_range_max;
  Measurement measurements[MEASUREMENT_BUFFER_SIZE]; /**< Actual measurements */
} AnalogPacket;

/** Actual analog reading / measurement from sensor */
typedef struct __attribute__((packed)) {
  uint16_t reading : 12;     /**< Measured range expressed as 0-4095 range value. */
  uint16_t overflowed : 1;   /**< Set to 1 if the measurement is more or less than min or max value for the
                                sensor. */
  uint16_t sensor_error : 1; /**< Set to 1 if sensor has error detected. */
  uint16_t reserved_3 : 1;   /**< Reserved - unused */
  uint16_t reserved_4 : 1;   /**< Reserved - unused */
} Measurement;

Is parsed as:

Field name: packet_type
<class 'NoneType'>
---
Field name: packet_version
<class 'NoneType'>
---
Field name: measurement_type
<class 'NoneType'>
---
Field name: measurement_unit
<class 'NoneType'>
---
Field name: location_id
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
uint16
Field size: 2
---
Field name: sensor_id
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
uint32
Field size: 4
---
Field name: manufacturer_id
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
uint8
Field size: 1
---
Field name: measurement_interval
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
uint16
Field size: 2
---
Field name: measurement_amount
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
uint16
Field size: 2
---
Field name: packet_number
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
uint8
Field size: 1
---
Field name: sensor_measurement_range_min
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
float32
Field size: 4
---
Field name: sensor_measurement_range_max
<class 'pycstruct.pycstruct.BasicTypeDef'>
Field type:
float32
Field size: 4
---
Field name: measurements
<class 'pycstruct.pycstruct.ArrayDef'>
Field type:
Name                          Bits      Offset    Signed
reading                       12        0         -
overflowed                    1         12        -
sensor_error                  1         13        -
reserved_3                    1         14        -
reserved_4                    1         15        -
Field size: 10
---

It kind of makes sense (the last "Field size" is probably 10, because MEASUREMENT_BUFFER_SIZE = 5), but the bitfields are interpreted as None types. On the other hand I really like the output of the Field type for the 'measurements' substruct - we can actually see the size of the field in bits - something like this output would be great for packet_type, packet_version etc fields.

midstar commented 3 years ago

The mix of "bitfield elements" and "other elements" in a struct is sort of hacked into pycstruct. Originally a struct was either without any bitfield members (StructDef) or only bitfield members (BitfieldDef). To solve this issue the pycparser checks which elements next to each other which are bitfields and create a BitfieldDef which is set added with same_level = True in the StructDef, i.e. the "subtype" BitfieldDef members are moved up to the parent StructDef so that it looks like all members are on the same level.

In you example above packet_type, packet_version, measurement_type and measurement_unit will together form a BitfieldDef. Which is added to the AnalogPacket struct def.

If you do this:

types = pycstruct.parse_str("your source code")
AnalogPacket = types["AnalogPacket"]
print(AnalogPacket) 

You will get output:

Name                          Type           Size      Length    Offset    Largest type
__auto_bitfield_0             bitfield       2                   0         2         
location_id                   uint16         2                   2         2         
sensor_id                     uint32         4                   4         4         
manufacturer_id               uint8          1                   8         1         
measurement_interval          uint16         2                   9         2         
measurement_amount            uint16         2                   11        2         
packet_number                 uint8          1                   13        1         
sensor_measurement_range_min  float32        4                   14        4         
sensor_measurement_range_max  float32        4                   18        4         
measurements                  bitfield       2         5         22        2 

As you see above your bitfield members will be part of __auto_bitfield_0.

You can print that one:

print(AnalogPacket._element_type("__auto_bitfield_0"))

Which will result in:

Name                          Bits      Offset    Signed    
packet_type                   4         0         -         
packet_version                4         4         -         
measurement_type              4         8         -         
measurement_unit              4         12        -