c0nb0x - Developers' info
------- under construction, hold on to your hat -------
Source Code (v1.01): c0nb0x_101_src.zip (118 KB)
The package includes a .cbp (Code::Blocks) project with windows and linux build targets.
allegro5.0.10 is required, and the project build settings may need adjustments to get the include/libs dirs right.
c0nb0x & x0xb0x Serial Protocol - Developers' info
To communicate with the x0xb0x, c0nb0x uses the same protocol which is used originally in c0ntr0l.
However, c0nb0x introduces some additional messages on top of that..
Everything between the app and x0x is sent in the form of packets:
[msgID] [size1] [size2] <data> [CRC]
msgID is the message ID
size1 and size2 are used together to form a uint16_t data_size for the data in the packet
data is a variable-sized chunk of bytes, if any
CRC is always the last byte, it is calculated by including the bytes from msgID all the way to the data section (if any)
The smallest possible packet (one which has no data) is 4 bytes long. The total packet size is thus 4+data_size.
While it's technically possible to have a very big packet (since the data_size is a uint16_t type) i would strongly recommend never use packets with more than 60 bytes of data, since the firmware accumulates the data for a packet into a buffer which is defined as 64 bytes long (see the definition of UART_BUFF_SIZE in compcontrol.c in the firmware).
In order to not break compatibility with the stock firmware - keep the size1 byte zero always, and don't put more than 60 bytes into one packet (4+60=64).
This means that we're stuck to having a useless byte in all packets, sadly it cannot be changed, since it will break compatibility with the existing x0x OSes.
It's not possible to even use this byte for other purposes (like to extend it as msgID2 or something else) since the firmware does use it to calculate the size and any nonzero value in that byte will be interpreted as a very big packet.
Messages
msgID | DESCRIPTION | USED | Direction x0x<->APP |
||
---|---|---|---|---|---|
NAME | DEC | HEX | |||
MSG_UNUSED | 0 | 00 | ? | ? | |
MSG_PING | 1 | 01 | Ping pong? | Y | <-> |
MSG_PATT_WR | 16 | 10 | App sends a pattern to the x0x | Y | <- |
MSG_PATT_RD | 17 | 11 | App requests a pattern from the x0x | Y | <- |
MSG_PATT_LOAD | 18 | 12 | App wants x0x to select pattern X? | - | <- |
MSG_PATT_GET | 19 | 13 | App wants to know the pattern slot? | - | <- |
MSG_PATT_PLAY | 20 | 14 | - | <- | |
MSG_PATT_STOP | 21 | 15 | - | <- | |
MSG_PATT | 25 | 19 | x0x sends a pattern to the App | Y | -> |
MSG_TRACK_WR | 32 | 20 | App sends a track to the x0x? | - | <- |
MSG_TRACK_RD | 33 | 21 | App requests a track from the x0x? | - | <- |
MSG_TRACK_LOAD | 34 | 22 | App wants x0x to select track X? | - | <- |
MSG_TRACK_GET | 35 | 23 | App wants to know the track slot? | - | <- |
MSG_TRACK | 41 | 29 | x0x sends a track to the App? | - | -> |
MSG_SEQ_START | 48 | 30 | App wants to press RUN? | - | <- |
MSG_SEQ_STOP | 49 | 31 | App wants to press STOP? | - | <- |
MSG_SEQ_GET | 50 | 32 | App wants to know if running or not? | - | <- |
MSG_SYNC_SET | 51 | 33 | App wants to change sync mode? | - | <- |
MSG_TEMPO_GET | 64 | 40 | App wants to know the BPM | Y | <- |
MSG_TEMPO_SET | 65 | 41 | App wants to change the BPM | Y | <- |
MSG_TEMPO | 66 | 42 | x0x sends BPM to the App | Y | -> |
MSG_STATUS | 128 | 80 | Status (OK / BAD) | Y | <-> |
EXTENDED MESSAGES | |||||
MSG_FW_VER | 129 | 81 | Firmware Version (OSID) | <-> | |
MSG_ISSUPP | 130 | 82 | Is Supported? | <-> | |
MSG_DIAG | 131 | 83 | DiagOS special message | <-> | |
MSG_TEXTOUT | 132 | 84 | Text Output to App | -> | |
MSG_GET_MEM | 133 | 85 | Get variable-sized chunk from the EEPROM | <-> | |
MSG_SET_MEM | 134 | 86 | Send variable-sized chunk to the EEPROM | <- | |
MSG_PATBUF | 135 | 87 | Send/Get pattern(s) from the pattern buffer | <- | |
MSG_GET_INFO | 136 | 88 | Get info | <-> | |
MSG_GET_PARAM | 137 | 89 | Get parameter | <-> | |
MSG_SET_PARAM | 138 | 8A | Set parameter | <- |
The dark-gray messages (marked with a dash under USED) have only been #defined in the protocol (either in the stock firmware or c0ntr0l) but haven't been implemented ever.
I've attempted to guess their meaning under description based on their names..
c0nb0x implements Extended messages with values above 128.
Note that those which are bi-directional may have different packet structure/content depending on the direction (see MSG_FW_VER as an example of this).
MSG_FW_VER
MSG_ISSUP
MSG_GET_MEM
MSG_SET_MEM
MSG_PATBUF
MSG_TEXTOUT
Parameters
MSG_GET_PARAM
MSG_SET_PARAM
On Connect...
MSG_FW_VER
c0nb0x introduces the MSG_FW_VER message, to make it possible for each firmware to be identified with it's unique ID and name (string).
The ID can be used in the app to have custom features or handle different pattern formats.
App-to-x0x:
[MSG_FW_VER] [0] [0] [CRC]
You can see this is just an empty packet (has no data).
x0x-to-App:
This is the reply which has the following data contents:
[osID] [VERSION] [osSTRING]
osID - uint16_t - unique ID of the firmware
VERSION - uint16_t - this value will be divided by 100 and printed as a fixed-point number in c0nb0x (for example, 123 -> "1.23")
osSTRING - variable sized c-string with the name of the Firmware which will be printed in c0nb0x. This must be at least 1 char, but not more than 32 chars, not null-terminated.
So the data_size of this message is then 2+2+length_of_osSTRING
Currently, c0nb0x has definitions for the following FW unique IDs:
OSID_UNKNOWN = 0, // unknown, adafruit v1.05 compatible
OSID_ADA105 = 1,
OSID_SOKKOS = 2,
OSID_N0NX0XBETA = 13,
OSID_DIAGFW = 101, // DiagOS
OSID_SOKKOS2561 = 20, // sokkos (cpumod)
OSID_N0NX0X = 23, // n0nx0x2 (cpumod)
If the firmware does not support the MSG_FW_VER (replies with STATUS_BAD message) - c0nb0x looks up the pattern size (by requesting MSG_PATT_RD with bank1, slot1).
- If the pattern size is 16 - c0nb0x assumes the firmware is OSID_ADA105
- If the pattern size is 21 - c0nb0x assumes the firmware is OSID_N0NX0XBETA
- Otherwise assumes OSID_UNKNOWN
In all these cases, that's "auto-detection" and thus c0nb0x prints "(auto)" in front of the Firmware name.
Only if the Firmware replies with MSG_FW_VER will c0nb0x print the actual osSTRING.
Implementing MSG_FW_VER reply in your firmware.
As an example, here's how to make the message packet for n0nx0x2 (osID=23):
case MSG_FW_VER:
{
// 2bytes 2bytes <variable>
// [ osID ] [ osVER ] [ os string ]
{
uint16_t* ptr = (uint16_t*)(tx_msg_buff+3);
*ptr = 23; // TODO: osID
++ptr;
*ptr = 200; // TODO: version
}
unsigned char* ptr = (tx_msg_buff+(3+4));
// os string should not be longer than 32 chars #define OSSTRING_LEN 6 // TODO: osSTRING_LENGTH
for (uint8_t i = 0; i < OSSTRING_LEN; i++)
{
ptr[i] = "n0nx0x"[i]; // TODO: osSTRING
}
uint8_t tmp = 2+2+OSSTRING_LEN; // data_size
tx_msg_buff[0] = MSG_FW_VER;
tx_msg_buff[1] = 0;
tx_msg_buff[2] = tmp;
tmp += 3;
tx_msg_buff[tmp] = calc_CRC8(tx_msg_buff, tmp);
++tmp;
send_msg(tx_msg_buff, tmp);
} break;
In the code above, you have to change 4 things (commented with TODO) and you're done.
MSG_TEXTOUT
MSG_TEXTOUT will be available in c0nb0x v1.01.
It's purpose is to let you "Monitor" or debug the x0x firmware.
The direction is only x0x->App and the message can contain a string, an integer, or binary data.
[MSG_TEXTOUT] [0] [size] <data> [CRC]
where the data contains:
[type] [rawdata]
type can be one of the following:
enum TEXTOUT_TYPE
{
TXTO_STR = 0, // null-terminated string
TXTO_UI8 = 1, // uint8_t
TXTO_SI8 = 2, // int8_t
TXTO_UI16 = 3, // uint16_t
TXTO_SI16 = 4, // int16_t
TXTO_UI32 = 5, // uint32_t
TXTO_SI32 = 6, // int32_t
TXTO_BIN = 7, // <binary data>
};
rawdata is the actual string of characters, or integer, or array of bytes of binary data.
c0nb0x will convert the integers to text (via ostringstream) and the binary data will be shown as HEX.
here are functions for textout:
#ifdef XMSG_TEXTOUT
void textout(char* s);
void textout_bin(uint8_t* data, uint8_t len);
void textout_ui8(uint8_t x);
void textout_ui16(uint16_t x);
void textout_ui32(uint32_t x);
void textout_si8(int8_t x);
void textout_si16(int16_t x);
void textout_si32(int32_t x);
#else
#define textout(x)
#define textout_bin(x,y)
#define textout_ui8(x)
#define textout_ui16(x)
#define textout_ui32(x)
#define textout_si8(x)
#define textout_si16(x)
#define textout_si32(x)
#endif//XMSG_TEXTOUT
#ifdef XMSG_TEXTOUT
void textout(char* s)
{
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_STR;
// get length of string
// don't pass long strings please!
uint8_t len = 0;
while (len < 255)
{
if (s[len] == 0x00) { break; }
++len;
}
// we'll slice the string if it's too big to fit into a single packet
uint8_t nump = len/48; // let's use 48 bytes at a time
if ((len-(nump*48))) { ++nump; } // round-up
uint8_t i = 0;
uint8_t i2 = 0;
uint8_t n = 0; while (n < nump)
{
// for each packet..
i = 0;
while (i < 48)
{
if (i2 >= len) { break; }
tx_msg_buff[4+i] = s[i2];
++i;
++i2;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
++n;
}
return;
}
void textout_bin(uint8_t* data, uint8_t len)
{
// binary!
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_BIN;
// we'll slice the data if it's too big to fit into a single packet
uint8_t nump = len/48; // let's use 48 bytes at a time
if ((len-(nump*48))) { ++nump; } // round-up
uint8_t i = 0;
uint8_t i2 = 0;
uint8_t n = 0;
while (n < nump)
{
// for each packet..
i = 0;
while (i < 48)
{
if (i2 >= len) { break; }
tx_msg_buff[4+i] = data[i2];
++i;
++i2;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
++n;
}
return;
}
void textout_ui8(uint8_t x)
{
uint8_t len = 1;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_UI8;
uint8_t i = 0;
while (i < len)
{
tx_msg_buff[4+i] = x;
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_ui16(uint16_t x)
{
uint8_t len = 2;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_UI16;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_ui32(uint32_t x)
{
uint8_t len = 4;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_UI32;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_si8(int8_t x)
{
uint8_t len = 1;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_SI8;
uint8_t i = 0;
while (i < len)
{
tx_msg_buff[4+i] = x;
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_si16(int16_t x)
{
uint8_t len = 2;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_SI16;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_si32(int32_t x)
{
uint8_t len = 4;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_SI32;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
#endif//XMSG_TEXTOUT
here's an example of how you can use it:
if (tempo_changed)
{
textout("Tempo changed to ");
textout_ui16(tempo);
textout(" bpm.\n");
}
MSG_ISSUP
This message will be used to query if some messages/features are supported by the firmware.
[MSG_ISSUP] [0] [2] <CODE> [CRC]
c0nb0x will usually send a few such messages on connect to see if the FW supports this and that.
CODE is a uint16_t, it's value is normally a msgID, but it is two-byte for possible future extension.
An example:
[MSG_ISSUP] [0] [2] [0, MSG_ISSUP] [CRC]
That's how c0nb0x will ask if the FW even supports the MSG_ISSUP itself.
The x0x will reply with:
[MSG_ISSUP] [0] [3] <CODE> <ANSWER> [CRC]
CODE must have the same value as the requested one.
ANSWER is 0 for "not supported" or 1 for "supported" (higher values may be used in some special cases).
Normally, c0nb0x will not query about the "normal" messages (those below 128) so they don't need to return a positive answer.
c0nb0x needs to know the pattern size, and on startup, requests MSG_PATT_RD just for that reason. From c0nb0x v1.01, another method will be used - MSG_ISSUP with MSG_PATT as an argument - the x0x should reply with PATT_SIZE as the answer, then c0nb0x will not request MSG_PATT_RD.
MSG_GET_MEM
Other than MSG_PATT_WR and MSG_PATT_RD, there is really no other way to get/set data from/to the EEPROM via the App.
So here it is.
App to x0x:
[MSG_GET_MEM] [0] [3] <ADDRESS> <LENGTH> [CRC]
ADDRESS is uint16_t, the address (or offset) in the EEPROM memory.
LENGTH is uint8_t, how many bytes does the App want to get.
Note: the App should NEVER try to request big amounts of data at once, the x0x should refuse such requests. Instead, the app should request multiple times using smaller packets.
If the x0x refuses to reply to a request - it should send a MSG_STATUS with STAT_BAD.
x0x to App:
[MSG_GET_MEM] [0] [size] <ADDRESS> <DATA> [CRC]
ADDRESS must be the same value as in the request.
DATA is the actual data.
size is the LENGTH of the data + 2.
MSG_SET_MEM
App to x0x:
[MSG_SET_MEM] [0] [size] <ADDRESS> <DATA> [CRC]
ADDRESS is uint16_t, the address (or offset) in the EEPROM memory.
DATA is the actual data to be written.
size is the LENGTH of the data + 2.
Note: the App should NOT send too much data at once, so the x0x is free to refuse to process such requests.
The x0x must reply with MSG_STATUS with either STAT_OK or STAT_BAD.
MSG_PATBUF
c0nb0x has a pattern editor, up till v1.00 - the editor uses MSG_PATT_RD and MSG_PATT_WR to load/save the pattern from the x0xb0x.
This means that the pattern was written to EEPROM everytime, especially with the "Auto L/S" option that's quite an abuse.
So, to end this abuse, i introduce the PATBUF message.
The idea is, instead of loading/saving the pattern you edit from/to the EEPROM directly - this can be done from the pattern buffer (which is in RAM).
The downsides are:
- The pattern is not really "saved" unless the firmware saves it
- The pattern editor in c0nb0x cannot load/save from/to a specific bank & slot
The benefits are:
- Hear the changes to the pattern as it plays instantly (how this will be implemented is up to the firmware)
- Use "Auto L/S" without worrying about abusing the EEPROM.
In the stock firmware, the pattern buffer holds only 1 pattern, but in other FWs it might hold more. Thus the message supports that scenario, but c0nb0x will not implement it as the pattern editor in c0nb0x works with one pattern at a time.
To get a pattern from the pattern buffer:
App to x0x:
[MSG_PATBUF] [0] [2] <0> <N> [CRC]
byte 4 is 0 for "get"
N is the pattern index in the patbuf (if it holds more than one pattern) .. starts from 0.
x0x to App:
The x0x must reply with MSG_PATT containing the requested pattern, or MSG_STATUS STAT_BAD if something goes wrong.
To send a pattern to the pattern buffer:
App to x0x:
[MSG_PATBUF] [0] [size] <1> <N> <DATA> [CRC]
size is 2 + PATT_SIZE.
byte 4 is 1 for "set"
N is the pattern index in the patbuf (if it holds more than one pattern) .. starts from 0.
DATA is the actual pattern to be written to the pattern buffer.
x0x to App:
The x0x must reply with MSG_STATUS with either STAT_OK or STAT_BAD.
MSG_GET_INFO
This message is to query some useful info about the firmware.
It will be available in c0nb0x v1.01.
App to x0x:
[MSG_GET_INFO] [0] [1] <CODE> [CRC]
CODE is uint8_t.
To get info about the EEPROMs:
[MSG_GET_INFO] [0] [1] <0> [CRC]
The x0x should reply with:
[MSG_GET_INFO] [0] [4] <INTERNAL> <EXTERNAL> [CRC]
INTERNAL uint16_t - size of the internal eeprom. The internal eeprom is not used by c0nb0x yet, but might be used in the future.
EXTERNAL uint16_t - size of the external eeprom (aka pattern memory).
The EXTERNAL eeprom is 4kB by design. However, the CPUMOD has a slot for an additional 4kB EEPROM which may be present. In that case, the firmware can return 8kB. For this purpose, the implementation of MSG_GET_MEM and MSG_SET_MEM (or MSG_PATT_RD and MSG_PATT_WR) should be changed to operate on the second EEPROM for addresses above 4095.
To get info about the pattern count:
[MSG_GET_INFO] [0] [1] <1> [CRC]
The x0x should reply with:
[MSG_GET_INFO] [0] [2] <BANKS> <SLOTS> [CRC]
BANKS uint8_t - number of banks (e.g. 16)
SLOTS uint8_t - number of pattern slots (per bank)
Thus the number of patterns is BANKS*SLOTS, which will be used for the "Import/Export Patterns" function in c0nb0x.
Is CPUMOD?
[MSG_GET_INFO] [0] [1] <8> [CRC]
If this is a firmware running on the CPUMOD (atmega2561) the x0x should reply with:
[MSG_GET_INFO] [0] [1] <1> [CRC]
Parameters
c0nb0x v1.02 introduces two new messages into the protocol: MSG_GET_PARAM and MSG_SET_PARAM.
The firmware can have up to 1024 parameters, which can be changed by the user from a menu in c0nb0x.
Parameters are represented as int16_t values.
There are 3 parameter types: BOOL, INT, ENUM.
BOOL: an On/Off-type parameter
INT: a (signed) integer
ENUM: a parameter where each value has a custom label
c0nb0x uses a special external file (*.prm) which describes each parameter, what type it is, what range it has, and what labels/info text to print on screen.
Adding Parameter support to your firmware:
Your FW must support at least MSG_FW_VER, and MSG_ISSUPP.
On connect, c0nb0x would ask if MSG_GET_PARAM is supported.
If it is, c0nb0x will send a MSG_GET_PARAM with param index 0xFFFF. The firmware should reply with a MSG_GET_PARAM containing [uint16_t num_params] [uint16_t params_unique_version].
If all that is successiful, c0nb0x will generate an unique *.prm filename for the firmware, and attempt to load that file from the main c0nb0x directory.
The filename will contain the firmware name string (exotic chars would be stripped, A-Z would be turned into a-z, and 0-9), the OSID, the number of params, and the unique params version.
This would make it possible to support multiple versions of the same firmware even if those versions have a different set of parameters and/or parameter ordering.
Example filename: n0nx0x_23_0_18.prm - OSID=23, params_unique_version=0, num_params=18.
The *.prm file would have to be distributed together with the firmware, and "installed" into the c0nb0x main directory.
If c0nb0x doesn't find the file, or something goes wrong during the parsing (wrong number of params, bad syntax.. etc..) the Parameters menu will be unaccessible to the user.
MSG_GET_PARAM
App to x0x:
[MSG_GET_PARAM] [0] [2] <param_index> [CRC]
param_index uint16_t - 0 to (num_params-1)
The FW should reply with:
[MSG_GET_PARAM] [0] [4] <param_index> <value> [CRC]
...or MSG_STATUS with STAT_BAD.
value uint16_t - the actual value
MSG_SET_PARAM
When the App wants to change a parameter:
App to x0x:
[MSG_SET_PARAM] [0] [4] <param_index> <value> [CRC]
The x0x should reply with either a MSG_GET_PARAM echoing back the value or MSG_STATUS with STAT_BAD.
The x0x may reject the parameter change.
On CONNECT
Here's what happens when c0nb0x connects to the x0xb0x:
Note: this is valid for c0nb0x v1.01.
Note: this does not apply for when connecting to upload firmware. The bootloader would freak out with such data.
* Serial port opened successifully *
-> MSG_PING
-> MSG_FW_VER
-> MSG_ISSUP "MSG_ISSUP"
If MSG_ISSUP is supported:
---> MSG_ISSUP "MSG_PATT" (x0x should return the pattern size)
---> MSG_ISSUP "MSG_GET_MEM"
---> MSG_ISSUP "MSG_SET_MEM"
---> MSG_ISSUP "MSG_PATBUF"
---> MSG_ISSUP "MSG_GET_INFO"
If MSG_GET_INFO is supported:
---> MSG_GET_INFO 0 (eeprom info)
---> MSG_GET_INFO 1 (pattern banks/slots info)
---> MSG_GET_INFO 8 (is you is, or is you ain't a CPUMOD)
If the pattern size was not detected yet:
---> MSG_PATT_RD 0, 0 (get the pattern at bank 1, slot 1)
If the OSID wasn't detected yet - guess it based on the info so far.
Guess the pattern format.
-> MSG_TEMPO_GET
* Connected *
CXM memory files
c0nb0x uses .cxm files for the EEPROM memory and Pattern memory import/export.
CXM is just a raw data file just like .xbp (used in c0ntr0l) but with a prepended header.
CXM file structure:
Magic Word.....: "c0nb0x/M", '\n'
(uint16_t) ....: data_offset, '\n'
(uint16_t) ....: os_id, '\n'
(uint16_t) ....: pattern_size, '\n'<raw data, same as xbp>
The idea behind this is to store the OS_ID into the file.
That makes it possible for c0nb0x to figure out what format the patterns in the file are, for when you try to import them into another firmware which uses a different pattern format.