Interacting with Bluefruit

The last thing we need to do to complete our sketch is to implement the function that will allow us to control the Bluefruit module. In the keymap, we defined 3 function keys. For each of these function keys, we defined an associated action in the "FN_ACTIONS" array:
FN_ACTIONS = {
    ACTION_FUNCTION(0),
    ACTION_FUNCTION(1),
    ACTION_FUNCTION(2),
};
The ACTION_FUNCTION macro tells the TMK framework that there should be a function called action_function defined in the sketch, and that when the given FN key is pressed or released the corresponding index should be passed into that function, along with metadata about the event (such as if the key was pressed or released).

Therefore, we can define our action function like this:
void action_function(keyrecord_t *record, uint8_t id, uint8_t opt)
{
    bool pressed = record->event.pressed;
    dprint("== action function called"); dprintln();
    dprint("=  id:      "); debug_dec(id); dprintln();
    dprint("=  pressed: "); debug_dec(record->event.pressed); dprintln();
    if (id == 0) {
        if (pressed) {
            layer_on(1);
        } else {
            layer_clear();
        }
    } else if (id == 1) {  // bluefruit pair button
        if (pressed) {
            dprintf("= setting pair button HIGH\n");
            digitalWrite(PAIR_BUTTON_PIN, HIGH);
        } else {
            dprintf("= setting pair button LOW\n");
            digitalWrite(PAIR_BUTTON_PIN, LOW);
        }
    } else if (id == 2) {
        if (pressed) {
            if (reset_press_time == 0) {
                reset_press_time = timer_read();
            }
        } else {
            reset_press_time = 0;
        }
    }
    dprint("== end of action function\n");
}
The logic here is pretty simple; the id variable that is passed represents the index that was defined for the given function key in FN_ACTIONS, so we just have an if-else structure to react to that appropriately. Pressing or releasing FN0 toggles the second layer, holding down FN1 for 5 seconds resets the pairing of the Bluefruit, and holding down FN2 for 5 seconds resets the module. You can implement any sort of arbitrary logic in this function, just try to make sure it returns quickly so that your keyboard is nice and responsive!

Since we're on the subject of interacting with Bluefruit, lets take a quick look at the BluefruitHost object:
void BluefruitHost::begin()
{
    Serial1.begin(9600);
}

uint8_t BluefruitHost::getLEDs()
{
    // not implemented on Bluefruit; method is virtual so feel free to override
    return 0;
}

void BluefruitHost::sendKeyboard(KeyboardReport &report)
{
    bluefruit_trace_header();
    dprintf("(keyboard) ");
    _serial_send(0xFD);
    _serial_send(report.getModifiers());
    _serial_send(report.getReserved());
    for (short i = 0; i < REPORT_SIZE; i++)
    {
        _serial_send(report.getKey(i));
    }
    bluefruit_trace_footer();
}

void BluefruitHost::sendMouse(MouseReport &report)
{
    bluefruit_trace_header();
    dprintf("(mouse) ");
    _serial_send(0xFD);
    _serial_send(0x00);
    _serial_send(0x03);
    _serial_send(report.getButtons());
    _serial_send(report.getX());
    _serial_send(report.getY());
    _serial_send(report.getV()); // TODO: determine if bluefruit 
    _serial_send(report.getH()); //       supports mouse wheel - BCG
    _serial_send(0x00);
    bluefruit_trace_footer();
};
Here we see that TMK is sending a keyboard and mouse report to the host object. As it happens, the format of the reports is basically exactly the same information that Bluefruit can consume over its serial connection. Starting with Bluefruit 1.1, you can send "raw HID" report to the Bluefruit module, enabling you to send state for up to 6 keys + modifiers, as well as mouse reports - that's exactly what the above code is doing.

In Bluefruit 1.2, support for "consumer keys" was added. You can also send this information over serial, allow it is a little trickier. The data that Bluefruit expects is a 16 bit map to denote which key is pressed. Some of the consumer key functions do not map to existing TMK keycodes, so I've ignored those; similarly some of the defined TMK consumer keys are not supported by Bluefruit, so I've ignored those as well. Feel free to change them to suit your purposes.
/*
+-----------------+-------------------+-------+
| Consumer Key    | Bit Map           | Hex   |
+-----------------+-------------------+-------+
| Home            | 00000001 00000000 | 01 00 |
| KeyboardLayout  | 00000010 00000000 | 02 00 |
| Search          | 00000100 00000000 | 04 00 |
| Snapshot        | 00001000 00000000 | 08 00 |
| VolumeUp        | 00010000 00000000 | 10 00 |
| VolumeDown      | 00100000 00000000 | 20 00 |
| Play/Pause      | 01000000 00000000 | 40 00 |
| Fast Forward    | 10000000 00000000 | 80 00 |
| Rewind          | 00000000 00000001 | 00 01 |
| Scan Next Track | 00000000 00000010 | 00 02 |
| Scan Prev Track | 00000000 00000100 | 00 04 |
| Random Play     | 00000000 00001000 | 00 08 |
| Stop            | 00000000 00010000 | 00 10 |
+-------------------------------------+-------+
*/
#define CONSUMER2BLUEFRUIT(usage) \
    (usage == AUDIO_MUTE           ? 0x0000  : \
    (usage == AUDIO_VOL_UP         ? 0x1000  : \
    (usage == AUDIO_VOL_DOWN       ? 0x2000  : \
    (usage == TRANSPORT_NEXT_TRACK ? 0x0002  : \
    (usage == TRANSPORT_PREV_TRACK ? 0x0004  : \
    (usage == TRANSPORT_STOP       ? 0x0010  : \
    (usage == TRANSPORT_STOP_EJECT ? 0x0000  : \
    (usage == TRANSPORT_PLAY_PAUSE ? 0x4000  : \
    (usage == AL_CC_CONFIG         ? 0x0000  : \
    (usage == AL_EMAIL             ? 0x0000  : \
    (usage == AL_CALCULATOR        ? 0x0000  : \
    (usage == AL_LOCAL_BROWSER     ? 0x0000  : \
    (usage == AC_SEARCH            ? 0x0400  : \
    (usage == AC_HOME              ? 0x0100  : \
    (usage == AC_BACK              ? 0x0000  : \
    (usage == AC_FORWARD           ? 0x0000  : \
    (usage == AC_STOP              ? 0x0000  : \
    (usage == AC_REFRESH           ? 0x0000  : \
    (usage == AC_BOOKMARKS         ? 0x0000  : 0)))))))))))))))))))
void BluefruitHost::sendConsumer(uint16_t data)
{
    if (data == _last_consumer_data) return;
    _last_consumer_data = data;
    
    uint16_t bitmap = CONSUMER2BLUEFRUIT(data);
    bluefruit_trace_header();
    dprintf("(consumer) ");
    _serial_send(0xFD);
    _serial_send(0x00);
    _serial_send(0x02);
    _serial_send((bitmap>>8)&0xFF);
    _serial_send(bitmap&0xFF); 
    _serial_send(0x00);
    _serial_send(0x00);
    _serial_send(0x00);
    _serial_send(0x00);
    bluefruit_trace_footer();
};

void BluefruitHost::sendSystem(uint16_t data)
{
    // not implemented in Bluefruit
}
The sendSystem() function of the host normally would be used to send "system" commands, for example to put to the computer to sleep or to wake it up from suspend... however Bluefruit does not support this type of HID profile so it is ignored in our code.
At this point, we've touched on all the major aspects of the code for this project, so after you've updated the keymaps and whatever else you'd like to customize, go ahead and compile it and load it on to your Arduino Micro to prepare to assemble and test the keyboard.
Last updated on 2015-05-04 at 04.25.25 PM Published on 2014-05-06 at 10.06.42 PM