Skip to main content

Give structure to hexadecimal messages.

Project description

Build Status codecov Interrogate CodeFactor Code style: black

PyMessageLib

A simple Python library for constructing/dissecting hexadecimal, decimal, octal, or binary data in a meaningful way. "Message" is in the name because this package was designed for communicating with devices by sending hexadecimal values that have very specific meaning.

Introduction

This project defines a system for extracting/packaging information into custom message formats. It's intended to be a flexible framework that bridges the gap between computer-talk and human-talk. For example, if I send a message of 0x8472FEF7ABC94838925146DEA to a device, and I'm expecting a response of either 0x00023000 or 0x89000000 - that's cool, but speaking in hexidecimal sucks! It would be so much better if I could describe the message formats in human-readable terms and generate the hex codes automatically.

Alternative Packages

The struct package is already heavily used for this, but lacks the following features:

  • Auto-updating fields
  • Nested field contexts

Our Solution at a High Level

PyMessageLib dynamically creates functional classes based on user-defined message formats. The generated classes allow us to work with messages in a human-readable, modular form. When instantiated, message objects are made up of fields that were specified in the original message format definitions.

Quick Start Guide

Installation

To install, simply use this command:

pip install pymessagelib

Message Definitions

At the highest level, we have a set of Message Definitions. A set of message definitions is represented as a Python dictionary where each key is the message name and the corresponding value is a dictionary mapping field names to field format definitions. Each message definition specifies a single level in the message hierarchy. Later, we'll get into nesting messages, but at the message definition level, keep it simple.

For example, suppose the unit I'm communicating with supports a command called GET_ADDR with the following fields:

  • A 2-byte field with constant value x0014 called "id"
  • A 4-byte field of any value called "ptr"
  • An 11-bit field of any value called "addr" where the first 3 bytes specify a device and the last byte specifies a location.
  • 3 bits of padding (b000) called "pad"
  • A CRC that is calculated over the ptr, addr, and pad fields.

We would start out by creating a message definition that describes the top-level of complexity and then we can define more messages to add more meaning to specific fields. In this case, the message definitions would look like:

>>> msg_fmts = {
... 	"GET_ADDR": {
... 		"id": Nibbles(4, value="0014"),
... 		"ptr": Bytes(4),
... 		"addr": Bits(11),
... 		"pad": Bits(3, value="000"),
... 		"crc": lambda ptr, addr, pad: EKMS32Bit(ptr, addr, pad)
... 	},
... 	"ADDR_FORMAT": {
... 		"device": Bits(3),
... 		"location": Byte(),
... 	}
... }

Field Definitions

When mapping field names to field format definitions, the following classes are available:

  • Bit
  • Bits
  • Nibble
  • Nibbles
  • Byte
  • Bytes
  • Word
  • Words
  • DWord
  • DWords
  • QWord
  • QWords

You can use any of the classes above that result in the exact number of bits being defined, making use of the first arugment which specifies length in units. For example, if I have a field that's 32 bits long, I could specify it as:

  • Bits(32)
  • Nibbles(8)
  • Bytes(4)
  • Words(2)
  • DWord()

Note that DWords(1) is invalid as is Word(2) because the singular or plural variant would better suit the situation.

When specifying a field value, the following rules apply:

  1. If a string value is given for a specific field in the message definition, that field will be read-only and cannot be changed. The string must conform to the value-specifier form.
  2. If no value is specified, the field will be writable.
  3. If the value is set to be a function, the field is considered an "auto-update" field. The argument names of the function must match field names of the same message and the function must be able to process Field objects.

Generating Message Classes

Highly-functional Message classes can be generated from the message definitions using a MessageBuilder. A MessageBuilder stores a set of message definitions and provides the following functionality:

  • Generates Message subclasses from message definitions
  • Constructs Message objects from raw data

To use the message builder, at least one set of message definitions must be provided. More sets of message definitions can be loaded after instantiation.

>>> builder = MessageBuilder(definitions=msg_fmts)
>>> builder.load_definitions(more_fmts)

Once message definitions are loaded, Message subclasses corresponding to the message definitions can be accessed using the dot-notation and the message names. To instantiate the message objects, values for all writable fields must be provided.

>>> get_addr = builder.GET_ADDR(ptr="x0012", addr='x11')

Once a message object has been instantiated, fields can be accessed and nested meaning can be given by setting the context of a specific field:

>>> get_addr.addr
'b00000010010'
>>> get_addr.addr.context = builder.ADDR_FORMAT # allows us to access further levels of information
>>> get_addr.addr.device
'b000'
>>> get_addr.addr.location
'b00010010'

Fields and messages can be rendered in different formats:

>>> get_addr.addr.render(fmt=Field.Format.Hex)
'x012'
>>> get_addr.addr.render(fmt=Field.Format.Dec)
'd018'
>>> get_addr.addr.render(fmt=Field.Format.Oct)
'o0022'
>>> get_addr.addr.render(fmt=Field.Format.Bin)
'b0000010010'

Writable fields can be set easily:

>>> get_addr.addr = 'b11100000001'
>>> get_addr.addr.devie = 'b001'

Messages can be rendered as tables. They can also be compared as tables using the compare_tables() function

>>> get_addr.addr.render_table(expand_nested=True)[1]
+GET_ADDR-------+-----------+
| Field Name    | Value     |
+---------------+-----------+
| id            | x0014     |
| ptr           | x00000012 |
| addr.device   | b000      |
| addr.location | x12       |
| pad           | b000      |
| crc           | xe45129da |
+---------------+-----------+

Value Specifier Form

This library is intended to work well with string values that follow a specific form which we call "Value Specifier Form". This form has 2 parts:

  1. A format specifier. Valid format specifiers are:
    • x for hexadecimal
    • d for decimal
    • o for octal
    • b for binary
  2. A value. The value must contain only characters that are valid according to the format specifier. For hexadecimal values, the characters can be upper or lower case.

Here are some strings that conform to value-specifier form:

  • "x00FF"
  • "d908984"
  • "b00100101011"
  • "o07154"

Here are some strings that do not conform:

  • "x 00FF" - ' ' is not a valid character
  • "x_00FF" - '_' is not a valid character
  • "bFF" - 'FF' isn't valid binary
  • "o99" - '99' isn't valid octal form
  • "99" - missing format specifier

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymessagelib-0.2.7.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymessagelib-0.2.7-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

File details

Details for the file pymessagelib-0.2.7.tar.gz.

File metadata

  • Download URL: pymessagelib-0.2.7.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.7.3 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.9.2

File hashes

Hashes for pymessagelib-0.2.7.tar.gz
Algorithm Hash digest
SHA256 4fe5a30d86655c4860fe7c3295f61d1ebb9a993ff4eb936f53af271854e9616e
MD5 e88c133dc2dca33585f29eabe23d8218
BLAKE2b-256 4d9a3ee8ba00b9b08f7122b187ddfbd801e3dcfc47d11fb1f081969276f2e7ab

See more details on using hashes here.

File details

Details for the file pymessagelib-0.2.7-py3-none-any.whl.

File metadata

  • Download URL: pymessagelib-0.2.7-py3-none-any.whl
  • Upload date:
  • Size: 18.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.7.3 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.9.2

File hashes

Hashes for pymessagelib-0.2.7-py3-none-any.whl
Algorithm Hash digest
SHA256 f17d80175ef9291860dc7d9b9e7718bc840c64a6658e7ffbd54424c3f4d7000e
MD5 e836f8433a83361dd1f9ba3a8b4188a1
BLAKE2b-256 2b667a7b5063efb5dcfd3f700977bfdacfc416743d5d86e9a30c0e9801297bf2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page