0

I want to create a communication protocol between two microcontrollers, I use class logic that incorporates message types between systems. I want to develop a more simple process of creating new messages by doing something very simple like following

BEGIN_MSG(LinearDriveMsg)
  ADD_2BIT(DriveSpeed, speed)
  ADD_16BIT(GMath::Position, position.x)
END_MSG

Ideally it would expand to:

BEGIN_MSG(LinearDriveMsg)
  BEGIN_SERIALIZER
    ADD_SERIALIZER_2BIT(DriveSpeed, speed)
    ADD_SERIALIZER_16BIT(GMath::Position, position.x)
  END_SERIALIZER

  BEGIN_DESERIALIZER
    ADD_DESERIALIZER_2BIT(DriveSpeed, speed)
    ADD_DESERIALIZER_16BIT(GMath::Position, position.x)
  END_DESERIALIZER
END_MSG

And then expand to cpp code

...

bool LinearDriveMsg::deserialize(const uint8_t *incoming_buffer, const uint8_t *incoming_size) override{
        if (*incoming_size != getMessageSize())
            return false;

        _speed = (DriveSpeed)((incoming_buffer[0] & SPEED_MASK) >> SPEED_OFFSET);
        weldTwoBytesToInt(&incoming_buffer[1], _position.x);
        return true;
}

int LinearDriveMsg::serialize(uint8_t *outgoing_buffer, uint8_t *outgoing_size) const override{
    if (*outgoing_size < getMessageSize())
        return -1;

    outgoing_buffer[0] |= ((uint8_t)_speed << SPEED_OFFSET) & SPEED_MASK;
    cutIntToTwoBytes(_position.x, outgoing_buffer + 1, 2);

    return getMessageSize();
}

...

I know that doing some advanced preprocessor stuff is pretty tricky but maybe there some way to do this task? It is also possible to adjust the actual cpp code to make it possible (in that way that the efficiency is still ok)

ptiza_v_nebe
  • 31
  • 1
  • 5
  • I would better write code generator on something like python or lua rather than deal with preprocessor. Your task should be doable with templates though. – Slava Oct 02 '19 at 16:59

1 Answers1

0

I will answer it myself. There is actually "simple" way to solve above task in preprocessor. The boost library offers tools to work approximately in preprocessor like in normal languages. They have a package called preprocessor as part of the boost suite read the docs, you can download it here it is part of the main package.

Main feature that helped to solve the task above was the idea to forward a tuple of arrays to constructs that we know from functional programming. Actually it is BOOST_PP_SEQ_FOR_EACH(macro, data, seq). Another feature that helped to solve the task was this guys implementation to create double parentheses around each element.

As idea it will work like following. I have the above list of tuples:

#define frame_composition (begin_byte(0), begin_bit(0), int8_t, speed)\
    (begin_byte(1), begin_bit(4), int16_t, direction)\

I have somekind of sequence of actions, because I want to have serial first and deserial second:

#define COMPOSE_FRAME(seq) \
    ADD_SERIAL(seq) \
    ADD_DESERIAL(seq)

Now the content of each add category:

#define ADD_SERIAL(seq) \
    BOOST_PP_SEQ_FOR_EACH(ADD_SERIAL_ELEM, ~, GLK_PP_SEQ_DOUBLE_PARENS(seq))
#define ADD_DESERIAL(seq) \
    BOOST_PP_SEQ_FOR_EACH(ADD_DESERIAL_ELEM, ~, GLK_PP_SEQ_DOUBLE_PARENS(seq)) \

The cool thing is, we have forwarded the same list of tuples to different constructs, to execute some different algorithms. Now the contents of X_ELEM defines:

#define ADD_SERIAL_ELEM(seq) \
    outgoing_buffer[BOOST_PP_TUPLE_ELEM( 0, elem)] = (uint8_t) BOOST_PP_TUPLE_ELEM( 3, elem); 
#define ADD_DESERIAL_ELEM(seq) \
    BOOST_PP_TUPLE_ELEM(3,elem) = (BOOST_PP_TUPLE_ELEM(2,elem)) incoming_buffer[BOOST_PP_TUPLE_ELEM(0,elem)];

And if you need you can distinguish between different use cases in element "functions" with

BOOST_PP_IF(cond, t, f)

To do different things for example on object and simple variables

This is noway complete solution, but to see the idea and that it is possible to create highly portable simple message generator framework out of cpp.

ptiza_v_nebe
  • 31
  • 1
  • 5