pvchat.c 7.1 KB

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