summaryrefslogtreecommitdiff
path: root/r2d2.c
diff options
context:
space:
mode:
Diffstat (limited to 'r2d2.c')
-rw-r--r--r2d2.c264
1 files changed, 264 insertions, 0 deletions
diff --git a/r2d2.c b/r2d2.c
new file mode 100644
index 0000000..07fcf6e
--- /dev/null
+++ b/r2d2.c
@@ -0,0 +1,264 @@
+/*
+ r2d2 - interact with Sphero R2-D2
+ Copyright (C) 2020 Ian Cowburn
+
+ 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, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <sys/socket.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+
+typedef struct
+{
+ const char *name;
+ int command[20];
+} Command;
+
+static Command plain_commands[] =
+{
+ {"laugh", {0x0A,0x18,0x00,0x1F,0x00,0x32,0x00,0x00,0x00,0x00,0x00,-1}},
+ {"yes", {0x0A,0x17,0x05,0x41,0x00,0x0F,-1}},
+ {"no", {0x0A,0x17,0x05,0x3F,0x00,0x10,-1}},
+ {"alarm", {0x0A,0x17,0x05,0x17,0x00,0x07,-1}},
+ {"angry", {0x0A,0x17,0x05,0x18,0x00,0x08,-1}},
+ {"annoyed", {0x0A,0x17,0x05,0x19,0x00,0x09,-1}},
+ {"ionblast", {0x0A,0x17,0x05,0x1A,0x00,0x0E,-1}},
+ {"sad", {0x0A,0x17,0x05,0x1C,0x00,0x11,-1}},
+ {"scared", {0x0A,0x17,0x05,0x1D,0x00,0x13,-1}},
+ {"chatty", {0x0A,0x17,0x05,0x17,0x00,0x0A,-1}},
+ {"confident", {0x0A,0x17,0x05,0x18,0x00,0x12,-1}},
+ {"excited", {0x0A,0x17,0x05,0x19,0x00,0x0C,-1}},
+ {"happy", {0x0A,0x17,0x05,0x1A,0x00,0x0D,-1}},
+ {"laugh", {0x0A,0x17,0x05,0x1B,0x00,0x0F,-1}},
+ {"surprise", {0x0A,0x17,0x05,0x1C,0x00,0x18,-1}},
+ {"tripod", {0x0A,0x17,0x0D,0x1D,0x01,-1}},
+ {"bipod", {0x0A,0x17,0x0D,0x1C,0x02,-1}},
+ {NULL, {0}}
+};
+
+static unsigned char force_command[] =
+{
+ 0x75,0x73,0x65,0x74,0x68,0x65,0x66,0x6F,0x72,0x63,0x65,0x2E,
+ 0x2E,0x2E,0x62,0x61,0x6E,0x64
+};
+
+static Command wake_command =
+{
+ "wakeup",
+ {
+ 0x0A,0x13,0x0D,0x00,-1
+ }
+};
+
+static void Fatal(const char *call)
+{
+ perror(call);
+ exit(EXIT_FAILURE);
+}
+
+static unsigned char Checksum(const char *p, int len)
+{
+ unsigned char c;
+ int f;
+
+ c = 0;
+
+ for(f = 0; f < len; f++)
+ {
+ c += p[f];
+ }
+
+ c = ~c;
+
+ return c;
+}
+
+static int Write(int sock, unsigned char *buff, int len)
+{
+ int wr = 0;
+ int tot = 0;
+
+ while(tot < len)
+ {
+ wr = write(sock, buff + tot, len - tot);
+
+ if (wr < 1)
+ {
+ return -1;
+ }
+
+ tot += wr;
+ }
+
+ return len;
+}
+
+static void CharWriteHandle(int sock, int handle,
+ const unsigned char *cmd, int len)
+{
+ unsigned char buff[1024];
+ unsigned short *us;
+
+ memcpy(buff + 3, cmd, len);
+ len += 3;
+
+ buff[0] = 0x12;
+
+ us = (unsigned short *)(buff + 1);
+ *us = htobs(handle);
+
+ if (Write(sock, buff, len) == -1)
+ {
+ Fatal("write");
+ }
+}
+
+static void SendCommand(int sock, int handle, const Command *cmd)
+{
+ unsigned char buff[1024];
+ unsigned short *us;
+ int len;
+ int f;
+
+ for(f = 0; cmd->command[f] != -1; f++)
+ {
+ buff[f] = (unsigned char)cmd->command[f];
+ }
+
+ len = f;
+
+ buff[len] = Checksum(buff, len);
+ len++;
+
+ memmove(buff + 1, buff, len++);
+ buff[0] = 0x8du;
+ buff[++len] = 0xd8u;
+
+ CharWriteHandle(sock, handle, buff, len);
+}
+
+int main(int argc, char *argv[])
+{
+ char address[32] = {0};
+ struct sockaddr_l2 src_addr = {0};
+ struct sockaddr_l2 dest_addr = {0};
+ int base;
+ int f;
+ int sock;
+
+ /* Get address
+ */
+ if (getenv("R2D2"))
+ {
+ snprintf(address, sizeof address, "%s", getenv("R2D2"));
+ }
+
+ if (argc > 2 && strcmp(argv[1], "-a") == 0)
+ {
+ snprintf(address, sizeof address, "%s", argv[2]);
+ base = 3;
+ }
+ else
+ {
+ base = 1;
+ }
+
+ /* Check address and arguments supplied
+ */
+ if (!address[0])
+ {
+ fprintf(stderr, "%s: pass address or set R2D2 variable\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (argc <= base)
+ {
+ fprintf(stderr, "%s: usage %s [-a address] command [..command]\n",
+ argv[0], argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ /* Open connection to R2
+ */
+ sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+
+ if (sock == -1)
+ {
+ Fatal("socket");
+ }
+
+ src_addr.l2_family = AF_BLUETOOTH;
+ src_addr.l2_cid = htobs(4);
+ bacpy(&src_addr.l2_bdaddr, BDADDR_ANY);
+ src_addr.l2_bdaddr_type = BDADDR_LE_RANDOM;
+
+ if (bind(sock, (void *)&src_addr, sizeof(src_addr)) == -1)
+ {
+ Fatal("bind");
+ }
+
+ dest_addr.l2_family = AF_BLUETOOTH;
+ dest_addr.l2_cid = htobs(4);
+ str2ba(address, &dest_addr.l2_bdaddr);
+ dest_addr.l2_bdaddr_type = BDADDR_LE_RANDOM;
+
+ if (connect(sock, (void *)&dest_addr, sizeof(dest_addr)) == -1)
+ {
+ Fatal("connect");
+ }
+
+ CharWriteHandle(sock, 0x15, force_command, sizeof force_command);
+ SendCommand(sock, 0x1c, &wake_command);
+
+ for(f = base; f < argc; f++)
+ {
+ int n;
+
+ Command *cmd = NULL;
+
+ for(n = 0; plain_commands[n].name && !cmd; n++)
+ {
+ if (strcmp(argv[f], plain_commands[n].name) == 0)
+ {
+ cmd = plain_commands + n;
+ }
+ }
+
+ if (cmd == NULL)
+ {
+ fprintf(stderr, "%s: unknown command %s\n", argv[0], argv[f]);
+ }
+ else
+ {
+ SendCommand(sock, 0x1c, cmd);
+ }
+ }
+
+ /* All done
+ */
+ if (close(sock) == -1)
+ {
+ Fatal("close");
+ }
+
+ return EXIT_SUCCESS;
+}