Breaking change - Update protocol.

This commit is contained in:
Derek Jamison 2023-03-23 14:24:24 -04:00
parent a97e6df1d9
commit dfe9b6371e
3 changed files with 211 additions and 195 deletions

View File

@ -126,29 +126,28 @@ sudo hackrf_transfer -r flipper-chat.rf -f 433920000 -s 8000000 -x 47
## Example data ## Example data
- Beacon for game 042: - Beacon for game 042:
RPS:BA042:Lumyphut Lumyphut: RPS:BA042
- Not Beacon for game 042:
Lumyphut: RPS:NA042
- Join game 042: - Join game 042:
RPS:JA042NYourNameHere :Lumyphut Lumyphut: RPS:JA042NYourNameHere
- Join-ack for game 042: - Join-ack for game 042:
RPS:AA042NYourNameHere :Lumyphut Lumyphut: RPS:AA042NYourNameHere
- Count 1 for game 042: - Count 1 for game 042:
RPS:MA0421:Lumyphut Lumyphut: RPS:MA0421
- Count 2 for game 042: - Count 2 for game 042:
RPS:MA0422:Lumyphut Lumyphut: RPS:MA0422
- Rock for game 042: - Rock for game 042:
RPS:MA042R:Lumyphut Lumyphut: RPS:MA042R
- Paper for game 042: - Paper for game 042:
RPS:MA042P:Lumyphut Lumyphut: RPS:MA042P
- Scissors for game 042: - Scissors for game 042:
RPS:MA042S:Lumyphut Lumyphut: RPS:MA042S
RPS:AA042NYourNameHere :Lumyphut
123456789 0123456789 = 19bytes.
60-19 = 41 bytes is max length of name.

View File

@ -133,43 +133,91 @@ static void rps_timer_callback(void* ctx) {
// @param game_context pointer to a GameContext // @param game_context pointer to a GameContext
// @param time (furi_get_tick) when event was initially made // @param time (furi_get_tick) when event was initially made
static void rps_receive_data(GameContext* game_context, uint32_t tick) { static void rps_receive_data(GameContext* game_context, uint32_t tick) {
char sender_name[MESSAGE_MAX_LEN] = {0};
GameRfPurpose purpose;
uint8_t version;
unsigned int game_number;
Move move = MoveUnknown;
char* sender_contact;
int index = 0;
uint8_t message[MESSAGE_MAX_LEN] = {0}; uint8_t message[MESSAGE_MAX_LEN] = {0};
memset(message, 0x00, MESSAGE_MAX_LEN); memset(message, 0x00, MESSAGE_MAX_LEN);
size_t len = subghz_tx_rx_worker_read(game_context->subghz_txrx, message, MESSAGE_MAX_LEN); int len = (int)subghz_tx_rx_worker_read(game_context->subghz_txrx, message, MESSAGE_MAX_LEN);
size_t game_name_len = strlen(RPS_GAME_NAME);
if(len < (game_name_len + 2)) {
FURI_LOG_D(TAG, "Message not long enough. >%s<", message);
return;
}
// The message for a move (M) (like 'R' for Rock) using version (A) should be "RPS:" + "M" + "A" + game"###" + move"R" + ":" + "YourFlip" + "\r\n"
if(strcmp(RPS_GAME_NAME, (const char*)message)) {
FURI_LOG_D(TAG, "Got message >%s<", message);
// The purpose immediately follows the game name.
GameRfPurpose purpose = message[game_name_len];
// The version follows the purpose.
uint8_t version = message[game_name_len + 1];
FURI_LOG_T(TAG, "Purpose is %c and version is %c", purpose, version);
// Null terminate buffer at the end of message so we can't overrun the buffer. // Null terminate buffer at the end of message so we can't overrun the buffer.
message[MESSAGE_MAX_LEN - 1] = 0; message[MESSAGE_MAX_LEN - 1] = 0;
unsigned int game_number; // Sender's Flipper Zero name.
char sender_contact[MESSAGE_MAX_LEN]; while(index < len && message[index] != ':') {
char sender_name[9]; sender_name[index] = message[index];
char tmp; index++;
Move move = MoveUnknown; }
if(index >= len) {
FURI_LOG_T(TAG, "Message too long, ignoring. >%s<", message);
return;
} else if(message[index] != ':') {
FURI_LOG_T(TAG, "Message missing ':' character, ignoring. >%s<", message);
return;
}
sender_name[index] = 0;
// Skip the ':' character & check for a space.
if(++index < len) {
if(message[index++] != ' ') {
FURI_LOG_T(TAG, "Message missing ' ' after name, ignoring. >%s<", message);
return;
}
}
// Check for the game name.
int game_name_len = (int)strlen(RPS_GAME_NAME);
for(int i = 0; i < game_name_len; i++) {
if((index >= len) || (message[index++] != RPS_GAME_NAME[i])) {
FURI_LOG_T(
TAG, "Message missing game name '%s', ignoring. >%s<", RPS_GAME_NAME, message);
return;
}
}
if(index < len) {
if(message[index++] != ':') {
FURI_LOG_T(TAG, "Message missing ':' after game name, ignoring. >%s<", message);
return;
}
}
FURI_LOG_D(TAG, "Got message >%s<", message);
// The purpose immediately follows the game name.
if(index >= len) {
FURI_LOG_W(TAG, "Message missing purpose, ignoring. >%s<", message);
return;
}
purpose = message[index++];
// The version follows the purpose.
if(index >= len) {
FURI_LOG_W(TAG, "Message missing version, ignoring. >%s<", message);
return;
}
version = message[index++];
FURI_LOG_T(TAG, "Purpose is %c and version is %c", purpose, version);
// Game number is 3 digits.
if(sscanf((const char*)message + index, "%03u", &game_number) != 1) {
FURI_LOG_W(TAG, "Message missing game number, ignoring. >%s<", message);
return;
}
index += 3;
switch(purpose) { switch(purpose) {
case GameRfPurposeMove: case GameRfPurposeMove:
// We expect this mesage to the game number, move and sender name. // We expect this mesage to the game number, move and sender name.
if(sscanf( if(index >= len) {
(const char*)message + game_name_len + 2, FURI_LOG_W(TAG, "Failed to parse move message. >%s<", message);
"%03u%c:%8s", return;
&game_number, } else {
&tmp, move = (Move)message[index];
sender_name) == 3) {
move = (Move)tmp;
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
FuriString* name = furi_string_alloc(); FuriString* name = furi_string_alloc();
furi_string_set(name, sender_name); furi_string_set(name, sender_name);
@ -181,61 +229,37 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
.sender_name = name, .sender_name = name,
.game_number = game_number}; .game_number = game_number};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else {
FURI_LOG_W(TAG, "Failed to parse move message. >%s<", message);
} }
break; break;
case GameRfPurposeBeacon: case GameRfPurposeBeacon: {
// We expect this mesage to the game number and sender name.
if(sscanf(
(const char*)message + game_name_len + 2,
"%03u:%8s",
&game_number,
sender_name) == 2) {
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
FuriString* name = furi_string_alloc(); FuriString* name = furi_string_alloc();
furi_string_set(name, sender_name); furi_string_set(name, sender_name);
GameEvent event = { GameEvent event = {
.type = GameEventRemoteBeacon, .type = GameEventRemoteBeacon, .sender_name = name, .game_number = game_number};
.sender_name = name,
.game_number = game_number};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else {
FURI_LOG_W(TAG, "Failed to parse beacon message. >%s<", message);
}
break; break;
}
case GameRfPurposeNotBeacon: case GameRfPurposeNotBeacon: {
// We expect this mesage to the game number and sender name.
if(sscanf(
(const char*)message + game_name_len + 2,
"%03u:%8s",
&game_number,
sender_name) == 2) {
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
FuriString* name = furi_string_alloc(); FuriString* name = furi_string_alloc();
furi_string_set(name, sender_name); furi_string_set(name, sender_name);
GameEvent event = { GameEvent event = {
.type = GameEventRemoteNotBeacon, .type = GameEventRemoteNotBeacon, .sender_name = name, .game_number = game_number};
.sender_name = name,
.game_number = game_number};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else {
FURI_LOG_W(TAG, "Failed to parse not beacon message. >%s<", message);
}
break; break;
}
case GameRfPurposeJoin: case GameRfPurposeJoin:
// We expect this mesage to the game number, move and sender name. if(index >= len) {
if(sscanf( FURI_LOG_W(TAG, "Failed to parse join message. >%s<", message);
(const char*)message + game_name_len + 2, return;
"%03u%s :%8s", } else {
&game_number, sender_contact = (char*)message + index;
sender_contact,
sender_name) == 3) {
FURI_LOG_T(TAG, "Join had contact of >%s<", sender_contact); FURI_LOG_T(TAG, "Join had contact of >%s<", sender_contact);
// IMPORTANT: The code processing the event needs to furi_string_free the senderName! // IMPORTANT: The code processing the event needs to furi_string_free the senderName!
@ -250,18 +274,16 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
.sender_contact = contact, .sender_contact = contact,
.game_number = game_number}; .game_number = game_number};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else {
FURI_LOG_W(TAG, "Failed to parse join message. >%s<", message);
} }
break; break;
case GameRfPurposeJoinAcknowledge: case GameRfPurposeJoinAcknowledge:
if(sscanf( if(index >= len) {
(const char*)message + game_name_len + 2, FURI_LOG_W(TAG, "Failed to parse join acknowledge message. >%s<", message);
"%03u%s :%8s", return;
&game_number, } else {
sender_contact, sender_contact = (char*)message + index;
sender_name) == 3) {
FURI_LOG_T(TAG, "Join acknowledge for game %d.", game_number); FURI_LOG_T(TAG, "Join acknowledge for game %d.", game_number);
FURI_LOG_T(TAG, "Join ack had contact of >%s<", sender_contact); FURI_LOG_T(TAG, "Join ack had contact of >%s<", sender_contact);
@ -276,8 +298,6 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
.sender_contact = contact, .sender_contact = contact,
.game_number = game_number}; .game_number = game_number};
furi_message_queue_put(game_context->queue, &event, FuriWaitForever); furi_message_queue_put(game_context->queue, &event, FuriWaitForever);
} else {
FURI_LOG_W(TAG, "Failed to parse join acknowledge message. >%s<", message);
} }
break; break;
@ -291,9 +311,6 @@ static void rps_receive_data(GameContext* game_context, uint32_t tick) {
} }
break; break;
} }
} else {
FURI_LOG_D(TAG, "Message not for our application. >%s<", message);
}
} }
// This gets invoked when input (button press) is detected. // This gets invoked when input (button press) is detected.
@ -877,103 +894,103 @@ static void rps_broadcast(GameContext* game_context, FuriString* buffer) {
} }
// Our GameEventSendCounter handler invokes this method. // Our GameEventSendCounter handler invokes this method.
// We broadcast - "RPS:" + move"M" + version"A" + game"###" + move"R" + ":" + "YourFlip" + "\r\n" // We broadcast - "YourFlip: " + "RPS:" + move"M" + version"A" + game"###" + move"R" + "\r\n"
// @param game_context pointer to a GameContext. // @param game_context pointer to a GameContext.
// @param moveToSend the move to send to the remote player. // @param moveToSend the move to send to the remote player.
static void rps_broadcast_move(GameContext* game_context, Move moveToSend) { static void rps_broadcast_move(GameContext* game_context, Move moveToSend) {
GameData* data = game_context->data; GameData* data = game_context->data;
FURI_LOG_I(TAG, "Sending move %c", moveToSend); FURI_LOG_I(TAG, "Sending move %c", moveToSend);
// The message for game 42 with a move with value Rock should look like... "RPS:MA042R:YourFlip\r\n" // The message for game 42 with a move with value Rock should look like... "YourFlip: RPS:MA042R\r\n"
furi_string_printf( furi_string_printf(
data->buffer, data->buffer,
"%s%c%c%03u%c:%s\r\n", "%s: %s:%c%c%03u%c\r\n",
furi_hal_version_get_name_ptr(),
RPS_GAME_NAME, RPS_GAME_NAME,
GameRfPurposeMove, GameRfPurposeMove,
MAJOR_VERSION, MAJOR_VERSION,
data->game_number, data->game_number,
moveToSend, moveToSend);
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
// Our GameEventTypeTimer handler invokes this method. // Our GameEventTypeTimer handler invokes this method.
// We broadcast - "RPS:" + beacon"B" + version"A" + game"###" + ":" + "YourFlip" + "\r\n" // We broadcast - "YourFlip: " + "RPS:" + beacon"B" + version"A" + game"###" + "\r\n"
// @param game_context pointer to a GameContext. // @param game_context pointer to a GameContext.
static void rps_broadcast_beacon(GameContext* game_context) { static void rps_broadcast_beacon(GameContext* game_context) {
GameData* data = game_context->data; GameData* data = game_context->data;
FURI_LOG_I(TAG, "Sending beacon"); FURI_LOG_I(TAG, "Sending beacon");
// The message for game 42 should look like... "RPS:BA042:YourFlip\r\n" // The message for game 42 should look like... "YourFlip: RPS:BA042\r\n"
furi_string_printf( furi_string_printf(
data->buffer, data->buffer,
"%s%c%c%03u:%s\r\n", "%s: %s:%c%c%03u\r\n",
furi_hal_version_get_name_ptr(),
RPS_GAME_NAME, RPS_GAME_NAME,
GameRfPurposeBeacon, GameRfPurposeBeacon,
MAJOR_VERSION, MAJOR_VERSION,
data->game_number, data->game_number);
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
// Our GameEventTypeTimer handler invokes this method. // Our GameEventTypeTimer handler invokes this method.
// We broadcast - "RPS:" + notbeacon"N" + version"A" + game"###" + ":" + "YourFlip" + "\r\n" // We broadcast - "YourFlip: " + "RPS:" + notbeacon"N" + version"A" + game"###" + "\r\n"
// @param game_context pointer to a GameContext. // @param game_context pointer to a GameContext.
static void rps_broadcast_not_beacon(GameContext* game_context) { static void rps_broadcast_not_beacon(GameContext* game_context) {
GameData* data = game_context->data; GameData* data = game_context->data;
FURI_LOG_I(TAG, "Sending not beacon"); FURI_LOG_I(TAG, "Sending not beacon");
// The message for game 42 should look like... "RPS:NA042:YourFlip\r\n" // The message for game 42 should look like... "YourFlip: RPS:NA042\r\n"
furi_string_printf( furi_string_printf(
data->buffer, data->buffer,
"%s%c%c%03u:%s\r\n", "%s: %s:%c%c%03u\r\n",
furi_hal_version_get_name_ptr(),
RPS_GAME_NAME, RPS_GAME_NAME,
GameRfPurposeNotBeacon, GameRfPurposeNotBeacon,
MAJOR_VERSION, MAJOR_VERSION,
data->game_number, data->game_number);
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
// Send message that indicates Flipper is joining a specific game. // Send message that indicates Flipper is joining a specific game.
// We broadcast - "RPS:" + join"J" + version"A" + game"###" + "NYourNameHere" + " :" + "YourFlip" + "\r\n" // We broadcast - "YourFlip: " + "RPS:" + join"J" + version"A" + game"###" + "NYourNameHere" + "\r\n"
// @param game_context pointer to a GameContext. // @param game_context pointer to a GameContext.
static void rps_broadcast_join(GameContext* game_context) { static void rps_broadcast_join(GameContext* game_context) {
GameData* data = game_context->data; GameData* data = game_context->data;
unsigned int gameNumber = data->game_number; unsigned int gameNumber = data->game_number;
FURI_LOG_I(TAG, "Joining game %d.", gameNumber); FURI_LOG_I(TAG, "Joining game %d.", gameNumber);
// The message for game 42 should look like... "RPS:JA042NYourNameHere :YourFlip\r\n" // The message for game 42 should look like... "YourFlip: RPS:JA042NYourNameHere\r\n"
furi_string_printf( furi_string_printf(
data->buffer, data->buffer,
"%s%c%c%03u%s :%s\r\n", "%s: %s:%c%c%03u%s\r\n",
furi_hal_version_get_name_ptr(),
RPS_GAME_NAME, RPS_GAME_NAME,
GameRfPurposeJoin, GameRfPurposeJoin,
MAJOR_VERSION, MAJOR_VERSION,
data->game_number, data->game_number,
furi_string_get_cstr(data->local_contact), furi_string_get_cstr(data->local_contact));
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }
// Send message that acknowledges Flipper joining a specific game. // Send message that acknowledges Flipper joining a specific game.
// We broadcast - "RPS:" + joinAck"A" + version"A" + game"###" + "NYourNameHere" +" :" + "YourFlip" + "\r\n" // We broadcast - "YourFlip: " + "RPS:" + joinAck"A" + version"A" + game"###" + "NYourNameHere" + "\r\n"
// @param game_context pointer to a GameContext. // @param game_context pointer to a GameContext.
static void rps_broadcast_join_acknowledge(GameContext* game_context) { static void rps_broadcast_join_acknowledge(GameContext* game_context) {
GameData* data = game_context->data; GameData* data = game_context->data;
unsigned int gameNumber = data->game_number; unsigned int gameNumber = data->game_number;
FURI_LOG_I(TAG, "Acknowledge joining game %d.", gameNumber); FURI_LOG_I(TAG, "Acknowledge joining game %d.", gameNumber);
// The message for game 42 should look like... "RPS:AA042NYourNameHere :YourFlip\r\n" // The message for game 42 should look like... "YourFlip: RPS:AA042NYourNameHere\r\n"
furi_string_printf( furi_string_printf(
data->buffer, data->buffer,
"%s%c%c%03u%s :%s\r\n", "%s: %s:%c%c%03u%s\r\n",
furi_hal_version_get_name_ptr(),
RPS_GAME_NAME, RPS_GAME_NAME,
GameRfPurposeJoinAcknowledge, GameRfPurposeJoinAcknowledge,
MAJOR_VERSION, MAJOR_VERSION,
data->game_number, data->game_number,
furi_string_get_cstr(data->local_contact), furi_string_get_cstr(data->local_contact));
furi_hal_version_get_name_ptr());
rps_broadcast(game_context, data->buffer); rps_broadcast(game_context, data->buffer);
} }

View File

@ -23,8 +23,8 @@
#define RPS_SOCIAL_FILE_NAME "social.me" #define RPS_SOCIAL_FILE_NAME "social.me"
#define RPS_SOCIAL_PATH RPS_GAME_FOLDER "/" RPS_SOCIAL_FILE_NAME #define RPS_SOCIAL_PATH RPS_GAME_FOLDER "/" RPS_SOCIAL_FILE_NAME
// This is sent at the beginning of all RF messages. NOTE: It must end with the ':' character. // This is sent at the beginning of all RF messages (after the flipper name).
#define RPS_GAME_NAME "RPS:" #define RPS_GAME_NAME "RPS"
#define TAG "rock_paper_scissors_app" #define TAG "rock_paper_scissors_app"
// Name for "N", followed by your name without any spaces. // Name for "N", followed by your name without any spaces.
@ -35,13 +35,13 @@
#define MESSAGE_MAX_LEN 60 #define MESSAGE_MAX_LEN 60
// The is the most characters you can enter at a keyboard prompt. // The is the most characters you can enter at a keyboard prompt.
#define KEYBOARD_MAX_LEN 40 #define KEYBOARD_MAX_LEN 32
// How often to send a beacon. // How often to send a beacon.
#define BEACON_DURATION 3 #define BEACON_DURATION 3
// The major version must be a single character (it can be anything - like '1' or 'A' or 'a'). // The major version must be a single character (it can be anything - like '1' or 'A' or 'a').
#define MAJOR_VERSION 'A' #define MAJOR_VERSION 'B'
// Temporary timings, since I don't have second Flipper & send commands via laptop. // Temporary timings, since I don't have second Flipper & send commands via laptop.
#define DURATION_NO_MOVE_DETECTED_ERROR 60000 #define DURATION_NO_MOVE_DETECTED_ERROR 60000