samples: microbit/pong: Add BLE-based multiplayer support
Add support for connecting two micro:bit devices to play pong between themselves. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
parent
9a7cb7134a
commit
5e59f13ca3
5 changed files with 771 additions and 30 deletions
|
@ -2,7 +2,7 @@ CONFIG_BLUETOOTH=y
|
|||
CONFIG_BLUETOOTH_CENTRAL=y
|
||||
CONFIG_BLUETOOTH_PERIPHERAL=y
|
||||
CONFIG_BLUETOOTH_GATT_CLIENT=y
|
||||
CONFIG_BLUETOOTH_GATT_DYNAMIC_DB=n
|
||||
CONFIG_BLUETOOTH_GATT_DYNAMIC_DB=y
|
||||
CONFIG_BLUETOOTH_DEVICE_NAME="Zephyr Pong"
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_MICROBIT_DISPLAY=y
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
obj-y = main.o
|
||||
ccflags-y +=-I${ZEPHYR_BASE}/samples/bluetooth
|
||||
|
||||
obj-y = main.o ble.o ../../../../bluetooth/gatt/gap.o
|
||||
|
|
543
samples/boards/microbit/pong/src/ble.c
Normal file
543
samples/boards/microbit/pong/src/ble.c
Normal file
|
@ -0,0 +1,543 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <misc/printk.h>
|
||||
#include <board.h>
|
||||
#include <gpio.h>
|
||||
#include <device.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <display/mb_display.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/uuid.h>
|
||||
#include <bluetooth/conn.h>
|
||||
#include <bluetooth/gatt.h>
|
||||
|
||||
#include <gatt/gap.h>
|
||||
|
||||
#include "pong.h"
|
||||
|
||||
#define SCAN_TIMEOUT K_SECONDS(2)
|
||||
|
||||
#define APPEARANCE 0
|
||||
#define DEVICE_NAME CONFIG_BLUETOOTH_DEVICE_NAME
|
||||
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
|
||||
|
||||
#define PONG_SVC_UUID 0x90, 0x6c, 0x55, 0x0f, 0xee, 0x6f, 0x4d, 0x0d, \
|
||||
0xa1, 0x7e, 0x24, 0x4e, 0x38, 0xea, 0x4f, 0xf9
|
||||
#define PONG_CHR_UUID 0xdd, 0x94, 0xaf, 0xd7, 0xcd, 0x2c, 0x40, 0xc6, \
|
||||
0xb5, 0x82, 0x6a, 0xc5, 0x1c, 0x8f, 0xbf, 0xab
|
||||
|
||||
static struct bt_uuid_128 pong_svc_uuid = BT_UUID_INIT_128(PONG_SVC_UUID);
|
||||
static struct bt_uuid_128 pong_chr_uuid = BT_UUID_INIT_128(PONG_CHR_UUID);
|
||||
static struct bt_uuid_16 gatt_ccc_uuid = BT_UUID_INIT_16(BT_UUID_GATT_CCC_VAL);
|
||||
|
||||
static struct bt_gatt_discover_params discov_param;
|
||||
static struct bt_gatt_subscribe_params subscribe_param;
|
||||
|
||||
static const struct bt_data ad[] = {
|
||||
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
||||
BT_DATA_BYTES(BT_DATA_UUID128_ALL, PONG_SVC_UUID),
|
||||
};
|
||||
|
||||
static const struct bt_data sd[] = {
|
||||
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
|
||||
};
|
||||
|
||||
static struct bt_conn *default_conn;
|
||||
|
||||
static const struct bt_gatt_attr *local_attr;
|
||||
static u16_t remote_handle;
|
||||
static bool remote_ready;
|
||||
static bool initiator;
|
||||
|
||||
static struct k_delayed_work ble_work;
|
||||
|
||||
static bool connect_canceled;
|
||||
|
||||
static enum {
|
||||
BLE_DISCONNECTED,
|
||||
BLE_SCAN_START,
|
||||
BLE_SCAN,
|
||||
BLE_CONNECT_CREATE,
|
||||
BLE_CONNECT_CANCEL,
|
||||
BLE_ADV_START,
|
||||
BLE_ADVERTISING,
|
||||
BLE_CONNECTED,
|
||||
} ble_state;
|
||||
|
||||
enum {
|
||||
BLE_BALL_INFO = 0x00,
|
||||
BLE_LOST = 0x01,
|
||||
};
|
||||
|
||||
struct ble_ball_info {
|
||||
s8_t x_pos;
|
||||
s8_t y_pos;
|
||||
s8_t x_vel;
|
||||
s8_t y_vel;
|
||||
} __packed;
|
||||
|
||||
struct ble_data {
|
||||
u8_t op;
|
||||
union {
|
||||
struct ble_ball_info ball;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
#define BALL_INFO_LEN (1 + sizeof(struct ble_ball_info))
|
||||
|
||||
void ble_send_ball(s8_t x_pos, s8_t y_pos, s8_t x_vel, s8_t y_vel)
|
||||
{
|
||||
struct ble_data data = {
|
||||
.op = BLE_BALL_INFO,
|
||||
.ball.x_pos = x_pos,
|
||||
.ball.y_pos = y_pos,
|
||||
.ball.x_vel = x_vel,
|
||||
.ball.y_vel = y_vel,
|
||||
};
|
||||
int err;
|
||||
|
||||
if (!default_conn || !remote_ready) {
|
||||
printk("ble_send_ball(): not ready\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printk("ble_send_ball(%d, %d, %d, %d)\n", x_pos, y_pos, x_vel, y_vel);
|
||||
|
||||
err = bt_gatt_notify(default_conn, local_attr, &data, BALL_INFO_LEN);
|
||||
if (err) {
|
||||
printk("GATT notify failed (err %d)\n", err);
|
||||
}
|
||||
}
|
||||
|
||||
void ble_send_lost(void)
|
||||
{
|
||||
u8_t lost = BLE_LOST;
|
||||
int err;
|
||||
|
||||
if (!default_conn || !remote_ready) {
|
||||
printk("ble_send_lost(): not ready\n");
|
||||
return;
|
||||
}
|
||||
|
||||
err = bt_gatt_notify(default_conn, local_attr, &lost, sizeof(lost));
|
||||
if (err) {
|
||||
printk("GATT notify failed (err %d)\n", err);
|
||||
}
|
||||
}
|
||||
|
||||
static u8_t notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *param,
|
||||
const void *buf, u16_t len)
|
||||
{
|
||||
const struct ble_data *data = buf;
|
||||
|
||||
printk("notify_func() data %p len %u\n", data, len);
|
||||
|
||||
if (!data || !len) {
|
||||
printk("Unsubscribed, disconnecting...\n");
|
||||
remote_handle = 0;
|
||||
if (default_conn) {
|
||||
bt_conn_disconnect(default_conn,
|
||||
BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
}
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
switch (data->op) {
|
||||
case BLE_BALL_INFO:
|
||||
if (len < BALL_INFO_LEN) {
|
||||
printk("Too small ball info\n");
|
||||
break;
|
||||
}
|
||||
|
||||
pong_ball_received(data->ball.x_pos, data->ball.y_pos,
|
||||
data->ball.x_vel, data->ball.y_vel);
|
||||
break;
|
||||
case BLE_LOST:
|
||||
pong_remote_lost();
|
||||
break;
|
||||
default:
|
||||
printk("Unknown op 0x%02x\n", data->op);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
static u8_t discover_func(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
struct bt_gatt_discover_params *param)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!attr) {
|
||||
printk("Discover complete\n");
|
||||
memset(&discov_param, 0, sizeof(discov_param));
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
printk("Attribute handle %u\n", attr->handle);
|
||||
|
||||
if (param->uuid == &pong_svc_uuid.uuid) {
|
||||
printk("Pong service discovered\n");
|
||||
discov_param.uuid = &pong_chr_uuid.uuid;
|
||||
discov_param.start_handle = attr->handle + 1;
|
||||
discov_param.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
||||
|
||||
err = bt_gatt_discover(conn, &discov_param);
|
||||
if (err) {
|
||||
printk("Char Discovery failed (err %d)\n", err);
|
||||
}
|
||||
} else if (param->uuid == &pong_chr_uuid.uuid) {
|
||||
printk("Pong characteristic discovered\n");
|
||||
discov_param.uuid = &gatt_ccc_uuid.uuid;
|
||||
discov_param.start_handle = attr->handle + 2;
|
||||
discov_param.type = BT_GATT_DISCOVER_DESCRIPTOR;
|
||||
subscribe_param.value_handle = attr->handle + 1;
|
||||
|
||||
err = bt_gatt_discover(conn, &discov_param);
|
||||
if (err) {
|
||||
printk("CCC Discovery failed (err %d)\n", err);
|
||||
}
|
||||
} else {
|
||||
printk("Pong CCC discovered\n");
|
||||
|
||||
subscribe_param.notify = notify_func;
|
||||
subscribe_param.value = BT_GATT_CCC_NOTIFY;
|
||||
subscribe_param.ccc_handle = attr->handle;
|
||||
|
||||
printk("CCC handle 0x%04x Value handle 0x%04x\n",
|
||||
subscribe_param.ccc_handle,
|
||||
subscribe_param.value_handle);
|
||||
|
||||
err = bt_gatt_subscribe(conn, &subscribe_param);
|
||||
if (err && err != -EALREADY) {
|
||||
printk("Subscribe failed (err %d)\n", err);
|
||||
} else {
|
||||
printk("Subscribed\n");
|
||||
}
|
||||
|
||||
remote_handle = attr->handle;
|
||||
}
|
||||
|
||||
if (remote_handle && remote_ready) {
|
||||
pong_conn_ready(initiator);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
static void connected(struct bt_conn *conn, u8_t err)
|
||||
{
|
||||
struct bt_conn_info info;
|
||||
|
||||
if (err) {
|
||||
printk("Connection failed (err %u)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!default_conn) {
|
||||
default_conn = bt_conn_ref(conn);
|
||||
}
|
||||
|
||||
bt_conn_get_info(conn, &info);
|
||||
initiator = (info.role == BT_CONN_ROLE_MASTER);
|
||||
remote_ready = false;
|
||||
remote_handle = 0;
|
||||
|
||||
printk("Connected\n");
|
||||
ble_state = BLE_CONNECTED;
|
||||
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
}
|
||||
|
||||
static void disconnected(struct bt_conn *conn, u8_t reason)
|
||||
{
|
||||
printk("Disconnected (reason %u)\n", reason);
|
||||
|
||||
if (default_conn) {
|
||||
bt_conn_unref(default_conn);
|
||||
default_conn = NULL;
|
||||
}
|
||||
|
||||
remote_handle = 0;
|
||||
|
||||
if (ble_state == BLE_CONNECTED) {
|
||||
ble_state = BLE_DISCONNECTED;
|
||||
pong_remote_disconnected();
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_conn_cb conn_callbacks = {
|
||||
.connected = connected,
|
||||
.disconnected = disconnected,
|
||||
};
|
||||
|
||||
void ble_connect(void)
|
||||
{
|
||||
if (ble_state != BLE_DISCONNECTED) {
|
||||
printk("Not ready to connect\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ble_state = BLE_SCAN_START;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
}
|
||||
|
||||
void ble_cancel_connect(void)
|
||||
{
|
||||
printk("ble_cancel_connect()\n");
|
||||
|
||||
k_delayed_work_cancel(&ble_work);
|
||||
|
||||
switch (ble_state) {
|
||||
case BLE_DISCONNECTED:
|
||||
break;
|
||||
case BLE_SCAN_START:
|
||||
ble_state = BLE_DISCONNECTED;
|
||||
break;
|
||||
case BLE_SCAN:
|
||||
connect_canceled = true;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
break;
|
||||
case BLE_ADV_START:
|
||||
ble_state = BLE_DISCONNECTED;
|
||||
break;
|
||||
case BLE_ADVERTISING:
|
||||
connect_canceled = true;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
break;
|
||||
case BLE_CONNECT_CREATE:
|
||||
ble_state = BLE_CONNECT_CANCEL;
|
||||
/* Intentional fall-through */
|
||||
case BLE_CONNECTED:
|
||||
connect_canceled = true;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
break;
|
||||
case BLE_CONNECT_CANCEL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool pong_uuid_match(const u8_t *data, u8_t len)
|
||||
{
|
||||
while (len >= 16) {
|
||||
if (!memcmp(data, pong_svc_uuid.val, 16)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
len -= 16;
|
||||
data += 16;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void create_conn(const bt_addr_le_t *addr)
|
||||
{
|
||||
if (default_conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
printk("Found matching device, initiating connection...\n");
|
||||
|
||||
default_conn = bt_conn_create_le(addr, BT_LE_CONN_PARAM_DEFAULT);
|
||||
if (!default_conn) {
|
||||
printk("Failed to initiate connection");
|
||||
return;
|
||||
}
|
||||
|
||||
ble_state = BLE_CONNECT_CREATE;
|
||||
k_delayed_work_submit(&ble_work, SCAN_TIMEOUT);
|
||||
}
|
||||
|
||||
static void device_found(const bt_addr_le_t *addr, s8_t rssi, u8_t type,
|
||||
struct net_buf_simple *ad)
|
||||
{
|
||||
if (type != BT_LE_ADV_IND) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (ad->len > 1) {
|
||||
u8_t len = net_buf_simple_pull_u8(ad);
|
||||
u8_t type;
|
||||
|
||||
/* Check for early termination */
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > ad->len || ad->len < 1) {
|
||||
printk("AD malformed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
type = net_buf_simple_pull_u8(ad);
|
||||
if (type == BT_DATA_UUID128_ALL &&
|
||||
pong_uuid_match(ad->data, len - 1)) {
|
||||
bt_le_scan_stop();
|
||||
create_conn(addr);
|
||||
return;
|
||||
}
|
||||
|
||||
net_buf_simple_pull(ad, len - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static u32_t adv_timeout(void)
|
||||
{
|
||||
u32_t timeout;
|
||||
|
||||
if (bt_rand(&timeout, sizeof(timeout) < 0)) {
|
||||
return K_SECONDS(10);
|
||||
}
|
||||
|
||||
timeout %= K_SECONDS(10);
|
||||
|
||||
return timeout + K_SECONDS(1);
|
||||
}
|
||||
|
||||
static void cancel_connect(void)
|
||||
{
|
||||
connect_canceled = false;
|
||||
|
||||
switch (ble_state) {
|
||||
case BLE_SCAN:
|
||||
bt_le_scan_stop();
|
||||
break;
|
||||
case BLE_ADVERTISING:
|
||||
bt_le_adv_stop();
|
||||
break;
|
||||
case BLE_CONNECT_CREATE:
|
||||
case BLE_CONNECTED:
|
||||
bt_conn_disconnect(default_conn,
|
||||
BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* For CONNECTED the state will be updated in the disconnected cb */
|
||||
if (ble_state != BLE_CONNECTED) {
|
||||
ble_state = BLE_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
static void ble_timeout(struct k_work *work)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (connect_canceled) {
|
||||
cancel_connect();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ble_state) {
|
||||
case BLE_DISCONNECTED:
|
||||
break;
|
||||
case BLE_SCAN_START:
|
||||
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
|
||||
if (err) {
|
||||
printk("Scanning failed to start (err %d)\n", err);
|
||||
}
|
||||
|
||||
printk("Started scanning for devices\n");
|
||||
ble_state = BLE_SCAN;
|
||||
k_delayed_work_submit(&ble_work, SCAN_TIMEOUT);
|
||||
break;
|
||||
case BLE_CONNECT_CREATE:
|
||||
printk("Connection attempt timed out\n");
|
||||
bt_conn_disconnect(default_conn,
|
||||
BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
ble_state = BLE_ADV_START;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
break;
|
||||
case BLE_SCAN:
|
||||
printk("No devices found during scan\n");
|
||||
bt_le_scan_stop();
|
||||
ble_state = BLE_ADV_START;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
break;
|
||||
case BLE_ADV_START:
|
||||
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
|
||||
sd, ARRAY_SIZE(sd));
|
||||
if (err) {
|
||||
printk("Advertising failed to start (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
printk("Advertising successfully started\n");
|
||||
ble_state = BLE_ADVERTISING;
|
||||
k_delayed_work_submit(&ble_work, adv_timeout());
|
||||
break;
|
||||
case BLE_ADVERTISING:
|
||||
printk("Timed out advertising\n");
|
||||
bt_le_adv_stop();
|
||||
ble_state = BLE_SCAN_START;
|
||||
k_delayed_work_submit(&ble_work, K_NO_WAIT);
|
||||
break;
|
||||
case BLE_CONNECTED:
|
||||
discov_param.uuid = &pong_svc_uuid.uuid;
|
||||
discov_param.func = discover_func;
|
||||
discov_param.start_handle = 0x0001;
|
||||
discov_param.end_handle = 0xffff;
|
||||
discov_param.type = BT_GATT_DISCOVER_PRIMARY;
|
||||
|
||||
err = bt_gatt_discover(default_conn, &discov_param);
|
||||
if (err) {
|
||||
printk("Discover failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case BLE_CONNECT_CANCEL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_gatt_ccc_cfg pong_ccc_cfg[CONFIG_BLUETOOTH_MAX_PAIRED];
|
||||
|
||||
static void pong_ccc_cfg_changed(const struct bt_gatt_attr *attr, u16_t val)
|
||||
{
|
||||
printk("val %u\n", val);
|
||||
|
||||
remote_ready = (val == BT_GATT_CCC_NOTIFY);
|
||||
|
||||
if (remote_ready && remote_handle) {
|
||||
pong_conn_ready(initiator);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_gatt_attr pong_attrs[] = {
|
||||
/* Vendor Primary Service Declaration */
|
||||
BT_GATT_PRIMARY_SERVICE(&pong_svc_uuid.uuid),
|
||||
BT_GATT_CHARACTERISTIC(&pong_chr_uuid.uuid, BT_GATT_CHRC_NOTIFY),
|
||||
BT_GATT_DESCRIPTOR(&pong_chr_uuid.uuid, BT_GATT_PERM_NONE,
|
||||
NULL, NULL, NULL),
|
||||
BT_GATT_CCC(pong_ccc_cfg, pong_ccc_cfg_changed),
|
||||
};
|
||||
|
||||
void ble_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_enable(NULL);
|
||||
if (err) {
|
||||
printk("Enabling Bluetooth failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
k_delayed_work_init(&ble_work, ble_timeout);
|
||||
|
||||
bt_conn_cb_register(&conn_callbacks);
|
||||
|
||||
gap_init(DEVICE_NAME, APPEARANCE);
|
||||
|
||||
local_attr = &pong_attrs[2];
|
||||
bt_gatt_register(pong_attrs, ARRAY_SIZE(pong_attrs));
|
||||
}
|
|
@ -15,8 +15,7 @@
|
|||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
|
||||
/* Define this to do a single-device game */
|
||||
#define SOLO 1
|
||||
#include "pong.h"
|
||||
|
||||
/* The micro:bit has a 5x5 LED display, using (x, y) notation the top-left
|
||||
* corner has coordinates (0, 0) and the bottom-right has (4, 4). To make
|
||||
|
@ -24,6 +23,8 @@
|
|||
* system where top-left is (0, 0) and bottom-right is (49, 49).
|
||||
*/
|
||||
|
||||
#define SCROLL_SPEED K_MSEC(400) /* Text scrolling speed */
|
||||
|
||||
#define PIXEL_SIZE 10 /* Virtual coordinates per real pixel */
|
||||
|
||||
#define GAME_REFRESH K_MSEC(100) /* Animation refresh rate of the game */
|
||||
|
@ -40,7 +41,7 @@
|
|||
#define BALL_POS_Y_MAX 39 /* Maximum ball Y coordinate */
|
||||
|
||||
#define START_THRESHOLD K_MSEC(100) /* Max time between A & B press */
|
||||
#define RESTART_THRESHOLD K_SECONDS(3) /* Time before restart is allowed */
|
||||
#define RESTART_THRESHOLD K_SECONDS(2) /* Time before restart is allowed */
|
||||
|
||||
#define REAL_TO_VIRT(r) ((r) * 10)
|
||||
#define VIRT_TO_REAL(v) ((v) / 10)
|
||||
|
@ -53,6 +54,35 @@ struct x_y {
|
|||
int y;
|
||||
};
|
||||
|
||||
enum pong_state {
|
||||
INIT,
|
||||
MULTI,
|
||||
SINGLE,
|
||||
CONNECTED,
|
||||
};
|
||||
|
||||
static enum pong_state state = INIT;
|
||||
|
||||
struct pong_choice {
|
||||
int val;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
struct pong_selection {
|
||||
const struct pong_choice *choice;
|
||||
size_t choice_count;
|
||||
void (*complete)(int val);
|
||||
};
|
||||
|
||||
static int select_idx;
|
||||
static const struct pong_selection *select;
|
||||
|
||||
static const struct pong_choice mode_choice[] = {
|
||||
{ SINGLE, "Single" },
|
||||
{ MULTI, "Multi" },
|
||||
};
|
||||
|
||||
static bool remote_lost;
|
||||
static bool started;
|
||||
static s64_t ended;
|
||||
|
||||
|
@ -73,6 +103,88 @@ static struct x_y ball_vel = { 0, 0 };
|
|||
static s64_t a_timestamp;
|
||||
static s64_t b_timestamp;
|
||||
|
||||
static void pong_select(const struct pong_selection *sel)
|
||||
{
|
||||
struct mb_display *disp = mb_display_get();
|
||||
|
||||
if (select) {
|
||||
printk("Other selection still busy\n");
|
||||
return;
|
||||
}
|
||||
|
||||
select = sel;
|
||||
select_idx = 0;
|
||||
|
||||
mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
|
||||
SCROLL_SPEED, "%s", select->choice[select_idx].str);
|
||||
}
|
||||
|
||||
static void pong_select_change(void)
|
||||
{
|
||||
struct mb_display *disp = mb_display_get();
|
||||
|
||||
select_idx = (select_idx + 1) % select->choice_count;
|
||||
mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
|
||||
SCROLL_SPEED, "%s", select->choice[select_idx].str);
|
||||
}
|
||||
|
||||
static void pong_select_complete(void)
|
||||
{
|
||||
struct mb_display *disp = mb_display_get();
|
||||
void (*complete)(int val) = select->complete;
|
||||
int val = select->choice[select_idx].val;
|
||||
|
||||
mb_display_stop(disp);
|
||||
|
||||
select = NULL;
|
||||
complete(val);
|
||||
}
|
||||
|
||||
static void game_init(bool initiator)
|
||||
{
|
||||
started = false;
|
||||
ended = 0;
|
||||
|
||||
ball_pos = BALL_START;
|
||||
if (!initiator) {
|
||||
ball_pos.y = -1;
|
||||
}
|
||||
|
||||
paddle_x = PADDLE_MIN;
|
||||
|
||||
a_timestamp = 0;
|
||||
b_timestamp = 0;
|
||||
}
|
||||
|
||||
static void mode_selected(int val)
|
||||
{
|
||||
struct mb_display *disp = mb_display_get();
|
||||
|
||||
state = val;
|
||||
|
||||
switch (state) {
|
||||
case SINGLE:
|
||||
game_init(true);
|
||||
k_sem_give(&disp_update);
|
||||
break;
|
||||
case MULTI:
|
||||
ble_connect();
|
||||
mb_display_print(disp,
|
||||
MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
|
||||
SCROLL_SPEED, "Connecting...");
|
||||
break;
|
||||
default:
|
||||
printk("Unknown state %d\n", state);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
static const struct pong_selection mode_selection = {
|
||||
.choice = mode_choice,
|
||||
.choice_count = ARRAY_SIZE(mode_choice),
|
||||
.complete = mode_selected,
|
||||
};
|
||||
|
||||
static bool ball_visible(void)
|
||||
{
|
||||
return (ball_pos.y >= BALL_POS_Y_MIN);
|
||||
|
@ -111,43 +223,63 @@ static void check_start(void)
|
|||
}
|
||||
|
||||
started = true;
|
||||
remote_lost = false;
|
||||
k_delayed_work_submit(&refresh, K_NO_WAIT);
|
||||
}
|
||||
|
||||
static void game_ended(bool won)
|
||||
{
|
||||
struct mb_display *disp = mb_display_get();
|
||||
const char *str;
|
||||
|
||||
remote_lost = won;
|
||||
ended = k_uptime_get();
|
||||
started = false;
|
||||
|
||||
if (won) {
|
||||
str = "You won!";
|
||||
struct mb_image img = MB_IMAGE({ 0, 1, 0, 1, 0 },
|
||||
{ 0, 1, 0, 1, 0 },
|
||||
{ 0, 0, 0, 0, 0 },
|
||||
{ 1, 0, 0, 0, 1 },
|
||||
{ 0, 1, 1, 1, 0 });
|
||||
mb_display_image(disp, MB_DISPLAY_MODE_SINGLE,
|
||||
RESTART_THRESHOLD, &img, 1);
|
||||
printk("You won!\n");
|
||||
} else {
|
||||
str = "You lost!";
|
||||
struct mb_image img = MB_IMAGE({ 0, 1, 0, 1, 0 },
|
||||
{ 0, 1, 0, 1, 0 },
|
||||
{ 0, 0, 0, 0, 0 },
|
||||
{ 0, 1, 1, 1, 0 },
|
||||
{ 1, 0, 0, 0, 1 });
|
||||
mb_display_image(disp, MB_DISPLAY_MODE_SINGLE,
|
||||
RESTART_THRESHOLD, &img, 1);
|
||||
printk("You lost!\n");
|
||||
}
|
||||
|
||||
printk("%s\n", str);
|
||||
|
||||
mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
|
||||
K_MSEC(500), "%s", str);
|
||||
k_delayed_work_submit(&refresh, RESTART_THRESHOLD);
|
||||
}
|
||||
|
||||
static void game_refresh(struct k_work *work)
|
||||
{
|
||||
if (ended) {
|
||||
game_init(state == SINGLE || remote_lost);
|
||||
k_sem_give(&disp_update);
|
||||
return;
|
||||
}
|
||||
|
||||
ball_pos.x += ball_vel.x;
|
||||
ball_pos.y += ball_vel.y;
|
||||
|
||||
/* Ball went over to the other side */
|
||||
if (ball_pos.y < BALL_POS_Y_MIN) {
|
||||
#if defined(SOLO)
|
||||
ball_pos.y = -ball_pos.y;
|
||||
ball_vel.y = -ball_vel.y;
|
||||
#else
|
||||
k_sem_give(&disp_update);
|
||||
return;
|
||||
#endif
|
||||
if (ball_vel.y < 0 && ball_pos.y < BALL_POS_Y_MIN) {
|
||||
if (state == SINGLE) {
|
||||
ball_pos.y = -ball_pos.y;
|
||||
ball_vel.y = -ball_vel.y;
|
||||
} else {
|
||||
ble_send_ball(BALL_POS_X_MAX - ball_pos.x, ball_pos.y,
|
||||
-ball_vel.x, -ball_vel.y);
|
||||
k_sem_give(&disp_update);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for side-wall collision */
|
||||
|
@ -164,10 +296,19 @@ static void game_refresh(struct k_work *work)
|
|||
if (ball_pos.x < REAL_TO_VIRT(paddle_x) ||
|
||||
ball_pos.x >= REAL_TO_VIRT(paddle_x + 2)) {
|
||||
game_ended(false);
|
||||
if (state == CONNECTED) {
|
||||
ble_send_lost();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ball_pos.y = (2 * BALL_POS_Y_MAX) - ball_pos.y;
|
||||
|
||||
/* Make the game play gradually harder */
|
||||
if (ball_vel.y < PIXEL_SIZE) {
|
||||
ball_vel.y++;
|
||||
}
|
||||
|
||||
ball_vel.y = -ball_vel.y;
|
||||
}
|
||||
|
||||
|
@ -175,30 +316,43 @@ static void game_refresh(struct k_work *work)
|
|||
k_sem_give(&disp_update);
|
||||
}
|
||||
|
||||
static void game_init(void)
|
||||
void pong_ball_received(s8_t x_pos, s8_t y_pos, s8_t x_vel, s8_t y_vel)
|
||||
{
|
||||
ended = 0;
|
||||
printk("ball_received(%d, %d, %d, %d)\n", x_pos, y_pos, x_vel, y_vel);
|
||||
|
||||
ball_pos = BALL_START;
|
||||
paddle_x = PADDLE_MIN;
|
||||
ball_pos.x = x_pos;
|
||||
ball_pos.y = y_pos;
|
||||
ball_vel.x = x_vel;
|
||||
ball_vel.y = y_vel;
|
||||
|
||||
a_timestamp = 0;
|
||||
b_timestamp = 0;
|
||||
|
||||
k_sem_give(&disp_update);
|
||||
k_delayed_work_submit(&refresh, K_NO_WAIT);
|
||||
}
|
||||
|
||||
static void button_pressed(struct device *dev, struct gpio_callback *cb,
|
||||
u32_t pins)
|
||||
{
|
||||
if (ended && (k_uptime_get() - ended) > RESTART_THRESHOLD) {
|
||||
game_init();
|
||||
k_delayed_work_cancel(&refresh);
|
||||
game_init(state == SINGLE || remote_lost);
|
||||
k_sem_give(&disp_update);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == MULTI) {
|
||||
ble_cancel_connect();
|
||||
state = INIT;
|
||||
pong_select(&mode_selection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pins & BIT(SW0_GPIO_PIN)) {
|
||||
printk("A pressed\n");
|
||||
|
||||
if (select) {
|
||||
pong_select_change();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
a_timestamp = k_uptime_get();
|
||||
check_start();
|
||||
|
@ -215,6 +369,11 @@ static void button_pressed(struct device *dev, struct gpio_callback *cb,
|
|||
} else {
|
||||
printk("B pressed\n");
|
||||
|
||||
if (select) {
|
||||
pong_select_complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
b_timestamp = k_uptime_get();
|
||||
check_start();
|
||||
|
@ -231,6 +390,25 @@ static void button_pressed(struct device *dev, struct gpio_callback *cb,
|
|||
}
|
||||
}
|
||||
|
||||
void pong_conn_ready(bool initiator)
|
||||
{
|
||||
state = CONNECTED;
|
||||
game_init(initiator);
|
||||
k_sem_give(&disp_update);
|
||||
}
|
||||
|
||||
void pong_remote_disconnected(void)
|
||||
{
|
||||
state = INIT;
|
||||
pong_select(&mode_selection);
|
||||
}
|
||||
|
||||
void pong_remote_lost(void)
|
||||
{
|
||||
printk("Remote lost!\n");
|
||||
game_ended(true);
|
||||
}
|
||||
|
||||
static void configure_buttons(void)
|
||||
{
|
||||
static struct gpio_callback button_cb;
|
||||
|
@ -260,7 +438,9 @@ void main(void)
|
|||
|
||||
k_delayed_work_init(&refresh, game_refresh);
|
||||
|
||||
game_init();
|
||||
ble_init();
|
||||
|
||||
pong_select(&mode_selection);
|
||||
|
||||
printk("Started\n");
|
||||
|
||||
|
|
16
samples/boards/microbit/pong/src/pong.h
Normal file
16
samples/boards/microbit/pong/src/pong.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
void pong_ball_received(s8_t x_pos, s8_t y_pos, s8_t x_vel, s8_t y_vel);
|
||||
void pong_conn_ready(bool initiator);
|
||||
void pong_remote_disconnected(void);
|
||||
void pong_remote_lost(void);
|
||||
|
||||
void ble_send_ball(s8_t x_pos, s8_t y_pos, s8_t x_vel, s8_t y_vel);
|
||||
void ble_send_lost(void);
|
||||
void ble_connect(void);
|
||||
void ble_cancel_connect(void);
|
||||
void ble_init(void);
|
Loading…
Add table
Add a link
Reference in a new issue