smartknob.proto 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. syntax = "proto3";
  2. import "nanopb.proto";
  3. package PB;
  4. /*
  5. * Message FROM the SmartKnob to the host
  6. */
  7. message FromSmartKnob {
  8. uint32 protocol_version = 1 [(nanopb).int_size = IS_8];
  9. oneof payload {
  10. Ack ack = 2;
  11. Log log = 3;
  12. SmartKnobState smartknob_state = 4;
  13. }
  14. }
  15. /*
  16. * Message TO the Smartknob from the host
  17. */
  18. message ToSmartknob {
  19. uint32 protocol_version = 1 [(nanopb).int_size = IS_8];
  20. uint32 nonce = 2;
  21. oneof payload {
  22. RequestState request_state = 3;
  23. SmartKnobConfig smartknob_config = 4;
  24. }
  25. }
  26. /** Lets the host know that a ToSmartknob message was received and should not be retried. */
  27. message Ack {
  28. uint32 nonce = 1;
  29. }
  30. message Log {
  31. string msg = 1 [(nanopb).max_length = 255];
  32. }
  33. message SmartKnobState {
  34. /** Current integer position of the knob. (Detent resolution is at integer positions) */
  35. int32 current_position = 1;
  36. /**
  37. * Current fractional position. Typically will only range from (-snap_point, snap_point)
  38. * since further rotation will result in the integer position changing, but may exceed
  39. * those values if snap_point_bias is non-zero, or if the knob is at a bound. When the
  40. * knob is at a bound, this value can grow endlessly as the knob is rotated further past
  41. * the bound.
  42. *
  43. * When visualizing sub_position_unit, you will likely want to apply a rubber-band easing
  44. * function past the bounds; a sublinear relationship will help suggest that a bound has
  45. * been reached.
  46. */
  47. float sub_position_unit = 2;
  48. /**
  49. * Current SmartKnobConfig in effect at the time of this State snapshot.
  50. *
  51. * Beware that this config contains position and sub_position_unit values, not to be
  52. * confused with the top level current_position and sub_position_unit values in this State
  53. * message. The position values in the embedded config message will almost never be useful
  54. * to you; you probably want to be reading the top level values from the State message.
  55. */
  56. SmartKnobConfig config = 3;
  57. /**
  58. * Value that changes each time the knob is pressed. Does not change when a press is released.
  59. *
  60. * Why this press state a "nonce" rather than a simple boolean representing the current
  61. * "pressed" state? It makes the protocol more robust to dropped/lost State messages; if
  62. * the knob was pressed/released quickly and State messages happened to be dropped during
  63. * that time, the press would be completely lost. Using a nonce allows the host to recognize
  64. * that a press has taken place at some point even if the State was lost during the press
  65. * itself. Is this overkill? Probably, let's revisit in future protocol versions.
  66. */
  67. uint32 press_nonce = 4 [(nanopb).int_size = IS_8];
  68. }
  69. message SmartKnobConfig {
  70. /**
  71. * Set the integer position.
  72. *
  73. * Note: in order to make SmartKnobConfig apply idempotently, the current position
  74. * will only be set to this value when it changes compared to a previous config (and
  75. * NOT compared to the current state!). So by default, if you send a config position
  76. * of 5 and the current position is 3, the position may remain at 3 if the config
  77. * change to 5 was previously handled. If you need to force a position update, see
  78. * position_nonce.
  79. */
  80. int32 position = 1;
  81. /**
  82. * Set the fractional position. Typical range: (-snap_point, snap_point).
  83. *
  84. * Actual range is technically unbounded, but in practice this value will be compared
  85. * against snap_point on the next control loop, so any value beyond the snap_point will
  86. * generally result in an integer position change (unless position is already at a
  87. * limit).
  88. *
  89. * Note: idempotency implications noted in the documentation for `position` apply here
  90. * as well
  91. */
  92. float sub_position_unit = 2;
  93. /**
  94. * Position is normally only applied when it changes, but sometimes it's desirable
  95. * to reset the position to the same value, so a nonce change can be used to force
  96. * the position values to be applied as well.
  97. *
  98. * NOTE: Must be < 256
  99. */
  100. uint32 position_nonce = 3 [(nanopb).int_size = IS_8];
  101. /** Minimum position allowed. */
  102. int32 min_position = 4;
  103. /**
  104. * Maximum position allowed.
  105. *
  106. * If this is the same as min_position, there will only be one allowed position.
  107. *
  108. * If this is less than min_position, bounds will be disabled.
  109. */
  110. int32 max_position = 5;
  111. /** The angular "width" of each position/detent, in radians. */
  112. float position_width_radians = 6;
  113. /**
  114. * Strength of detents to apply. Typical range: [0, 1].
  115. *
  116. * A value of 0 disables detents.
  117. *
  118. * Values greater than 1 are not recommended and may lead to unstable behavior.
  119. */
  120. float detent_strength_unit = 7;
  121. /**
  122. * Strength of endstop torque to apply at min/max bounds. Typical range: [0, 1].
  123. *
  124. * A value of 0 disables endstop torque, but does not make position unbounded, meaning
  125. * the knob will not try to return to the valid region. For unbounded rotation, use
  126. * min_position and max_position.
  127. *
  128. * Values greater than 1 are not recommended and may lead to unstable behavior.
  129. */
  130. float endstop_strength_unit = 8;
  131. /**
  132. * Fractional (sub-position) threshold where the position will increment/decrement.
  133. * Typical range: (0.5, 1.5).
  134. *
  135. * This defines how hysteresis is applied to positions, which is why values >
  136. */
  137. float snap_point = 9;
  138. /**
  139. * Arbitrary 50-byte string representing this "config". This can be used to identify major
  140. * config/mode changes. The value will be echoed back to the host via a future State's
  141. * embedded config field so the host can use this value to determine the mode that was
  142. * in effect at the time of the State snapshot instead of having to infer it from the
  143. * other config fields.
  144. */
  145. string text = 10 [(nanopb).max_length = 50];
  146. /**
  147. * For a "magnetic" detent mode - where not all positions should have detents - this
  148. * specifies which positions (up to 5) have detents enabled. The knob will feel like it
  149. * is "magnetically" attracted to those positions, and will rotate smoothy past all
  150. * other positions.
  151. *
  152. * If you want to have more than 5 magnetic detent positions, you will need to dynamically
  153. * update this list as the knob is rotated. A recommended approach is to always send the
  154. * _nearest_ 5 detent positions, and send a new Config message whenever the list of
  155. * positions nearest the current position (as reported via State messages) changes.
  156. *
  157. * This approach enables effectively unbounded detent positions while keeping Config
  158. * bounded in size, and is resilient against tightly-packed detents with fast rotation
  159. * since multiple detent positions can be sent in advance; a full round-trip Config-State
  160. * isn't needed between each detent in order to keep up.
  161. */
  162. repeated int32 detent_positions = 11 [(nanopb).max_count = 5];
  163. /**
  164. * Advanced feature for shifting the defined snap_point away from the center (position 0)
  165. * for implementing asymmetric detents. Typical value: 0 (symmetric detent force).
  166. *
  167. * This can be used to create detents that will hold the position when carefully released,
  168. * but can be easily disturbed to return "home" towards position 0.
  169. */
  170. float snap_point_bias = 12;
  171. /**
  172. * Hue (0-255) for all 8 ring LEDs, if supported. Note: this will likely be replaced
  173. * with more configurability in a future protocol version.
  174. */
  175. int32 led_hue = 13 [(nanopb).int_size = IS_16];
  176. }
  177. message RequestState {}
  178. message PersistentConfiguration {
  179. uint32 version = 1;
  180. MotorCalibration motor = 2;
  181. StrainCalibration strain = 3;
  182. }
  183. message MotorCalibration {
  184. bool calibrated = 1;
  185. float zero_electrical_offset = 2;
  186. bool direction_cw = 3;
  187. uint32 pole_pairs = 4;
  188. }
  189. message StrainCalibration {
  190. int32 idle_value = 1;
  191. int32 press_delta = 2;
  192. }