| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details: |
| * |
| * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es> |
| */ |
| |
| #include "config.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <locale.h> |
| #include <string.h> |
| |
| #include <glib.h> |
| #include <gio/gio.h> |
| |
| #include <mm-log.h> |
| #include <mm-port-serial.h> |
| #include <mm-port-serial-at.h> |
| #include <mm-serial-parsers.h> |
| |
| #define PROGRAM_NAME "mmtty" |
| #define PROGRAM_VERSION PACKAGE_VERSION |
| |
| /* Globals */ |
| static MMPortSerialAt *port; |
| static GMainLoop *loop; |
| static GIOChannel *input; |
| static guint input_watch_id; |
| |
| /* Context */ |
| static gchar *device_str; |
| static gboolean no_flash_flag; |
| static gboolean no_echo_removal_flag; |
| static gboolean spew_control_flag; |
| static gint64 send_delay = -1; |
| static gboolean send_lf_flag; |
| static gboolean verbose_flag; |
| static gboolean version_flag; |
| |
| static GOptionEntry main_entries[] = { |
| { "device", 'd', 0, G_OPTION_ARG_STRING, &device_str, |
| "Specify device path", |
| "[PATH]" |
| }, |
| { "no-flash", 0, 0, G_OPTION_ARG_NONE, &no_flash_flag, |
| "Avoid flashing the port while opening", |
| NULL |
| }, |
| { "no-echo-removal", 0, 0, G_OPTION_ARG_NONE, &no_echo_removal_flag, |
| "Avoid logic to remove echo", |
| NULL |
| }, |
| { "spew-control", 0, 0, G_OPTION_ARG_NONE, &spew_control_flag, |
| "Enable spew control logic", |
| NULL |
| }, |
| { "send-delay", 0, 0, G_OPTION_ARG_INT64, &send_delay, |
| "Send delay for each byte in microseconds (default=1000)", |
| "[DELAY]" |
| }, |
| { "send-lf", 0, 0, G_OPTION_ARG_NONE, &send_lf_flag, |
| "Send <CR><LF> (default <CR> only)", |
| NULL |
| }, |
| { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag, |
| "Run action with verbose logs", |
| NULL |
| }, |
| { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag, |
| "Print version", |
| NULL |
| }, |
| { NULL } |
| }; |
| |
| static void |
| signals_handler (int signum) |
| { |
| if (loop && g_main_loop_is_running (loop)) { |
| g_printerr ("%s\n", |
| "cancelling the main loop...\n"); |
| g_main_loop_quit (loop); |
| } |
| } |
| |
| static void |
| print_version_and_exit (void) |
| { |
| g_print ("\n" |
| PROGRAM_NAME " " PROGRAM_VERSION "\n" |
| "Copyright (2015) Aleksander Morgado\n" |
| "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n" |
| "This is free software: you are free to change and redistribute it.\n" |
| "There is NO WARRANTY, to the extent permitted by law.\n" |
| "\n"); |
| exit (EXIT_SUCCESS); |
| } |
| |
| static void |
| at_command_ready (MMPortSerialAt *serial_at, |
| GAsyncResult *res) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| |
| response = mm_port_serial_at_command_finish (serial_at, res, &error); |
| if (response) |
| g_print ("%s\n", response); |
| if (error) { |
| g_printerr ("%s\n", error->message); |
| g_error_free (error); |
| } |
| |
| g_print ("> "); |
| } |
| |
| static gboolean |
| input_callback (GIOChannel *channel, |
| GIOCondition condition) |
| { |
| GError *error = NULL; |
| GIOStatus status; |
| gchar *line = NULL; |
| |
| status = g_io_channel_read_line (channel, &line, NULL, NULL, &error); |
| |
| switch (status) { |
| case G_IO_STATUS_NORMAL: { |
| gsize line_len = 0; |
| |
| /* remove \r\n before running as AT command */ |
| line_len = strlen (line); |
| while (line_len > 0 && (line[line_len - 1] == '\r' || line[line_len - 1] == '\n')) { |
| line[line_len - 1] = '\0'; |
| line_len--; |
| } |
| |
| mm_port_serial_at_command (port, line, 60, FALSE, FALSE, NULL, |
| (GAsyncReadyCallback) at_command_ready, NULL); |
| g_free (line); |
| return TRUE; |
| } |
| case G_IO_STATUS_ERROR: |
| g_printerr ("error: %s\n", error->message); |
| g_error_free (error); |
| return FALSE; |
| |
| case G_IO_STATUS_EOF: |
| g_warning ("error: No input data available"); |
| return TRUE; |
| |
| case G_IO_STATUS_AGAIN: |
| return TRUE; |
| |
| default: |
| g_assert_not_reached (); |
| } |
| |
| return FALSE; |
| } |
| |
| static void |
| flash_ready (MMPortSerial *serial, |
| GAsyncResult *res) |
| { |
| GError *error = NULL; |
| |
| if (!mm_port_serial_flash_finish (serial, res, &error)) { |
| g_printerr ("error: cannot flash serial port: %s\n", error->message); |
| exit (EXIT_FAILURE); |
| } |
| |
| g_print ("ready\n"); |
| g_print ("> "); |
| |
| /* Setup input reading */ |
| input = g_io_channel_unix_new (STDIN_FILENO); |
| input_watch_id = g_io_add_watch (input, G_IO_IN, (GIOFunc) input_callback, NULL); |
| } |
| |
| static void |
| serial_buffer_full (void) |
| { |
| g_printerr ("error: serial buffer full\n"); |
| } |
| |
| static gboolean |
| start_cb (void) |
| { |
| GError *error = NULL; |
| const gchar *device_name; |
| |
| device_name = device_str; |
| if (g_str_has_prefix (device_name, "/dev/")) |
| device_name += strlen ("/dev/"); |
| |
| g_print ("creating AT capable serial port for device '%s'...\n", device_name); |
| port = mm_port_serial_at_new (device_name, MM_PORT_SUBSYS_TTY); |
| |
| /* Setup send delay */ |
| if (send_delay >= 0) { |
| if (send_delay > 0) |
| g_print ("updating send delay to %" G_GINT64_FORMAT "us...\n", send_delay); |
| else |
| g_print ("disabling send delay...\n"); |
| g_object_set (port, MM_PORT_SERIAL_SEND_DELAY, send_delay, NULL); |
| } |
| |
| /* Setup echo removal */ |
| if (no_echo_removal_flag) { |
| g_print ("disabling echo removal...\n"); |
| g_object_set (port, MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE, NULL); |
| } |
| |
| /* Setup spew control */ |
| if (spew_control_flag) { |
| g_print ("enabling spew control...\n"); |
| g_object_set (port, MM_PORT_SERIAL_SPEW_CONTROL, TRUE, NULL); |
| } |
| |
| /* Setup LF */ |
| if (send_lf_flag) { |
| g_print ("enabling LF...\n"); |
| g_object_set (port, MM_PORT_SERIAL_AT_SEND_LF, TRUE, NULL); |
| } |
| |
| /* Set common response parser */ |
| mm_port_serial_at_set_response_parser (MM_PORT_SERIAL_AT (port), |
| mm_serial_parser_v1_parse, |
| mm_serial_parser_v1_new (), |
| mm_serial_parser_v1_destroy); |
| |
| /* Try to open the port... */ |
| g_print ("opening serial port...\n"); |
| if (!mm_port_serial_open (MM_PORT_SERIAL (port), &error)) { |
| g_printerr ("error: cannot open serial port: %s\n", error->message); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Warn on full buffer by default */ |
| g_signal_connect (port, "buffer-full", G_CALLBACK (serial_buffer_full), NULL); |
| |
| /* If we set FLASH_OK to FALSE, the flashing operation does nothing */ |
| if (no_flash_flag) { |
| g_print ("disabling serial port flash...\n"); |
| g_object_set (port, MM_PORT_SERIAL_FLASH_OK, FALSE, NULL); |
| } else |
| g_print ("flashing serial port...\n"); |
| mm_port_serial_flash (MM_PORT_SERIAL (port), |
| 100, |
| FALSE, |
| (GAsyncReadyCallback) flash_ready, |
| NULL); |
| return G_SOURCE_REMOVE; |
| } |
| |
| void |
| _mm_log (gpointer obj, |
| const gchar *module, |
| const gchar *loc, |
| const gchar *func, |
| guint32 level, |
| const gchar *fmt, |
| ...) |
| { |
| va_list args; |
| g_autofree gchar *msg = NULL; |
| const gchar *level_str = NULL; |
| |
| if (!verbose_flag) |
| return; |
| |
| switch (level) { |
| case MM_LOG_LEVEL_DEBUG: |
| level_str = "debug"; |
| break; |
| case MM_LOG_LEVEL_WARN: |
| level_str = "warning"; |
| break; |
| case MM_LOG_LEVEL_INFO: |
| level_str = "info"; |
| break; |
| case MM_LOG_LEVEL_ERR: |
| level_str = "error"; |
| break; |
| default: |
| break; |
| } |
| |
| va_start (args, fmt); |
| msg = g_strdup_vprintf (fmt, args); |
| va_end (args); |
| g_print ("[%s] %s\n", level_str ? level_str : "unknown", msg); |
| } |
| |
| int main (int argc, char **argv) |
| { |
| GOptionContext *context; |
| |
| setlocale (LC_ALL, ""); |
| |
| /* Setup option context, process it and destroy it */ |
| context = g_option_context_new ("- ModemManager TTY testing"); |
| g_option_context_add_main_entries (context, main_entries, NULL); |
| g_option_context_parse (context, &argc, &argv, NULL); |
| g_option_context_free (context); |
| |
| if (version_flag) |
| print_version_and_exit (); |
| |
| /* No device path given? */ |
| if (!device_str) { |
| g_printerr ("error: no device path specified\n"); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Setup signals */ |
| signal (SIGINT, signals_handler); |
| signal (SIGHUP, signals_handler); |
| signal (SIGTERM, signals_handler); |
| |
| /* Setup main loop and shedule start in idle */ |
| loop = g_main_loop_new (NULL, FALSE); |
| g_idle_add ((GSourceFunc)start_cb, NULL); |
| g_main_loop_run (loop); |
| |
| /* Cleanup */ |
| g_main_loop_unref (loop); |
| if (port) { |
| if (mm_port_serial_is_open (MM_PORT_SERIAL (port))) |
| mm_port_serial_close (MM_PORT_SERIAL (port)); |
| g_object_unref (port); |
| } |
| if (input) |
| g_io_channel_unref (input); |
| return 0; |
| } |