Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.pyc
__pycache__
bluepy/*.o
bluepy/version.?
#bluepy/bluepy-helper
# eclipse project related files
.settings/
Expand Down
46 changes: 41 additions & 5 deletions bluepy/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ BLUEZ_SRCS += src/shared/io-glib.c src/shared/timeout-glib.c

IMPORT_SRCS = $(addprefix $(BLUEZ_PATH)/, $(BLUEZ_SRCS))
LOCAL_SRCS = bluepy-helper.c
VERSION_SRCS = version.c

IMPORT_OBJS = $(IMPORT_SRCS:.c=.o)
LOCAL_OBJS = $(LOCAL_SRCS:.c=.o)
VERSION_OBJS = $(VERSION_SRCS:.c=.o)

CC ?= gcc
CFLAGS += -Os -g -Wall # -Werror
CFLAGS += -g -Wall # -Werror

CPPFLAGS += -DHAVE_CONFIG_H
ifneq ($(DEBUGGING),)
CFLAGS += -DBLUEPY_DEBUG=1
CFLAGS += -DBLUEPY_DEBUG=1 -O0
else
CFLAGS += -Os
endif

CPPFLAGS += -I$(BLUEZ_PATH)/attrib -I$(BLUEZ_PATH) -I$(BLUEZ_PATH)/lib -I$(BLUEZ_PATH)/src -I$(BLUEZ_PATH)/gdbus -I$(BLUEZ_PATH)/btio -I$(BLUEZ_PATH)/sys
Expand All @@ -24,8 +31,37 @@ LDLIBS += $(shell pkg-config glib-2.0 --libs)

all: bluepy-helper

bluepy-helper: $(LOCAL_SRCS) $(IMPORT_SRCS)
$(CC) -L. $(CFLAGS) $(CPPFLAGS) -o $@ $(LOCAL_SRCS) $(IMPORT_SRCS) $(LDLIBS)
# Generate the "version.c" based on the python package version
# and GIT revision (if available).
#
# The 'grep|sed|tr' extract the version string from the 'setup.py' file.
# The 'git describe' extract the git version (if there is a .git directory).
# The output of the above is a single string like so:
# 1.2.0 (v/1.2.0-16-g3ce360c-dirty)
# The top and bottom printf's generate valid C code, like so:
# const char* bluepy_helper_version = "1.2.0 (v/1.2.0-16-g3ce360c-dirty)";
version.c: ../setup.py $(LOCAL_SRCS) $(IMPORT_SRCS)
@echo Regenerating $@
@( \
printf "const char* bluepy_helper_version = \"" ; \
\
grep "version=" ../setup.py \
| sed -e "s/[^0-9]*\([0-9\.]*\).*/\1/" \
| tr -d '\n' ; \
\
test -d ../.git \
&& { printf " (" ; \
git describe --always --dirty | tr -d '\n' ; \
printf ")" ; } ; \
\
printf '";\n' ) > $@

bluepy-helper: $(LOCAL_OBJS) $(IMPORT_OBJS) $(VERSION_OBJS)
$(CC) -L. -o $@ $^ $(LDLIBS)

# NOTE:
# make's built-in rule for compiling C to obj files is sufficient
# to build the objects, no need for explicit rule.

$(IMPORT_SRCS): bluez-src.tgz
tar xzf $<
Expand All @@ -45,7 +81,7 @@ TAGS: *.c $(BLUEZ_PATH)/attrib/*.[ch] $(BLUEZ_PATH)/btio/*.[ch]
etags $^

clean:
rm -rf *.o bluepy-helper TAGS $(BLUEZ_PATH)
rm -rf $(VERSION_OBJS) $(VERSION_SRCS) $(LOCAL_OBJS) $(IMPORT_OBJS) bluepy-helper TAGS $(BLUEZ_PATH)



89 changes: 78 additions & 11 deletions bluepy/bluepy-helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <err.h>
#include <glib.h>


Expand Down Expand Up @@ -67,6 +69,9 @@ static void try_open(void) {
#endif
#endif

#define BLUEPY_URL "https://github.com/IanHarvey/bluepy"
extern const char* bluepy_helper_version; /* defined in version.c */

static GIOChannel *iochannel = NULL;
static GAttrib *attrib = NULL;
static GMainLoop *event_loop;
Expand Down Expand Up @@ -154,8 +159,11 @@ static const char
*st_CONNECTED = "conn",
*st_SCANNING = "scan";

// delimits fields in response message
#define RESP_DELIM "\x1e"
/* field delimiter response messages.
Using RECORD SEPARATOR (ASCII \x1e) as delimiter makes it easier to
accomodate strings with spaces without escaping them.
Using SPACE (ascii \x20) is easier for manual debugging. */
static gchar resp_delim = '\x1e';

static void resp_begin(const char *rsptype)
{
Expand All @@ -164,30 +172,35 @@ static void resp_begin(const char *rsptype)

static void send_sym(const char *tag, const char *val)
{
printf(RESP_DELIM "%s=$%s", tag, val);
putchar(resp_delim);
printf("%s=$%s", tag, val);
}

static void send_uint(const char *tag, unsigned int val)
{
printf(RESP_DELIM "%s=h%X", tag, val);
putchar(resp_delim);
printf("%s=h%X", tag, val);
}

static void send_str(const char *tag, const char *val)
{
printf(RESP_DELIM "%s='%s", tag, val);
putchar(resp_delim);
printf("%s='%s", tag, val);
}

static void send_data(const unsigned char *val, size_t len)
{
printf(RESP_DELIM "%s=b", tag_DATA);
putchar(resp_delim);
printf("%s=b", tag_DATA);
while ( len-- > 0 )
printf("%02X", *val++);
}

static void send_addr(const struct mgmt_addr_info *addr)
{
const uint8_t *val = addr->bdaddr.b;
printf(RESP_DELIM "%s=b", tag_ADDR);
putchar(resp_delim);
printf("%s=b", tag_ADDR);
int len = 6;
/* Human-readable byte order is reverse of bdaddr.b */
while ( len-- > 0 )
Expand Down Expand Up @@ -1722,7 +1735,10 @@ static void parse_line(char *line_read)
if (*line_read == '\0')
goto done;

g_shell_parse_argv(line_read, &argcp, &argvp, NULL);
if (!g_shell_parse_argv(line_read, &argcp, &argvp, NULL)) {
resp_error(err_BAD_CMD);
goto done;
}

for (i = 0; commands[i].cmd; i++)
if (strcasecmp(commands[i].cmd, argvp[0]) == 0)
Expand Down Expand Up @@ -1860,10 +1876,40 @@ static void mgmt_setup(unsigned int idx)
}
}

static void show_help(const char* progname)
{
printf("usage: %s [-hvs] [index]\n", progname);
puts("\n\
bluepy-helper is a BlueZ-based bluetooth low-level interface.\n\
It is used by the Bluepy python package.\n\
See: " BLUEPY_URL "\n\
\n\
Options:\n\
index - an integer specifing the bluetooth hci interface\n\
number (0 = hci0). Optional if connecting to an already-paired\n\
device, but required for explicit pair/unpair commands.\n\
\n\
-h Show this help screen.\n\
-v Show version and exit.\n\
-s Use SPACE character as field delimiter (instead of the default\n\
FIELD SEPARATOR (ascii \\x1E). Useful for manual debugging.\n\
");

exit(EXIT_SUCCESS);
}

static void show_version()
{
printf("bluepy-helper version %s\n", bluepy_helper_version);
puts("See: " BLUEPY_URL);
exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
GIOChannel *pchan;
gint events;
int opt;

opt_sec_level = g_strdup("low");

Expand All @@ -1873,11 +1919,32 @@ int main(int argc, char *argv[])

DBG(__FILE__ " built at " __TIME__ " on " __DATE__);

if (argc > 1) {

while ((opt = getopt(argc, argv, "shv")) != -1) {
switch (opt)
{
case 'h':
show_help(argv[0]); /* does not return */

case 'v':
show_version(); /* does not return */

case 's':
/* use SPACE for field delimiter in response strings */
resp_delim = ' ';
break;

default: /* '?' */
errx(EXIT_FAILURE, "Use -h for help");
}
}

if (argc > optind) {
int index;

if (sscanf (argv[1], "%i", &index)!=1) {
DBG("error converting argument: %s to device index integer",argv[1]);
if (sscanf (argv[optind], "%i", &index)!=1) {
warnx("invalid device index '%s'", argv[optind]);
DBG("error converting argument: %s to device index integer",argv[optind]);
} else {
mgmt_setup(index);
}
Expand Down
55 changes: 45 additions & 10 deletions bluepy/btle.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,44 @@ class BTLEException(Exception):
GATT_ERROR = 4
MGMT_ERROR = 5

def __init__(self, code, message):
ERROR_STR = {
DISCONNECTED : "Disconnect Error",
COMM_ERROR : "Communication Error",
INTERNAL_ERROR : "Internal Error",
GATT_ERROR : "GATT Error",
MGMT_ERROR : "MGMT Error"
}

def __init__(self, code, message, resp_dict=None):
self.code = code
self.message = message

# optional messages from bluepy-helper
self.estat = None
self.emsg = None
if resp_dict:
self.estat = resp_dict.get('estat',None)
if isinstance(self.estat,list):
self.estat = self.estat[0]
self.emsg = resp_dict.get('emsg',None)
if isinstance(self.emsg,list):
self.emsg = self.emsg[0]


def __str__(self):
return self.message
msg = self.ERROR_STR.get(self.code,"UNKNOWN Error (code: %s)" % (self.code))
msg = msg + ": " + self.message
if self.estat or self.emsg:
msg = msg + " ("
if self.estat:
msg = msg + "estat: %s" % self.estat
if self.estat and self.emsg:
msg = msg + " "
if self.emsg:
msg = msg + "emsg: %s" % self.emsg
msg = msg + ")"

return msg


class UUID:
Expand Down Expand Up @@ -280,7 +312,7 @@ def _mgmtCmd(self, cmd):
if rsp['code'][0] != 'success':
self._stopHelper()
raise BTLEException(BTLEException.DISCONNECTED,
"Failed to execute mgmt cmd '%s'" % (cmd))
"Failed to execute mgmt cmd '%s'" % (cmd), rsp)

@staticmethod
def parseResp(line):
Expand Down Expand Up @@ -331,13 +363,13 @@ def _waitResp(self, wantType, timeout=None):
elif respType == 'stat':
if 'state' in resp and len(resp['state']) > 0 and resp['state'][0] == 'disc':
self._stopHelper()
raise BTLEException(BTLEException.DISCONNECTED, "Device disconnected")
raise BTLEException(BTLEException.DISCONNECTED, "Device disconnected", resp)
elif respType == 'err':
errcode=resp['code'][0]
if errcode=='nomgmt':
raise BTLEException(BTLEException.MGMT_ERROR, "Management not available (permissions problem?)")
raise BTLEException(BTLEException.MGMT_ERROR, "Management not available (permissions problem?)", resp)
else:
raise BTLEException(BTLEException.COMM_ERROR, "Error from Bluetooth stack (%s)" % errcode)
raise BTLEException(BTLEException.COMM_ERROR, "Error from Bluetooth stack (%s)" % errcode, resp)
elif respType == 'scan':
# Scan response when we weren't interested. Ignore it
continue
Expand Down Expand Up @@ -407,7 +439,7 @@ def _connect(self, addr, addrType=ADDR_TYPE_PUBLIC, iface=None):
if rsp['state'][0] != 'conn':
self._stopHelper()
raise BTLEException(BTLEException.DISCONNECTED,
"Failed to connect to peripheral %s, addr type: %s" % (addr, addrType))
"Failed to connect to peripheral %s, addr type: %s" % (addr, addrType), rsp)

def connect(self, addr, addrType=ADDR_TYPE_PUBLIC, iface=None):
if isinstance(addr, ScanEntry):
Expand Down Expand Up @@ -454,7 +486,7 @@ def getServiceByUUID(self, uuidVal):
self._writeCmd("svcs %s\n" % uuid)
rsp = self._getResp('find')
if 'hstart' not in rsp:
raise BTLEException(BTLEException.GATT_ERROR, "Service %s not found" % (uuid.getCommonName()))
raise BTLEException(BTLEException.GATT_ERROR, "Service %s not found" % (uuid.getCommonName()), rsp)
svc = Service(self, uuid, rsp['hstart'][0], rsp['hend'][0])

if self._serviceMap is None:
Expand Down Expand Up @@ -513,8 +545,11 @@ def setSecurityLevel(self, level):
self._writeCmd("secu %s\n" % level)
return self._getResp('stat')

def unpair(self, address):
self._mgmtCmd("unpair %s" % (address))
def pair(self):
self._mgmtCmd("pair")

def unpair(self):
self._mgmtCmd("unpair")

def setMTU(self, mtu):
self._writeCmd("mtu %x\n" % mtu)
Expand Down