/* © 2007 - 2009 Michal Čihař */ /** * @file motorola.c * @author Michal Čihař */ /** * @ingroup Phone * @{ */ /** * @addtogroup ATPhone * @{ */ #include #ifdef GSM_ENABLE_ATGEN #include "../../gsmcomon.h" #include "../../gsmstate.h" #include "../../service/gsmpbk.h" #include "atgen.h" #include #include "../../../libgammu/misc/string.h" typedef struct { char Command[20]; int Mode; } MOTOROLA_CommandInfo; /** * AT Commands which can be issued only in one mode. * * List is based on c18 AT Commands document revision C. */ MOTOROLA_CommandInfo Commands[] = { {"+CGMI", 2}, {"+CGMM", 2}, {"+CGMR", 2}, {"+CGSN", 2}, {"+CSCS", 2}, {"+CIMI", 2}, {"+CNUM", 2}, {"+CVHU", 2}, {"+CHUP", 2}, {"+CIND", 2}, {"+CLCK", 2}, {"D", 2}, /* We want voice call */ {"H", 2}, /* We want voice call */ {"A", 2}, /* We want voice call */ {"+CRING", 2}, {"+CLIP", 2}, {"+CLIR", 2}, {"+CCFC", 2}, {"+CHLD", 2}, {"+COLP", 2}, {"+CCWA", 2}, {"+CLCC", 2}, {"+CPBS", 2}, {"+CPBR", 2}, {"+CPBF", 2}, {"+CPBW", 2}, {"+CCLK", 2}, {"+CNMI", 2}, {"+CMGD", 2}, {"+CMSS", 2}, {"+CSMS", 2}, {"+CPMS", 2}, {"+CMGF", 2}, {"+CSDH", 2}, {"+CMTI", 2}, {"+CMGL", 2}, {"+CMGR", 2}, {"+CMGW", 2}, {"+CSCA", 2}, {"+COPS", 2}, {"+CBC", 2}, {"+CRTT", 2}, {"+CMEE", 2}, {"+CKPD", 2}, {"+CHV", 0}, {"+CDV", 0}, {"+CPAS", 0}, {"+CREG", 0}, {"+CSQ", 0}, {"+GCAP", 0}, {"+CMUT", 0}, {"+CIMSI", 0}, {"", 0}, }; GSM_Error MOTOROLA_SetModeReply(GSM_Protocol_Message *msg UNUSED, GSM_StateMachine *s) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; switch (s->Phone.Data.Priv.ATGEN.ReplyState) { case AT_Reply_OK: case AT_Reply_Connect: /* * The typo in next line (Unkown) is intentional, * phone returns it this way. */ if (strstr(GetLineString(msg->Buffer, &Priv->Lines, 2), "Unkown mode value") != NULL) { return ERR_NOTSUPPORTED; } return ERR_NONE; case AT_Reply_Error: return ERR_UNKNOWN; case AT_Reply_CMSError: return ATGEN_HandleCMSError(s); case AT_Reply_CMEError: return ATGEN_HandleCMEError(s); default: break; } return ERR_UNKNOWNRESPONSE; } GSM_Error MOTOROLA_SetMode(GSM_StateMachine *s, const char *command) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; MOTOROLA_CommandInfo *cmd; const char *realcmd; char buffer[30]={0}; GSM_Error error = ERR_NONE; size_t len; /* Do we need any mode switching? */ if (!Priv->Mode) { return ERR_NONE; } /* We don't care about non AT commands */ if (strncasecmp(command, "AT", 2) != 0) { return ERR_NONE; } /* Skip AT prefix */ realcmd = command + 2; /* Do we have it in our list? */ for (cmd = Commands; cmd->Command[0] != 0; cmd++) { if (strncasecmp(realcmd, cmd->Command, strlen(cmd->Command)) == 0) { break; } } /* Not found? */ if (cmd->Command[0] == 0) { smprintf(s, "Nothing known about %s command, using current mode\n", command); return ERR_NONE; } /* Compare current mode */ if (cmd->Mode == Priv->CurrentMode) { smprintf(s, "Already in mode %d\n", cmd->Mode); return ERR_NONE; } /* Switch mode */ smprintf(s, "Switching to mode %d\n", cmd->Mode); len = sprintf(buffer, "AT+MODE=%d\r", cmd->Mode); error = GSM_WaitFor(s, buffer, len, 0x00, 100, ID_ModeSwitch); /* On succes we remember it */ if (error == ERR_NONE) { /* We might need to restore charset as phone resets it */ if (cmd->Mode == 2) { smprintf(s, "Waiting for banner...\n"); /* Wait for banner */ error = GSM_WaitForOnce(s, NULL, 0x00, 0x00, 40); if (error != ERR_NONE) { return error; } /* Check for banner result */ if (Priv->CurrentMode != 2) { smprintf(s, "Failed to set mode 2!\n"); return ERR_BUG; } /* Now we can work with phone */ error = ATGEN_SetCharset(s, AT_PREF_CHARSET_RESET); } else { Priv->CurrentMode = cmd->Mode; } } return error; } /** * Catches +MBAN: reply. */ GSM_Error MOTOROLA_Banner(GSM_Protocol_Message *msg UNUSED, GSM_StateMachine *s) { s->Phone.Data.Priv.ATGEN.CurrentMode = 2; return ERR_NONE; } GSM_Error MOTOROLA_ReplyGetMemoryInfo(GSM_Protocol_Message *msg, GSM_StateMachine *s) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; GSM_Error error; Priv->PBK_MPBR = AT_NOTAVAILABLE; switch (Priv->ReplyState) { case AT_Reply_OK: /* Reply string: * +MPBR: 10-18259,40,24,14,0-1,64,(255),(0),(0-1),(1-11),(255),25,(0-1,255),264,(0),62,62,32,32,32,32,50,264 */ Priv->PBK_MPBR = AT_AVAILABLE; error = ATGEN_ParseReply(s, GetLineString(msg->Buffer, &Priv->Lines, 2), "+MPBR: @i-@i, @0", &Priv->MotorolaFirstMemoryEntry, &Priv->MotorolaMemorySize); if (error != ERR_NONE) { return error; } Priv->MotorolaMemorySize -= Priv->MotorolaFirstMemoryEntry; return ERR_NONE; case AT_Reply_Error: /* The phone returns simply ERROR on empty entry */ return ERR_EMPTY; case AT_Reply_CMSError: return ATGEN_HandleCMSError(s); case AT_Reply_CMEError: return ATGEN_HandleCMEError(s); default: return ERR_UNKNOWNRESPONSE; } } GSM_Error MOTOROLA_ReplyGetMemory(GSM_Protocol_Message *msg, GSM_StateMachine *s) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; GSM_MemoryEntry *Memory = s->Phone.Data.Memory; GSM_Error error; const char *str; int number_type, entry_type; switch (Priv->ReplyState) { case AT_Reply_OK: smprintf(s, "Phonebook entry received\n"); Memory->EntriesNum = 2; Memory->Entries[0].AddError = ERR_NONE; Memory->Entries[0].VoiceTag = 0; Memory->Entries[0].SMSList[0] = 0; Memory->Entries[0].Location = PBK_Location_Unknown; Memory->Entries[1].EntryType = PBK_Text_Name; Memory->Entries[1].Location = PBK_Location_Unknown; Memory->Entries[1].AddError = ERR_NONE; Memory->Entries[1].VoiceTag = 0; Memory->Entries[1].SMSList[0] = 0; /* Get line from reply */ str = GetLineString(msg->Buffer, &Priv->Lines, 2); /* Detect empty entry */ if (strcmp(str, "OK") == 0) return ERR_EMPTY; /* * Parse reply string * * +MPBR: 18,"user@domain.net",128,"Contact Name",6,0,255,0,0,1,255,255,0,"",0,0,"","","","","","","","" */ error = ATGEN_ParseReply(s, str, "+MPBR: @i, @p, @i, @s, @i, @0", &Memory->Location, Memory->Entries[0].Text, sizeof(Memory->Entries[0].Text), &number_type, Memory->Entries[1].Text, sizeof(Memory->Entries[1].Text), &entry_type); Memory->Location = Memory->Location + 1 - Priv->MotorolaFirstMemoryEntry; switch (entry_type) { case 0: Memory->Entries[0].EntryType = PBK_Number_General; Memory->Entries[0].Location = PBK_Location_Work; GSM_TweakInternationalNumber(Memory->Entries[0].Text, number_type); break; case 1: Memory->Entries[0].EntryType = PBK_Number_General; Memory->Entries[0].Location = PBK_Location_Home; GSM_TweakInternationalNumber(Memory->Entries[0].Text, number_type); break; case 2: case 10: case 11: Memory->Entries[0].EntryType = PBK_Number_General; Memory->Entries[0].Location = PBK_Location_Unknown; GSM_TweakInternationalNumber(Memory->Entries[0].Text, number_type); break; case 3: Memory->Entries[0].EntryType = PBK_Number_Mobile; Memory->Entries[0].Location = PBK_Location_Unknown; GSM_TweakInternationalNumber(Memory->Entries[0].Text, number_type); break; case 4: Memory->Entries[0].EntryType = PBK_Number_Fax; Memory->Entries[0].Location = PBK_Location_Unknown; GSM_TweakInternationalNumber(Memory->Entries[0].Text, number_type); break; case 5: Memory->Entries[0].EntryType = PBK_Number_Pager; Memory->Entries[0].Location = PBK_Location_Unknown; GSM_TweakInternationalNumber(Memory->Entries[0].Text, number_type); break; case 6: Memory->Entries[0].EntryType = PBK_Text_Email; Memory->Entries[0].Location = PBK_Location_Unknown; break; case 7: Memory->Entries[0].EntryType = PBK_Text_Email; /* Mailing list */ Memory->Entries[0].Location = PBK_Location_Unknown; break; default: Memory->Entries[0].EntryType = PBK_Text_Note; Memory->Entries[0].Location = PBK_Location_Unknown; } if (error != ERR_NONE) { return error; } return ERR_NONE; case AT_Reply_Error: return ERR_UNKNOWN; case AT_Reply_CMSError: return ATGEN_HandleCMSError(s); case AT_Reply_CMEError: return ATGEN_HandleCMEError(s); default: break; } return ERR_UNKNOWNRESPONSE; } GSM_Error MOTOROLA_LockCalendar(GSM_StateMachine *s) { GSM_Error error; error = ATGEN_WaitForAutoLen(s, "AT+MDBL=1\r", 0x00, 10, ID_SetCalendarNote); return error; } GSM_Error MOTOROLA_UnlockCalendar(GSM_StateMachine *s) { GSM_Error error; error = ATGEN_WaitForAutoLen(s, "AT+MDBL=0\r", 0x00, 10, ID_SetCalendarNote); return error; } GSM_Error MOTOROLA_ReplyGetCalendarStatus(GSM_Protocol_Message *msg, GSM_StateMachine *s) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; GSM_Error error; int ignore; if (Priv->ReplyState != AT_Reply_OK) { switch (s->Phone.Data.Priv.ATGEN.ReplyState) { case AT_Reply_Error: return ERR_NOTSUPPORTED; case AT_Reply_CMSError: return ATGEN_HandleCMSError(s); case AT_Reply_CMEError: return ATGEN_HandleCMEError(s); default: return ERR_UNKNOWNRESPONSE; } } /* * Reply looks like: * +MDBR: 500,1,64,8,2 * (max events that can be stored, number of stored events, ?, ?, ?) */ error = ATGEN_ParseReply(s, GetLineString(msg->Buffer, &Priv->Lines, 2), "+MDBR: @i, @i, @i, @i, @i", &s->Phone.Data.CalStatus->Free, &s->Phone.Data.CalStatus->Used, &ignore, &ignore, &ignore); if (error != ERR_NONE) return error; s->Phone.Data.CalStatus->Free += s->Phone.Data.CalStatus->Used; return ERR_NONE; } GSM_Error MOTOROLA_GetCalendarStatus(GSM_StateMachine *s, GSM_CalendarStatus *Status) { GSM_Error error; s->Phone.Data.CalStatus = Status; error = ATGEN_WaitForAutoLen(s, "AT+MDBR=?\r", 0x00, 10, ID_GetCalendarNotesInfo); return error; } GSM_Error MOTOROLA_ParseCalendarSimple(GSM_StateMachine *s, const char *line) { GSM_Error error; int ignore; GSM_CalendarEntry *Note = s->Phone.Data.Cal; int duration, repeat, has_time, has_alarm; /* * Parse following format: * * +MDBR: 0,"Meeting",1,0,"17:00","02-24-2006",60,"00:00","00-00-2000",0 * +MDBR: 1,"Breakfast",1,1,"10:00","02-25-2006",60,"09:30","02-25-2006",2 * event num, description, time flag (if 0, start time is meaningless), alarm enabl * ed flag (if 0, alarm time is meaningless), time, date, duration (mins), alarm ti * me, alarm date, repeat type * * repeat type: * 1 = daily * 2 = weekly * 3 = monthly on date * 4 = monthly on day * 5 = yearly */ Note->Type = GSM_CAL_MEMO; Note->Entries[0].EntryType = CAL_TEXT; Note->Entries[1].EntryType = CAL_START_DATETIME; Note->Entries[1].Date.Timezone = 0; Note->Entries[1].Date.Second = 0; Note->Entries[2].EntryType = CAL_TONE_ALARM_DATETIME; Note->Entries[2].Date.Timezone = 0; Note->Entries[2].Date.Second = 0; Note->EntriesNum = 3; error = ATGEN_ParseReply(s, line, "+MDBR: @i, @s, @i, @i, @d, @i, @d, @i", &ignore, Note->Entries[0].Text, sizeof(Note->Entries[0].Text), &has_time, &has_alarm, &(Note->Entries[1].Date), &duration, &(Note->Entries[2].Date), &repeat); if (!has_time && !has_alarm) { Note->EntriesNum = 1; } else if (!has_alarm) { Note->EntriesNum = 2; } else if (!has_time) { Note->EntriesNum = 2; Note->Entries[1].EntryType = Note->Entries[2].EntryType; Note->Entries[1].Date = Note->Entries[2].Date; } switch (repeat) { case 1: Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_FREQUENCY; Note->Entries[Note->EntriesNum].Number = 1; Note->EntriesNum++; break; case 2: Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_FREQUENCY; Note->Entries[Note->EntriesNum].Number = 7; Note->EntriesNum++; break; case 3: Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_FREQUENCY; Note->Entries[Note->EntriesNum].Number = 1; Note->EntriesNum++; Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_DAY; Note->Entries[Note->EntriesNum].Number = Note->Entries[1].Date.Day; Note->EntriesNum++; break; case 4: Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_FREQUENCY; Note->Entries[Note->EntriesNum].Number = 1; Note->EntriesNum++; Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_DAY; Note->Entries[Note->EntriesNum].Number = Note->Entries[1].Date.Day; Note->EntriesNum++; break; case 5: Note->Entries[Note->EntriesNum].EntryType = CAL_REPEAT_FREQUENCY; Note->Entries[Note->EntriesNum].Number = 365; Note->EntriesNum++; break; } return error; } GSM_Error MOTOROLA_ReplyGetCalendar(GSM_Protocol_Message *msg, GSM_StateMachine *s) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; GSM_Error error; const char *line; if (Priv->ReplyState != AT_Reply_OK) { switch (s->Phone.Data.Priv.ATGEN.ReplyState) { case AT_Reply_Error: return ERR_NOTSUPPORTED; case AT_Reply_CMSError: return ATGEN_HandleCMSError(s); case AT_Reply_CMEError: return ATGEN_HandleCMEError(s); default: return ERR_UNKNOWNRESPONSE; } } line = GetLineString(msg->Buffer, &Priv->Lines, 2); if (strcmp("OK", line) == 0) { return ERR_EMPTY; } error = MOTOROLA_ParseCalendarSimple(s, line); if (error != ERR_NONE) { /* Fallback to parse complex later */ } return error; } GSM_Error MOTOROLA_GetNextCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Note, gboolean start) { GSM_Error error; GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; if (start) { /* One below actual first position */ Note->Location = 0; error = MOTOROLA_GetCalendarStatus(s, &Priv->CalendarStatus); if (error != ERR_NONE) { return error; } Priv->CalendarRead = 0; } s->Phone.Data.Cal = Note; Note->EntriesNum = 0; smprintf(s, "Getting calendar entry\n"); error = ERR_EMPTY; while (error == ERR_EMPTY) { Note->Location++; if (Note->Location >= Priv->CalendarStatus.Used + Priv->CalendarStatus.Free) { /* We're at the end */ return ERR_EMPTY; } if (Priv->CalendarRead >= Priv->CalendarStatus.Used) { /* We've read all entries */ return ERR_EMPTY; } error = MOTOROLA_GetCalendar(s, Note); if (error == ERR_NONE) { Priv->CalendarRead++; } } return error; } GSM_Error MOTOROLA_GetCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Note) { char req[50]; GSM_Error error; size_t len; error = MOTOROLA_LockCalendar(s); if (error != ERR_NONE) return ERR_NONE; s->Phone.Data.Cal = Note; len = sprintf(req, "AT+MDBR=%d\r", Note->Location - 1); error = ATGEN_WaitFor(s, req, len, 0x00, 10, ID_GetCalendarNote); MOTOROLA_UnlockCalendar(s); return error; } GSM_Error MOTOROLA_ReplySetCalendar(GSM_Protocol_Message *msg, GSM_StateMachine *s) { return ERR_NOTIMPLEMENTED; } GSM_Error MOTOROLA_DelCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Note) { char req[50]; GSM_Error error; size_t len; error = MOTOROLA_LockCalendar(s); if (error != ERR_NONE) return ERR_NONE; len = sprintf(req, "AT+MDBWE=%d,0,0\r", Note->Location); error = ATGEN_WaitFor(s, req, len, 0x00, 10, ID_DeleteCalendarNote); MOTOROLA_UnlockCalendar(s); return error; } GSM_Error MOTOROLA_SetCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Note) { GSM_Error error; error = MOTOROLA_LockCalendar(s); if (error != ERR_NONE) return ERR_NONE; MOTOROLA_UnlockCalendar(s); return ERR_NOTIMPLEMENTED; } GSM_Error MOTOROLA_AddCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Note) { GSM_Error error; error = MOTOROLA_LockCalendar(s); if (error != ERR_NONE) return ERR_NONE; MOTOROLA_UnlockCalendar(s); return ERR_NOTIMPLEMENTED; } GSM_Error MOTOROLA_ReplyGetMPBRMemoryStatus(GSM_Protocol_Message *msg, GSM_StateMachine *s) { GSM_Phone_ATGENData *Priv = &s->Phone.Data.Priv.ATGEN; switch (Priv->ReplyState) { case AT_Reply_OK: smprintf(s, "Memory status received\n"); return ERR_NONE; case AT_Reply_Error: return ERR_UNKNOWN; case AT_Reply_CMSError: return ATGEN_HandleCMSError(s); case AT_Reply_CMEError: return ATGEN_HandleCMEError(s); default: return ERR_UNKNOWNRESPONSE; } } #endif /*@}*/ /*@}*/ /* How should editor hadle tabs in this file? Add editor commands here. * vim: noexpandtab sw=8 ts=8 sts=8: */