pvchat.c 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #include "../app.h"
  2. /* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
  3. * See the LICENSE file for information about the license.
  4. *
  5. * ----------------------------------------------------------
  6. * ProtoView chat protocol. This is just a fun test protocol
  7. * that can be used between two Flippers in order to send
  8. * and receive text messages.
  9. * ----------------------------------------------------------
  10. *
  11. * Protocol description
  12. * ====================
  13. *
  14. * "1" represents a pulse of one-third bit time (100us)
  15. * "0" represents a gap of one-third bit time (100us)
  16. *
  17. * The message starts with a preamble + a sync pattern:
  18. *
  19. * preamble = 1010101010101010 x 3
  20. * sync = 1100110011001010
  21. *
  22. * The a variable amount of bytes follow, where each bit
  23. * is encoded in the following way:
  24. *
  25. * zero 100 (100 us pulse, 200 us gap)
  26. * one 110 (200 us pulse, 100 us gap)
  27. *
  28. * Bytes are sent MSB first, so receiving, in sequence, bits
  29. * 11100001, means byte E1.
  30. *
  31. * This is the data format:
  32. *
  33. * +--+------+-------+--+--+--+
  34. * |SL|Sender|Message|FF|AA|CS|
  35. * +--+------+-------+--+--+--+
  36. * | | |
  37. * | | \_ N bytes of message terminated by FF AA + 1 byte of checksum
  38. * | |
  39. * | \_ SL bytes of sender name
  40. * \
  41. * \_ 1 byte of sender len, 8 bit unsigned integer.
  42. *
  43. *
  44. * Checksum = sum of bytes modulo 256, with checksum set
  45. * to 0 for the computation.
  46. *
  47. * Design notes
  48. * ============
  49. *
  50. * The protocol is designed in order to have certain properties:
  51. *
  52. * 1. Pulses and gaps can only be 100 or 200 microseconds, so the
  53. * message can be described, encoded and decoded with only two
  54. * fixed durations.
  55. *
  56. * 2. The preamble + sync is designed to have a well recognizable
  57. * pattern that can't be reproduced just for accident inside
  58. * the encoded pattern. There is no combinatio of encoded bits
  59. * leading to the preamble+sync. Also the sync pattern final
  60. * part can't be mistaken for actual bits of data, since it
  61. * contains alternating short pulses/gaps at 100us.
  62. *
  63. * 3. Data encoding wastes some bandwidth in order to be more
  64. * robust. The consumed bandwidth is compensated with the ability
  65. * to send the signal with a 100us clock period, so an actual
  66. * bit takes 300us, reaching a data transfer of 416 bytes per
  67. * second. More than enough for the chat.
  68. */
  69. static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
  70. const char *sync_pattern = "1010101010101010" // Preamble
  71. "1100110011001010"; // Sync
  72. uint8_t sync_len = 32;
  73. /* This is a variable length message, however the minimum length
  74. * requires a sender len byte (of value zero) and the terminator
  75. * FF 00 plus checksum: a total of 4 bytes. */
  76. if (numbits-sync_len < 8*4) return false;
  77. uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
  78. if (off == BITMAP_SEEK_NOT_FOUND) return false;
  79. FURI_LOG_E(TAG, "Chat preamble+sync found");
  80. /* If there is room on the left, let's mark the start of the message
  81. * a bit before: we don't try to detect all the preamble, but only
  82. * the first part, however it is likely present. */
  83. if (off >= 16) {
  84. off -= 16;
  85. sync_len += 16;
  86. }
  87. info->start_off = off;
  88. off += sync_len; /* Skip preamble and sync. */
  89. uint8_t raw[64] = {(uint8_t)'.'};
  90. uint32_t decoded =
  91. convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
  92. "100","110"); /* PWM */
  93. FURI_LOG_E(TAG, "Chat decoded bits: %lu", decoded);
  94. if (decoded < 8*4) return false; /* Min message len. */
  95. // The message needs to have a two bytes terminator before
  96. // the checksum.
  97. uint32_t j;
  98. for (j = 0; j < sizeof(raw)-1; j++)
  99. if (raw[j] == 0xff && raw[j+1] == 0xaa) break;
  100. if (j == sizeof(raw)-1) {
  101. FURI_LOG_E(TAG, "Chat: terminator not found");
  102. return false; // No terminator found.
  103. }
  104. uint32_t datalen = j+3; // If the terminator was found at j, then
  105. // we need to sum three more bytes to have
  106. // the len: FF itself, AA, checksum.
  107. info->pulses_count = sync_len + 8*3*datalen;
  108. // Check if the control sum matches.
  109. if (sum_bytes(raw,datalen-1,0) != raw[datalen-1]) {
  110. FURI_LOG_E(TAG, "Chat: checksum mismatch");
  111. return false;
  112. }
  113. // Check if the length of the sender looks sane
  114. uint8_t senderlen = raw[0];
  115. if (senderlen >= sizeof(raw)) {
  116. FURI_LOG_E(TAG, "Chat: invalid sender length");
  117. return false; // Overflow
  118. }
  119. fieldset_add_str(info->fieldset,"sender",(char*)raw+1,senderlen);
  120. fieldset_add_str(info->fieldset,"message",(char*)raw+1+senderlen,
  121. datalen-senderlen-4);
  122. return true;
  123. }
  124. /* Give fields and defaults for the signal creator. */
  125. static void get_fields(ProtoViewFieldSet *fieldset) {
  126. fieldset_add_str(fieldset,"sender","Carol",5);
  127. fieldset_add_str(fieldset,"message","Anyone hearing?",15);
  128. }
  129. /* Create a signal. */
  130. static void build_message(RawSamplesBuffer *samples, ProtoViewFieldSet *fs)
  131. {
  132. uint32_t te = 100; /* Short pulse duration in microseconds.
  133. Our protocol needs three symbol times to send
  134. a bit, so 300 us per bit = 3.33 kBaud. */
  135. // Preamble: 24 alternating 100us pulse/gap pairs.
  136. for (int j = 0; j < 24; j++) {
  137. raw_samples_add(samples,true,te);
  138. raw_samples_add(samples,false,te);
  139. }
  140. // Sync: 3 alternating 200 us pulse/gap pairs.
  141. for (int j = 0; j < 3; j++) {
  142. raw_samples_add(samples,true,te*2);
  143. raw_samples_add(samples,false,te*2);
  144. }
  145. // Sync: plus 2 alternating 100 us pluse/gap pairs.
  146. for (int j = 0; j < 2; j++) {
  147. raw_samples_add(samples,true,te);
  148. raw_samples_add(samples,false,te);
  149. }
  150. // Data: build the array.
  151. uint32_t datalen = 1 + fs->fields[0]->len + // Userlen + Username
  152. fs->fields[1]->len + 3; // Message + FF + 00 + CRC
  153. uint8_t *data = malloc(datalen), *p = data;
  154. *p++ = fs->fields[0]->len;
  155. memcpy(p,fs->fields[0]->str,fs->fields[0]->len);
  156. p += fs->fields[0]->len;
  157. memcpy(p,fs->fields[1]->str,fs->fields[1]->len);
  158. p += fs->fields[1]->len;
  159. *p++ = 0xff;
  160. *p++ = 0xaa;
  161. *p = sum_bytes(data,datalen-1,0);
  162. // Emit bits
  163. for (uint32_t j = 0; j < datalen*8; j++) {
  164. if (bitmap_get(data,datalen,j)) {
  165. raw_samples_add(samples,true,te*2);
  166. raw_samples_add(samples,false,te);
  167. } else {
  168. raw_samples_add(samples,true,te);
  169. raw_samples_add(samples,false,te*2);
  170. }
  171. }
  172. free(data);
  173. }
  174. ProtoViewDecoder ProtoViewChatDecoder = {
  175. .name = "ProtoView chat",
  176. .decode = decode,
  177. .get_fields = get_fields,
  178. .build_message = build_message
  179. };