Merge remote-tracking branch 'plugin/FocusSerial/f/monorepo' into f/monorepo

pull/365/head
Gergely Nagy 6 years ago
commit 7db8bb779a
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# focus-test - Trivial Focus testing tool
# Copyright (C) 2018 Keyboard.io, Inc.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
set -e
DEVICE="${DEVICE:-/dev/ttyACM0}"
stty -F "${DEVICE}" 9600 raw -echo
exec 3<"${DEVICE}"
echo "$@" >"${DEVICE}"
while read -r line <&3; do
line="$(echo -n "${line}" | tr -d '\r')"
if [ "${line}" == "." ]; then
break
fi
echo "${line}"
done

@ -0,0 +1,292 @@
# Kaleidoscope-FocusSerial
Bidirectional communication for Kaleidoscope. With this plugin enabled, plugins that implement the `onFocusEvent` hook will start responding to Focus commands sent via `Serial`, allowing bidirectional communication between firmware and host.
This plugin is an upgrade of the former [Kaleidoscope-Focus][kaleidoscope:focus] plugin. See the [UPGRADING][upgrading] section for information about how to transition to the new system.
[kaleidoscope:focus]: Focus.md
[upgrading]: #upgrading-from-focus-to-onfocusevent--focusserial
## Using the plugin
This plugin is **not** meant to be used by the end-user (apart from setting it up to use plugin-provided hooks), but by plugin authors instead. As an end user, you just need to use Focus-enabled plugins like you normally would, and once `FocusSerial` is enabled, their commands will be available too.
Nevertheless, a very simple example is shown below:
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-FocusSerial.h>
namespace kaleidoscope {
class FocusTestCommand : public Plugin {
public:
FocusTestCommand() {}
EventHandlerResult onFocusEvent(const char *command) {
if (strcmp_P(command, PSTR("test")) != 0)
return EventHandlerResult::OK;
Serial.println(F("Congratulations, the test command works!"));
return EventHandlerResult::EVENT_CONSUMED;
}
};
}
kaleidoscope::FocusTestCommand FocusTestCommand;
KALEIDOSCOPE_INIT_PLUGINS(Focus, FocusTestCommand);
void setup () {
Kaleidoscope.setup ();
}
```
## Plugin methods
The plugin provides the `Focus` object, with a couple of helper methods aimed at developers. Documenting those is a work in progress for now.
## Wire protocol
`Focus` uses a simple, textual, request-response-based wire protocol.
Each request has to be on one line, anything before the first space is the command part (if there is no space, just a newline, then the whole line will be considered a command), everything after are arguments. The plugin itself only parses until the end of the command part, argument parsing is left to the various hooks. If there is anything left on the line after hooks are done processing, it will be ignored.
Responses can be multi-line, but most aren't. Their content is also up to the hooks, `Focus` does not enforce anything, except a trailing dot and a newline. Responses should end with a dot on its own line.
Apart from these, there are no restrictions on what can go over the wire, but to make the experience consistent, find a few guidelines below:
* Commands should be namespaced, so that the plugin name, or functionality comes first, then the sub-command or property. Such as `led.theme`, or `led.setAll`.
* One should not use setters and getters, but a single property command instead. One, which when called without arguments, will act as a getter, and as a setter otherwise.
* Namespaces should be lowercase, while the commands within them camel-case.
* Do as little work in the hooks as possible. While the protocol is human readable, the expectation is that tools will be used to interact with the keyboard.
* As such, keep formatting to the bare minimum. No fancy table-like responses.
* In general, the output of a getter should be copy-pasteable to a setter.
These are merely guidelines, and there can be - and are - exceptions. Use your discretion when writing Focus hooks.
### Example
In the examples below, `<` denotes what the host sends to the keyboard, `>` what
the keyboard responds.
```
< test
> Congratulations, the test command works!
> .
```
```
< help
> help
> test
> palette
> .
```
```
< palette
> 0 0 0 128 128 128 255 255 255
> .
< palette 0 0 0 128 128 128 255 255 255
> .
```
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting started with the plugin.
[plugin:example]: ../../examples/FocusSerial/FocusSerial.ino
Upgrading from Focus to onFocusEvent + FocusSerial
==================================================
Upgrading from `Focus` to `onFocusEvent` and `FocusSerial` is a reasonably simple process, the interface is quite similar. Nevertheless, we present a step-by-step guide here, covering two use cases: one where we wish to always provide a Focus command when both our plugin and `FocusSerial` are enabled; and another where we only wish to provide the command when explicitly asked to do so.
## The most trivial example
The biggest difference between `Focus` and `onFocusEvent` is that the former required explicit registering of hooks, while the latter does it automatically: every plugin that implements the `onFocusEvent` method will be part of the system. As a consequence, only plugins are able to supply new commands: there is no explicit registration, thus, no way to inject a command that isn't part of a plugin. This also means that these functions now return `kaleidoscope::EventHandlerResult` instead of `bool`. Lets see a trivial example!
### Focus
```c++
bool exampleFocusHook(const char *command) {
if (strcmp_P(command, PSTR("example")) != 0)
return false;
Serial.println(F("This is an example response. Hello world!"));
return true;
}
KALEIDOSCOPE_INIT_PLUGINS(Focus)
void setup() {
Serial.begin(9600);
Kaleidoscope.setup();
Focus.addHook(FOCUS_HOOK(exampleFocusHook, "example"));
}
```
### onFocusEvent
```c++
namespace kaleidoscope {
class FocusExampleCommand : public Plugin {
public:
FocusExampleCommand() {}
EventHandlerResult onFocusEvent(const char *command) {
if (strcmp_P(command, PSTR("example")) != 0)
return EventHandlerResult::OK;
Serial.println(F("This is an example response. Hello world!"));
return EventHandlerResult::EVENT_CONSUMED;
}
};
}
kaleidoscope::FocusExampleCommand FocusExampleCommand;
KALEIDOSCOPE_INIT_PLUGINS(Focus, FocusExampleCommand);
void setup() {
Kaleidoscope.setup();
}
```
### Summary
The new version is slightly more verbose for the trivial use case, because we have to wrap it up in an object. But other than that, the changes are minimal, and we don't need to explicitly register it!
Observe that the return values changed: with `Focus`, if we wanted other hooks to have a chance at processing the same command, the hook returned `false`; if we wanted to stop processing, and consider it consumed, it returned `true`. With the new system, this is more descriptive with the `EventHandlerResult::OK` and `EventHandlerResult::EVENT_CONSUMED` return values.
## A stateful example
Perhaps a better example that shows the quality of life improvements the new system brings is the case where the command needs access to either plugin state, or plugin methods. With the former system, the focus hooks needed to be static methods, and needed to be public. This is not necessarily the case now, because `onFocusEvent` is a non-static object method. It has full access to plugin internals!
### Focus
```c++
namespace kaleidoscope {
class ExamplePlugin : public Plugin {
public:
ExamplePlugin();
static bool exampleToggle() {
example_toggle_ = !example_toggle_;
return example_toggle_;
}
static bool focusHook(const char *command) {
if (strcmp_P(command, PSTR("example.toggle")) != 0)
return false;
::Focus.printBool(exampleToggle());
return true;
}
private:
static bool example_toggle_;
};
}
kaleidoscope::ExamplePlugin ExamplePlugin;
KALEIDOSCOPE_PLUGIN_INIT(Focus, ExamplePlugin)
void setup() {
Serial.begin(9600);
Kaleidoscope.setup();
Focus.addHook(FOCUS_HOOK(ExamplePlugin.focusHook, "example.toggle"));
}
```
### onFocusEvent
```c++
namespace kaleidoscope {
class ExamplePlugin : public Plugin {
public:
ExamplePlugin();
EventHandlerResult onFocusEvent(const char *command) {
if (strcmp_P(command, PSTR("example.toggle")) != 0)
return EventHandlerResult::OK;
example_toggle_ = !example_toggle_;
::Focus.printBool(example_toggle_);
return EventHandlerResult::EVENT_CONSUMED;
}
private:
static bool example_toggle_;
};
}
kaleidoscope::ExamplePlugin ExamplePlugin;
KALEIDOSCOPE_PLUGIN_INIT(Focus, ExamplePlugin)
void setup() {
Kaleidoscope.setup();
}
```
### Summary
It's just another plugin, with just another event handler method implemented, nothing more. No need to explicitly register the focus hook, no need to provide access to private variables - we can just keep them private.
## Optional commands
Optional commands are something that were perhaps easier with the `Focus` method: one just didn't register them. With `onFocusEvent`, we need to do a bit more than that, and move the command to a separate plugin, if we do not wish to enable it in every case. This adds a bit of overhead, but still less than `Focus` did.
### Focus
```c++
bool exampleOptionalHook(const char *command) {
if (strcmp_P(command, PSTR("optional")) != 0)
return false;
Serial.println(Layer.getLayerState(), BIN);
return true;
}
KALEIDOSCOPE_INIT_PLUGINS(Focus)
void setup() {
Kaleidoscope.setup();
}
```
Do note that we do not register the `exampleOptionalHook` here! As such, because it is unused code, it will get optimized out during compilation. While this is a simplistic example, the optional hook might have been part of a class, that provides other hooks.
### onFocusEvent
```c++
namespace kaleidoscope {
class ExampleOptionalCommand : public Plugin {
public:
ExampleOptionalCommand() {}
EventHandlerResult onFocusEvent(const char *command) {
if (strcmp_P(command, PSTR("optional")) != 0)
return EventHandlerResult::OK;
Serial.println(Layer.getLayerState(), BIN);
return EventHandlerResult::EVENT_CONSUMED;
}
};
}
KALEIDOSCOPE_INIT_PLUGINS(Focus)
void setup() {
Kaleidoscope.setup();
}
```
### Summary
The trick here is to move optional commands out into a separate plugin. It's a bit more boilerplate, but not by much.

@ -0,0 +1,86 @@
/* -*- mode: c++ -*-
* Kaleidoscope-FocusSerial -- Bidirectional communication plugin
* Copyright (C) 2017, 2018 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-FocusSerial.h>
// *INDENT-OFF*
const Key keymaps[][ROWS][COLS] PROGMEM = {
[0] = KEYMAP_STACKED
(Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
Key_skip,
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
Key_skip),
};
// *INDENT-OFF*
namespace kaleidoscope {
class FocusTestCommand : public Plugin {
public:
FocusTestCommand() {}
EventHandlerResult onFocusEvent(const char *command) {
const char *cmd = PSTR("test");
if (::Focus.handleHelp(command, cmd))
return EventHandlerResult::OK;
if (strcmp_P(command, cmd) == 0) {
Serial.println(F("ok!"));
return EventHandlerResult::EVENT_CONSUMED;
}
return EventHandlerResult::OK;
}
};
class FocusHelpCommand : public Plugin {
public:
FocusHelpCommand() {}
EventHandlerResult onFocusEvent(const char *command) {
::Focus.handleHelp(command, PSTR("help"));
return EventHandlerResult::OK;
}
};
}
kaleidoscope::FocusTestCommand FocusTestCommand;
kaleidoscope::FocusHelpCommand FocusHelpCommand;
KALEIDOSCOPE_INIT_PLUGINS(Focus, FocusTestCommand, FocusHelpCommand);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,20 @@
/* -*- mode: c++ -*-
* Kaleidoscope-FocusSerial -- Bidirectional communication plugin
* Copyright (C) 2017, 2018 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <kaleidoscope/plugin/FocusSerial.h>

@ -0,0 +1,110 @@
/* -*- mode: c++ -*-
* Kaleidoscope-FocusSerial -- Bidirectional communication plugin
* Copyright (C) 2017, 2018 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope-FocusSerial.h>
#ifdef __AVR__
#include <avr/pgmspace.h>
#endif
namespace kaleidoscope {
namespace plugin {
char FocusSerial::command_[32];
void FocusSerial::drain(void) {
if (Serial.available())
while (Serial.peek() != '\n')
Serial.read();
}
EventHandlerResult FocusSerial::beforeReportingState() {
if (Serial.available() == 0)
return EventHandlerResult::OK;
uint8_t i = 0;
do {
command_[i++] = Serial.read();
if (Serial.peek() == '\n')
break;
} while (command_[i - 1] != ' ' && i < 32);
if (command_[i - 1] == ' ')
command_[i - 1] = '\0';
else
command_[i] = '\0';
Kaleidoscope.onFocusEvent(command_);
Serial.println(F("."));
drain();
if (Serial.peek() == '\n')
Serial.read();
return EventHandlerResult::OK;
}
bool FocusSerial::handleHelp(const char *command,
const char *help_message) {
if (strcmp_P(command, PSTR("help")) != 0)
return false;
Serial.println((const __FlashStringHelper *)help_message);
return true;
}
EventHandlerResult FocusSerial::onFocusEvent(const char *command) {
handleHelp(command, PSTR("help"));
return EventHandlerResult::OK;
}
void FocusSerial::printSpace(void) {
Serial.print(F(" "));
}
void FocusSerial::printNumber(uint16_t num) {
Serial.print(num);
}
void FocusSerial::printColor(uint8_t r, uint8_t g, uint8_t b) {
printNumber(r);
printSpace();
printNumber(g);
printSpace();
printNumber(b);
}
void FocusSerial::printSeparator(void) {
Serial.print(F(" | "));
}
void FocusSerial::printBool(bool b) {
Serial.print((b) ? F("true") : F("false"));
}
void FocusSerial::readColor(cRGB &color) {
color.r = Serial.parseInt();
color.g = Serial.parseInt();
color.b = Serial.parseInt();
}
}
}
kaleidoscope::plugin::FocusSerial Focus;

@ -0,0 +1,56 @@
/* -*- mode: c++ -*-
* Kaleidoscope-FocusSerial -- Bidirectional communication plugin
* Copyright (C) 2017, 2018 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Kaleidoscope.h>
namespace kaleidoscope {
namespace plugin {
class FocusSerial : public kaleidoscope::Plugin {
public:
FocusSerial(void) {}
bool handleHelp(const char *command,
const char *help_message);
/* Helpers */
static void printNumber(uint16_t number);
static void printSpace(void);
static void printColor(uint8_t r, uint8_t g, uint8_t b);
static void printSeparator(void);
static void printBool(bool b);
static void readColor(cRGB &color);
/* Hooks */
EventHandlerResult onSetup() {
Serial.begin(9600);
return EventHandlerResult::OK;
}
EventHandlerResult beforeReportingState();
EventHandlerResult onFocusEvent(const char *command);
private:
static char command_[32];
static void drain(void);
};
}
}
extern kaleidoscope::plugin::FocusSerial Focus;
Loading…
Cancel
Save