ant-wireless 0.1.0.pre.20210617213631
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/History.md +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +79 -0
- data/ext/ant_ext/ant_ext.c +791 -0
- data/ext/ant_ext/ant_ext.h +101 -0
- data/ext/ant_ext/antdefines.h +355 -0
- data/ext/ant_ext/antmessage.h +445 -0
- data/ext/ant_ext/build_version.h +18 -0
- data/ext/ant_ext/callbacks.c +233 -0
- data/ext/ant_ext/channel.c +524 -0
- data/ext/ant_ext/defines.h +40 -0
- data/ext/ant_ext/extconf.rb +22 -0
- data/ext/ant_ext/message.c +377 -0
- data/ext/ant_ext/types.h +202 -0
- data/ext/ant_ext/version.h +41 -0
- data/lib/ant.rb +138 -0
- data/lib/ant/channel.rb +66 -0
- data/lib/ant/channel/event_callbacks.rb +207 -0
- data/lib/ant/message.rb +10 -0
- data/lib/ant/mixins.rb +34 -0
- data/lib/ant/response_callbacks.rb +528 -0
- data/lib/ant/wireless.rb +5 -0
- data/spec/ant_spec.rb +88 -0
- data/spec/spec_helper.rb +37 -0
- metadata +131 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
/*
|
2
|
+
* callbacks.c - ANT callback handling
|
3
|
+
* $Id$
|
4
|
+
*
|
5
|
+
* This contains code adapted from Kim Burge Strand's Library-of-Massive-Fun-And-Overjoy
|
6
|
+
* project, namely the code that calls back into Ruby from ANT callbacks. Used under
|
7
|
+
* the conditions of the WTFPL. For more info on how it works, see the associated article:
|
8
|
+
*
|
9
|
+
* https://www.burgestrand.se//articles/asynchronous-callbacks-in-ruby-c-extensions/
|
10
|
+
*
|
11
|
+
* Authors:
|
12
|
+
* * Michael Granger <ged@FaerieMUD.org>
|
13
|
+
*
|
14
|
+
*/
|
15
|
+
|
16
|
+
#include "ant_ext.h"
|
17
|
+
|
18
|
+
|
19
|
+
/*
|
20
|
+
* Three globals to allow for Ruby/C-thread communication:
|
21
|
+
* - mutex & condition to synchronize access to callback_queue
|
22
|
+
* - callback_queue to store actual callback data in
|
23
|
+
* Be careful with the functions that manipulate the callback
|
24
|
+
* queue; they must do so in the protection of a mutex.
|
25
|
+
*/
|
26
|
+
pthread_mutex_t rant_callback_mutex = PTHREAD_MUTEX_INITIALIZER;
|
27
|
+
pthread_cond_t rant_callback_cond = PTHREAD_COND_INITIALIZER;
|
28
|
+
rant_callback_t *rant_callback_queue = NULL;
|
29
|
+
|
30
|
+
typedef struct callback_waiting_t callback_waiting_t;
|
31
|
+
struct callback_waiting_t {
|
32
|
+
rant_callback_t *callback;
|
33
|
+
bool abort;
|
34
|
+
};
|
35
|
+
|
36
|
+
|
37
|
+
/*
|
38
|
+
* Use this function to add a callback node onto the global
|
39
|
+
* callback queue.
|
40
|
+
* Do note that we are adding items to the front of the linked
|
41
|
+
* list, and as such events will always be handled by most recent
|
42
|
+
* first. To remedy this, add to the end of the queue instead.
|
43
|
+
*/
|
44
|
+
static void
|
45
|
+
callback_queue_push( rant_callback_t *callback )
|
46
|
+
{
|
47
|
+
callback->next = rant_callback_queue;
|
48
|
+
rant_callback_queue = callback;
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
/*
|
53
|
+
* Use this function to pop off a callback node from the
|
54
|
+
* global callback queue. Returns NULL if queue is empty.
|
55
|
+
*/
|
56
|
+
static rant_callback_t *
|
57
|
+
callback_queue_pop(void)
|
58
|
+
{
|
59
|
+
rant_callback_t *callback = rant_callback_queue;
|
60
|
+
|
61
|
+
if ( callback )
|
62
|
+
{
|
63
|
+
rant_callback_queue = callback->next;
|
64
|
+
}
|
65
|
+
return callback;
|
66
|
+
}
|
67
|
+
|
68
|
+
|
69
|
+
/*
|
70
|
+
* Queue a +callback+ for handling by Ruby. Blocks until it's handled.
|
71
|
+
*/
|
72
|
+
bool
|
73
|
+
rant_callback( rant_callback_t *callback )
|
74
|
+
{
|
75
|
+
pthread_mutex_init( &callback->mutex, NULL );
|
76
|
+
pthread_cond_init( &callback->cond, NULL );
|
77
|
+
|
78
|
+
callback->handled = false;
|
79
|
+
|
80
|
+
// Put callback data in global callback queue
|
81
|
+
pthread_mutex_lock( &rant_callback_mutex );
|
82
|
+
callback_queue_push( callback );
|
83
|
+
pthread_mutex_unlock( &rant_callback_mutex );
|
84
|
+
|
85
|
+
// Notify waiting Ruby thread that we have callback data
|
86
|
+
pthread_cond_signal( &rant_callback_cond );
|
87
|
+
|
88
|
+
// Wait for callback to be handled
|
89
|
+
pthread_mutex_lock( &callback->mutex );
|
90
|
+
while ( callback->handled == false )
|
91
|
+
{
|
92
|
+
pthread_cond_wait( &callback->cond, &callback->mutex );
|
93
|
+
}
|
94
|
+
pthread_mutex_unlock( &callback->mutex );
|
95
|
+
|
96
|
+
// Clean up
|
97
|
+
pthread_mutex_destroy( &callback->mutex );
|
98
|
+
pthread_cond_destroy( &callback->cond );
|
99
|
+
|
100
|
+
return callback->rval;
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
/*
|
105
|
+
* Executed for each callback notification; what we receive
|
106
|
+
* are the callback parameters. The job of this method is to:
|
107
|
+
* 1. Convert callback parameters into Ruby values
|
108
|
+
* 2. Call the appropriate callback with said parameters
|
109
|
+
* 3. Convert the Ruby return value into a C value
|
110
|
+
* 4. Hand over the C value to the C callback
|
111
|
+
*/
|
112
|
+
static VALUE
|
113
|
+
handle_callback( void *cb )
|
114
|
+
{
|
115
|
+
rant_callback_t *callback = (rant_callback_t *)cb;
|
116
|
+
int state = 0;
|
117
|
+
VALUE rval;
|
118
|
+
|
119
|
+
// callback->fn( callback->data );
|
120
|
+
rval = rb_protect( callback->fn, (VALUE)callback->data, &state );
|
121
|
+
|
122
|
+
// tell the callback that it has been handled, we are done
|
123
|
+
pthread_mutex_lock( &callback->mutex );
|
124
|
+
|
125
|
+
callback->handled = true;
|
126
|
+
callback->rval = RTEST( rval ) ? true : false;
|
127
|
+
|
128
|
+
pthread_cond_signal( &callback->cond );
|
129
|
+
pthread_mutex_unlock( &callback->mutex );
|
130
|
+
|
131
|
+
if ( state ) {
|
132
|
+
rb_jump_tag( state );
|
133
|
+
}
|
134
|
+
|
135
|
+
return rval;
|
136
|
+
}
|
137
|
+
|
138
|
+
|
139
|
+
/*
|
140
|
+
* Wait for the next callback in the global callback queue.
|
141
|
+
*/
|
142
|
+
static void *
|
143
|
+
wait_for_callback_signal( void *w_ptr )
|
144
|
+
{
|
145
|
+
callback_waiting_t *waiting = (callback_waiting_t*) w_ptr;
|
146
|
+
|
147
|
+
pthread_mutex_lock( &rant_callback_mutex );
|
148
|
+
|
149
|
+
// abort signal is used when ruby wants us to stop waiting
|
150
|
+
while ( waiting->abort == false && (waiting->callback = callback_queue_pop()) == NULL )
|
151
|
+
{
|
152
|
+
pthread_cond_wait( &rant_callback_cond, &rant_callback_mutex );
|
153
|
+
}
|
154
|
+
|
155
|
+
pthread_mutex_unlock( &rant_callback_mutex );
|
156
|
+
|
157
|
+
return NULL;
|
158
|
+
}
|
159
|
+
|
160
|
+
|
161
|
+
/*
|
162
|
+
* Unblocking function: tell the callback thread to abort if Ruby says it's
|
163
|
+
* shutdown time.
|
164
|
+
*/
|
165
|
+
static void
|
166
|
+
stop_waiting_for_callback_signal( void *w_ptr )
|
167
|
+
{
|
168
|
+
callback_waiting_t *waiting = (callback_waiting_t*) w_ptr;
|
169
|
+
|
170
|
+
pthread_mutex_lock( &rant_callback_mutex );
|
171
|
+
|
172
|
+
waiting->abort = true;
|
173
|
+
|
174
|
+
pthread_cond_signal( &rant_callback_cond );
|
175
|
+
pthread_mutex_unlock( &rant_callback_mutex );
|
176
|
+
}
|
177
|
+
|
178
|
+
|
179
|
+
/*
|
180
|
+
* Callback handler thread routine; loops until told to abort. Each loop:
|
181
|
+
*
|
182
|
+
* - Release the GVL
|
183
|
+
* - Wait on a signal on the global condition variable with an unblock function
|
184
|
+
* that tells it to abort.
|
185
|
+
* - If there's a callback, create a child thread to run it.
|
186
|
+
*
|
187
|
+
*/
|
188
|
+
static VALUE
|
189
|
+
callback_thread( void *unused )
|
190
|
+
{
|
191
|
+
callback_waiting_t waiting = {
|
192
|
+
.callback = NULL,
|
193
|
+
.abort = false
|
194
|
+
};
|
195
|
+
|
196
|
+
while ( waiting.abort == false )
|
197
|
+
{
|
198
|
+
// release the GIL while waiting for a callback notification
|
199
|
+
rb_thread_call_without_gvl( wait_for_callback_signal, &waiting,
|
200
|
+
stop_waiting_for_callback_signal, &waiting );
|
201
|
+
|
202
|
+
// if ruby wants us to abort, this will be NULL
|
203
|
+
if ( waiting.callback )
|
204
|
+
{
|
205
|
+
// rant_log( "debug", "Starting a callback thread." );
|
206
|
+
rb_thread_create( handle_callback, (void *)waiting.callback );
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
return Qnil;
|
211
|
+
}
|
212
|
+
|
213
|
+
|
214
|
+
|
215
|
+
/*
|
216
|
+
* Start a Thread which will wait for ANT callbacks and dispatch them when they arrive.
|
217
|
+
*/
|
218
|
+
void
|
219
|
+
rant_start_callback_thread()
|
220
|
+
{
|
221
|
+
// ThreadGroup isn't a public symbol, so have to look it up
|
222
|
+
VALUE cThGroup = rb_define_class( "ThreadGroup", rb_cObject );
|
223
|
+
VALUE thread_group = rb_class_new_instance( 0, NULL, cThGroup );
|
224
|
+
VALUE callback_dispatcher = rb_thread_create( callback_thread, (void *)NULL );
|
225
|
+
|
226
|
+
rb_funcallv( thread_group, rb_intern("add"), 1, &callback_dispatcher );
|
227
|
+
rb_ivar_set( rant_mAnt, rb_intern("@callback_threads"), thread_group );
|
228
|
+
rb_ivar_set( rant_mAnt, rb_intern("@callback_dispatcher"), callback_dispatcher );
|
229
|
+
rb_attr( rb_singleton_class(rant_mAnt), rb_intern("callback_threads"), 1, 0, 0 );
|
230
|
+
rb_attr( rb_singleton_class(rant_mAnt), rb_intern("callback_dispatcher"), 1, 0, 0 );
|
231
|
+
}
|
232
|
+
|
233
|
+
|
@@ -0,0 +1,524 @@
|
|
1
|
+
/*
|
2
|
+
* channel.c - Ant::Channel class
|
3
|
+
* $Id$
|
4
|
+
*
|
5
|
+
* Authors:
|
6
|
+
* * Michael Granger <ged@FaerieMUD.org>
|
7
|
+
*
|
8
|
+
*/
|
9
|
+
|
10
|
+
#include "ant_ext.h"
|
11
|
+
|
12
|
+
VALUE rant_cAntChannel;
|
13
|
+
|
14
|
+
VALUE rant_mAntDataUtilities;
|
15
|
+
|
16
|
+
static void rant_channel_free( void * );
|
17
|
+
static void rant_channel_mark( void * );
|
18
|
+
|
19
|
+
|
20
|
+
static const rb_data_type_t rant_channel_datatype_t = {
|
21
|
+
.wrap_struct_name = "Ant::Channel",
|
22
|
+
.function = {
|
23
|
+
.dmark = rant_channel_mark,
|
24
|
+
.dfree = rant_channel_free,
|
25
|
+
},
|
26
|
+
.data = NULL,
|
27
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
28
|
+
};
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
/*
|
33
|
+
* Free function
|
34
|
+
*/
|
35
|
+
static void
|
36
|
+
rant_channel_free( void *ptr )
|
37
|
+
{
|
38
|
+
if ( ptr ) {
|
39
|
+
rant_channel_t *channel = (rant_channel_t *)ptr;
|
40
|
+
ANT_AssignChannelEventFunction( channel->channel_num, NULL, NULL );
|
41
|
+
ANT_UnAssignChannel( channel->channel_num );
|
42
|
+
|
43
|
+
channel->callback = Qnil;
|
44
|
+
|
45
|
+
xfree( ptr );
|
46
|
+
ptr = NULL;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
|
51
|
+
/*
|
52
|
+
* Mark function
|
53
|
+
*/
|
54
|
+
static void
|
55
|
+
rant_channel_mark( void *ptr )
|
56
|
+
{
|
57
|
+
rant_channel_t *channel = (rant_channel_t *)ptr;
|
58
|
+
rb_gc_mark( channel->callback );
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
/*
|
63
|
+
* Alloc function
|
64
|
+
*/
|
65
|
+
static VALUE
|
66
|
+
rant_channel_alloc( VALUE klass )
|
67
|
+
{
|
68
|
+
rant_channel_t *ptr;
|
69
|
+
|
70
|
+
VALUE rval = TypedData_Make_Struct( klass, rant_channel_t, &rant_channel_datatype_t, ptr );
|
71
|
+
ptr->callback = Qnil;
|
72
|
+
|
73
|
+
return rval;
|
74
|
+
}
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
/*
|
79
|
+
* Fetch the data pointer and check it for sanity.
|
80
|
+
*/
|
81
|
+
rant_channel_t *
|
82
|
+
rant_get_channel( VALUE self )
|
83
|
+
{
|
84
|
+
rant_channel_t *ptr;
|
85
|
+
|
86
|
+
if ( !IsChannel(self) ) {
|
87
|
+
rb_raise( rb_eTypeError, "wrong argument type %s (expected Ant::Channel)",
|
88
|
+
rb_class2name(CLASS_OF( self )) );
|
89
|
+
}
|
90
|
+
|
91
|
+
ptr = DATA_PTR( self );
|
92
|
+
assert( ptr );
|
93
|
+
|
94
|
+
return ptr;
|
95
|
+
}
|
96
|
+
|
97
|
+
|
98
|
+
/*
|
99
|
+
* Clear the registry after channel have been reset.
|
100
|
+
*/
|
101
|
+
void
|
102
|
+
rant_channel_clear_registry()
|
103
|
+
{
|
104
|
+
VALUE registry = rb_iv_get( rant_cAntChannel, "@registry" );
|
105
|
+
rb_hash_clear( registry );
|
106
|
+
}
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
/*
|
111
|
+
* call-seq:
|
112
|
+
* channel.initialize
|
113
|
+
*
|
114
|
+
* Set up the channel.
|
115
|
+
*
|
116
|
+
*/
|
117
|
+
static VALUE
|
118
|
+
rant_channel_init( VALUE self, VALUE channel_number, VALUE channel_type, VALUE network_number,
|
119
|
+
VALUE extended_options )
|
120
|
+
{
|
121
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
122
|
+
VALUE registry = rb_iv_get( rant_cAntChannel, "@registry" );
|
123
|
+
|
124
|
+
ptr->channel_num = NUM2USHORT( channel_number );
|
125
|
+
MEMZERO( ptr->buffer, unsigned char, MESG_MAX_SIZE );
|
126
|
+
|
127
|
+
rb_iv_set( self, "@channel_type", channel_type );
|
128
|
+
rb_iv_set( self, "@network_number", network_number );
|
129
|
+
rb_iv_set( self, "@extended_options", extended_options );
|
130
|
+
|
131
|
+
rb_hash_aset( registry, channel_number, self );
|
132
|
+
|
133
|
+
return self;
|
134
|
+
}
|
135
|
+
|
136
|
+
|
137
|
+
/*
|
138
|
+
* call-seq:
|
139
|
+
* channel.channel_number -> integer
|
140
|
+
*
|
141
|
+
* Return the channel number assigned to the Channel.
|
142
|
+
*
|
143
|
+
*/
|
144
|
+
static VALUE
|
145
|
+
rant_channel_channel_number( VALUE self )
|
146
|
+
{
|
147
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
148
|
+
|
149
|
+
return INT2FIX( ptr->channel_num );
|
150
|
+
}
|
151
|
+
|
152
|
+
|
153
|
+
/*
|
154
|
+
* call-seq:
|
155
|
+
* channel.set_channel_id( device_number, device_type, transmission_type, timeout=0 )
|
156
|
+
*
|
157
|
+
* Set the channel ID using the combination of the +device_number+,
|
158
|
+
* +device_type+, and +transmission_type+. If the assignment hasn't been set in
|
159
|
+
* +timeout+ seconds, aborts and returns +nil+.
|
160
|
+
*
|
161
|
+
*/
|
162
|
+
static VALUE
|
163
|
+
rant_channel_set_channel_id( int argc, VALUE *argv, VALUE self )
|
164
|
+
{
|
165
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
166
|
+
VALUE device_number, device_type, transmission_type, timeout;
|
167
|
+
unsigned short usDeviceNumber;
|
168
|
+
unsigned char ucDeviceType,
|
169
|
+
ucTransmissionType;
|
170
|
+
unsigned int ulResponseTime = 0;
|
171
|
+
bool result;
|
172
|
+
|
173
|
+
rb_scan_args( argc, argv, "31", &device_number, &device_type, &transmission_type, &timeout );
|
174
|
+
|
175
|
+
usDeviceNumber = NUM2USHORT( device_number );
|
176
|
+
ucDeviceType = NUM2USHORT( device_type );
|
177
|
+
ucTransmissionType = NUM2USHORT( transmission_type );
|
178
|
+
|
179
|
+
if ( RTEST(timeout) )
|
180
|
+
ulResponseTime = NUM2UINT( timeout );
|
181
|
+
|
182
|
+
result = ANT_SetChannelId_RTO( ptr->channel_num, usDeviceNumber, ucDeviceType,
|
183
|
+
ucTransmissionType, ulResponseTime );
|
184
|
+
|
185
|
+
if ( !result ) {
|
186
|
+
rb_raise( rb_eRuntimeError, "Failed to set the channel id." );
|
187
|
+
}
|
188
|
+
|
189
|
+
return Qtrue;
|
190
|
+
}
|
191
|
+
|
192
|
+
|
193
|
+
// ANT_SetChannelPeriod_RTO(UCHAR ucANTChannel_, USHORT usMesgPeriod_, ULONG ulResponseTime_);
|
194
|
+
// ANT_SetChannelSearchTimeout_RTO(UCHAR ucANTChannel_, UCHAR ucSearchTimeout_, ULONG ulResponseTime_);
|
195
|
+
// ANT_SetChannelRFFreq_RTO(UCHAR ucANTChannel_, UCHAR ucRFFreq_, ULONG ulResponseTime_);
|
196
|
+
|
197
|
+
|
198
|
+
/*
|
199
|
+
* call-seq:
|
200
|
+
* channel.open( tineout=0 )
|
201
|
+
*
|
202
|
+
* Open the channel. If it hasn't completed within +timeout+ seconds, raises a RuntimeError.
|
203
|
+
*/
|
204
|
+
static VALUE
|
205
|
+
rant_channel_open( int argc, VALUE *argv, VALUE self )
|
206
|
+
{
|
207
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
208
|
+
VALUE timeout;
|
209
|
+
unsigned int ulResponseTime = 0;
|
210
|
+
|
211
|
+
rb_scan_args( argc, argv, "01", &timeout );
|
212
|
+
|
213
|
+
if ( RTEST(timeout) )
|
214
|
+
ulResponseTime = NUM2UINT( timeout );
|
215
|
+
|
216
|
+
if ( !ANT_OpenChannel_RTO( ptr->channel_num, ulResponseTime ) ) {
|
217
|
+
rb_raise( rb_eRuntimeError, "Failed to open the channel." );
|
218
|
+
}
|
219
|
+
|
220
|
+
return Qtrue;
|
221
|
+
}
|
222
|
+
|
223
|
+
|
224
|
+
/*
|
225
|
+
* call-seq:
|
226
|
+
* channel.close
|
227
|
+
*
|
228
|
+
* Close the channel and remove it from the registry.
|
229
|
+
*
|
230
|
+
*/
|
231
|
+
static VALUE
|
232
|
+
rant_channel_close( int argc, VALUE *argv, VALUE self )
|
233
|
+
{
|
234
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
235
|
+
VALUE timeout;
|
236
|
+
VALUE registry = rb_iv_get( rant_cAntChannel, "@registry" );
|
237
|
+
unsigned int ulResponseTime = 0;
|
238
|
+
|
239
|
+
rb_scan_args( argc, argv, "01", &timeout );
|
240
|
+
|
241
|
+
if ( RTEST(timeout) )
|
242
|
+
ulResponseTime = NUM2UINT( timeout );
|
243
|
+
|
244
|
+
rant_log_obj( self, "info", "Closing channel %d (with timeout %d).", ptr->channel_num, ulResponseTime );
|
245
|
+
if ( !ANT_CloseChannel_RTO( ptr->channel_num, ulResponseTime ) ) {
|
246
|
+
rb_raise( rb_eRuntimeError, "Failed to close the channel." );
|
247
|
+
}
|
248
|
+
rant_log_obj( self, "info", "Channel %d closed.", ptr->channel_num );
|
249
|
+
|
250
|
+
rb_hash_delete( registry, INT2FIX( ptr->channel_num ) );
|
251
|
+
|
252
|
+
return Qtrue;
|
253
|
+
}
|
254
|
+
|
255
|
+
|
256
|
+
/*
|
257
|
+
* call-seq:
|
258
|
+
* channel.closed? -> true or false
|
259
|
+
*
|
260
|
+
* Returns +true+ if the channel has been closed; i.e., if it's not longer the
|
261
|
+
* registered channel for its channel number.
|
262
|
+
*
|
263
|
+
*/
|
264
|
+
static VALUE
|
265
|
+
rant_channel_closed_p( VALUE self )
|
266
|
+
{
|
267
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
268
|
+
VALUE registry = rb_iv_get( rant_cAntChannel, "@registry" );
|
269
|
+
VALUE channel = rb_hash_lookup( registry, INT2FIX( ptr->channel_num ) );
|
270
|
+
|
271
|
+
return self == channel ? Qfalse : Qtrue;
|
272
|
+
}
|
273
|
+
|
274
|
+
|
275
|
+
/*
|
276
|
+
* Event callback functions
|
277
|
+
*/
|
278
|
+
|
279
|
+
struct on_event_call {
|
280
|
+
unsigned char ucANTChannel;
|
281
|
+
unsigned char ucEvent;
|
282
|
+
};
|
283
|
+
|
284
|
+
|
285
|
+
/*
|
286
|
+
* Handle the event callback -- Ruby side.
|
287
|
+
*/
|
288
|
+
static VALUE
|
289
|
+
rant_channel_call_event_callback( VALUE callPtr )
|
290
|
+
{
|
291
|
+
struct on_event_call *call = (struct on_event_call *)callPtr;
|
292
|
+
VALUE registry = rb_iv_get( rant_cAntChannel, "@registry" );
|
293
|
+
VALUE channel = rb_hash_fetch( registry, INT2FIX(call->ucANTChannel) );
|
294
|
+
rant_channel_t *ptr = rant_get_channel( channel );
|
295
|
+
VALUE rb_callback = ptr->callback;
|
296
|
+
VALUE rval = Qnil;
|
297
|
+
|
298
|
+
if ( RTEST(rb_callback) ) {
|
299
|
+
VALUE args[3];
|
300
|
+
|
301
|
+
args[0] = INT2FIX( call->ucANTChannel );
|
302
|
+
args[1] = INT2FIX( call->ucEvent );
|
303
|
+
args[2] = rb_enc_str_new( (char *)ptr->buffer, MESG_MAX_SIZE, rb_ascii8bit_encoding() );
|
304
|
+
|
305
|
+
rval = rb_funcallv_public( rb_callback, rb_intern("call"), 3, args );
|
306
|
+
}
|
307
|
+
|
308
|
+
MEMZERO( ptr->buffer, unsigned char, MESG_MAX_SIZE );
|
309
|
+
|
310
|
+
return rval;
|
311
|
+
}
|
312
|
+
|
313
|
+
|
314
|
+
/*
|
315
|
+
* Handle the event callback -- C side.
|
316
|
+
*/
|
317
|
+
static BOOL
|
318
|
+
rant_channel_on_event_callback( unsigned char ucANTChannel, unsigned char ucEvent )
|
319
|
+
{
|
320
|
+
rant_callback_t callback;
|
321
|
+
struct on_event_call call;
|
322
|
+
|
323
|
+
call.ucANTChannel = ucANTChannel;
|
324
|
+
call.ucEvent = ucEvent;
|
325
|
+
|
326
|
+
callback.data = &call;
|
327
|
+
callback.fn = rant_channel_call_event_callback;
|
328
|
+
|
329
|
+
return rant_callback( &callback );
|
330
|
+
}
|
331
|
+
|
332
|
+
|
333
|
+
/*
|
334
|
+
* call-seq:
|
335
|
+
* channel.on_event {|channel_num, event_id, data| ... }
|
336
|
+
*
|
337
|
+
* Set up a callback for events on the receiving channel.
|
338
|
+
*
|
339
|
+
*/
|
340
|
+
static VALUE
|
341
|
+
rant_channel_on_event( int argc, VALUE *argv, VALUE self )
|
342
|
+
{
|
343
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
344
|
+
VALUE callback = Qnil;
|
345
|
+
|
346
|
+
rb_scan_args( argc, argv, "0&", &callback );
|
347
|
+
|
348
|
+
if ( !RTEST(callback) ) {
|
349
|
+
rb_raise( rb_eLocalJumpError, "block required, but not given" );
|
350
|
+
}
|
351
|
+
|
352
|
+
rant_log_obj( self, "debug", "Channel event callback is: %s", RSTRING_PTR(rb_inspect(callback)) );
|
353
|
+
ptr->callback = callback;
|
354
|
+
|
355
|
+
ANT_AssignChannelEventFunction( ptr->channel_num, rant_channel_on_event_callback, ptr->buffer );
|
356
|
+
|
357
|
+
return Qtrue;
|
358
|
+
}
|
359
|
+
|
360
|
+
|
361
|
+
/*
|
362
|
+
* call-seq:
|
363
|
+
* channel.send_burst_transfer( data )
|
364
|
+
*
|
365
|
+
* Send the given +data+ as one or more burst packets.
|
366
|
+
*
|
367
|
+
*/
|
368
|
+
static VALUE
|
369
|
+
rant_channel_send_burst_transfer( VALUE self, VALUE data )
|
370
|
+
{
|
371
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
372
|
+
unsigned char *data_s;
|
373
|
+
long data_len = RSTRING_LEN( data );
|
374
|
+
unsigned short usNumDataPackets = data_len / 8,
|
375
|
+
remainingBytes = data_len % 8;
|
376
|
+
|
377
|
+
data_s = ALLOC_N( unsigned char, data_len );
|
378
|
+
strncpy( (char *)data_s, StringValuePtr(data), data_len );
|
379
|
+
|
380
|
+
// Pad it to 8-byte alignment
|
381
|
+
if ( remainingBytes ) {
|
382
|
+
int pad_bytes = (8 - remainingBytes);
|
383
|
+
REALLOC_N( data_s, unsigned char, data_len + pad_bytes );
|
384
|
+
memset( data_s + data_len, 0, pad_bytes );
|
385
|
+
|
386
|
+
usNumDataPackets += 1;
|
387
|
+
}
|
388
|
+
|
389
|
+
VALUE data_string = rb_enc_str_new( (char *)data_s, usNumDataPackets * 8, rb_ascii8bit_encoding() );
|
390
|
+
VALUE hexdump = rb_funcall( rant_mAntDataUtilities, rb_intern("hexdump"), 1, data_string );
|
391
|
+
|
392
|
+
rant_log_obj( self, "debug", "Sending burst packets:\n%s", RSTRING_PTR(hexdump) );
|
393
|
+
if ( !ANT_SendBurstTransfer(ptr->channel_num, data_s, usNumDataPackets) ) {
|
394
|
+
rb_raise( rb_eRuntimeError, "failed to send burst transfer." );
|
395
|
+
}
|
396
|
+
|
397
|
+
return Qtrue;
|
398
|
+
}
|
399
|
+
|
400
|
+
|
401
|
+
/*
|
402
|
+
* call-seq:
|
403
|
+
* channel.send_acknowledged_data( data )
|
404
|
+
*
|
405
|
+
* Send the given +data+ as an acknowledged transmission. The +data+ cannot be longer
|
406
|
+
* than 8 bytes in length.
|
407
|
+
*
|
408
|
+
*/
|
409
|
+
static VALUE
|
410
|
+
rant_channel_send_acknowledged_data( VALUE self, VALUE data )
|
411
|
+
{
|
412
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
413
|
+
UCHAR aucTempBuffer[] = {0, 0, 0, 0, 0, 0, 0, 0};
|
414
|
+
|
415
|
+
StringValue( data );
|
416
|
+
if ( RSTRING_LEN(data) > 8 ) {
|
417
|
+
rb_raise( rb_eArgError, "Data can't be longer than 8 bytes." );
|
418
|
+
}
|
419
|
+
strncpy( (char *)aucTempBuffer, StringValuePtr(data), RSTRING_LEN(data) );
|
420
|
+
|
421
|
+
ANT_SendAcknowledgedData( ptr->channel_num, aucTempBuffer );
|
422
|
+
|
423
|
+
return Qtrue;
|
424
|
+
}
|
425
|
+
|
426
|
+
|
427
|
+
/*
|
428
|
+
* call-seq:
|
429
|
+
* channel.send_broadcast_data( data )
|
430
|
+
*
|
431
|
+
* Send the given +data+ as a broadcast transmission. The +data+ cannot be longer
|
432
|
+
* than 8 bytes in length.
|
433
|
+
*
|
434
|
+
*/
|
435
|
+
static VALUE
|
436
|
+
rant_channel_send_broadcast_data( VALUE self, VALUE data )
|
437
|
+
{
|
438
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
439
|
+
UCHAR aucTempBuffer[8] = {0, 0, 0, 0, 0, 0, 0, 0,};
|
440
|
+
|
441
|
+
StringValue( data );
|
442
|
+
if ( RSTRING_LEN(data) > 8 ) {
|
443
|
+
rb_raise( rb_eArgError, "Data can't be longer than 8 bytes." );
|
444
|
+
}
|
445
|
+
strncpy( (char *)aucTempBuffer, StringValuePtr(data), RSTRING_LEN(data) );
|
446
|
+
|
447
|
+
ANT_SendBroadcastData( ptr->channel_num, aucTempBuffer );
|
448
|
+
|
449
|
+
return Qtrue;
|
450
|
+
}
|
451
|
+
|
452
|
+
|
453
|
+
/*
|
454
|
+
* call-seq:
|
455
|
+
* channel.set_channel_rf_freq( frequency )
|
456
|
+
*
|
457
|
+
* Set the ANT RF +frequency+.
|
458
|
+
*
|
459
|
+
*/
|
460
|
+
static VALUE
|
461
|
+
rant_channel_set_channel_rf_freq( VALUE self, VALUE frequency )
|
462
|
+
{
|
463
|
+
rant_channel_t *ptr = rant_get_channel( self );
|
464
|
+
unsigned short ucRFFreq = NUM2USHORT( frequency );
|
465
|
+
|
466
|
+
if ( ucRFFreq > 124 ) {
|
467
|
+
rb_raise( rb_eArgError, "frequency must be between 0 and 124." );
|
468
|
+
}
|
469
|
+
|
470
|
+
ANT_SetChannelRFFreq( ptr->channel_num, ucRFFreq );
|
471
|
+
|
472
|
+
return Qtrue;
|
473
|
+
}
|
474
|
+
|
475
|
+
|
476
|
+
void
|
477
|
+
init_ant_channel()
|
478
|
+
{
|
479
|
+
#ifdef FOR_RDOC
|
480
|
+
rb_cData = rb_define_class( "Data" );
|
481
|
+
rant_mAnt = rb_define_module( "Ant" );
|
482
|
+
#endif
|
483
|
+
|
484
|
+
/*
|
485
|
+
* Document-module: Ant::Channel
|
486
|
+
*
|
487
|
+
* An assigned ANT channel object.
|
488
|
+
*
|
489
|
+
*/
|
490
|
+
rant_cAntChannel = rb_define_class_under( rant_mAnt, "Channel", rb_cObject );
|
491
|
+
rb_iv_set( rant_cAntChannel, "@registry", rb_hash_new() );
|
492
|
+
|
493
|
+
rant_mAntDataUtilities = rb_define_module_under( rant_mAnt, "DataUtilities" );
|
494
|
+
|
495
|
+
rb_define_alloc_func( rant_cAntChannel, rant_channel_alloc );
|
496
|
+
rb_define_protected_method( rant_cAntChannel, "initialize", rant_channel_init, 4 );
|
497
|
+
|
498
|
+
rb_define_method( rant_cAntChannel, "channel_number", rant_channel_channel_number, 0 );
|
499
|
+
|
500
|
+
rb_attr( rant_cAntChannel, rb_intern("channel_type"), 1, 0, 0 );
|
501
|
+
rb_attr( rant_cAntChannel, rb_intern("network_number"), 1, 0, 0 );
|
502
|
+
rb_attr( rant_cAntChannel, rb_intern("extended_options"), 1, 0, 0 );
|
503
|
+
|
504
|
+
rb_define_method( rant_cAntChannel, "set_channel_id", rant_channel_set_channel_id, -1 );
|
505
|
+
// rb_define_method( rant_cAntChannel, "set_channel_period",
|
506
|
+
// rant_channel_set_channel_period, -1 );
|
507
|
+
// rb_define_method( rant_cAntChannel, "set_channel_search_timeout",
|
508
|
+
// rant_channel_set_channel_search_timeout, -1 );
|
509
|
+
rb_define_method( rant_cAntChannel, "set_channel_rf_freq", rant_channel_set_channel_rf_freq, 1 );
|
510
|
+
|
511
|
+
rb_define_method( rant_cAntChannel, "open", rant_channel_open, -1 );
|
512
|
+
rb_define_method( rant_cAntChannel, "close", rant_channel_close, -1 );
|
513
|
+
rb_define_method( rant_cAntChannel, "closed?", rant_channel_closed_p, 0 );
|
514
|
+
|
515
|
+
rb_define_method( rant_cAntChannel, "send_burst_transfer", rant_channel_send_burst_transfer, 1 );
|
516
|
+
rb_define_method( rant_cAntChannel, "send_acknowledged_data", rant_channel_send_acknowledged_data, 1 );
|
517
|
+
rb_define_method( rant_cAntChannel, "send_broadcast_data", rant_channel_send_broadcast_data, 1 );
|
518
|
+
|
519
|
+
rb_define_method( rant_cAntChannel, "on_event", rant_channel_on_event, -1 );
|
520
|
+
|
521
|
+
rb_require( "ant/channel" );
|
522
|
+
}
|
523
|
+
|
524
|
+
|