Package: pyembc
pyembcDeclarative library for for describing embedded C data types in pythonMotivationpyembcis a wrapper above thectypeslibrary providing a more simple and declarative syntax,
what I find more easy to use.The motivation behind creating this library is to be able to write down a c structure/union very
similarly as its written down in a c file, and then to be able to get the memory contents of
such a data structure, that can be transferred to an embedded device with a direct memory
access method (like XCP) and in the other way as well, and also to be able to generate the actual
c code that describes the same data structure, from the python description.I have used several similar libraries, such asconstruct,structures, or even Protocol Buffers, however one was too slow,
the other could not generate code, the third is waaay too big, and actually what I needed was very
very limited, and that's exactly the scope of this library. Also, it was fun to write, and beware,
I useexecunder the hood as well! Yes, I know.ExamplesDeclaring structures / unionsstructures and unions can be declared in a similar way as withdataclasses:frompyembcimportpyembc_struct,pyembc_unionfromctypesimport*@pyembc_structclassInner:a:c_uint8b:c_uint8@pyembc_structclassOuter:first:Innersecond:c_uint8third:c_uint8@pyembc_unionclassMyUnion:as_struct:Outeras_int:c_uint32InstantiatingThe instances of the above declared classes can be created as shown below.# empty constructorinner=Inner()print(inner)>>>Inner(a:u8=0x0,b:u8=0x0)# constructor with default valuesinner=Inner(a=1,b=2)print(inner)>>>Inner(a:u8=0x1,b:u8=0x2)# value checkinginner=Inner(a=256,b=300)>>>ValueError:256cannotbesetforc_ubyte(error('ubyte format requires 0 <= number <= 255'))!# embedded structuresouter=Outer()>>>Outer(first=Inner(a:u8=0x0,b:u8=0x0),second:u8=0x0,third:u8=0x0)outer=Outer(Inner(42,43),1,2)>>>Outer(first=Inner(a:u8=0x2A,b:u8=0x2B),second:u8=0x1,third:u8=0x2)# creating a union instancemy_union=MyUnion()>>>MyUnion(as_struct=Outer(first=Inner(a:u8=0x0,b:u8=0x0),second:u8=0x0,third:u8=0x0),as_int:u32=0x0)# with defaultsmy_union=MyUnion(as_struct=Outer(Inner(1,2),3,4))>>>MyUnion(as_struct=Outer(first=Inner(a:u8=0x1,b:u8=0x2),second:u8=0x3,third:u8=0x4),as_int:u32=0x4030201)Setting field/member valuesValue setting is protected for fields:outer.second=0x1234>>>ValueError:4660cannotbesetforc_ubyte(error('ubyte format requires 0 <= number <= 255'))!Parsing from binary dataouter=Outer(first=Inner(a=1,b=2),second=3)print(outer)>>>Outer(first=Inner(a:u8=0x1,b:u8=0x2),second:u8=0x3)outer.parse(b'\x11\x22\x33')print(outer)>>>Outer(first=Inner(a:u8=0x11,b:u8=0x22),second:u8=0x33)Packing of structuresThe structures are by default packed to 4 bytes. This means, that empty fill bytes are added
between struct members to align them to the 4 byte boundaries.
This packing can be modified by the user.@pyembc_structclassInner:a:c_uint8@pyembc_structclassOuter:first:Innersecond:c_uint32outer=Outer(first=Inner(1),second=0xFFEEDDCC)outer.stream()>>>b'\x01\x00\x00\x00\xcc\xdd\xee\xff'If we define the outer structure as below, the fill bytes will disappear@pyembc_struct(pack=1)classOuter:first:Innersecond:c_uint32outer=Outer(first=Inner(1),second=0xFFEEDDCC)outer.stream()>>>b'\x01\xcc\xdd\xee\xff'Note: this is true for the parse() method as well!EndiannessThe default endianness / byteorder is the one of the system's. (sys.byteorder).
However, it can be adjusted in the decorators.@pyembc_struct(endian="little")classLittle:a:c_uint16little=Little(a=0xFF00)little.stream()>>>b'\x00\xff'@pyembc_struct(endian="big")classBig:a:c_uint16big=Big(a=0xFF00)big.stream()>>>b'\xff\x00'However, for now, unions only support native byteorder, as the ctypes module does not
implement the appropriate BigEndianUnion and LittleEndianUnion types.
See details:https://stackoverflow.com/questions/49524952/bigendianunion-is-not-part-of-pythons-ctypeshttps://bugs.python.org/issue33178BitfieldsBitfields can be defined with the following syntax:@pyembc_structclassS:a:(c_uint8,2)b:(c_uint8,6)s=S()len(s)>>>1print(s)>>>S(a:u8@2=0x0,b:u8@6=0x0)s.parse(b'\xAA')print(s)>>>S(a:u8@2=0x2,b:u8@6=0x2A)The parsing and streaming works for them just like for normal structures.Bitfield definition orderNote, that just as in c, the definition order of the bitfields in one byte
depends on the byteorder of the containing structure.This means, that for a little-endian structure, the bitfields inside a byte shall be
defined from top-down as LSB to MSB, however, for big-endian structures, the top-down
order means MSB to LSB. See the example below, these two bitfield structures describe
the same thing, note the change in the order of the bitfields!@pyembc_struct(endian="little")classBF_LE:# byte 0a:(c_uint8,3)# LSBb:(c_uint8,5)# MSB# byte 1c:c_uint8@pyembc_struct(endian="big")classBF_BE:# byte 0b:(c_uint8,5)# MSBa:(c_uint8,3)# LSB# byte 1c:c_uint8Generating c codeThe ANSI c representation of a structure/union can be created from the class itself
or from its instance. Theccode()static method returns a list of lines.print('\n'.join(Outer.ccode()))# orprint('\n'.join(outer.ccode()))# orouter.print_ccode()typedefstruct_tag_Inner{unsignedchara;unsignedcharb;}Inner;typedefstruct_tag_Outer{Innerfirst;unsignedcharsecond;}Outer;Generating c code for bitfieldsBF_LE.print_ccode()typedefstruct_tag_BF_LE{unsignedchara:3;unsignedcharb:5;unsignedcharc;}BF_LE;