Sistema di prenotazione SMS (Studio Action)

1. Background

Action Studio Health and Fitness Club company è un club fitness e benessere, fondato nel 2001. Action Studio dispone di una vasta gamma di servizi, tra cui aerobica, palestre attrezzate, piscine e squash. Il club conta circa 1200 membri attivi ed è aperto 7 giorni alla settimana, dalle 7 alle 22. I membri del club pagano un canone mensile ed hanno accesso illimitato a tutte le strutture.

Action Studio dispone di due campi da squash, disponibili per tutti i membri. Questo case study si concentra sul sistema di prenotazione dei due campi da squash dell’ Action Studio Health and Fitness Club.

2. Individuazione del problema

Action Studio dispone di 2 campi da squash che possono essere prenotati da qualsiasi membro del club. I campi da squash possono essere prenotati dalle 7 alle 22. I membri possono prenotare un massimo di un campo da squash con 7 giorni di anticipo.

Il club utilizza una grande tavola di pianificazione manuale per gestire le prenotazioni dei campi da squash. Per effettuare una nuova prenotazione, un membro deve recarsi presso il centro per registrare la sua prenotazione sulla tavola di pianificazione, manualmente. In alternativa, i membri possono chiamare al club per chiedere a un suo dipendente di prenotare per loro.

Questo sistema manuale presenta molti inconvenienti:

3. Obiettivi del nuovo sistema

La scheda di pianificazione corrente deve essere sostituita da un nuovo sistema più efficiente. Obiettivi del nuovo sistema sono:

4. Soluzione ActiveXperts SMS Messaging Server

ActiveXperts SMS Messaging Server andrà a sostituire il manuale di prenotazione dei campi da squash. I membri possono inviare un messaggio SMS al centro SMS Messaging Server, inviando le richieste di prenotazione ad un numero dedicato di cellulare, associato al sistema di prenotazione SMS MessagingServer.

Il sistema accetta solo i messaggi che corrispondono ad una sintassi predefinita. Questo formato dei messaggi deve essere conosciuto dai membri. Se non si conosce il formato del messaggio, è possibile inviare un comando “aiuto” ('?') per richiedere la sintassi del messaggio. Il sistema risponderà al membro indicando la sintassi del messaggio da usare.

Formato messaggio SMS

Il sistema è progettato per accettare i seguenti messaggi SMS:

SMS message body (syntax) Sample Explanation
? ? Comando di aiuto. Il membro otterrà una risposta su come creare o cancellare una prenotazione
Q Q Comando di domanda. Il socio riceverà un messaggio SMS contenente tutte le sue prenotazioni per i prossimi 7 giorni
R [su|mo|..|sa] [7am|8am|..|9pm] R tu 11am  Comando di prenotazione. Il socio riceverà un messaggio SMS contenente le informazioni sulla sua prenotazione: se è stata accettata o meno, e il numero di campo da gioco.
Nel campione, la prenotazione è richiesta per il prossimo Martedì, 11.
C [su|mo|..|sa] [7am|8am|..|9pm] C tu 11am  Comando di cancellazione. Il socio riceverà un messaggio SMS con le informazioni di conferma che la prenotazione è stata cancellata.
Nel campione, la prenotazione per il prossimo Martedì viene annullata
<any other message> Hello everybody  Qualsiasi messaggio che non corrisponde alla sintassi predefinita verrà risposto con un messaggio SMS che indica come utilizzare il sistema

Hardware and Software

Il sistema comprende i seguenti componenti:

Action Studio ha dovuto scegliere fra due tecniche di comunicazione SMS: SMPP o modem GSM. Dal momento che SMPP è stato progettato per alti volumi di messaggistica SMS, centinaia di messaggi SMS all'ora, non avrebbe senso per Action Studio usare SMPP. Il modem GSM è in grado di gestire circa 10 messaggi al minuto, il che risulta essere sufficiente per questa soluzione.

Hanno optato per l'acquisto di un modem GSM Wavecom. Hanno acquistato una scheda Sim con un pacchetto SMS presso il loro provider di telecomunicazioni.

Per questo esempio si suppone che la scheda SIM è associato il seguente numero: +3162234329

Database Action Studio

Le prenotazioni vengono registrate in un database separato. La società Action Studio ha deciso di scegliere un database di MS Access per questa funzione. Quando l'applicazione avrà bisogno di essere incrementata saranno in grado di migrare su un database Microsoft SQL Server.

Il database è costituito da due tabelle:

Trigger

Per effettuare una prenotazione, i membri possono inviare un messaggio SMS al numero 3162234329. Questa è la scheda SIM che viene inserita nel modem GSM che è collegato al server di messaggistica SMS. Per consentire a SMS Messaging Server di elaborare tutte le richieste in ingresso su +3162234329, viene definito il seguente trigger:

Enabled Description Condition Script
YES Process incoming reservation requests To = '+3162234329' Projects\Action Studio\Triggers\ActionStudio.vbs

Il trigger verrà chiamato per ogni nuovo messaggio in arrivo. Il trigger effettuare le seguenti operazioni:

- Set incoming message to 'processed', so it will not be processed again by the system
- Look up Action Studio member-ID by incoming SMS number. 
- Is sender's mobile unknown ?        => send a reply SMS to the sender that he/she is not a 
                                         member
- Is there a syntax error ?           => send a reply SMS to the sender how to make a reservation
- If message is a Query request       => send a reply SMS to the sender with all reservations of 
                                         the next 7 days
- If message is a Cancel request      => Cancel the reservation (if possible), send a reply SMS 
                                         with the result of the cancel request
- If message is a Reservation request => Make the reservation (if possible), send a reply SMS with 
                                         the result of the reservation request

codice di Trigger.vbs

' Set incoming SMS message status to: Processed
objMessageIn.Status = objConstants.MESSAGESTATUS_IN_PROCESSED
objMessageIn.Save

' Retrieve Action Studio's MemberID that sent the message.
numMemberID = CheckMember( objMessageIn.Sender )

' Process the message
If( numMemberID <= 0 ) Then
   strMessageOutBody = ShowUnauthorizedMember()
ElseIf( objMessageIn.Body consists of only one word ) Then
   ' Just 1 param, so ? or Q expected
   If( objMessageIn.Body = "?" ) Then
      strMessageOutBody = ShowHelp()
   ElseIf( objMessageIn.Body = "Q" ) Then
      strMessageOutBody = QueryReservations( numMemberID )
   Else
      strMessageOutBody = ShowSyntaxError()
   End If
ElseIf( objMessageIn.Body consists of 3 words  ) Then

   Calculate Date of reservation
   Calculate Time of reservation

   If( Date of reservation could not be calculated ) Then
      strMessageOutBody = ShowWrongParam( .. )
   ElseIf( Time of reservation could not be calculated ) Then
      strMessageOutBody = ShowWrongParam( arrMessage(2) )
   ElseIf( objMessageIn.Body's first word is 'R' ) Then
      strMessageOutBody = MakeReservation( numMemberID, date, time )
   ElseIf( objMessageIn.Body's first word is 'C' ) Then
      strMessageOutBody = CancelReservation( numMemberID, date, time )
   Else
      strMessageOutBody = ShowWrongCommand( ... )
   End If
Else
   strMessageOutBody = ShowSyntaxError()
End If

' Create a new reply message
Set objMessageOut = objMessageDB.Create
objMessageOut.Direction = objConstants.MESSAGEDIRECTION_OUT
objMessageOut.To        = objMessageIn.From 
objMessageOut.Body      = strMessageOutBody
objMessageOut.Save

Trigger.vbs (codice completo)

Option Explicit

CONST STR_ACTIONSTUDIODBFILE = "Projects\Action Studio\Database\ActionStudio.mdb"
CONST STR_DEBUGFILE          = "Tmp\ActionStudio.txt"

CONST STR_USAGE              = "To query, type: Q  To make a new reservation, type: R " & _
                               "[su|mo|..|sa] [7am|8am|..|9pm]"

' Declaration of global objects
Dim g_objMessageDB, g_objDebugger, g_objConstants

' Creation of global objects
Set g_objConstants  = CreateObject( "Axsms-messaging-server.Constants" )
Set g_objMessageDB  = CreateObject( "Axsms-messaging-server.MessageDB" ) 
Set g_objDebugger   = CreateObject( "ActiveXperts.VbDebugger" )

' Set Debug file
g_objDebugger.DebugFile = STR_DEBUGFILE



' // ========================================================================
' // ProcessMessage
' // ------------------------------------------------------------------------
' // ProcessMessage trigger function
' // ========================================================================

Function ProcessMessage( numMessageID )
   Dim objMessageIn, objMessageOut, arrMessage
   Dim strMessageOutBody
   Dim numMemberID, wDay, dtDate, dtTime

   g_objDebugger.WriteLine ">> ProcessMessage"

   ' Open the message database
   g_objMessageDB.Open
   If( g_objMessageDB.LastError <> 0 ) Then
      g_objDebugger.WriteLine "<< ProcessMessage,  unable to open database"
      Exit Function
   End If

   ' Retrieve the message that has just been received. If it fails then exit script 
   Set objMessageIn   = g_objMessageDB.FindFirstMessage( "ID = " & numMessageID ) 
   If g_objMessageDB.LastError <> 0 Then
      g_objMessageDB.Close
      g_objDebugger.WriteLine "<< ProcessMessage,  unable to locate the message"
      Exit Function
   End If

   ' Set incoming SMS message status to: SUCCESS (previous state was: PENDING)
   objMessageIn.Status = g_objConstants.MESSAGESTATUS_SUCCESS
   g_objMessageDB.Save objMessageIn
   g_objDebugger.WriteLine " Incoming message saved, result: [" & g_objMessageDB.LastError & "]"

   ' Split received message body into pieces (separated by spaces)
   arrMessage = Split( UCase( objMessageIn.Body ), " " )

   ' Retrieve ActionStudio's MemberID that sent the message.
   numMemberID = CheckMember( objMessageIn.Sender )

   ' Process the message
   If( numMemberID <= 0 ) Then
      strMessageOutBody = ShowUnauthorizedMember()
   ElseIf( UBound( arrMessage ) = 0 ) Then
      ' Just 1 param, so ? or Q expected
      If( arrMessage(0) = "?" ) Then
         strMessageOutBody = ShowHelp()
      ElseIf( Left( UCase( arrMessage(0) ), 1 ) = "Q" ) Then
         strMessageOutBody = QueryReservations( numMemberID )
      Else
         strMessageOutBody = ShowSyntaxError()
      End If
   ElseIf( UBound( arrMessage ) = 2 ) Then

      wDay   = GetWDay( arrMessage(1) )
      dtDate = NextDate( wDay )
      dtTime = GetTimeValue( arrMessage(2) )

      If( wDay < 0 Or dtDate = "" ) Then
         strMessageOutBody = ShowWrongParam( arrMessage(1) )
      ElseIf( dtTime = "" ) Then
         strMessageOutBody = ShowWrongTimeParam( arrMessage(2) )
      ElseIf( Left( UCase( arrMessage(0) ), 1 ) = "R" ) Then
         strMessageOutBody = MakeReservation( numMemberID, dtDate, dtTime )
      ElseIf( Left( UCase( arrMessage(0) ), 1 ) = "C" ) Then
         strMessageOutBody = CancelReservation( numMemberID, dtDate, dtTime )
      Else
         strMessageOutBody = ShowWrongCommand( arrMessage(0) )
      End If
   Else
      strMessageOutBody = ShowSyntaxError()
   End If

   ' Create a new reply message
   Set objMessageOut = g_objMessageDB.Create
   g_objDebugger.WriteLine " New message created, result: [" & g_objMessageDB.LastError & "]"
   If( g_objMessageDB.LastError = 0 ) Then
      objMessageOut.Direction = g_objConstants.MESSAGEDIRECTION_OUT
      objMessageOut.Type      = objMessageIn.Type 
      objMessageOut.Status    = g_objConstants.MESSAGESTATUS_PENDING
      objMessageOut.To        = objMessageIn.From 
      objMessageOut.ChannelID = 0  ' Any available SMS channel
      objMessageOut.Body      = strMessageOutBody
      g_objMessageDB.Save objMessageOut
      g_objDebugger.WriteLine " New message saved, result: [" & g_objMessageDB.LastError & "]"
   End If

   g_objDebugger.WriteLine "<< ProcessMessage"

End Function


' // ========================================================================
' // CheckMember
' // ------------------------------------------------------------------------
' // Check if a mobile number is in the Members database to confirm that the
' // person is a member
' // ========================================================================

Function CheckMember( strInputNumber )
   Dim objConn, RS, strQuery

   g_objDebugger.WriteLine( ">> CheckMember" )

   Set objConn = CreateObject("ADODB.Connection")
   objConn.Open "Driver={Microsoft Access Driver (*.mdb)}; DBQ=" & STR_ACTIONSTUDIODBFILE & ";"

   Set RS = objConn.Execute( "SELECT * FROM Members WHERE MobileNumber='" & strInputNumber & "'" )
   If RS.EOF Then
     CheckMember = 0
   Else
     CheckMember = RS( "ID" )
   End If

   objConn.Close
   Set objConn = Nothing

   g_objDebugger.WriteLine( "<< CheckMember, result: " &  CheckMember )

End Function


' // ========================================================================
' // MakeReservation
' // ------------------------------------------------------------------------
' // Check if a mobile number is in the Members database to confirm that the
' // person is a member
' // ========================================================================
Function MakeReservation( numMember, dtDate, dtTime )
On Error Resume Next
   Dim objConn, iCourt, strQuery, hexError

   g_objDebugger.WriteLine( ">> MakeReservation( " & numMember & "," & dtDate & "," & _
      dtTime & ")" )

   If( dtDate < Date() OR ( dtDate = Date() AND dtTime < Time() ) ) Then
      MakeReservation = "You tried to make a reservation in history. Please try again."
      g_objDebugger.WriteLine( "<< MakeReservation, reservation in history not allowed" )
      Exit Function 
   End If

   Set objConn = CreateObject("ADODB.Connection")
   objConn.Open "Driver={Microsoft Access Driver (*.mdb)}; DBQ=" & STR_ACTIONSTUDIODBFILE & ";"

   For iCourt = 1 To 2
      g_objDebugger.WriteLine( "Try court " & iCourt & " ..." )

      strQuery = "INSERT INTO Reservations ( ReservationDate, ReservationTime, CourtID, " & _
                 "MemberID ) VALUES ( '" & dtDate & _
                 "', '" & dtTime & "', " & iCourt & ", " & numMember & " )"
     
      Err.Clear ' Clear a previous error
      objConn.Execute( strQuery )

      hexError = Hex( Err.Number )
      If( hexError <> "80040E14" ) Then 
         Exit For
      End If 
   Next

   If( hexError = "80040E14" ) Then
      g_objDebugger.WriteLine( "Err.Number: " & Err.Number & _
          "; Err.Description: " & Err.Description )
      MakeReservation = "All courts are booked at the indicated date and time. " & _
          "Reservation failed."
   ElseIf( hexError <> "0" ) Then
      g_objDebugger.WriteLine( "Err.Number: " & Err.Number & "; Err.Description: " & _
          Err.Description )
      MakeReservation = "Reservation could not be processed."
   Else
      MakeReservation = "Court " & iCourt & " is successfully reserved for you on " & _
          dtDate & " at " & dtTime
   End If

   objConn.Close
   Set objConn = Nothing

   g_objDebugger.WriteLine( "<< MakeReservation, result: " & MakeReservation )

End Function


' // ========================================================================
' // CancelReservation
' // ------------------------------------------------------------------------
' // Cancel a reservation
' // ========================================================================
Function CancelReservation( numMember, dtDate, dtTime )

   Dim objConn, strQuery, numReservation, RS

   g_objDebugger.WriteLine( ">> CancelReservation( " & numMember & "," & dtDate & "," & _
      dtTime & ")" )

   If( dtDate < Date() OR ( dtDate = Date() AND dtTime < Time() ) ) Then
      CancelReservation = "You cannot cancel reservations in history. Please try again."
      g_objDebugger.WriteLine( "<< CancelReservation, reservation in history not allowed" )
      Exit Function 
   End If

   Set objConn = CreateObject("ADODB.Connection")
   objConn.Open "Driver={Microsoft Access Driver (*.mdb)}; DBQ=" & STR_ACTIONSTUDIODBFILE & ";"

   strQuery = "SELECT * FROM Reservations WHERE MemberID=" & numMember & _
              " AND ReservationDate = #" & dtDate & _
              "# AND ReservationTime=# " & dtTime & "#" 
   Set RS = objConn.Execute( strQuery )

   If Not RS.EOF Then
      numReservation = RS( "ID" )
   Else
      numReservation = 0
   End If
   g_objDebugger.WriteLine( "Reservation ID: " & numReservation )

   If( numReservation = 0 ) Then
     CancelReservation = "Unable to cancel reservation; reservation not found."
   Else
     strQuery = "Delete * FROM Reservations WHERE ID=" & numReservation
     objConn.Execute( strQuery )
     CancelReservation = "Reservation successfully cancelled."
   End If

   objConn.Close
   Set objConn = Nothing

   g_objDebugger.WriteLine( "<< CancelReservation, result: " & MakeReservation )

End Function


' // ========================================================================
' // QueryReservations
' // ------------------------------------------------------------------------
' // Query reservations for given member
' // ========================================================================
Function QueryReservations( numMemberID )
   Dim objConn, RS, strReservations, strQuery

   g_objDebugger.WriteLine( ">> QueryReservations( " & numMemberID & " )" )

   QueryReservations = ""
   strReservations   = ""

   Set objConn = CreateObject("ADODB.Connection")
   objConn.Open "Driver={Microsoft Access Driver (*.mdb)}; DBQ=" & STR_ACTIONSTUDIODBFILE & ";"
   strQuery =  "SELECT * FROM Reservations WHERE MemberID=" & numMemberID & _
               " AND ( ReservationDate > #" & Date() & "# OR ( ReservationDate = #" & Date() & _
               "# AND ReservationTime >= #" & Time() & "# ) )" & _
               " ORDER BY ReservationDate ASC, ReservationTime ASC"
   Set RS = objConn.Execute( strQuery )
   While Not RS.EOF
     If( strReservations <> "" ) Then
       strReservations = strReservations & "; "
     End If
     strReservations = strReservations & RS("ReservationDate") & " " & RS( "ReservationTime" ) & _
          " Court " & RS( "CourtID" )
     RS.MoveNext
   WEnd

   objConn.Close
   Set objConn = Nothing

   If( strReservations = "" ) Then
     QueryReservations = "No reservations"
   Else
     QueryReservations = strReservations
   End If

   g_objDebugger.WriteLine( "<< QueryReservations, result: " & QueryReservations )

End Function


' // ========================================================================
' // Show... functions
' // ------------------------------------------------------------------------
' // Some error handling Show errors
' // ========================================================================

Function ShowWrongParam( strParam )
   ShowWrongParam = "Invalid value: " & strParam
End Function

Function ShowWrongTimeParam( strParam )
   ShowWrongTimeParam = "Wrong time value: " & strParam & _
      ". Only valid time values are: 7am - 9pm"
End Function

Function ShowWrongCommand( strCommand )
   ShowWrongCommand = "Wrong command: " & strCommand
End Function

Function ShowUnauthorizedMember()
   ShowUnauthorizedMember = "You cannot make reservations using this mobile number."
End Function

Function ShowSyntaxError()
   ShowSyntaxError = "Syntax error. " & STR_USAGE
End Function

Function ShowHelp()
   ShowHelp = STR_USAGE
End Function

  
' // ========================================================================
' // GetTimeValue
' // ------------------------------------------------------------------------
' // Convert any user time input to 
' // ========================================================================

Function GetTimeValue( strTime )
   Dim tmVal

' Define to resume after error, so tmVal will hold an empty string when input is not OK
On Error Resume Next

   tmVal = TimeValue( strTime )
   If( InStr( tmVal, "00:00" ) = 0 ) Then
      ' This indicates that user didn't enter a round hour. Only round hours are accepted
      tmVal = ""
   ElseIf( tmVal < TimeValue( "7am" ) ) Then
      ' No reservations allowed before 7am
      tmVal = ""
   ElseIf( tmVal > TimeValue( "9pm" ) ) Then
      ' No reservations allowed after 9pm
      tmVal = ""
   End If

   GetTimeValue = tmVal
End Function


' // ========================================================================
' // GetWDay
' // ------------------------------------------------------------------------
' // Function returns the VB day constant (vbSaturday, vbSunday, etc.)
' // based on the day passed by the user ("sa", "su", ...)
' // ========================================================================

Function GetWDay( strDay )
   Dim strUDay

   strUDay = UCase( strDay )

   If( Left( strUDay, 2 ) = "SA" ) Then 
      GetWDay = vbSaturday
   ElseIf( Left( strUDay, 2 ) = "SU" ) Then 
      GetWDay = vbSunday 
   ElseIf( Left( strUDay, 2 ) = "MO" ) Then 
      GetWDay = vbMonday
   ElseIf( Left( strUDay, 2 ) = "TU" ) Then 
      GetWDay = vbTuesday
   ElseIf( Left( strUDay, 2 ) = "WE" ) Then 
      GetWDay = vbWednesday
   ElseIf( Left( strUDay, 2 ) = "TH" ) Then 
      GetWDay = vbThursday
   ElseIf( Left( strUDay, 2 ) = "FR" ) Then 
      GetWDay = vbFriday
   Else
      GetWDay = -1
   End If
End Function


' // ========================================================================
' // WeekDayOffset
' // ------------------------------------------------------------------------
' // Function returns the number of days between today
' // and the next given wDay. Result: 0 <= result < 7
' // ========================================================================

Function WeekDayOffset( wDay )
   Dim dtToday, wToday 
   dtToday = Date()
   wToday = DatePart("W", dtToday )

   WeekDayOffset = wDay - wToday
   If( WeekDayOffset < 0 ) Then
      WeekDayOffset = WeekDayOffset + 7 
   End If
End Function


' // ========================================================================
' // NextDate
' // ------------------------------------------------------------------------
' // Function calculates the next wDay date.
' // ========================================================================

Function NextDate( wDay )
   Dim numIncrement
   numIncrement = WeekDayOffset( wDay )
   NextDate = DateAdd( "W", numIncrement, Date() ) 
End Function