Sim A9G bài 4: Điều khiển thiết bị từ xa thông qua tin nhắn SMS

Sim A9G bài 4: Điều khiển thiết bị từ xa thông qua tin nhắn SMS

Series: Lập trình module sim A9G với GPRS_C_SDK

wp-content/uploads/2019/09/A9G_GPRS_GPS_SS1.png

Giới thiệu chức năng

Xin chào các bạn! Hôm nay chúng ta sẽ thực hiện một project mới cho phép:

  • Bật/tắt thiết bị (led) từ xa thông qua tin nhắn SMS
  • Gửi tin nhắn SMS báo cáo trạng thái hiện tại của thiết bị (led)
  • Chỉ thực hiện lệnh SMS đến từ các số điện thoại đã lưu trước

Video hướng dẫn

Chuẩn bị phần cứng

  • 1 board Pudding có gắn anten GSM
  • 1 mạch USB-UART dùng chip CP2102
  • bus kết nối, cáp microUSB
  • tùy chọn: 1 board relay 4 kênh-5v (hoặc board led)

Trên board Pudding đã có 2 led được nối vào GPIO_27 và GPIO_28, chúng ta có thể tận dụng lại cũng được. Nhưng để thuận tiện cho việc theo dõi led cũng như hiển thị được nhiều led hơn, chúng ta nên dùng 1 board relay như bên dưới.

Sơ đồ kết nối như sau:

A9G Pudding USB-UART
HST_TX RX
HST_RX TX
GND GND
A9G Pudding Relay
5v 5v
GND GND
GPIO_25 Channel 1
GPIO_30 Channel 2
GPIO_26 Channel 3

Cấp nguồn cho Pudding thông qua 1 sợi dây microUSB, có thể dùng cục sạc điện thoại hoặc cắm trực tiếp vào máy tính cũng được.

Tóm tắt luồng xử lý

Sau đây, mình sẽ giới thiệu sơ qua về sơ đồ luồng xử lý của project. Sơ đồ này cũng được áp dụng cho các project khác của series. Điểm khác biệt nằm ở công đoạn xử lý event, mỗi project có các event và cách xử lý khác nhau. Các bạn xem video hướng dẫn để có thêm chi tiết.

Chương trình sẽ có 2 luồng chạy song song. Luồng thứ nhất sẽ lắng nghe và xử lý các sự kiện (event) được phát sinh từ hệ thống (OS). Đây cũng là luồng chính, nơi xử lý các tin nhắn SMS nhận được.

Luồng thứ 2 là luồng phụ, có nhiệm vụ nhấp nháy 1 led để ta biết được module đang hoạt động.

Phân tích kĩ hơn về luồng thứ nhất, ta có một vòng lặp vô tận. Bên trong vòng lặp đó, chương trình sẽ lắng nghe các event được phát sinh từ OS. Với mỗi event, chương trình sẽ kiểm tra xem các event đó thuộc diện cần xử lý hay không, nếu không thì bỏ qua và tiếp tục vòng lặp.

Bản thân mỗi event có chứa một ID và Data. ID của event dùng để định danh nó thuộc loại event gì, từ đó sẽ xác định được Data đang chứa bên trong nó sẽ có những thông tin gì.

Với bài toán điều khiển LED qua SMS thì các event cần phải xử lý như sau:

Event ID Data Giải thích
SYSTEM_READY
  Hệ thống đã boot xong và đã sẵn sàng
NETWORK_REGISTERED_HOME
NETWORK_REGISTERED_ROAMING
  Đã kết nối mạng di động thành công
SMS_SENT
  Đã gửi SMS thành công
SMS_RECEIVED
 

SMS Header
SMS Content
SMS Encode Type

Vừa nhận tin nhắn SMS

Coding

Bên dưới là sơ lược các hàm quan trọng của project. Ngoài ra còn có các hàm utils khác sẽ được nhắc đến trong video demo.

Trong hàm sms_gpio_Main (Entry Point), chúng ta sẽ khởi chạy luồng xử lý các event (Event_Task).

main.c

void sms_gpio_Main()
{
    eventTaskHandle = OS_CreateTask(Event_Task,
                                    NULL, NULL, MAIN_TASK_STACK_SIZE, MAIN_TASK_PRIORITY, 0, 0, MAIN_TASK_NAME);
    OS_SetUserMainHandle(&eventTaskHandle);
}

Event_Task sẽ tạo thêm một luồng thứ 2 tên là Blink_Task, sau đó sẽ vào vòng lặp vô tận.

main.c

void Event_Task(void *pData)
{
    API_Event_t *event = NULL;
 
    //khởi chạy thêm một luồng mới tên là Blink_Task để nhấp nháy led báo trạng thái module đang hoạt động
    smsGpioTaskHandle = OS_CreateTask(Blink_Task,
                                      NULL, NULL, MAIN_TASK_STACK_SIZE, MAIN_TASK_PRIORITY, 0, 0, MAIN_TASK_NAME);
 
    //vòng lặp vô tận, lắng nghe và xử lý các event.
    while (1)
    {
        if (OS_WaitEvent(eventTaskHandle, (void **)&event, OS_TIME_OUT_WAIT_FOREVER))
        {
            Event_Dispatch(event);
            OS_Free(event->pParam1);
            OS_Free(event->pParam2);
            OS_Free(event);
        }
    }
}

Công việc của Blink_Task đơn giản như cái tên của nó 🙂

main.c

void Blink_Task()
{
    while (1)
    {
        GPIO_Set(FLASH_LED, !LED_STATE_ON);
        OS_Sleep(1000);
        GPIO_Set(FLASH_LED, LED_STATE_ON);
        OS_Sleep(1000);
    }
}

Hàm Event_Dispatch, nơi phân loại và xử lý event.

main.c

//xử lý một event
void Event_Dispatch(API_Event_t *pEvent)
{
    switch (pEvent->id)
    {
    case API_EVENT_ID_SYSTEM_READY:
        //khởi tạo GPIO
        Init_Pins();
 
        //xóa bộ lưu tin nhắn
        ClearSmsStorage();
 
        Trace(1, "System initialized");
        break;
    case API_EVENT_ID_NETWORK_REGISTERED_HOME:
    case API_EVENT_ID_NETWORK_REGISTERED_ROAMING:
        //khởi tạo một tin nhắn SMS để tí nữa gửi sau
        InitSMS();
 
        Trace(1, "Network registered");
        break;
    case API_EVENT_ID_SMS_SENT:
        //ghi log báo gửi thành công
        Trace(1, "SMS sent");
        break;
    case API_EVENT_ID_SMS_RECEIVED:
        Trace(1, "Received message");
        char *header = pEvent->pParam1;
        char *content = pEvent->pParam2;
 
        Trace(1, "SMS length:%d,content:%s", strlen(content), content);
 
        char *phoneNumber[20];
 
        //trích số điện thoại từ header
        Get_PhoneNumer(header, phoneNumber);
 
        //kiểm tra xem có phải số của master không
        if (Is_Master_Phone_Number(phoneNumber))
        {
            Trace(1, "SMS from master");
 
            //đây là số của master, xử lý thôi
            Handle_SMS(phoneNumber, content);
        }
        else
        {
            //số lạ hoặc số QC của nhà mạng thì next
            Trace(1, "SMS from stranger or [QC], header: %s", header);
        }
        OS_Free(phoneNumber);
        break;
    default:
        //các event khác thì next
        break;
    }
}

Hàm Handle_SMS sẽ cắt tin nhắn theo dòng, mỗi dòng cắt được sẽ là một lệnh.

main.c

//xử lý một tin nhắn SMS
void Handle_SMS(char *phoneNumber, char *smsContent)
{
    Trace(1, "Command: %s", smsContent);
 
    //chuyển tin nhắn về dạng chữ thường
    lowerStr(smsContent);
    bool shouldReport = false;
    char *line;
 
    //cắt từng dòng của tin nhắn, mỗi dòng là một lệnh
    //ví dụ một tin nhắn:
    /*
        led1 on
        led2 off
        report
    */
    while ((line = strsep(&smsContent, "\n")))
    {
        //nếu có lệnh "report", sau khi xử lý hết tất cả các lệnh sẽ phải gửi report
        if (!strcmp(line, "report"))
        {
            shouldReport = true;
        }
        else
        {
            //xử lý các lệnh dạng on/off
            Handle_Command(line);
        }
    }
 
    if (shouldReport)
    {
        //gửi report
        Send_SMS_Report(phoneNumber);
    }
 
    Trace(1, "Command handled");
}

Hàm Handle_Command sẽ xử lý mỗi dòng lệnh được cắt ra từ hàm Handle_SMS.

main.c

//xử lý một lệnh dạng on/off
void Handle_Command(char *cmd)
{
    Trace(1, "Execute: %s", cmd);
 
    char *key, *value;
 
    //kiểm tra xem câu lệnh có hợp lệ không
    //câu lệnh hợp lệ là câu lệnh có dạng "blahblah <space> on" hoặc "blahblah <space> off"
    if(!(key = strsep(&cmd, " "))){
        Trace(1, "Invalid command");
        return;
    }
    if (key[0] == '\0')
    {
        Trace(1, "Invalid command");
        return;
    }
 
    if(!(value = strsep(&cmd, " "))){
        Trace(1, "Invalid command");
        return;
    }
    if (value[0] == '\0')
    {
        Trace(1, "Invalid command");
        return;
    }
 
    if (strcmp(value, "on") && strcmp(value, "off"))
        return;
 
    //chuyển đổi on/off sang logic level
    GPIO_LEVEL level = strcmp(value, "on") ? !LED_STATE_ON : LED_STATE_ON;
 
    if (!strcmp(key, "led1"))
    {
        //set level cho led 1
        GPIO_Set(LEDS[1], level);
    }
    else if (!strcmp(key, "led2"))
    {
        //set level cho led 2
        GPIO_Set(LEDS[2], level);
    }
    Trace(1, "Executed");
}

Hàm Send_SMS_Report sẽ gửi tin nhắn báo cáo tới số điện thoại của master.

main.c

//gửi report
void Send_SMS_Report(char *phoneNumber)
{
    GPIO_LEVEL level;
    char *status1, *status2, msg[100];
 
    //lấy trạng thái của led 1
    GPIO_Get(LEDS[1], &level);
    status1 = level == LED_STATE_ON ? "on" : "off";
 
    //lấy trạng thái của led 2
    GPIO_Get(LEDS[2], &level);
    status2 = level == LED_STATE_ON ? "on" : "off";
 
    //build thành nội dung tin nhắn
    snprintf(msg, sizeof(msg), "Leds status:\nLed1 %s\nLed2 %s", status1, status2);
 
    //gửi tin nhắn
    SendUtf8Sms(phoneNumber, msg);
    Trace(1, "Report SMS: %s", msg);
}

Ưu và nhược điểm của phương pháp dùng tin nhắn SMS

Ưu điểm:

  • Đơn giản, dễ làm và dễ trúng thưởng 🙂
  • Tầm phủ sóng rộng, phù hợp những nơi sóng yếu hoặc không có GPRS
  • Độ ổn định cao vì không giữ kết nối GPRS

Nhược điểm:

  • Với các ứng dụng cần giao tiếp nhiều, chi phí có thể cao hơn so với GPRS
  • Phản hồi không tức thời như GPRS

Những điểm cần cải tiến

Còn khá nhiều chức năng “pro” mà chúng ta có thể thêm vào project. Các bạn hãy thử làm và share code cho mọi người trên github của xcode.vn nhé.

  • Tự xóa bộ nhớ SMS khi đầy
  • Lưu log và SMS vào thẻ nhớ
  • Báo cáo số dư tài khoản
  • Báo cáo giá trị ADC đọc được
  • Chuyển tiếp tin nhắn từ nhà mạng (QC, KM)
  • v.v…

Kết

Như vậy chúng ta đã hoàn thành project điều khiển LED qua tin nhắn SMS. Theo đánh giá của mình thì tốc độ phản hồi tuy không tức thời nhưng cũng khá nhanh.
Có một việc cần lưu ý đó là phải chọn gói cước sao cho có lợi cho việc nhắn tin SMS.

Source code & Tham khảo

Source code: xcode.vn|sim-a9g-#4
SMS API

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

7 Bình luận

  1. Thank you for your contribution.
    it really helped me.

    can we use the sdcard to store settings?
    mqtt settings, phone number to send sms, call with mp3 audio, etc?

    regards,
    Darma