M ant-wireless.gemspec +3 -3
@@ 1,16 1,16 @@
# -*- encoding: utf-8 -*-
-# stub: ant-wireless 0.3.0.pre.20211011092718 ruby lib
+# stub: ant-wireless 0.4.0.pre.20211028134625 ruby lib
# stub: ext/ant_ext/extconf.rb
Gem::Specification.new do |s|
s.name = "ant-wireless".freeze
- s.version = "0.3.0.pre.20211011092718"
+ s.version = "0.4.0.pre.20211028134625"
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
s.metadata = { "bug_tracker_uri" => "https://todo.sr.ht/~ged/ruby-ant-wireless", "changelog_uri" => "https://deveiate.org/code/ant-wireless/History_md.html", "documentation_uri" => "https://deveiate.org/code/ant-wireless", "homepage_uri" => "https://sr.ht/~ged/ruby-ant-wireless/", "source_uri" => "https://hg.sr.ht/~ged/ruby-ant-wireless" } if s.respond_to? :metadata=
s.require_paths = ["lib".freeze]
s.authors = ["Michael Granger".freeze, "Mahlon E. Smith".freeze]
- s.date = "2021-10-11"
+ s.date = "2021-10-28"
s.description = "A binding for the ANT ultra-low power wireless protocol via the Garmin USB ANT Stick. ANT can be used to send information wirelessly from one device to another device, in a robust and flexible manner.".freeze
s.email = ["ged@FaerieMUD.org".freeze, "mahlon@martini.nu".freeze]
s.extensions = ["ext/ant_ext/extconf.rb".freeze]
M ext/ant_ext/ant_ext.c +83 -3
@@ 225,6 225,15 @@ rant_s_init( int argc, VALUE *argv, VALU
}
+/*
+ * call-seq:
+ * Ant.initialized? -> true or false
+ *
+ * Returns +true+ if the ANT library has been initialized.
+ *
+ * Note: this requires a modified version of the Garmin ANT-SDK.
+ *
+ */
static VALUE
rant_s_initialized_p( VALUE _module )
{
@@ 418,6 427,56 @@ rant_s_use_extended_messages_eq( VALUE _
}
+/*
+ * call-seq:
+ * Ant.configure_advanced_burst( enabled, max_packet_length, required_fields, optional_fields,
+ * stall_count=3210, retry_count=4 )
+ *
+ * Enable/disable and configure advanced burst. This is the lower-level method; the
+ * higher-level methods are: #enable_advanced_burst and #disable_advanced_burst.
+ *
+ */
+static VALUE
+rant_s_configure_advanced_burst( int argc, VALUE *argv, VALUE _module )
+{
+ VALUE enabled,
+ max_packet_length,
+ required_fields,
+ optional_fields,
+ stall_count = Qnil,
+ retry_count = Qnil;
+ bool bEnable;
+ unsigned char ucMaxPacketLength,
+ ucRetryCount = 0;
+ unsigned long ulRequiredFields,
+ ulOptionalFields;
+ unsigned short usStallCount = 0;
+ bool rval;
+
+ rb_scan_args( argc, argv, "42", &enabled, &max_packet_length, &required_fields,
+ &optional_fields, &stall_count, &retry_count );
+
+ bEnable = RTEST( enabled );
+ ucMaxPacketLength = NUM2CHR( max_packet_length );
+ ulRequiredFields = NUM2ULONG( required_fields );
+ ulOptionalFields = NUM2ULONG( optional_fields );
+
+ if ( RTEST(stall_count) ) {
+ usStallCount = NUM2USHORT( stall_count );
+ }
+ if ( RTEST(retry_count) ) {
+ ucRetryCount = NUM2CHR( retry_count );
+ }
+
+ rant_log( "warn", "Configuring advanced burst: enable = %d, maxpacketlength = %d",
+ bEnable, ucMaxPacketLength );
+ rval = ANT_ConfigureAdvancedBurst_ext( bEnable, ucMaxPacketLength, ulRequiredFields,
+ ulOptionalFields, usStallCount, ucRetryCount );
+
+ return rval ? Qtrue : Qfalse;
+}
+
+
// Buffer for response data.
// static UCHAR pucResponseBuffer[ MESG_RESPONSE_EVENT_SIZE ];
static UCHAR pucResponseBuffer[ MESG_MAX_SIZE_VALUE ];
@@ 510,7 569,7 @@ rant_s_on_response( int argc, VALUE *arg
*
*/
static VALUE
-rant_s_request_capabilities( VALUE module )
+rant_s_request_capabilities( VALUE _module )
{
bool rval = ANT_RequestMessage( 0, MESG_CAPABILITIES_ID );
return rval ? Qtrue : Qfalse;
@@ 527,7 586,7 @@ rant_s_request_capabilities( VALUE modul
*
*/
static VALUE
-rant_s_request_serial_num( VALUE module )
+rant_s_request_serial_num( VALUE _module )
{
bool rval = ANT_RequestMessage( 0, MESG_GET_SERIAL_NUM_ID );
return rval ? Qtrue : Qfalse;
@@ 544,7 603,7 @@ rant_s_request_serial_num( VALUE module
*
*/
static VALUE
-rant_s_request_version( VALUE module )
+rant_s_request_version( VALUE _module )
{
bool rval = ANT_RequestMessage( 0, MESG_VERSION_ID );
return rval ? Qtrue : Qfalse;
@@ 553,6 612,23 @@ rant_s_request_version( VALUE module )
/*
* call-seq:
+ * Ant.request_advanced_burst_capabilities
+ *
+ * Request the current device's advanced burst capabilities. The result will
+ * be delivered via a callback to the #on_version response callback, which by
+ * default extracts it and stores it at Ant.advanced_burst_capabilities.
+ *
+ */
+static VALUE
+rant_s_request_advanced_burst_capabilities( VALUE _module )
+{
+ bool rval = ANT_RequestMessage( 0, MESG_CONFIG_ADV_BURST_ID );
+ return rval ? Qtrue : Qfalse;
+}
+
+
+/*
+ * call-seq:
* Ant.log_directory = "path/to/log/dir"
*
* Write debugging logs to the specified directory, which should already exist.
@@ 606,6 682,8 @@ Init_ant_ext()
rb_define_singleton_method( rant_mAnt, "use_extended_messages=",
rant_s_use_extended_messages_eq, 1 );
+ rb_define_singleton_method( rant_mAnt, "configure_advanced_burst",
+ rant_s_configure_advanced_burst, -1 );
rb_define_singleton_method( rant_mAnt, "on_response", rant_s_on_response, -1 );
// EXPORT void ANT_UnassignAllResponseFunctions(); //Unassigns all response functions
@@ 613,6 691,8 @@ Init_ant_ext()
rb_define_singleton_method( rant_mAnt, "request_capabilities", rant_s_request_capabilities, 0 );
rb_define_singleton_method( rant_mAnt, "request_serial_num", rant_s_request_serial_num, 0 );
rb_define_singleton_method( rant_mAnt, "request_version", rant_s_request_version, 0 );
+ rb_define_singleton_method( rant_mAnt, "request_advanced_burst_capabilities",
+ rant_s_request_advanced_burst_capabilities, 0 );
rb_define_singleton_method( rant_mAnt, "log_directory=", rant_s_log_directory_eq, 1 );
M ext/ant_ext/channel.c +59 -0
@@ 9,6 9,8 @@
#include "ant_ext.h"
+#define DEFAULT_ADV_PACKETS 3
+
VALUE rant_cAntChannel;
VALUE rant_mAntDataUtilities;
@@ 300,6 302,14 @@ rant_channel_set_channel_rf_freq( VALUE
}
+/*
+ * call-seq:
+ * channel.set_frequency_agility( freq1, freq2, freq3 )
+ *
+ * Set the frequencies to use in frequency agility (integers between 0 and 124). These values
+ * use the same convention as the +rf_frequency+ setting; i.e., they're offsets from 2400 MHz.
+ *
+ */
static VALUE
rant_channet_set_frequency_agility( VALUE self, VALUE freq1, VALUE freq2, VALUE freq3 )
{
@@ 582,6 592,54 @@ rant_channel_send_broadcast_data( VALUE
}
+/*
+ * call-seq:
+ * channel.send_advanced_transfer( data, packets_per_message=3 )
+ *
+ * Send the given +data+ as one or more advanced burst packets. The +packets_per_message+
+ * may be set to a value between 1 and 3 to control how many 8-byte packets are send with
+ * each message.
+ *
+ */
+static VALUE
+rant_channel_send_advanced_transfer( int argc, VALUE *argv, VALUE self )
+{
+ rant_channel_t *ptr = rant_get_channel( self );
+ VALUE data = Qnil, packets = Qnil;
+ unsigned char *data_s;
+ long data_len;
+ unsigned short usNumDataPackets = data_len / 8,
+ remainingBytes = data_len % 8;
+ unsigned char ucStdPcktsPerSerialMsg = DEFAULT_ADV_PACKETS;
+
+ rb_scan_args( argc, argv, "11", &data, &packets );
+ data_len = RSTRING_LEN( data );
+ if ( RTEST(packets) ) {
+ ucStdPcktsPerSerialMsg = NUM2CHR(packets);
+ }
+
+ data_s = ALLOC_N( unsigned char, data_len );
+ strncpy( (char *)data_s, StringValuePtr(data), data_len );
+
+ // Pad it to 8-byte alignment
+ if ( remainingBytes ) {
+ int pad_bytes = (8 - remainingBytes);
+ REALLOC_N( data_s, unsigned char, data_len + pad_bytes );
+ memset( data_s + data_len, 0, pad_bytes );
+
+ usNumDataPackets += 1;
+ }
+
+ rant_log_obj( self, "warn", "Sending advanced burst packets (%d-byte messages)",
+ ucStdPcktsPerSerialMsg * 8 );
+ if ( !ANT_SendAdvancedBurstTransfer(ptr->channel_num, data_s, usNumDataPackets, ucStdPcktsPerSerialMsg) ) {
+ rb_raise( rb_eRuntimeError, "failed to send advanced burst transfer." );
+ }
+
+ return Qtrue;
+}
+
+
void
init_ant_channel()
{
@@ 630,6 688,7 @@ init_ant_channel()
rb_define_method( rant_cAntChannel, "send_burst_transfer", rant_channel_send_burst_transfer, 1 );
rb_define_method( rant_cAntChannel, "send_acknowledged_data", rant_channel_send_acknowledged_data, 1 );
rb_define_method( rant_cAntChannel, "send_broadcast_data", rant_channel_send_broadcast_data, 1 );
+ rb_define_method( rant_cAntChannel, "send_advanced_transfer", rant_channel_send_advanced_transfer, -1 );
rb_define_method( rant_cAntChannel, "on_event", rant_channel_on_event, -1 );
M ext/ant_ext/extconf.rb +5 -0
@@ 18,6 18,11 @@ have_func( 'ANT_IsInitialized', 'libant.
have_func( 'ANT_LibVersion', 'libant.h' )
have_func( 'ANT_GetDeviceSerialNumber', 'libant.h' )
+# Ref: https://bugs.ruby-lang.org/issues/17865
+$CPPFLAGS << " -Wno-compound-token-split-by-macro "
+
+$CFLAGS << " -g "
+
create_header()
create_makefile( 'ant_ext' )
M lib/ant.rb +58 -0
@@ 26,6 26,14 @@ module Ant
# The valid offsets for the "RF Frequency" setting; this is an offset from 2400Hz.
VALID_RF_FREQUENCIES = ( 0...124 ).freeze
+ # Default options for advanced burst when it's enabled.
+ DEFAULT_ADVANCED_OPTIONS = {
+ max_packet_length: 24,
+ frequency_hopping: :optional,
+ stall_count: 0,
+ retry_count: 0
+ }
+
# Loggability API -- set up a logger for the library
log_as :ant
@@ 143,5 151,55 @@ module Ant
return offset
end
+
+ ### Enable advanced burst mode with the given +options+.
+ def self::enable_advanced_burst( **options )
+ options = DEFAULT_ADVANCED_OPTIONS.merge( options )
+
+ max_packet_length = self.convert_max_packet_length( options[:max_packet_length] )
+
+ required_fields = self.make_required_fields_config( options )
+ optional_fields = self.make_optional_fields_config( options )
+
+ stall_count = options[:stall_count]
+ retry_count = options[:retry_count]
+
+ self.configure_advanced_burst( true, max_packet_length, required_fields, optional_fields,
+ stall_count, retry_count )
+ end
+
+
+ ### Validate that the specified +length+ (in bytes) is a valid setting as an
+ ### advanced burst max packet length configuration value. Returns the equivalent
+ ### configuration value.
+ def self::convert_max_packet_length( length )
+ case length
+ when 8 then return 0x01
+ when 16 then return 0x02
+ when 24 then return 0x03
+ else
+ raise ArgumentError,
+ "invalid max packet length; expected 8, 16, or 24, got %p" % [ length ]
+ end
+ end
+
+
+ ### Given an options hash, return a configuration value for required fields.
+ def self::make_required_fields_config( **options )
+ value = 0
+ value |= 0x01 if options[:frequency_hopping] == :required
+
+ return value
+ end
+
+
+ ### Given an options hash, return a configuration value for optional fields.
+ def self::make_optional_fields_config( **options )
+ value = 0
+ value |= 0x01 if options[:frequency_hopping] == :optional
+
+ return value
+ end
+
end # module Ant
M lib/ant/response_callbacks.rb +52 -2
@@ 9,7 9,8 @@ require 'ant/bitvector'
# A module that handles response callbacks by logging them.
module Ant::ResponseCallbacks
- extend Loggability
+ extend Loggability,
+ Ant::DataUtilities
# Loggability API -- send logs to the Ant logger
log_to :ant
@@ 49,9 50,10 @@ module Ant::ResponseCallbacks
Ant::Message::MESG_RADIO_TX_POWER_ID => :on_radio_tx_power,
Ant::Message::MESG_AUTO_FREQ_CONFIG_ID => :on_auto_freq_config,
+ Ant::Message::MESG_CONFIG_ADV_BURST_ID => :on_config_adv_burst_id,
# :TODO: There are many other MESG_ constants, but I think most or all of
- # them are for the serial protocol.
+ # them are only used for the serial protocol.
}
@@ 236,6 238,54 @@ module Ant::ResponseCallbacks
end
+ ### Handle callback when requesting advanced burst configuration.
+ def on_config_adv_burst_id( type, data )
+ self.log.debug "Advanced burst config/capabilities: 0x%02x: %p" % [ type, data ]
+
+ # Advanced burst capabilities
+ if type == 0
+ max_packet_length, features = data.unpack( 'CV' )
+ features = Ant::BitVector.new( features )
+
+ caps = {
+ max_packet_length: max_packet_length,
+ frequency_hopping: features.on?( Ant::ADV_BURST_CONFIG_FREQ_HOP )
+ }
+
+ self.log.info "Advanced burst capabilities: %p" % [ caps ]
+ Ant.instance_variable_set( :@advanced_burst_capabilities, caps );
+
+ # Advanced burst current configuration
+ elsif type == 1
+ enabled, max_packet_length, required, optional, stall_count, retry_count =
+ data.unpack( 'CCVVvC' )
+ required = Ant::BitVector.new( required )
+ optional = Ant::BitVector.new( optional )
+
+ required_features = []
+ required_features << :frequency_hopping if required.on?( Ant::ADV_BURST_CONFIG_FREQ_HOP )
+
+ optional_features = []
+ optional_features << :frequency_hopping if optional.on?( Ant::ADV_BURST_CONFIG_FREQ_HOP )
+
+ config = {
+ enabled: enabled == 1,
+ max_packet_length: max_packet_length,
+ required_features: required_features,
+ optional_features: optional_features,
+ stall_count: stall_count,
+ retry_count_extension: retry_count
+ }
+
+ self.log.info "Advanced burst configuration: %p" % [ config ]
+ Ant.instance_variable_set( :@advanced_burst_config, config );
+
+ else
+ self.log.warn "Unknown advanced burst response type %p." % [ type ]
+ end
+ end
+
+
### Handle capabilities response event.
def on_capabilities( channel_num, data )
std_opts = Ant::BitVector.new( data.bytes[2] )