bluez-profile 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENCE +22 -0
- data/README.md +189 -0
- data/ext/bluez/extconf.rb +13 -0
- data/ext/bluez/profile.c +448 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 768299afaa1258a583149f1ff896b6522159c49c2d0090d57491f144a9cfa94b
|
4
|
+
data.tar.gz: d69e4bb1204ceb41f3ab4710a5bfb7948407533455f54c8d7b45c375f62f67b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e48632a5208e5f08973bbad6fde0ee347edf685ddff1dd2ab69db1e1edfac3f909390fe8809158bd2911945d23fcb0ebb75818676f87369fd012e130e85a4d4
|
7
|
+
data.tar.gz: fbd115fee66256f7115f63f11a82830e96c72387eb1528101a2562b73aad34535ead60b722afd65d906a2ce117deb6308364366e05f73e927fcaae8dd6de50cc
|
data/LICENCE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2021 Clive Andrews
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# bluez Profile API bindings
|
2
|
+
|
3
|
+
this gem allows easy creation of bluetooth services.
|
4
|
+
For example create a serial link over to another device using bluetooth.
|
5
|
+
|
6
|
+
https://github.com/pauloborges/bluez/blob/master/doc/profile-api.txt
|
7
|
+
|
8
|
+
|
9
|
+
Clive Andrews 2021
|
10
|
+
|
11
|
+
## dependencies
|
12
|
+
|
13
|
+
* Linux/Unix type system
|
14
|
+
* bluez 5
|
15
|
+
* glib-2 (dev)
|
16
|
+
|
17
|
+
## prerequisites
|
18
|
+
|
19
|
+
your bluetooth devices must be paired and trusted outside of this gem
|
20
|
+
in order for them to communicate.
|
21
|
+
|
22
|
+
use eg `bluetoothctl`
|
23
|
+
|
24
|
+
## API
|
25
|
+
|
26
|
+
example code:
|
27
|
+
|
28
|
+
class MyProfile < Bluez::Profile
|
29
|
+
|
30
|
+
# This method gets called when the service daemon
|
31
|
+
# unregisters the profile. A profile can use it to do
|
32
|
+
# cleanup tasks.
|
33
|
+
|
34
|
+
def release
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# This method gets called when a new service level
|
39
|
+
# connection has been made and authorized.
|
40
|
+
|
41
|
+
def connection(device, fd, fd_properties)
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method gets called when a profile gets
|
46
|
+
# disconnected.
|
47
|
+
#
|
48
|
+
# The file descriptor is no longer owned by the service
|
49
|
+
# daemon and the profile implementation needs to take
|
50
|
+
# care of cleaning up all connections.
|
51
|
+
#
|
52
|
+
# If multiple file descriptors are indicated via
|
53
|
+
# NewConnection, it is expected that all of them
|
54
|
+
# are disconnected before returning from this
|
55
|
+
# method call.
|
56
|
+
|
57
|
+
def disconnection(device)
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
path = '/my/dbus/path'
|
64
|
+
uuid = '1101'
|
65
|
+
|
66
|
+
options = {
|
67
|
+
name: 'my profile',
|
68
|
+
channel: 3
|
69
|
+
connect: false
|
70
|
+
}
|
71
|
+
|
72
|
+
profile = MyProfile.new(path, uuid, options)
|
73
|
+
profile.run # enters loop and blocks here.
|
74
|
+
|
75
|
+
profile.stop # stops the loop
|
76
|
+
|
77
|
+
|
78
|
+
path: string
|
79
|
+
|
80
|
+
the dbus object path of the profile.
|
81
|
+
eg: "/serial/special/profile"
|
82
|
+
|
83
|
+
uuid: string
|
84
|
+
|
85
|
+
the dbus profile uuid
|
86
|
+
|
87
|
+
|
88
|
+
Available options:
|
89
|
+
|
90
|
+
name: string
|
91
|
+
|
92
|
+
Human readable name for the profile
|
93
|
+
|
94
|
+
service: string
|
95
|
+
|
96
|
+
The primary service class UUID
|
97
|
+
(if different from the actual
|
98
|
+
profile UUID)
|
99
|
+
|
100
|
+
role: string
|
101
|
+
|
102
|
+
For asymmetric profiles that do not
|
103
|
+
have UUIDs available to uniquely
|
104
|
+
identify each side this
|
105
|
+
parameter allows specifying the
|
106
|
+
precise local role.
|
107
|
+
|
108
|
+
Possible values:
|
109
|
+
|
110
|
+
Bluez::Profile::Client
|
111
|
+
Bluez::Profile::Server
|
112
|
+
|
113
|
+
channel: int
|
114
|
+
|
115
|
+
RFCOMM channel number that is used
|
116
|
+
for client and server UUIDs.
|
117
|
+
|
118
|
+
If applicable it will be used in the
|
119
|
+
SDP record as well.
|
120
|
+
|
121
|
+
psm: int
|
122
|
+
|
123
|
+
PSM number that is used for client
|
124
|
+
and server UUIDs.
|
125
|
+
|
126
|
+
If applicable it will be used in the
|
127
|
+
SDP record as well.
|
128
|
+
|
129
|
+
authentication: boolean
|
130
|
+
|
131
|
+
Pairing is required before connections
|
132
|
+
will be established. No devices will
|
133
|
+
be connected if not paired.
|
134
|
+
|
135
|
+
authorization: boolean
|
136
|
+
|
137
|
+
Request authorization before any
|
138
|
+
connection will be established.
|
139
|
+
|
140
|
+
connect: boolean
|
141
|
+
|
142
|
+
In case of a client UUID this will
|
143
|
+
force connection of the RFCOMM or
|
144
|
+
L2CAP channels when a remote device
|
145
|
+
is connected.
|
146
|
+
|
147
|
+
record: string
|
148
|
+
|
149
|
+
Provide a manual SDP record.
|
150
|
+
|
151
|
+
version: int
|
152
|
+
|
153
|
+
Profile version (for SDP record)
|
154
|
+
|
155
|
+
features: int
|
156
|
+
|
157
|
+
Profile features (for SDP record)
|
158
|
+
|
159
|
+
## server
|
160
|
+
|
161
|
+
If a channel number is already in use your service may not be registered.
|
162
|
+
Check that your services have been registered correctly with eg:
|
163
|
+
|
164
|
+
sudo sdptool browse local
|
165
|
+
|
166
|
+
## examples
|
167
|
+
|
168
|
+
See the `/examples` folder.
|
169
|
+
|
170
|
+
## issues
|
171
|
+
|
172
|
+
The server side of things seems to work fine.
|
173
|
+
|
174
|
+
run several servers over the same serial service using different
|
175
|
+
channel numbers
|
176
|
+
|
177
|
+
on the client ( paired and trusted) connect each channel eg :
|
178
|
+
|
179
|
+
sudo rfcomm connect /dev/rfcomm0 remoteaddr channel1
|
180
|
+
sudo rfcomm connect /dev/rfcomm1 remoteaddr channel2
|
181
|
+
sudo rfcomm connect /dev/rfcomm2 remoteaddr channel3
|
182
|
+
|
183
|
+
this works perfectly. each device file allows interaction with the correct server.
|
184
|
+
|
185
|
+
*BUT* if on the client a profile is registered with bluez using this gem
|
186
|
+
and a connection made to the server then things get in a muddle and output
|
187
|
+
for the one channel gets sent to the wrong profile !!
|
188
|
+
|
189
|
+
any ideas ??
|
data/ext/bluez/profile.c
ADDED
@@ -0,0 +1,448 @@
|
|
1
|
+
// this ruby binding allows a profile to be registered
|
2
|
+
// with the bluez stack.
|
3
|
+
//
|
4
|
+
// (c) C. Andrews 2021
|
5
|
+
//
|
6
|
+
// References:
|
7
|
+
//
|
8
|
+
// https://github.com/pauloborges/bluez/blob/master/doc/profile-api.txt
|
9
|
+
// https://github.com/tonyespy/bluez5-spp-example
|
10
|
+
// https://github.com/bratsche/glib/blob/master/gio/tests/gdbus-example-server.c
|
11
|
+
|
12
|
+
|
13
|
+
#include <gio/gio.h>
|
14
|
+
#include <gio/gunixfdlist.h>
|
15
|
+
#include <stdlib.h>
|
16
|
+
#include <ruby.h>
|
17
|
+
|
18
|
+
#define BLUEZ_BUS_NAME "org.bluez"
|
19
|
+
#define BLUEZ_BUS_PATH "/org/bluez"
|
20
|
+
#define BLUEZ_INTERFACE_DEVICE1 "org.bluez.Device1"
|
21
|
+
#define BLUEZ_INTERFACE_PROFILE1 "org.bluez.Profile1"
|
22
|
+
#define BLUEZ_INTERFACE_PROFILEMANAGER1 "org.bluez.ProfileManager1"
|
23
|
+
|
24
|
+
static VALUE mBluez;
|
25
|
+
static VALUE cProfile;
|
26
|
+
static VALUE eProfileError;
|
27
|
+
|
28
|
+
typedef struct t_profile_data{
|
29
|
+
GDBusConnection* conn;
|
30
|
+
GDBusProxy* proxy;
|
31
|
+
GDBusNodeInfo* introspection_data;
|
32
|
+
guint id;
|
33
|
+
GMainLoop* loop;
|
34
|
+
int running;
|
35
|
+
int fd;
|
36
|
+
} t_profile_data;
|
37
|
+
|
38
|
+
|
39
|
+
/* Introspection data for the service we are exporting */
|
40
|
+
static const gchar introspection_xml[] =
|
41
|
+
"<node>"
|
42
|
+
" <interface name='org.bluez.Profile1'>"
|
43
|
+
" <method name='Release'>"
|
44
|
+
" </method>"
|
45
|
+
" <method name='NewConnection'>"
|
46
|
+
" <arg type='o' name='device' direction='in'/>"
|
47
|
+
" <arg type='h' name='fd' direction='in'/>"
|
48
|
+
" <arg type='a{sv}' name='fd_properties' direction='in'/>"
|
49
|
+
" </method>"
|
50
|
+
" <method name='RequestDisconnection'>"
|
51
|
+
" <arg type='o' name='device' direction='in'/>"
|
52
|
+
" </method>"
|
53
|
+
" </interface>"
|
54
|
+
"</node>";
|
55
|
+
|
56
|
+
// free the c data structure
|
57
|
+
|
58
|
+
static void profile_free_data(t_profile_data* p){
|
59
|
+
if (p->id !=0 ) g_dbus_connection_unregister_object(p->conn, p->id);
|
60
|
+
if (p->introspection_data !=NULL ) g_dbus_node_info_unref (p->introspection_data);
|
61
|
+
if (p->proxy !=NULL ) g_object_unref (p->proxy);
|
62
|
+
if (p->conn !=NULL ) g_object_unref (p->conn);
|
63
|
+
if (p->loop !=NULL ) g_main_loop_unref(p->loop);
|
64
|
+
p->introspection_data = NULL;
|
65
|
+
p->proxy = NULL;
|
66
|
+
p->conn = NULL;
|
67
|
+
p->loop = NULL;
|
68
|
+
free(p);
|
69
|
+
}
|
70
|
+
|
71
|
+
// allocate the c data structure
|
72
|
+
|
73
|
+
static VALUE profile_alloc_data(VALUE self){
|
74
|
+
VALUE obj;
|
75
|
+
t_profile_data* data = malloc(sizeof(t_profile_data));
|
76
|
+
data->conn = NULL;
|
77
|
+
data->proxy = NULL;
|
78
|
+
data->introspection_data = NULL;
|
79
|
+
data->loop = NULL;
|
80
|
+
obj = Data_Wrap_Struct(self, 0, profile_free_data, data);
|
81
|
+
return obj;
|
82
|
+
}
|
83
|
+
|
84
|
+
// handle object method calls ----------------------------------------------
|
85
|
+
static void
|
86
|
+
handle_method_call (GDBusConnection *connection,
|
87
|
+
const gchar *sender,
|
88
|
+
const gchar *object_path,
|
89
|
+
const gchar *interface_name,
|
90
|
+
const gchar *method_name,
|
91
|
+
GVariant *parameters,
|
92
|
+
GDBusMethodInvocation *invocation,
|
93
|
+
gpointer user_data)
|
94
|
+
{
|
95
|
+
|
96
|
+
VALUE self;
|
97
|
+
GDBusMessage *message;
|
98
|
+
GUnixFDList *fd_list;
|
99
|
+
GError *error = NULL;
|
100
|
+
gchar* path;
|
101
|
+
int fd_idx;
|
102
|
+
int fd;
|
103
|
+
GVariantIter *list;
|
104
|
+
|
105
|
+
self = (VALUE) user_data;
|
106
|
+
|
107
|
+
if (g_strcmp0 (method_name, "Release") == 0)
|
108
|
+
{
|
109
|
+
//printf("Release Called\n");
|
110
|
+
rb_funcall(self, rb_intern("release"), 0 );
|
111
|
+
}
|
112
|
+
else if (g_strcmp0 (method_name, "NewConnection") == 0)
|
113
|
+
// in_signature="oha{sv}"
|
114
|
+
{
|
115
|
+
g_variant_get (parameters, "(oha{sv})", &path, &fd_idx, &list);
|
116
|
+
message = g_dbus_method_invocation_get_message (invocation);
|
117
|
+
fd_list = g_dbus_message_get_unix_fd_list (message);
|
118
|
+
fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
|
119
|
+
if (error!=NULL){
|
120
|
+
rb_raise(eProfileError, "invalid file descriptor");
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
rb_funcall(self, rb_intern("connection"), 2,rb_str_new2(path), INT2NUM(fd) );
|
124
|
+
}
|
125
|
+
else if (g_strcmp0 (method_name, "RequestDisconnection") == 0)
|
126
|
+
// in_signature="o"
|
127
|
+
{
|
128
|
+
//printf("RequestDisconnection Called\n");
|
129
|
+
g_variant_get (parameters, "o", &path);
|
130
|
+
rb_funcall(self, rb_intern("disconnection"), 1,rb_str_new2(path) );
|
131
|
+
}
|
132
|
+
|
133
|
+
}
|
134
|
+
|
135
|
+
// static GVariant *
|
136
|
+
// handle_get_property (GDBusConnection *connection,
|
137
|
+
// const gchar *sender,
|
138
|
+
// const gchar *object_path,
|
139
|
+
// const gchar *interface_name,
|
140
|
+
// const gchar *property_name,
|
141
|
+
// GError **error,
|
142
|
+
// gpointer user_data)
|
143
|
+
// {
|
144
|
+
// return NULL;
|
145
|
+
// }
|
146
|
+
//
|
147
|
+
// static gboolean
|
148
|
+
// handle_set_property (GDBusConnection *connection,
|
149
|
+
// const gchar *sender,
|
150
|
+
// const gchar *object_path,
|
151
|
+
// const gchar *interface_name,
|
152
|
+
// const gchar *property_name,
|
153
|
+
// GVariant *value,
|
154
|
+
// GError **error,
|
155
|
+
// gpointer user_data)
|
156
|
+
// {
|
157
|
+
// return TRUE;
|
158
|
+
// }
|
159
|
+
//
|
160
|
+
|
161
|
+
static const GDBusInterfaceVTable vtable =
|
162
|
+
{
|
163
|
+
handle_method_call,
|
164
|
+
NULL,
|
165
|
+
NULL
|
166
|
+
};
|
167
|
+
|
168
|
+
// register our profile object with dbus --------------------------------------
|
169
|
+
|
170
|
+
static guint register_object(VALUE self, t_profile_data* data, const gchar* path){
|
171
|
+
guint id;
|
172
|
+
GError *error = NULL;
|
173
|
+
|
174
|
+
id = g_dbus_connection_register_object (
|
175
|
+
data->conn,
|
176
|
+
path,
|
177
|
+
data->introspection_data->interfaces[0],
|
178
|
+
&vtable,
|
179
|
+
(gpointer) self, //user_data,
|
180
|
+
NULL, //user_data_free_func,
|
181
|
+
&error
|
182
|
+
);
|
183
|
+
|
184
|
+
return id;
|
185
|
+
}
|
186
|
+
|
187
|
+
// register our profile with bluez --------------------------------------------
|
188
|
+
|
189
|
+
int register_profile(VALUE path, VALUE uuid, VALUE options, GDBusProxy *proxy){
|
190
|
+
|
191
|
+
GVariant *profile;
|
192
|
+
GVariantBuilder profile_builder;
|
193
|
+
GError *error = NULL;
|
194
|
+
VALUE val;
|
195
|
+
|
196
|
+
//printf("register_profile called!\n");
|
197
|
+
|
198
|
+
g_variant_builder_init(&profile_builder, G_VARIANT_TYPE("(osa{sv})"));
|
199
|
+
|
200
|
+
g_variant_builder_add (&profile_builder, "o",StringValueCStr(path));
|
201
|
+
|
202
|
+
g_variant_builder_add (&profile_builder, "s", StringValueCStr(uuid));
|
203
|
+
|
204
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("a{sv}"));
|
205
|
+
|
206
|
+
// Name
|
207
|
+
|
208
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("name")));
|
209
|
+
if (val != Qnil){
|
210
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
211
|
+
g_variant_builder_add (&profile_builder, "s", "Name");
|
212
|
+
g_variant_builder_add (&profile_builder, "v",
|
213
|
+
g_variant_new_string(StringValueCStr(val) ));
|
214
|
+
g_variant_builder_close(&profile_builder);
|
215
|
+
}
|
216
|
+
|
217
|
+
// Service
|
218
|
+
|
219
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("service")));
|
220
|
+
if (val != Qnil){
|
221
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
222
|
+
g_variant_builder_add (&profile_builder, "s", "Service");
|
223
|
+
g_variant_builder_add (&profile_builder, "v",
|
224
|
+
g_variant_new_string(StringValueCStr(val) ));
|
225
|
+
g_variant_builder_close(&profile_builder);
|
226
|
+
}
|
227
|
+
|
228
|
+
// Role
|
229
|
+
|
230
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("role")));
|
231
|
+
if (val != Qnil){
|
232
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
233
|
+
g_variant_builder_add (&profile_builder, "s", "Role");
|
234
|
+
g_variant_builder_add (&profile_builder, "v",
|
235
|
+
g_variant_new_string(StringValueCStr(val) ));
|
236
|
+
g_variant_builder_close(&profile_builder);
|
237
|
+
}
|
238
|
+
|
239
|
+
// Channel
|
240
|
+
|
241
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("channel")));
|
242
|
+
if (val != Qnil){
|
243
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
244
|
+
g_variant_builder_add (&profile_builder, "s", "Channel");
|
245
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
246
|
+
g_variant_builder_close(&profile_builder);
|
247
|
+
}
|
248
|
+
|
249
|
+
// PSM
|
250
|
+
|
251
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("psm")));
|
252
|
+
if (val != Qnil){
|
253
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
254
|
+
g_variant_builder_add (&profile_builder, "s", "PSM");
|
255
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
256
|
+
g_variant_builder_close(&profile_builder);
|
257
|
+
}
|
258
|
+
|
259
|
+
// RequireAuthentication
|
260
|
+
|
261
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("authentication")));
|
262
|
+
if (val != Qnil){
|
263
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
264
|
+
g_variant_builder_add (&profile_builder, "s", "RequireAuthentication");
|
265
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_boolean(RTEST(val) ));
|
266
|
+
g_variant_builder_close(&profile_builder);
|
267
|
+
}
|
268
|
+
|
269
|
+
// RequireAuthorization
|
270
|
+
|
271
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("authorization")));
|
272
|
+
if (val != Qnil){
|
273
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
274
|
+
g_variant_builder_add (&profile_builder, "s", "RequireAuthorization");
|
275
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_boolean(RTEST(val) ));
|
276
|
+
g_variant_builder_close(&profile_builder);
|
277
|
+
}
|
278
|
+
|
279
|
+
// AutoConnect
|
280
|
+
|
281
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("connect")));
|
282
|
+
if (val != Qnil){
|
283
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
284
|
+
g_variant_builder_add (&profile_builder, "s", "AutoConnect");
|
285
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_boolean(RTEST(val) ));
|
286
|
+
g_variant_builder_close(&profile_builder);
|
287
|
+
}
|
288
|
+
|
289
|
+
// ServiceRecord
|
290
|
+
|
291
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("record")));
|
292
|
+
if (val != Qnil){
|
293
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
294
|
+
g_variant_builder_add (&profile_builder, "s", "ServiceRecord");
|
295
|
+
g_variant_builder_add (&profile_builder, "v",
|
296
|
+
g_variant_new_string(StringValueCStr(val) ));
|
297
|
+
g_variant_builder_close(&profile_builder);
|
298
|
+
}
|
299
|
+
|
300
|
+
// Version
|
301
|
+
|
302
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("version")));
|
303
|
+
if (val != Qnil){
|
304
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
305
|
+
g_variant_builder_add (&profile_builder, "s", "Version");
|
306
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
307
|
+
g_variant_builder_close(&profile_builder);
|
308
|
+
}
|
309
|
+
|
310
|
+
// Features
|
311
|
+
|
312
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("features")));
|
313
|
+
if (val != Qnil){
|
314
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
315
|
+
g_variant_builder_add (&profile_builder, "s", "Features");
|
316
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
317
|
+
g_variant_builder_close(&profile_builder);
|
318
|
+
}
|
319
|
+
|
320
|
+
g_variant_builder_close(&profile_builder);
|
321
|
+
profile = g_variant_builder_end(&profile_builder);
|
322
|
+
|
323
|
+
g_dbus_proxy_call_sync (proxy,
|
324
|
+
"RegisterProfile",
|
325
|
+
profile,
|
326
|
+
G_DBUS_CALL_FLAGS_NONE,
|
327
|
+
-1,
|
328
|
+
NULL,
|
329
|
+
&error);
|
330
|
+
|
331
|
+
if (error!=NULL){
|
332
|
+
return 0;
|
333
|
+
}
|
334
|
+
|
335
|
+
return 1;
|
336
|
+
}
|
337
|
+
|
338
|
+
|
339
|
+
// initialize our profile here. ------------------------------------------------
|
340
|
+
|
341
|
+
static VALUE profile_initialize(VALUE self, VALUE path, VALUE uuid, VALUE options){
|
342
|
+
t_profile_data* data = NULL;
|
343
|
+
GError *error = NULL;
|
344
|
+
int ok;
|
345
|
+
const gchar * cpath = StringValueCStr(path);
|
346
|
+
Data_Get_Struct(self, t_profile_data, data);
|
347
|
+
|
348
|
+
// validate the path
|
349
|
+
|
350
|
+
if (!g_variant_is_object_path(cpath)) {
|
351
|
+
rb_raise(eProfileError, "invalid object path");
|
352
|
+
return Qnil;
|
353
|
+
}
|
354
|
+
|
355
|
+
data->loop = g_main_loop_new (NULL, FALSE);
|
356
|
+
data->running = FALSE;
|
357
|
+
|
358
|
+
data->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
|
359
|
+
if (data->introspection_data==NULL){
|
360
|
+
rb_raise(eProfileError, "error generating introspection data");
|
361
|
+
return Qnil;
|
362
|
+
}
|
363
|
+
|
364
|
+
// connect to dbus system bus
|
365
|
+
data->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
|
366
|
+
if (error!=NULL){
|
367
|
+
rb_raise(eProfileError, "error connecting to dbus");
|
368
|
+
return Qnil;
|
369
|
+
}
|
370
|
+
|
371
|
+
// obtain a proxy to bluez profile manager.
|
372
|
+
|
373
|
+
data->proxy = g_dbus_proxy_new_sync (data->conn,
|
374
|
+
G_DBUS_PROXY_FLAGS_NONE,
|
375
|
+
NULL,/* GDBusInterfaceInfo */
|
376
|
+
BLUEZ_BUS_NAME,/* name */
|
377
|
+
BLUEZ_BUS_PATH,/* object path */
|
378
|
+
BLUEZ_INTERFACE_PROFILEMANAGER1,/* interface */
|
379
|
+
NULL,/* GCancellable */
|
380
|
+
&error);
|
381
|
+
|
382
|
+
if (error!=NULL){
|
383
|
+
rb_raise(eProfileError, "error connecting to bluez profile manager");
|
384
|
+
return Qnil;
|
385
|
+
}
|
386
|
+
|
387
|
+
// register our profile object with dbus
|
388
|
+
|
389
|
+
data->id = register_object(self, data, cpath);
|
390
|
+
|
391
|
+
if (data->id==0){
|
392
|
+
rb_raise(eProfileError, "error registering profile object");
|
393
|
+
return Qnil;
|
394
|
+
}
|
395
|
+
|
396
|
+
// and register the profile with the profile manager.
|
397
|
+
|
398
|
+
ok = register_profile(path, uuid, options, data->proxy);
|
399
|
+
|
400
|
+
if (ok==0){
|
401
|
+
rb_raise(eProfileError, "error register profile with bluez");
|
402
|
+
return Qnil;
|
403
|
+
}
|
404
|
+
|
405
|
+
return self;
|
406
|
+
}
|
407
|
+
|
408
|
+
// run the event loop ----------------------------------------------------------
|
409
|
+
|
410
|
+
static VALUE profile_run(VALUE self){
|
411
|
+
t_profile_data* data = NULL;
|
412
|
+
GMainContext * context = g_main_context_default();
|
413
|
+
Data_Get_Struct(self, t_profile_data, data);
|
414
|
+
if (!data->running){
|
415
|
+
data->running = TRUE;
|
416
|
+
while(data->running){
|
417
|
+
g_main_context_iteration (context, FALSE);
|
418
|
+
rb_thread_schedule();
|
419
|
+
}
|
420
|
+
}
|
421
|
+
return self;
|
422
|
+
}
|
423
|
+
|
424
|
+
// stop the event loop ---------------------------------------------------------
|
425
|
+
|
426
|
+
static VALUE profile_stop(VALUE self){
|
427
|
+
t_profile_data* data = NULL;
|
428
|
+
Data_Get_Struct(self, t_profile_data, data);
|
429
|
+
data->running = FALSE;
|
430
|
+
return self;
|
431
|
+
}
|
432
|
+
|
433
|
+
//=============================== ruby interface definition ====================
|
434
|
+
|
435
|
+
void Init_profile() {
|
436
|
+
|
437
|
+
mBluez = rb_define_module("Bluez");
|
438
|
+
cProfile = rb_define_class_under(mBluez, "Profile", rb_cObject);
|
439
|
+
eProfileError = rb_define_class_under(cProfile, "Error",rb_eStandardError);
|
440
|
+
|
441
|
+
rb_define_const(cProfile, "Client", rb_str_new_cstr("client"));
|
442
|
+
rb_define_const(cProfile, "Server", rb_str_new_cstr("server"));
|
443
|
+
|
444
|
+
rb_define_alloc_func(cProfile, profile_alloc_data);
|
445
|
+
rb_define_method(cProfile, "initialize", profile_initialize, 3);
|
446
|
+
rb_define_method(cProfile, "run", profile_run, 0);
|
447
|
+
rb_define_method(cProfile, "stop", profile_stop, 0);
|
448
|
+
}
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bluez-profile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Clive Andrews
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Create Custom Bluez Bluetooth Profiles
|
14
|
+
email:
|
15
|
+
- gems@realitybites.eu
|
16
|
+
executables: []
|
17
|
+
extensions:
|
18
|
+
- ext/bluez/extconf.rb
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
- LICENCE
|
22
|
+
files:
|
23
|
+
- LICENCE
|
24
|
+
- README.md
|
25
|
+
- ext/bluez/extconf.rb
|
26
|
+
- ext/bluez/profile.c
|
27
|
+
homepage: https://github.com/realbite/bluez-profile
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- ext
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.9.2
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubygems_version: 3.1.4
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: binding to Bluez Profile API
|
50
|
+
test_files: []
|