evomin is a device and platform independent communication protocol with automatic checksum calculation and verification (CRC). The payload is wrapped into a specific protocol to ensure a valid transmission and reception.
Evomin is an abstract class that needs to be implemented by the user, depending on your application.
Therefor, override the class:
from evomin.evomin import Evomin
class EvominImpl(Evomin):
"""
Concrete application side implementation
"""
def reply_received(self, reply_payload: bytes) -> None:
print('** Reply received, {ps} bytes **'.format(ps=len(reply_payload)))
for b in reply_payload:
print(b)
def frame_received(self, frame: EvominFrame) -> None:
print('** Frame received **')
self.log_debug('[Evomin] EvominFrame received, Command: {}, Payload length: {}'.format(frame.command, frame.payload_length))
print('Payload: ')
for b in frame.get_payload():
print(b)
# replying happens here using reply(..)The two abstract methods reply_received and frame_received are callbacks that get automatically called by the
protocol whenever there's valid data available.
Gets called when an attached slave has replied. Find the replied payload in payload_bytes.
Note: This is only happening in a master-slave setup, i.e. when using SPI as the low-level transportation layer.
Gets called when a slave received a frame until the CRC byte from the master in a master-slave setup OR any participant
received a complete frame at the end of the EOF byte.
This callback gets called at different states depending on whether the setup is master-slave. If in master-slave setup, the callback is executed right before the reception of the CRC byte, to allow the slave to prepare the answer (i.e. reading and pushing a sensor value).
Before initialization, you need to provide an implementation of EvominComInterface. This class acts as the low-level wrapper
and is independent to the underlying transportation layer, i.e. UART, SPI, I2C etc.
It features two methods for transportation (send_byte() and receive_byte()) as well as a method called describe() which
is used to provide information about the communication layer through the ComDescription tuple.
Currently the only valid option for ComDescription is is_master_slave that can either be True or False.
For master-slave communication like SPI, choose True, else False (i.e. UART).
Gets called whenever evomin wants to send a byte over the low-level layer. For master-slave communication, it is possible
to return a byte from the send_byte() method in order to allow the slave device to reply to a master byte. For a mocked SPI
implementation, refer to EvominFakeSPIInterface in com_fake.py.
Gets called by the internal state machine. You need to implement your low-level receiving procedure here in order to
receive bytes. For UART this could be reading the DR register of your UART device.
Note: The
receive_byte()method is only valid for a non master-slave setup! In a master-slave setup, the received bytes must be read within thesend_byte()method as the slave cannot transmit any bytes without a corresponding master byte.
As we now have implemented both abstract classes, we can now initialize a Evomin instance.
Initialize an instance of the inherited Evomin interface:
# Initialize evomin communication interface with SPI transport
evomin = EvominImpl(com_interface=EvominFakeSPIInterface())You can define your own application dependent commands within the EvominFrameCommandType enumeration, to be found in frame.py.
By default, there are two commands available:
class EvominFrameCommandType(Enum):
"""
EvominFrameCommandType defines all protocol internal command types.
These can be extended, but sometimes it's better to use a predefined command and add a payload, than extending
the command list for every new operation.
RESERVED: Not used
SEND_IDN: Used for defining a self identification frame
"""
RESERVED = 0x00
SEND_IDN = 0xCDFeel free to add commands, i.e. LIGHT_ON = 0xA0.
Note: The highest enumeration value must not exceed 255 (0xFF)! This ensures compatibility with the C-API.
All data is sent within a package of bytes, called EvominFrame. A EvominFrame consists of the following bytes:
3 start bytes (SOF)
-------------------
0xAA
0xAA
0xAA
1 command type byte, i.e. SEND_IDN = 0xCD
-----------------------------------------
0xCD
1 payload length byte, i.e. 2 bytes
-----------------------------------
0x2
n payload bytes
---------------
0x00
..
0x05
1 CRC8 checksum byte
--------------------
..
2 end of frame bytes (EOF)
--------------------------
0x55
0x55
Note that this is a minimum frame, there's also a feature called automatic stuff byte insertion, which inserts additional bytes
after two consequent 0xAA bytes in the payload to ensure the state machine won't mistake three 0xAAs as the beginning
of a new frame. Those stuffed bytes are automatically removed by the protocol on reception and are invisible to the user.
To send a frame, call evomin.send() and provide the desired command type and the payload as
a bytes array, i.e. evomin.send(EvominFrameCommandType.SEND_IDN, bytes([0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xFF])).
There's a single method called poll() which needs to be called wherever your application's logic lives, i.e. in your main() loop
or in a thread etc.
The poll() method performs both, receiving and sending of bytes and frames.
Simple example:
# Polling interface
while True:
evomin.poll()
sleep(1)To reply directly to master's message, i.e. to reply to a READ_SENSOR message, use the reply() method.
This method call needs to be placed inside the frame_received() method inside your concrete implementation of the Evomin class.
def frame_received(self, frame: EvominFrame) -> None:
print('** Frame received **')
# Reply with two bytes 0x1 and 0x2
self.reply(bytes([0x01, 0x02]))The reply bytes are then transferred to an internal buffer and sent on the next master bytes.
Below is a complete frame with comments to show how evomin performs on a single transfer of 8 bytes from the master and a 4 bytes length reply from the attached slave device.
Note: This excerpt has been simulated using the
EvominFakeSPIInterface.
-> Send byte: 170 Master -> 0xAA SOF
Received response byte in: 0 Slave <- dummy byte
-> Send byte: 170 Master -> 0xAA SOF
Received response byte in: 1 Slave <- dummy byte
-> Send byte: 170 Master -> 0xAA SOF
Received response byte in: 2 Slave <- dummy byte
-> Send byte: 205 Master -> Command type (CMD_IDN)
Received response byte in: 3 Slave <- dummy byte
-> Send byte: 8 Master -> Payload length (8 bytes)
Received response byte in: 4 Slave <- dummy byte
-> Send byte: 170 Master -> Payload byte #1 (0xAA)
Received response byte in: 5 Slave <- dummy byte
-> Send byte: 170 Master -> Payload byte #2 (0xAA)
Received response byte in: 6 Slave <- dummy byte
-> Send byte: 85 Master -> Automatically inserted stuff byte (not part of payload!)
Received response byte in: 7 Slave <- dummy byte
-> Send byte: 170 Master -> Payload byte #3 (0xAA)
Received response byte in: 8 ...
-> Send byte: 170 Master -> Payload byte #4 (0xAA)
Received response byte in: 9
-> Send byte: 85 Master -> Automatically inserted stuff byte (not part of payload!)
Received response byte in: 10
-> Send byte: 170 Master -> Payload byte #5 (0xAA)
Received response byte in: 11
-> Send byte: 170 Master -> Payload byte #6 (0xAA)
Received response byte in: 12
-> Send byte: 85 Master -> Automatically inserted stuff byte (not part of payload!)
Received response byte in: 13
-> Send byte: 187 Master -> Payload byte #7
Received response byte in: 14
-> Send byte: 255 Master -> Payload byte #8
Received response byte in: 15
-> Send byte: 159 Master -> CRC8 checksum (159 dec)
Received response byte in: 241 Slave <- dummy byte
-> Send byte: 85 Master -> EOF #1
Received response byte in: 255 Slave <- ACK (checksum correct)
-> Send byte: 85 Master -> EOF #2
Received response byte in: 4 Slave <- Number of slave`s answer bytes (reply)
-> Send byte: 240 Master -> Dummy byte to keep SPI communication
Received response byte in: 222 Slave <- Reply byte #1
-> Send byte: 240 ...
Received response byte in: 173 Slave <- Reply byte #2
-> Send byte: 240
Received response byte in: 190 Slave <- Reply byte #3
-> Send byte: 240
Received response byte in: 239 Slave <- Reply byte #4
** Reply received, 4 bytes **
222
173
190
239
-> Send byte: 255 Master -> Send ACK to finalize communication