/
ble_keyboard.ino
148 lines (112 loc) · 4.51 KB
/
ble_keyboard.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
///
/// ble_keyboard.ino
///
/// created: 2020-11
///
/// Simulate an evil writing machine in an isolated hotel on winter.
/// No hardware required other than an Arduino Nano 33 ble.
///
#include "Nano33BleHID.h"
#include "signal_utils.h"
/* -------------------------------------------------------------------------- */
static const char kSentence[] = "All work and no play makes Jack a dull boy";
static constexpr char kTickerChar = '.';
static constexpr int kNumTickerSteps = 4;
// How long it takes for the sentence to be written.
static constexpr uint32_t kSentenceDurationMilliseconds = 4029;
// How long it takes before the sentence is rewritten.
static constexpr uint32_t kSentenceDelayMilliseconds = 1977;
// How long one writing animation will run.
static constexpr uint32_t kSentenceTotalTimeMilliseconds = kSentenceDurationMilliseconds + kSentenceDelayMilliseconds;
// Safeguard to terminate this mess of an app before going crazy.
static constexpr uint32_t kTotalRuntime = 8 * kSentenceTotalTimeMilliseconds;
// Builtin LED animation delays when disconnect.
static constexpr uint32_t kLedBeaconDelayMilliseconds = 1185;
static constexpr uint32_t kLedErrorDelayMilliseconds = kLedBeaconDelayMilliseconds / 10;
// Builtin LED intensity when connected.
static constexpr int kLedConnectedIntensity = 30;
/* -------------------------------------------------------------------------- */
// Basic Keyboard service.
Nano33BleKeyboard bleKb("Shining Keyboard");
// Tracking index for the end of the writing animation ticker.
int sTickerIndex = -1;
/* -------------------------------------------------------------------------- */
/** Utility struct to send a text through the Keyboard HID for a given time. */
struct SentenceWriter {
std::string sentence;
int current_index;
uint32_t duration_ms;
SentenceWriter(const char* str, uint32_t duration_ms)
: sentence(str)
, current_index(-1)
, duration_ms(duration_ms)
{}
void write(HIDKeyboardService &kb, uint32_t frame_time)
{
// Calculate the absolute time in the animation (in [0.0f, 1.0f[)
float dt = frame_time / static_cast<float>(duration_ms);
// Smoothly interpolate to add "lifeness".
dt = smoothstep(0.0f, 1.0f, dt);
// Map absolute time to the letter index.
const int index = floor(dt * sentence.size());
// When the index change, send its letter via the keyboard service.
if (current_index != index) {
current_index = index;
const uint8_t letter = sentence[index];
kb.sendCharacter(letter);
}
}
} stringWriter(kSentence, kSentenceDurationMilliseconds);
/* -------------------------------------------------------------------------- */
void setup()
{
// General setup.
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
// Initialize both BLE and the HID.
bleKb.initialize();
// Launch the event queue that will manage both BLE events and the loop.
// After this call the main thread will be halted.
MbedBleHID_RunEventThread();
}
void loop()
{
// When disconnected, we animate the builtin LED to indicate the device state.
if (bleKb.disconnected()) {
animateLED(LED_BUILTIN, (bleKb.has_error()) ? kLedErrorDelayMilliseconds
: kLedBeaconDelayMilliseconds);
return;
}
// When connected, we slightly dim the builtin LED.
analogWrite(LED_BUILTIN, kLedConnectedIntensity);
// Stop when we reach a certain runtime.
if (bleKb.connection_time() > kTotalRuntime) {
return;
}
// Retrieve the HIDService to update.
auto *kb = bleKb.hid();
// Local time in the looping animation.
const uint32_t frame_time = bleKb.connection_time() % kSentenceTotalTimeMilliseconds;
// The animation is divided in two parts :
if (frame_time < kSentenceDurationMilliseconds)
{
// Write the sentence using the StringWriter object.
stringWriter.write(*kb, frame_time);
}
else
{
// Wait by writing dots at a different speed using the same logic as StringWriter.
// Second-part delta time.
float dt = (frame_time-kSentenceDurationMilliseconds) / float(kSentenceDelayMilliseconds);
// "Slow-out" filtering.
dt = 1.0f - (1.0f - dt) * pow(dt, dt);
// Detect current index based on time.
const int current_index = floor(dt * kNumTickerSteps);
// Update keystroke when index changes.
if (current_index != sTickerIndex) {
kb->sendCharacter(kTickerChar);
sTickerIndex = current_index;
}
}
}
/* -------------------------------------------------------------------------- */