Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1512 lines (960 sloc) 34.8 KB
/*
* C Seaplus driver in charge of converting, for each function exposed by the
* 'mobile' (Ceylan-Mobile) service API, the Erlang parameters received from the
* port into C variables that can be passed to the service functions (provided
* by libgammu), and to perform the reciprocal operation on their results, so
* that they can be sent back to the Erlang calling side.
*
*/
// Generated by the Seaplus parse transform, based on mobile.erl:
#include "mobile_seaplus_api_mapping.h"
// Generic helpers to facilitate the writing of this C part of the bridge:
#include "seaplus.h"
// To access to the actual C implementation of the Gammu library:
#include "gammu.h"
// For exit:
#include <stdlib.h>
// For signal:
#include <signal.h>
// For memset:
#include <string.h>
// For va_start and friends:
#include <stdarg.h>
/* Apparently, when the callback set in SetSendSMSStatusCallback/3 is triggered,
no piece of information allows to relate a specific SMS that was sent to that
callback (message reference is set by the carrier in the PDU that is received
back).
So, if sending a series of SMS in a row, we can only suppose that the
callbacks triggered will be in-order (first callback corresponding to first
SMS sent, etc.).
Anyway, even with the dummy device, if performing two sendings in a row, the
callback for the first will be triggered before the second is sent, so no
problematic interleaving is expected to happen (hopefully!).
*/
/* The reference onto a SMS is supposed to be an unsigned char, but the
* signature of the callback tells otherwise:
*
*/
typedef int sms_tpmr ;
// Matches mobile:encoding_to_enum/1:
enum encoding { unicode_uncompressed=1,
unicode_compressed,
gsm_uncompressed,
gsm_compressed,
eight_bit } ;
// Forward declarations:
void start_gammu( GSM_StateMachine * gammu_fsm ) ;
void send_regular_sms( input_buffer read_buff, buffer_index * index,
GSM_StateMachine * gammu_fsm ) ;
void send_multipart_sms( input_buffer read_buf, buffer_index * index,
GSM_StateMachine * gammu_fsm ) ;
void read_all_sms( input_buffer read_buf, buffer_index * index,
GSM_StateMachine * gammu_fsm ) ;
GSM_Coding_Type get_gammu_encoding( enum encoding e ) ;
enum encoding get_mobile_encoding( GSM_Coding_Type e ) ;
void check_gammu_error( GSM_Error error, GSM_StateMachine * gammu_fsm ) ;
void raise_gammu_error( GSM_StateMachine * gammu_fsm,
const char * format, ... ) ;
void stop_gammu( GSM_StateMachine * gammu_fsm ) ;
// Defined and used by Seaplus:
extern FILE * log_file ;
// If wanting to enable global debugging of Gammu in Seaplus logs:
bool enable_gammu_logging = true ;
// If wanting to enable FSM-level debugging of Gammu in Seaplus logs:
bool enable_gammu_state_machine_logging = false ;
// Typically for a sending:
typedef int status ;
// UNIX signal:
typedef int signal_reported ;
typedef unsigned int sms_count ;
volatile bool shutdown_requested = false ;
// Tells whether the last SMS sending succeeded:
volatile status sms_send_status = ERR_NONE ;
GSM_Debug_Info * debug_info ;
GSM_SMSMessage sms ;
GSM_SMSC device_smsc ;
/* The Seaplus-managed main output (smart) buffer, where returned terms will be
* written:
*
*/
output_buffer output_sm_buf ;
/*
* Due to libgammu's mode of operation, an interrupt-specific output (smart)
* buffer is useful, in order not to interfere with the main Seaplus one that
* could be already in use.
*
* We believe that, here, up to one interrupt can be active at any time (not
* multiple ones).
*
* Note that, at least for this case, the main output buffer could be directly
* used here (as either the answer to the Erlang part is directly sent once the
* command is triggered, or when a corresponding interrupt fires - never in both
* cases).
*
*/
output_buffer interrupt_sm_buf ;
// Buffer to store temporary strings (including the text of very long SMS):
char * string_buffer ;
// Poor's man pseudo-mutex (most probably useless by design):
bool interrupt_in_use = false ;
const size_t main_buffer_size = 10000 ;
// Maximum number of SMS read in one operation:
//const sms_count max_sms_read = 500 ;
/*
* Callback triggered by Gammu after a request to send an SMS was issued.
*
* Returns a relevant term to the Erlang side.
*
*/
void sms_sending_callback( GSM_StateMachine * gammu_fsm, status send_status,
sms_tpmr ref, void * user_data )
{
LOG_DEBUG( "Entering sending callback." ) ;
/* Not expected to ever happen, as the (Erlang) caller is blocked, waiting for
* an answer not sent yet.
*
*/
if ( interrupt_in_use )
raise_gammu_error( gammu_fsm, "Unexpected nested interrupt" ) ;
interrupt_in_use = true ;
// Only active in the course of this callback (cleared at its end):
init_output_buffer( &interrupt_sm_buf ) ;
sms_send_status = ERR_NONE ;
/* In case of success, returning { send_success, SMSRef }, otherwise
* returning { send_failure, SMSRef }:
*
*/
write_tuple_header_result( &interrupt_sm_buf, 2 ) ;
if ( send_status == 0 )
{
LOG_DEBUG( "Received a success notification regarding the sending of "
"the SMS whose reference is #%i on device %s.", ref,
GSM_GetConfig( gammu_fsm, -1 )->Device ) ;
write_atom_result( &interrupt_sm_buf, "send_success" ) ;
}
else
{
LOG_DEBUG( "Received a failure notification regarding the sending of "
"the SMS whose reference is #%i on device %s.", ref,
GSM_GetConfig( gammu_fsm, -1 )->Device ) ;
write_atom_result( &interrupt_sm_buf, "send_failure" ) ;
}
write_int_result( &interrupt_sm_buf, ref ) ;
finalize_command_after_writing( &interrupt_sm_buf ) ;
LOG_DEBUG( "Leaving sending callback." ) ;
interrupt_in_use = false ;
}
// Mobile-specific interrupt signal handler.
void mobile_interrupt( signal_reported sign )
{
LOG_WARNING( "Signal #%i caught, shutting down Ceylan-Mobile.", sign ) ;
signal( sign, SIG_IGN ) ;
shutdown_requested = true ;
}
// No parameter expected nor taken into account:
int main( int argc, char **argv )
{
// Provided by the Seaplus library:
byte * current_read_buf ;
input_buffer read_buf = &current_read_buf ;
start_seaplus_driver( read_buf ) ;
LOG_TRACE( "Mobile Driver started." ) ;
// Gammu uses strings in the local encoding:
GSM_InitLocales( NULL ) ;
// Gammu is state-machine based:
GSM_StateMachine * gammu_fsm = GSM_AllocStateMachine() ;
if ( gammu_fsm == NULL )
raise_gammu_error( gammu_fsm, "Unable to allocate Gammu state machine." ) ;
// Pre-allocations:
string_buffer = malloc( main_buffer_size * sizeof(char) ) ;
if ( string_buffer == NULL )
raise_error( "Allocation of main string buffer failed." ) ;
// Secondary buffer to store temporary strings:
char * aux_string_buffer = malloc( 250 * sizeof(char) ) ;
if ( aux_string_buffer == NULL )
raise_error( "Allocation of auxiliary string buffer failed." ) ;
GSM_Error gammu_error ;
start_gammu( gammu_fsm ) ;
/* Tells whether a command will directly send back to the Erlang part a term
* (i.e. after having performed at least one write_*_result call).
*
* Here it is not always the case as, for example when sending a SMS, the
* operation will be known to be completed only in an asynchronously manner,
* i.e. only from an interrupt handler - which is thus the only part of the
* program able to send back an answer.
*
* As a result, the initiating command will trigger the sending but not write
* anything back, thus it should not finalize that command (the interrupt
* handler will take care of that).
*
*/
bool answer_sent ;
/* Reads a full command from (receive) buffer, based on its initial length:
*
* (a single term is expected hence read)
*
*/
while ( read_command( read_buf ) > 0 )
{
LOG_TRACE( "New command received." ) ;
// Relevant default, as most operations will send directly an answer:
answer_sent = true ;
// Current index in the input buffer (for decoding purpose):
buffer_index index = 0 ;
/* Will be set to the corresponding Seaplus-defined function identifier (ex:
* whose value is FOO_1_ID):
*
*/
fun_id current_fun_id ;
/* Will be set to the number of parameters obtained from Erlang for the
* function whose identifier has been transmitted:
*
*/
arity param_count ;
read_function_information( read_buf, &index, &current_fun_id, &param_count ) ;
LOG_DEBUG( "Function identifier is %u, arity is %u (new index is %u).",
current_fun_id, param_count, index ) ;
prepare_for_command( &output_sm_buf ) ;
// Now, taking care of the corresponding function call:
switch( current_fun_id )
{
case GET_BACKEND_INFORMATION_0_ID:
/* -spec get_backend_information() ->
* { backend_type(), backend_version() }.
*/
LOG_DEBUG( "Executing get_backend_information/0." ) ;
check_arity_is( 0, param_count, GET_BACKEND_INFORMATION_0_ID ) ;
// Returning for example: { gammu, "1.40.0" }:
write_tuple_header_result( &output_sm_buf, 2 ) ;
write_atom_result( &output_sm_buf, "gammu" ) ;
write_string_result( &output_sm_buf, GetGammuVersion() ) ;
break ;
case GET_DEVICE_NAME_0_ID:
// -spec get_device_name() -> device_name().
LOG_DEBUG( "Executing get_device_name/0." ) ;
check_arity_is( 0, param_count, GET_DEVICE_NAME_0_ID ) ;
// Internal field, not owned:
const char * device_name = GSM_GetConfig( gammu_fsm, -1 )->Device ;
write_binary_string_result( &output_sm_buf, device_name ) ;
break ;
case GET_DEVICE_MANUFACTURER_0_ID:
// -spec get_device_manufacturer() -> manufacturer_name().
LOG_DEBUG( "Executing get_device_manufacturer/0." ) ;
check_arity_is( 0, param_count, GET_DEVICE_MANUFACTURER_0_ID ) ;
// Life-cycle of a global string buffer, not to manage here:
gammu_error = GSM_GetManufacturer( gammu_fsm, string_buffer ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
write_binary_string_result( &output_sm_buf, string_buffer ) ;
break ;
case GET_DEVICE_MODEL_0_ID:
// -spec get_device_model() -> model_name().
LOG_DEBUG( "Executing get_device_model/0." ) ;
check_arity_is( 0, param_count, GET_DEVICE_MODEL_0_ID ) ;
// Life-cycle of a global string buffer, not to manage here:
gammu_error = GSM_GetModel( gammu_fsm, string_buffer ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
write_binary_string_result( &output_sm_buf, string_buffer ) ;
break ;
case GET_FIRMWARE_INFORMATION_0_ID:
/* -spec get_firmware_information() ->
* { revision_text(), date_text(), revision_number() }.
*/
LOG_DEBUG( "Executing get_firmware_information/0." ) ;
check_arity_is( 0, param_count, GET_FIRMWARE_INFORMATION_0_ID ) ;
double rev_number ;
// Life-cycle of global string buffers, not to manage here:
gammu_error = GSM_GetFirmware( gammu_fsm, string_buffer,
aux_string_buffer, &rev_number ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
write_tuple_header_result( &output_sm_buf, 3 ) ;
write_binary_string_result( &output_sm_buf, string_buffer ) ;
write_binary_string_result( &output_sm_buf, aux_string_buffer ) ;
write_double_result( &output_sm_buf, rev_number ) ;
break ;
case GET_IMEI_CODE_0_ID:
// -spec get_imei_code() -> imei().
LOG_DEBUG( "Executing get_imei_code/0." ) ;
check_arity_is( 0, param_count, GET_IMEI_CODE_0_ID ) ;
// Life-cycle of a global string buffer, not to manage here:
gammu_error = GSM_GetIMEI( gammu_fsm, string_buffer ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
write_binary_string_result( &output_sm_buf, string_buffer ) ;
break ;
case GET_HARDWARE_INFORMATION_0_ID:
// -spec get_hardware_information() -> hardware_info().
LOG_DEBUG( "Executing get_hardware_information/0." ) ;
check_arity_is( 0, param_count, GET_HARDWARE_INFORMATION_0_ID ) ;
// Life-cycle of a global string buffer, not to manage here:
gammu_error = GSM_GetHardware( gammu_fsm, string_buffer ) ;
if ( gammu_error == ERR_NONE )
{
write_binary_string_result( &output_sm_buf, string_buffer ) ;
}
else
{
/*
* Returning a string instead (convention here is that an exception is
* thrown by mobile.erl should a non-binary result be received):
*
*/
int res = sprintf( string_buffer, "Gammu error: %s",
GSM_ErrorString( gammu_error ) ) ;
if ( res < 0 )
raise_gammu_error( gammu_fsm, "Error reporting failed" ) ;
write_binary_string_result( &output_sm_buf, string_buffer ) ;
}
break ;
case GET_IMSI_CODE_0_ID:
// -spec get_imsi_code() -> imsi_code().
LOG_DEBUG( "Executing get_imsi_code/0." ) ;
check_arity_is( 0, param_count, GET_IMSI_CODE_0_ID ) ;
// Life-cycle of a global string buffer, not to manage here:
gammu_error = GSM_GetSIMIMSI( gammu_fsm, string_buffer ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
write_binary_string_result( &output_sm_buf, string_buffer ) ;
break ;
case GET_SIGNAL_QUALITY_0_ID:
/* -spec get_signal_quality() ->
* { signal_strength(), signal_strength_percent(), error_rate() }.
*/
LOG_DEBUG( "Executing get_signal_quality/0." ) ;
check_arity_is( 0, param_count, GET_SIGNAL_QUALITY_0_ID ) ;
GSM_SignalQuality sq ;
gammu_error = GSM_GetSignalQuality( gammu_fsm, &sq ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
write_tuple_header_result( &output_sm_buf, 3 ) ;
write_int_result( &output_sm_buf, sq.SignalStrength ) ;
write_int_result( &output_sm_buf, sq.SignalPercent ) ;
write_int_result( &output_sm_buf, sq.BitErrorRate ) ;
LOG_DEBUG( "get_signal_quality/0 executed." ) ;
break ;
case SEND_REGULAR_SMS_2_ID:
/* No SEND_REGULAR_SMS_2_ID case: the Erlang part is to trigger only the
* SEND_REGULAR_SMS_4_ID version ultimately.
*
*/
raise_error( "Unexpected call to driver-level send_regular_sms/2." ) ;
break ;
case SEND_REGULAR_SMS_3_ID:
/* No SEND_REGULAR_SMS_3_ID case: the Erlang part is to trigger only the
* SEND_REGULAR_SMS_4_ID version ultimately.
*
*/
raise_error( "Unexpected call to driver-level send_regular_sms/3." ) ;
break ;
case SEND_REGULAR_SMS_4_ID:
/* -spec send_regular_sms( message(), recipient_number(), class(),
* encoding() ) -> sms_id().
*
*/
LOG_DEBUG( "Executing send_regular_sms/4." ) ;
check_arity_is( 4, param_count, SEND_REGULAR_SMS_4_ID ) ;
send_regular_sms( read_buf, &index, gammu_fsm ) ;
// Sending operation to be finalized by the interrupt handler:
answer_sent = false ;
break ;
case SEND_MULTIPART_SMS_2_ID:
/* No SEND_MULTIPART_SMS_2_ID case: the Erlang part is to trigger only
* the SEND_MULTIPART_SMS_4_ID version ultimately.
*
*/
raise_error( "Unexpected call to driver-level send_multipart_sms/2." ) ;
break ;
case SEND_MULTIPART_SMS_3_ID:
/* No SEND_MULTIPART_SMS_3_ID case: the Erlang part is to trigger only
* the SEND_MULTIPART_SMS_4_ID version ultimately.
*
*/
raise_error( "Unexpected call to driver-level send_multipart_sms/3." ) ;
break ;
case SEND_MULTIPART_SMS_4_ID:
/* -spec send_multipart_sms( message(), recipient_number(),
* class(), encoding() ) -> sms_id().
*
*/
LOG_DEBUG( "Executing send_multipart_sms/4." ) ;
check_arity_is( 4, param_count, SEND_MULTIPART_SMS_4_ID ) ;
send_multipart_sms( read_buf, &index, gammu_fsm ) ;
// Sending operation to be finalized by the interrupt handler:
answer_sent = false ;
break ;
case SEND_SMS_2_ID:
/* No SEND_SMS_2_ID case: the Erlang part is to select the right version
* among the SEND_*_SMS_4_ID.
*
*/
raise_error( "Unexpected call to driver-level send_sms/2." ) ;
break ;
case SEND_SMS_3_ID:
/* No SEND_SMS_3_ID case: the Erlang part is to select the right version
* among the SEND_*_SMS_4_ID ultimately.
*
*/
raise_error( "Unexpected call to driver-level send_sms/3." ) ;
break ;
case READ_ALL_SMS_1_ID:
/* -spec -spec read_all_sms( boolean() ) -> [ received_sms() ].
*
*/
LOG_DEBUG( "Executing read_all_sms/1." ) ;
check_arity_is( 1, param_count, READ_ALL_SMS_1_ID ) ;
read_all_sms( read_buf, &index, gammu_fsm ) ;
LOG_DEBUG( "read_all_sms/1 executed." ) ;
break ;
default:
// Hopefully no 'break' has been forgotten above!
raise_gammu_error( gammu_fsm, "Unknown function identifier: %u",
current_fun_id ) ;
}
if ( answer_sent )
finalize_command_after_writing( &output_sm_buf ) ;
}
LOG_DEBUG( "No more command to read." ) ;
// output_sm_buf internally already freed appropriately.
free( string_buffer ) ;
string_buffer = NULL ;
free( aux_string_buffer ) ;
aux_string_buffer = NULL ;
stop_gammu( gammu_fsm ) ;
stop_seaplus_driver( read_buf ) ;
}
void start_gammu( GSM_StateMachine * gammu_fsm )
{
LOG_DEBUG( "Starting Gammu." ) ;
// Registering our signal handler:
signal( SIGINT, mobile_interrupt ) ;
signal( SIGTERM, mobile_interrupt) ;
FILE * debug_file = NULL ;
if ( enable_gammu_logging )
{
if ( log_file != NULL )
{
LOG_DEBUG( "Directing Gammu logs to Seaplus ones." ) ;
debug_file = log_file ;
}
else
{
LOG_DEBUG( "Gammu logs directed to standard error." ) ;
debug_file = stderr ;
}
// Internal pointer, not to be deallocated afterwards:
debug_info = GSM_GetGlobalDebug() ;
GSM_SetDebugFileDescriptor( debug_file, false, debug_info ) ;
GSM_SetDebugLevel( "textall", debug_info ) ;
}
else
{
LOG_DEBUG( "No Gammu logs requested." ) ;
}
if ( enable_gammu_state_machine_logging )
{
if ( log_file != NULL )
{
LOG_DEBUG( "Directing Gammu state machine logs to Seaplus ones." ) ;
debug_file = log_file ;
}
else
{
LOG_DEBUG( "Gammu state machine logs directed to standard error." ) ;
debug_file = stderr ;
}
debug_info = GSM_GetDebug( gammu_fsm ) ;
GSM_SetDebugGlobal( false, debug_info ) ;
GSM_SetDebugFileDescriptor( debug_file, false, debug_info ) ;
GSM_SetDebugLevel( "textall", debug_info ) ;
}
else
{
LOG_DEBUG( "No Gammu state machine logs requested." ) ;
}
INI_Section * iniConfig ;
// Autodetect the configuration file (ex: ~/.gammurc):
GSM_Error error = GSM_FindGammuRC( &iniConfig, NULL ) ;
check_gammu_error( error, gammu_fsm ) ;
// Read it:
int read_section_count = 0 ;
// To be populated from INI content (and deallocated just after):
GSM_Config * config = GSM_GetConfig( gammu_fsm, read_section_count ) ;
check_gammu_error( error, gammu_fsm ) ;
error = GSM_ReadConfig( iniConfig, config, read_section_count ) ;
check_gammu_error( error, gammu_fsm ) ;
INI_Free( iniConfig ) ;
// We care only about the first configuration:
int section_id = 1 ;
GSM_SetConfigNum( gammu_fsm, section_id ) ;
check_gammu_error( error, gammu_fsm ) ;
// Number of replies to await:
int reply_count = 3 ;
error = GSM_InitConnection( gammu_fsm, reply_count ) ;
check_gammu_error( error, gammu_fsm ) ;
// No user data:
GSM_SetSendSMSStatusCallback( gammu_fsm, sms_sending_callback, NULL ) ;
// We need to know the SMSC number:
device_smsc.Location = 1 ;
error = GSM_GetSMSC( gammu_fsm, &device_smsc ) ;
check_gammu_error( error, gammu_fsm ) ;
// interrupt_sm_buf managed fully and directly in its callback.
/* Note: a check whether phone needs to enter some PIN could be added, as done
* in gammu/smsd/core.c (SMSD_CheckSecurity/1)
*
*/
}
/* Helpers defined to avoid variable name clashes in the function switch, and to
* factor code:
*
*/
/* Sends a regular (single-part) SMS with specified class and encoding.
*
*/
void send_regular_sms( input_buffer read_buf, buffer_index * index,
GSM_StateMachine * gammu_fsm )
{
char * message = read_binary_parameter( read_buf, index ) ;
if ( message == NULL )
raise_gammu_error( gammu_fsm,
"SMS message could not be obtained (regular)." ) ;
// Clean-up the struct:
memset( &sms, 0, sizeof( sms ) ) ;
EncodeUnicode( sms.Text, message, strlen( message ) ) ;
// Message recipient:
char * recipient_number = read_binary_parameter( read_buf, index ) ;
if ( recipient_number == NULL )
raise_gammu_error( gammu_fsm,
"SMS recipient mobile number could not be obtained." ) ;
EncodeUnicode( sms.Number, recipient_number, strlen( recipient_number ) ) ;
// We want to submit message ("SMS for sending or in Outbox"):
sms.PDU = SMS_Submit ;
// No User Data Header, just a plain message:
sms.UDH.Type = UDH_NoUDH ;
sms.Class = read_int_parameter( read_buf, index ) ;
sms.Coding = get_gammu_encoding(
read_int_parameter( read_buf, index ) ) ;
// Sets the SMSC number in message:
CopyUnicodeString( sms.SMSC.Number, device_smsc.Number ) ;
// Resets it first, some phones might give instant response:
sms_send_status = ERR_TIMEOUT ;
// Finally:
GSM_Error gammu_error = GSM_SendSMS( gammu_fsm, &sms ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
/* We do not have yet anything to return, but the callback will. */
//write_as_XXX( buffer, ... ) ;
/* However, using real devices (not the dummy one), we see that the callback
* is never triggered unless we poll explicitly from a network reply.
*
* Loops as long as the status is ERR_TIMEOUT:
*
*/
while ( ( ! shutdown_requested ) && ( sms_send_status == ERR_TIMEOUT ) )
{
LOG_DEBUG( "Reading device..." ) ;
/* Expected to trigger sms_sending_callback/4 (true: wait for reply;
* number of read bytes ignored):
*
*/
GSM_ReadDevice( gammu_fsm, true ) ;
/* Answer to be sent by the callback, just ensuring here we read the device
* until an answer is known.
*
* Loops as long as the status is ERR_TIMEOUT:
*
*/
}
free( recipient_number ) ;
free( message ) ;
LOG_DEBUG( "Device read." ) ;
}
/* Sends a multipart SMS with specified class and encoding.
*
*/
void send_multipart_sms( input_buffer read_buf, buffer_index * index,
GSM_StateMachine * gammu_fsm )
{
char * message = read_binary_parameter( read_buf, index ) ;
if ( message == NULL )
raise_gammu_error( gammu_fsm,
"SMS message could not be obtained (multipart)." ) ;
// Message recipient:
char * recipient_number = read_binary_parameter( read_buf, index ) ;
if ( recipient_number == NULL )
raise_gammu_error( gammu_fsm,
"SMS recipient mobile number could not be obtained." ) ;
unsigned int buf_size = ( strlen( message ) + 1 ) * 2 ;
// To store message as Unicode (unsigned char rather than byte):
unsigned char * msg_buffer = (unsigned char *) malloc( buf_size ) ;
if ( msg_buffer == NULL )
raise_error( "Multipart message buffer could not be allocated." ) ;
GSM_MultiPartSMSInfo SMSInfo ;
GSM_ClearMultiPartSMSInfo( &SMSInfo ) ;
SMSInfo.Class = read_int_parameter( read_buf, index ) ;
// A message will consist of one part:
SMSInfo.EntriesNum = 1 ;
// Encoding has ultimately only to be Unicode or not:
enum encoding e = read_int_parameter( read_buf, index ) ;
switch( e )
{
case unicode_uncompressed:
case unicode_compressed:
SMSInfo.UnicodeCoding = true ;
break ;
case gsm_uncompressed:
case gsm_compressed:
case eight_bit:
SMSInfo.UnicodeCoding = false ;
break ;
default:
raise_error( "Unexpected encoding: %i", e ) ;
break ;
}
// This part has for type 'long text':
SMSInfo.Entries[0].ID = SMS_ConcatenatedTextLong ;
// Encode message text:
EncodeUnicode( msg_buffer, message, strlen( message ) ) ;
SMSInfo.Entries[0].Buffer = msg_buffer ;
LOG_DEBUG( "Message once encoded in UCS-2: '%s'.",
DecodeUnicodeConsole( SMSInfo.Entries[0].Buffer ) ) ;
GSM_MultiSMSMessage MultiSMS ;
// Encode message into PDU parts:
GSM_Error gammu_error = GSM_EncodeMultiPartSMS( debug_info, &SMSInfo,
&MultiSMS ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
size_t recipient_number_len = strlen( recipient_number ) ;
// Now sending the message parts:
for ( sms_count i = 0; i < MultiSMS.Number; i++)
{
LOG_DEBUG( "Sending SMS part %i/%i", i+1, MultiSMS.Number ) ;
// Sets the SMSC number in the current SMS:
CopyUnicodeString( MultiSMS.SMS[i].SMSC.Number, device_smsc.Number ) ;
// Encodes the recipient number:
EncodeUnicode( MultiSMS.SMS[i].Number, recipient_number,
recipient_number_len ) ;
// We want to submit message:
MultiSMS.SMS[i].PDU = SMS_Submit ;
/*
* Sets flag before calling SendSMS, as some phones might give instant
* response:
*/
sms_send_status = ERR_TIMEOUT ;
// Send this message:
gammu_error = GSM_SendSMS( gammu_fsm, &MultiSMS.SMS[i] ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
/* We do not have yet anything to return, but the callback will. */
//write_XXX_result( output_sm_buffer, ... ) ;
/* However, using real devices (not the dummy one), we see that the callback
* is never triggered unless we poll explicitly from a network reply.
*
* Loops as long as the status is ERR_TIMEOUT:
*
*/
while ( ( ! shutdown_requested ) && ( sms_send_status == ERR_TIMEOUT ) )
{
LOG_DEBUG( "Reading device..." ) ;
/* Expected to trigger sms_sending_callback/4 (true: wait for reply;
* number of read bytes ignored):
*
*/
GSM_ReadDevice( gammu_fsm, true ) ;
/* Answer to be sent by the callback, just ensuring here we read the
* device until an answer is known.
*
*/
}
LOG_DEBUG( "Device read." ) ;
}
free( msg_buffer ) ;
free( recipient_number ) ;
free( message ) ;
}
GSM_Coding_Type get_gammu_encoding( enum encoding e )
{
// Actually the two enums match, but we prefer checking:
switch( e )
{
case unicode_uncompressed:
return SMS_Coding_Unicode_No_Compression ;
break ;
case unicode_compressed:
return SMS_Coding_Unicode_Compression ;
break ;
case gsm_uncompressed:
return SMS_Coding_Default_No_Compression ;
break ;
case gsm_compressed:
return SMS_Coding_Default_Compression ;
break ;
case eight_bit:
return SMS_Coding_8bit ;
break ;
default:
raise_error( "Unexpected Mobile encoding: %i", e ) ;
break ;
}
// As the compiler is not smart:
//raise_error( "Unexpected Mobile encoding: %i", e ) ;
return 0 ;
}
enum encoding get_mobile_encoding( GSM_Coding_Type e )
{
// Actually the two enums match, but we prefer checking:
switch( e )
{
case SMS_Coding_Unicode_No_Compression :
return unicode_uncompressed ;
break ;
case SMS_Coding_Unicode_Compression:
return unicode_compressed ;
break ;
case SMS_Coding_Default_No_Compression:
return gsm_uncompressed;
break ;
case SMS_Coding_Default_Compression:
return gsm_compressed ;
break ;
case SMS_Coding_8bit:
return eight_bit ;
break ;
default:
raise_error( "Unexpected Gammu encoding: %i", e ) ;
break ;
}
// As the compiler is not smart:
//raise_error( "Unexpected Gammu encoding: %i", e ) ;
return 0 ;
}
/* Reads all SMS already received (if any).
*
* Returns a list of { BinSenderNumber, EncodingValue, BinText,
* MessageReference, Timestamp }.
*
* Does not block.
*
*/
void read_all_sms( input_buffer read_buf, buffer_index * index,
GSM_StateMachine * gammu_fsm )
{
/* A few documentation pointers:
*
* - in gammu/include/gammu-message.h: GSM_MultiSMSMessage, mostly comprising
* GSM_SMSMessage
*
* - GSM_SMSMessage: GSM_Coding_Type is the usual enum
*
*
* Once relying on GSM_GetNextSMS (declared in gammu/include/gammu-message.h,
* defined in gammu/libgammu/api.c; rather than GSM_GetSMS), two inspirations
* could be used in order to decode messages:
*
* - gammu/docs/examples/sms-read.c, using, here, DecodeUnicodeString
*
* - gammu/smsd/core.c, using GSM_DecodeMultiPartSMS - which is higher-level,
* hence preferred here
*
*/
bool delete_on_reading ;
int delete_int = read_int_parameter( read_buf, index ) ;
switch( delete_int )
{
case 0:
LOG_DEBUG( "Read SMS will be kept." ) ;
delete_on_reading = false ;
break;
case 1:
LOG_DEBUG( "Read SMS will be deleted." ) ;
delete_on_reading = true ;
break;
default:
raise_error( "Unexpected deletion parameter: %i", delete_int ) ;
break ;
}
GSM_MultiSMSMessage receivedSMS ;
GSM_Error read_error = ERR_NONE ;
// Needed by GSM_GetNextSMS/3:
bool isFirst = true ;
// Pointer to a static string:
char * decoded_string ;
/* Here we do not know from the start the size of the SMS list to return, so
* instead of creating [ SMS1, SMS2, SMS3 ] we create it from cons':
* [ SMS1 | [ SMS2 | [ SMS3 | [] ] ] ].
*
*/
while ( ( read_error == ERR_NONE ) && ( ! shutdown_requested ) )
{
LOG_DEBUG( "Trying to read a new SMS from device..." ) ;
read_error = GSM_GetNextSMS( gammu_fsm, &receivedSMS, isFirst ) ;
if ( read_error == ERR_EMPTY )
{
LOG_DEBUG( "Empty read, no more SMS to read." ) ;
break;
}
LOG_DEBUG( "Reading a new SMS." ) ;
// Size is 2, as we are cons'ing, like in [ X, [ Y, [] ] ]:
write_list_header_result( &output_sm_buf, 2 ) ;
check_gammu_error( read_error, gammu_fsm ) ;
isFirst = false ;
/* For each SMS, the Erlang counterpart expects:
*
* { BinSenderNumber, EncodingValue, MessageReference, Timestamp, BinText }
*
* Fields of interest currently not returned here: SMS, PDU, Class.
*
* BinText is better aggregated (from the various SMS parts involved)
* directly here, in the C part, and written last; the other tuple elements
* are expected to be the same in all SMS parts, therefore we write them
* once, from the first SMS part.
*
*/
size_t copy_index = 0 ;
write_tuple_header_result( &output_sm_buf, 5 ) ;
/* Interpreting now the overall message for each SMS, per SMS part
* (concatenating split texts).
*
* Note, that, apparently, even when sending longer SMS, each of them is not
* interpreted as a single, multipart SMS but as multiple SMS, each with one
* part (i.e. receivedSMS.Number == 1 and the parts are actually considered
* as separate SMS); however we do our best to aggregate them if ever
* necessary.
*
*/
for ( sms_count i = 0; i < receivedSMS.Number; i++ )
{
LOG_DEBUG( "Reading SMS part %i/%i...", i+1, receivedSMS.Number ) ;
/* Except the payload (text/data), we expect the metadata of the various
* SMS parts to be equal (ex: for SMS classes) or roughly the same (ex:
* for sending timestamp), so we record them only once, for the first SMS
* part (the only one known to exist in all cases):
*
*/
if ( i == 0 )
{
// See, in gammu/include/gammu-message.h, GSM_SMSMessage:
/* DecodeUnicodeString defined in gammu/libgammu/misc/coding/coding.c
* (search for 'char *DecodeUnicodeString'); its static buffer is quite
* small (500 bytes), so we use our considerably larger string_buffer,
* notably for texts.
*
*/
// First, BinSenderNumber:
decoded_string = DecodeUnicodeString( receivedSMS.SMS[i].Number ) ;
size_t decoded_len = strlen( decoded_string ) ;
LOG_DEBUG( " - sender number (%i bytes): '%s'", decoded_len,
decoded_string ) ;
// As length already known:
write_binary_result( &output_sm_buf, decoded_string, decoded_len ) ;
}
// Then EncodingValue (needed afterwards for all parts, hence the scope):
GSM_Coding_Type encoding = receivedSMS.SMS[i].Coding ;
LOG_DEBUG( " - encoding: %i", encoding ) ;
if ( i == 0 )
{
write_int_result( &output_sm_buf, get_mobile_encoding( encoding ) ) ;
// Information not sent back:
LOG_DEBUG( " - class: %i", receivedSMS.SMS[i].Class + 1 ) ;
LOG_DEBUG( " - PDU type: %i", receivedSMS.SMS[i].PDU ) ;
// Then MessageReference:
sms_tpmr msg_ref = receivedSMS.SMS[i].MessageReference ;
LOG_DEBUG( " - message reference: %i", msg_ref ) ;
write_int_result( &output_sm_buf, msg_ref ) ;
/* Then Timestamp; we directly convert the struct into its
* time_utils:timestamp/0 counterpart, which is { date(), time() },
* namely: { { Year, Month, Day }, { Hour, Minute, Second } }.
*
* (Timezone not used here)
*
*/
// Note that GSM_DateTime is defined in gammu/include/gammu-datetime.h.
// For { date(), time() }:
write_tuple_header_result( &output_sm_buf, 2 ) ;
// For date(), hence { Year, Month, Day }:
write_tuple_header_result( &output_sm_buf, 3 ) ;
write_int_result( &output_sm_buf, receivedSMS.SMS[i].DateTime.Year ) ;
write_int_result( &output_sm_buf, receivedSMS.SMS[i].DateTime.Month ) ;
write_int_result( &output_sm_buf, receivedSMS.SMS[i].DateTime.Day ) ;
// For time(), hence { Hour, Minute, Second }:
write_tuple_header_result( &output_sm_buf, 3 ) ;
write_int_result( &output_sm_buf, receivedSMS.SMS[i].DateTime.Hour ) ;
write_int_result( &output_sm_buf, receivedSMS.SMS[i].DateTime.Minute ) ;
write_int_result( &output_sm_buf, receivedSMS.SMS[i].DateTime.Second ) ;
// All tuple elements but BinText written.
}
/* Section gone through for all SMS parts, aggregating BinText across them
* all:
*
*/
if ( encoding != SMS_Coding_8bit
&& receivedSMS.SMS[i].UDH.Type != UDH_UserUDH )
{
decoded_string = DecodeUnicodeString( receivedSMS.SMS[i].Text ) ;
size_t decoded_len = strlen( decoded_string ) ;
LOG_DEBUG( " - text (%i bytes): '%s'", decoded_len, decoded_string ) ;
size_t new_copy_index = copy_index + decoded_len ;
if ( new_copy_index > main_buffer_size )
raise_error( "Length of main buffer exceeded." ) ;
strncpy( string_buffer + copy_index, decoded_string, decoded_len ) ;
copy_index = new_copy_index ;
}
else
{
// Both could be true:
if ( encoding == SMS_Coding_8bit )
LOG_DEBUG( " - no text read (8-bit encoded)" ) ;
if ( receivedSMS.SMS[i].UDH.Type == UDH_UserUDH )
LOG_DEBUG( " - no text read (UDH type: user)" ) ;
}
/* In the future we might use here GSM_DecodeMultiPartSMS/4, as done in
* gammu/smsd/core.c.
*
*/
if ( delete_on_reading )
{
LOG_DEBUG( "Deleting SMS." ) ;
GSM_Error gammu_error = GSM_DeleteSMS( gammu_fsm, &receivedSMS.SMS[ i ] ) ;
check_gammu_error( gammu_error, gammu_fsm ) ;
}
}
/* Here we went through all SMS parts, their texts have been aggregated in
* BinText, so we write it and this 5-tuple is over.
*
*/
// Null-terminated:
string_buffer[ copy_index ] = 0 ;
write_string_with_length_result( &output_sm_buf, string_buffer,
copy_index ) ;
}
// Closing the last cons:
write_empty_list_result( &output_sm_buf ) ;
}
void check_gammu_error( GSM_Error error, GSM_StateMachine * gammu_fsm )
{
if ( error != ERR_NONE )
raise_gammu_error( gammu_fsm, "Gammu error: %s",
GSM_ErrorString( error ) ) ;
}
/* Raises specified error: reports it in logs, shutdown relevant phone services,
* and halts.
*
*/
void raise_gammu_error( GSM_StateMachine * gammu_fsm, const char * format, ... )
{
if ( gammu_fsm != NULL )
{
if ( GSM_IsConnected( gammu_fsm ) )
GSM_TerminateConnection( gammu_fsm ) ;
}
// Uses Seaplus service, variadic-forwarding:
va_list extra_args ;
va_start( extra_args, format ) ;
raise_error( format, extra_args ) ;
// This clean-up will never happen:
va_end( extra_args ) ;
}
void stop_gammu( GSM_StateMachine * gammu_fsm )
{
LOG_DEBUG( "Stopping Gammu." ) ;
if ( GSM_IsConnected( gammu_fsm ) )
{
GSM_Error error = GSM_TerminateConnection( gammu_fsm ) ;
check_gammu_error( error, gammu_fsm ) ;
}
GSM_FreeStateMachine( gammu_fsm ) ;
// Irrelevant: debug_file = NULL ;
// log_file managed by Seaplus.
}
You can’t perform that action at this time.