Phone
/* $Id$ */
							/* Copyright (c) 2011-2020 Pierre Pronchery <khorben@defora.org> */
							/* This file is part of DeforaOS Desktop Phone */
							/* Redistribution and use in source and binary forms, with or without
							 * modification, are permitted provided that the following conditions are
							 * met:
							 *
							 * 1. Redistributions of source code must retain the above copyright notice,
							 *    this list of conditions and the following disclaimer.
							 *
							 * 2. Redistributions in binary form must reproduce the above copyright notice,
							 *    this list of conditions and the following disclaimer in the documentation
							 *    and/or other materials provided with the distribution.
							 *
							 * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
							 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
							 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
							 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
							 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
							 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
							 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
							 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
							 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
							 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
							#include <stdlib.h>
							#include <stdio.h>
							#include <string.h>
							#include <errno.h>
							#include <gtk/gtk.h>
							#include <System.h>
							#include "Phone.h"
							/* Engineering */
							/* private */
							/* types */
							typedef enum _EngineeringLocationPagingParameters
							{
								ELPP_BS_PA_MFRMS = 0,
								ELPP_T3212,
								ELPP_MCC,
								ELPP_MNC,
								ELPP_TMSI
							} EngineeringLocationPagingParameter;
							typedef enum _EngineeringNeighborCellInformation
							{
								ENCI_ARFCN = 0,
								ENCI_C1,
								ENCI_C2,
								ENCI_RXLEV,
								ENCI_BSIC,
								ENCI_CELL_ID,
								ENCI_LAC,
								ENCI_FRAME_OFFSET,
								ENCI_TIME_ALIGNMENT,
								ENCI_CBA,
								ENCI_CBQ,
								ENCI_CELL_TYPE_IND,
								ENCI_RAC,
								ENCI_CELL_RESEL_OFFSET,
								ENCI_TEMP_OFFSET,
								ENCI_RXLEV_ACC_MIN
							} EngineeringNeighborCellInformation;
							typedef enum _EngineeringServingCellInformation
							{
								ESCI_ARFCN = 0,
								ESCI_C1,
								ESCI_C2,
								ESCI_RXLEV,
								ESCI_BSIC,
								ESCI_CELL_ID,
								ESCI_DSC,
								ESCI_TXLEV,
								ESCI_TN,
								ESCI_RLT,
								ESCI_TAV,
								ESCI_RXLEV_F,
								ESCI_RXLEV_S,
								ESCI_RXQUAL_F,
								ESCI_RXQUAL_S,
								ESCI_LAC,
								ESCI_CBA,
								ESCI_CBQ,
								ESCI_CELL_TYPE_IND,
								ESCI_VOCODER
							} EngineeringServingCellInformation;
							typedef struct _PhonePlugin
							{
								PhonePluginHelper * helper;
								guint source;
								EngineeringNeighborCellInformation enci;
								size_t enci_cnt;
								/* widgets */
								GtkWidget * window;
								GtkToolItem * tb_play;
								GtkToolItem * tb_fullscreen;
								GtkListStore * sc_store;
								GtkWidget * sc_view;
								GtkListStore * nc_store;
								GtkWidget * nc_view;
							} Engineering;
							/* constants */
							/* serving cell */
							enum {
								SC_COL_FREQUENCY, SC_COL_C1, SC_COL_C2, SC_COL_RX_LEVEL,
								SC_COL_STATION_ID, SC_COL_CELL_ID, SC_COL_TX_LEVEL,
								SC_COL_TIMESLOT_NUMBER, SC_COL_LAC, SC_COL_TMSI
							};
							#define SC_COL_LAST SC_COL_TMSI
							#define SC_COL_COUNT (SC_COL_LAST + 1)
							static struct
							{
								int col;
								char const * title;
							} _engineering_sc_columns[SC_COL_COUNT + 1] =
							{
								{ SC_COL_FREQUENCY, "Frequency" },
								{ SC_COL_C1, "C1" },
								{ SC_COL_C2, "C2" },
								{ SC_COL_RX_LEVEL, "RX level" },
								{ SC_COL_STATION_ID, "Station ID" },
								{ SC_COL_CELL_ID, "Cell ID" },
								{ SC_COL_LAC, "Area code" },
								{ SC_COL_TX_LEVEL, "TX level" },
								{ SC_COL_TIMESLOT_NUMBER, "Timeslot" },
								{ SC_COL_TMSI, "TMSI" },
								{ 0, NULL }
							};
							/* neighbor cells */
							enum { NC_COL_FREQUENCY, NC_COL_C1, NC_COL_C2, NC_COL_RXLEV, NC_COL_BSIC,
								NC_COL_CELL_ID, NC_COL_LAC, NC_COL_FRAME_OFFSET, NC_COL_CBA, NC_COL_CBQ,
								NC_COL_CELL_TYPE_IND, NC_COL_RAC };
							#define NC_COL_LAST NC_COL_RAC
							#define NC_COL_COUNT (NC_COL_LAST + 1)
							static struct
							{
								int col;
								char const * title;
							} _engineering_nc_columns[NC_COL_COUNT + 1] =
							{
								{ NC_COL_FREQUENCY, "Frequency" },
								{ NC_COL_C1, "C1" },
								{ NC_COL_C2, "C2" },
								{ NC_COL_RXLEV, "RX level" },
								{ NC_COL_BSIC, "Station ID" },
								{ NC_COL_CELL_ID, "Cell ID" },
								{ NC_COL_LAC, "Area code" },
								{ NC_COL_FRAME_OFFSET, "Frame offset" },
								{ NC_COL_CBA, "Cell Bar Access" },
								{ NC_COL_CBQ, "Cell Bar Qualify" },
								{ NC_COL_CELL_TYPE_IND, "Cell type" },
								{ NC_COL_RAC, "Routing area code" },
								{ 0, NULL }
							};
							/* prototypes */
							static Engineering * _engineering_init(PhonePluginHelper * helper);
							static void _engineering_destroy(Engineering * engineering);
							static double _engineering_get_frequency(unsigned int arfcn);
							/* callbacks */
							static gboolean _on_engineering_closex(gpointer data);
							static void _on_engineering_play_toggled(gpointer data);
							static void _on_engineering_fullscreen_toggled(gpointer data);
							static gboolean _on_engineering_timeout(gpointer data);
							static int _on_engineering_trigger_em(Engineering * engineering,
									char const * result);
							/* public */
							/* variables */
							PhonePluginDefinition plugin =
							{
								"Engineering",
								"network-wireless",
								NULL,
								_engineering_init,
								_engineering_destroy,
								NULL,
								NULL
							};
							/* private */
							/* functions */
							/* engineering_init */
							static Engineering * _engineering_init(PhonePluginHelper * helper)
							{
								Engineering * engineering;
								GtkWidget * vbox;
								GtkWidget * toolbar;
								GtkWidget * paned;
								GtkWidget * frame;
								GtkWidget * scrolled;
								size_t i;
								GtkCellRenderer * renderer;
								GtkTreeViewColumn * column;
								if((engineering = object_new(sizeof(*engineering))) == NULL)
									return NULL;
								engineering->helper = helper;
								engineering->source = 0;
								engineering->enci = 0;
								engineering->enci_cnt = 0;
								/* widgets */
								/* window */
								engineering->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
								gtk_window_set_default_size(GTK_WINDOW(engineering->window), 200, 300);
							#if GTK_CHECK_VERSION(2, 6, 0)
								gtk_window_set_icon_name(GTK_WINDOW(engineering->window),
										"stock_compile");
							#endif
								gtk_window_set_title(GTK_WINDOW(engineering->window),
										"Engineering mode");
								g_signal_connect_swapped(engineering->window, "delete-event",
										G_CALLBACK(_on_engineering_closex), engineering);
							#if GTK_CHECK_VERSION(3, 0, 0)
								vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
							#else
								vbox = gtk_vbox_new(FALSE, 0);
							#endif
								/* toolbar */
								toolbar = gtk_toolbar_new();
								engineering->tb_play = gtk_toggle_tool_button_new_from_stock(
										GTK_STOCK_MEDIA_PLAY);
								g_signal_connect_swapped(G_OBJECT(engineering->tb_play), "toggled",
										G_CALLBACK(_on_engineering_play_toggled), engineering);
								gtk_toolbar_insert(GTK_TOOLBAR(toolbar), engineering->tb_play, -1);
								engineering->tb_fullscreen = gtk_toggle_tool_button_new_from_stock(
										GTK_STOCK_FULLSCREEN);
								g_signal_connect_swapped(engineering->tb_fullscreen, "toggled",
										G_CALLBACK(_on_engineering_fullscreen_toggled),
										engineering);
								gtk_toolbar_insert(GTK_TOOLBAR(toolbar), engineering->tb_fullscreen,
										-1);
								gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, TRUE, 0);
								/* serving cell view */
							#if GTK_CHECK_VERSION(3, 0, 0)
								paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
							#else
								paned = gtk_vpaned_new();
							#endif
								frame = gtk_frame_new("Serving cell");
								engineering->sc_store = gtk_list_store_new(SC_COL_COUNT,
										G_TYPE_STRING,		/* SC_COL_FREQUENCY */
										G_TYPE_STRING,		/* SC_COL_C1 */
										G_TYPE_STRING,		/* SC_COL_C2 */
										G_TYPE_STRING,		/* SC_COL_RX_LEVEL */
										G_TYPE_STRING,		/* SC_COL_STATION_ID */
										G_TYPE_STRING,		/* SC_COL_CELL_ID */
										G_TYPE_STRING,		/* SC_COL_TX_LEVEL */
										G_TYPE_STRING,		/* SC_COL_TIMESLOT_NUMBER */
										G_TYPE_STRING,		/* SC_COL_LAC */
										G_TYPE_STRING);		/* SC_COL_TMSI */
								scrolled = gtk_scrolled_window_new(NULL, NULL);
								gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
										GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
								engineering->sc_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
											engineering->sc_store));
								gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(engineering->sc_view), TRUE);
								gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(
												engineering->sc_view)),
										GTK_SELECTION_NONE);
								/* columns */
								for(i = 0; _engineering_sc_columns[i].title != NULL; i++)
								{
									renderer = gtk_cell_renderer_text_new();
									column = gtk_tree_view_column_new_with_attributes(
											_engineering_sc_columns[i].title, renderer,
											"text", _engineering_sc_columns[i].col, NULL);
									gtk_tree_view_append_column(GTK_TREE_VIEW(engineering->sc_view),
											column);
								}
								gtk_container_add(GTK_CONTAINER(scrolled), engineering->sc_view);
								gtk_container_add(GTK_CONTAINER(frame), scrolled);
								gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
								gtk_paned_add1(GTK_PANED(paned), frame);
								/* neighbor cells view */
								frame = gtk_frame_new("Neighbor cells");
								engineering->nc_store = gtk_list_store_new(NC_COL_COUNT,
										G_TYPE_STRING,		/* NC_COL_FREQUENCY */
										G_TYPE_STRING,		/* NC_COL_C1 */
										G_TYPE_STRING,		/* NC_COL_C2 */
										G_TYPE_STRING,		/* NC_COL_RXLEV */
										G_TYPE_STRING,		/* NC_COL_BSIC */
										G_TYPE_STRING,		/* NC_COL_CELL_ID */
										G_TYPE_STRING,		/* NC_COL_LAC */
										G_TYPE_STRING,		/* NC_COL_FRAME_OFFSET */
										G_TYPE_STRING,		/* NC_COL_CBA */
										G_TYPE_STRING,		/* NC_COL_CBQ */
										G_TYPE_STRING,		/* NC_COL_CELL_TYPE_IND */
										G_TYPE_STRING);		/* NC_COL_RAC */
								scrolled = gtk_scrolled_window_new(NULL, NULL);
								gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
										GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
								engineering->nc_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
											engineering->nc_store));
								gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(engineering->nc_view), TRUE);
								gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(
												engineering->nc_view)),
										GTK_SELECTION_NONE);
								/* columns */
								for(i = 0; _engineering_nc_columns[i].title != NULL; i++)
								{
									renderer = gtk_cell_renderer_text_new();
									column = gtk_tree_view_column_new_with_attributes(
											_engineering_nc_columns[i].title, renderer,
											"text", _engineering_nc_columns[i].col, NULL);
									gtk_tree_view_append_column(GTK_TREE_VIEW(engineering->nc_view),
											column);
								}
								gtk_container_add(GTK_CONTAINER(scrolled), engineering->nc_view);
								gtk_container_add(GTK_CONTAINER(frame), scrolled);
								gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
								gtk_container_add(GTK_CONTAINER(engineering->window), vbox);
								gtk_paned_add2(GTK_PANED(paned), frame);
								gtk_box_pack_start(GTK_BOX(vbox), paned, TRUE, TRUE, 0);
								gtk_widget_show_all(engineering->window);
								/* trigger */
							#if 0 /* FIXME reimplement using an extension to the Hayes modem plug-in */
								helper->register_trigger(helper->phone, plugin, "%EM",
										_on_engineering_trigger_em);
							#endif
								return engineering;
							}
							/* engineering_destroy */
							static void _engineering_destroy(Engineering * engineering)
							{
								if(engineering->source != 0)
									g_source_remove(engineering->source);
								gtk_widget_destroy(engineering->window);
								object_delete(engineering);
							}
							/* engineering_get_frequency */
							static double _engineering_get_frequency(unsigned int arfcn)
							{
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(%u)\n", __func__, arfcn);
							#endif
								if(arfcn == 0)
									return 0 / 0.0;
								else if(arfcn < 124) /* 900 MHz */
									return 935 + (arfcn - 511) * 0.2;
								else if(arfcn <= 172)
									return 0 / 0.0;
								else if(arfcn < 252) /* 850 MHz */
									return 869 + (arfcn - 127) * 0.2;
								else if(arfcn <= 511)
									return 0 / 0.0;
								/* XXX the values for the 1800 and 1900 MHz band overlap */
								else if(arfcn < 811) /* 1900 MHz */
									return 1930 + (arfcn - 511) * 0.2;
								else if(arfcn < 886) /* 1800 MHz */
									return 1805 + (arfcn - 511) * 0.2;
								else if(arfcn <= 974)
									return 0 / 0.0;
								else if(arfcn < 1023) /* 900 MHz */
									return 925 + (arfcn - 974) * 0.2;
								else
									return 0 / 0.0;
							}
							/* callbacks */
							/* on_engineering_closex */
							static gboolean _on_engineering_closex(gpointer data)
							{
								Engineering * engineering = data;
								gtk_widget_hide(engineering->window);
								return TRUE;
							}
							/* on_engineering_fullscreen_toggled */
							static void _on_engineering_fullscreen_toggled(gpointer data)
							{
								Engineering * engineering = data;
								if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(
												engineering->tb_fullscreen)))
									gtk_window_fullscreen(GTK_WINDOW(engineering->window));
								else
									gtk_window_unfullscreen(GTK_WINDOW(engineering->window));
							}
							/* on_engineering_play_toggled */
							static void _on_engineering_play_toggled(gpointer data)
							{
								Engineering * engineering = data;
								if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(
												engineering->tb_play)))
									engineering->source = _on_engineering_timeout(engineering);
								else if(engineering->source != 0)
								{
									g_source_remove(engineering->source);
									engineering->source = 0;
								}
							}
							/* on_engineering_timeout */
							static gboolean _on_engineering_timeout(gpointer data)
							{
								Engineering * engineering = data;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								engineering->source = 0;
							#if 0 /* FIXME implement again otherwise */
								engineering->helper->queue(engineering->helper->phone, "AT%EM=2,1");
								engineering->helper->queue(engineering->helper->phone, "AT%EM=2,2");
								engineering->helper->queue(engineering->helper->phone, "AT%EM=2,3");
								engineering->helper->queue(engineering->helper->phone, "AT%EM=2,4");
							#endif
								return FALSE;
							}
							/* on_engineering_trigger_em */
							static int _trigger_em1_do(Engineering * engineering, unsigned int * p,
									size_t cnt);
							static int _trigger_em2_do(Engineering * engineering, unsigned int * p,
									size_t cnt);
							static int _trigger_em3_do(Engineering * engineering, char const * buf,
									GtkTreeIter * iter);
							static int _trigger_em4_do(Engineering * engineering, unsigned int * p,
									size_t cnt);
							static int _do_arfcn(Engineering * engineering, unsigned int arfcn,
									GtkTreeIter * iter);
							static int _do_c1(Engineering * engineering, int c1, GtkTreeIter * iter);
							static int _do_c2(Engineering * engineering, int c2, GtkTreeIter * iter);
							static int _do_rxlev(Engineering * engineering, unsigned int rxlev,
									GtkTreeIter * iter);
							static int _do_bsic(Engineering * engineering, unsigned int bsic,
									GtkTreeIter * iter);
							static int _do_cell_id(Engineering * engineering, unsigned int cell_id,
									GtkTreeIter * iter);
							static int _do_lac(Engineering * engineering, unsigned int lac,
									GtkTreeIter * iter);
							static int _do_frame_offset(Engineering * engineering,
									unsigned int frame_offset, GtkTreeIter * iter);
							static int _do_time_alignment(Engineering * engineering,
									unsigned int time_alignment);
							static int _do_cba(Engineering * engineering, unsigned int cba,
									GtkTreeIter * iter);
							static int _do_cbq(Engineering * engineering, unsigned int cbq,
									GtkTreeIter * iter);
							static int _do_cell_type_ind(Engineering * engineering,
									unsigned int cell_type_ind, GtkTreeIter * iter);
							static int _do_rac(Engineering * engineering, unsigned int rac,
									GtkTreeIter * iter);
							static int _do_cell_resel_offset(Engineering * engineering,
									unsigned int cell_resel_offset);
							static int _do_temp_offset(Engineering * engineering, unsigned int temp_offset);
							static int _do_rxlev_acc_min(Engineering * engineering,
									unsigned int rxlev_acc_min);
							static int _on_engineering_trigger_em(Engineering * engineering, char const * result)
							{
								int res;
								unsigned int p[20];
								size_t i;
								size_t j;
								gboolean valid;
								GtkTreeIter iter;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, result);
							#endif
								if(engineering->source == 0)
									engineering->source = g_timeout_add(5000,
											_on_engineering_timeout, engineering);
								/* scan as many as we may expect */
								res = sscanf(result, "%%EM: %u,%u,%u,%u,%u"",%u,%u,%u,%u,%u"
										",%u,%u,%u,%u,%u"",%u,%u,%u,%u,%u",
										&p[0], &p[1], &p[2], &p[3], &p[4],
										&p[5], &p[6], &p[7], &p[8], &p[9],
										&p[10], &p[11], &p[12], &p[13], &p[14],
										&p[15], &p[16], &p[17], &p[18], &p[19]);
								if(res == 20)
									return _trigger_em1_do(engineering, p, 20);
								if(res == 11)
									return _trigger_em2_do(engineering, p, 11);
								if(res == 5)
									return _trigger_em4_do(engineering, p, 5);
								if(res == 1)
								{
									engineering->enci = 0;
									engineering->enci_cnt = p[0];
							#ifdef DEBUG
									fprintf(stderr, "DEBUG: %s(\"%s\") => %u\n", __func__, result,
											p[0]);
							#endif
									return 0;
								}
								if(res != 0)
									return 1;
								/* XXX assumes it's em3 */
								valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(
											engineering->nc_store), &iter);
								for(i = 0; i < engineering->enci_cnt; i++)
								{
									if(valid == FALSE)
										gtk_list_store_append(engineering->nc_store, &iter);
									for(j = 0; result[j] != ',' && result[j] != '\0'; j++);
									_trigger_em3_do(engineering, result, &iter);
									if(result[j] == '\0')
										break; /* XXX report when did not parse as many cells */
									result += j + 1;
									valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(
												engineering->nc_store), &iter);
								}
								if(valid && gtk_tree_model_iter_next(GTK_TREE_MODEL(
												engineering->nc_store), &iter))
									while(gtk_list_store_remove(engineering->nc_store, &iter));
								engineering->enci++;
								return 0;
							}
							static int _trigger_em1_do(Engineering * engineering, unsigned int * p,
									size_t cnt)
							{
								EngineeringServingCellInformation esci;
								GtkTreeIter iter;
								size_t i;
								char buf[32];
								double freq;
								if(cnt != 20)
									return 1;
								if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(engineering->sc_store),
											&iter) == FALSE)
									gtk_list_store_append(engineering->sc_store, &iter);
								for(i = 0; i < cnt; i++)
								{
									esci = i;
									snprintf(buf, sizeof(buf), "%u", p[i]);
									switch(esci)
									{
										case ESCI_ARFCN:
											freq = _engineering_get_frequency(p[i]);
											snprintf(buf, sizeof(buf), "%.1f", freq);
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_FREQUENCY, buf, -1);
											break;
										case ESCI_C1:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_C1, buf, -1);
											break;
										case ESCI_C2:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_C2, buf, -1);
											break;
										case ESCI_RXLEV:
											snprintf(buf, sizeof(buf), "%u", (p[i] / 2)
													+ 2);
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_RX_LEVEL, buf, -1);
											break;
										case ESCI_BSIC:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_STATION_ID, buf, -1);
											break;
										case ESCI_CELL_ID:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_CELL_ID, buf, -1);
											break;
										case ESCI_TXLEV:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_TX_LEVEL, buf, -1);
											break;
										case ESCI_TN:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_TIMESLOT_NUMBER, buf,
													-1);
											break;
										case ESCI_LAC:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_LAC, buf, -1);
											break;
										case ESCI_DSC:
										case ESCI_RLT:
										case ESCI_TAV:
										case ESCI_RXLEV_F:
										case ESCI_RXLEV_S:
										case ESCI_RXQUAL_F:
										case ESCI_RXQUAL_S:
										case ESCI_CBA:
										case ESCI_CBQ:
										case ESCI_CELL_TYPE_IND:
										case ESCI_VOCODER:
											/* FIXME implement */
											break;
									}
								}
								return 0;
							}
							static int _trigger_em2_do(Engineering * engineering, unsigned int * p,
									size_t cnt)
							{
								if(cnt != 11)
									return 1;
								/* FIXME implement */
								return 1;
							}
							static int _trigger_em3_do(Engineering * engineering, char const * buf,
									GtkTreeIter * iter)
							{
								unsigned int u;
								if(sscanf(buf, "%u", &u) != 1)
									return 1;
								switch(engineering->enci)
								{
									case ENCI_ARFCN:
										return _do_arfcn(engineering, u, iter);
									case ENCI_C1:
										return _do_c1(engineering, u, iter);
									case ENCI_C2:
										return _do_c2(engineering, u, iter);
									case ENCI_RXLEV:
										return _do_rxlev(engineering, u, iter);
									case ENCI_BSIC:
										return _do_bsic(engineering, u, iter);
									case ENCI_CELL_ID:
										return _do_cell_id(engineering, u, iter);
									case ENCI_LAC:
										return _do_lac(engineering, u, iter);
									case ENCI_FRAME_OFFSET:
										return _do_frame_offset(engineering, u, iter);
									case ENCI_TIME_ALIGNMENT:
										return _do_time_alignment(engineering, u);
									case ENCI_CBA:
										return _do_cba(engineering, u, iter);
									case ENCI_CBQ:
										return _do_cbq(engineering, u, iter);
									case ENCI_CELL_TYPE_IND:
										return _do_cell_type_ind(engineering, u, iter);
									case ENCI_RAC:
										return _do_rac(engineering, u, iter);
									case ENCI_CELL_RESEL_OFFSET:
										return _do_cell_resel_offset(engineering, u);
									case ENCI_TEMP_OFFSET:
										return _do_temp_offset(engineering, u);
									case ENCI_RXLEV_ACC_MIN:
										return _do_rxlev_acc_min(engineering, u);
								}
								return 1;
							}
							static int _trigger_em4_do(Engineering * engineering, unsigned int * p,
									size_t cnt)
							{
								GtkTreeIter iter;
								size_t i;
								EngineeringLocationPagingParameter elpp;
								char buf[32];
								if(cnt != 5)
									return 1;
								if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(engineering->sc_store),
											&iter) == FALSE)
									gtk_list_store_append(engineering->sc_store, &iter);
								for(i = 0; i < cnt; i++)
								{
									elpp = i;
									snprintf(buf, sizeof(buf), "%u", p[i]);
									switch(elpp)
									{
										case ELPP_BS_PA_MFRMS:
										case ELPP_T3212:
										case ELPP_MCC:
										case ELPP_MNC:
											/* FIXME implement */
											break;
										case ELPP_TMSI:
											gtk_list_store_set(engineering->sc_store, &iter,
													SC_COL_TMSI, buf, -1);
											break;
									}
								}
								return 0;
							}
							static int _do_arfcn(Engineering * engineering, unsigned int arfcn,
									GtkTreeIter * iter)
							{
								double freq;
								char buf[32];
								freq = _engineering_get_frequency(arfcn);
								snprintf(buf, sizeof(buf), "%.1f", freq);
								gtk_list_store_set(engineering->nc_store, iter, 0, buf, -1);
								return 0;
							}
							static int _do_c1(Engineering * engineering, int c1, GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%d", c1);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_C1, buf, -1);
								return 0;
							}
							static int _do_c2(Engineering * engineering, int c2, GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%d", c2);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_C2, buf, -1);
								return 0;
							}
							static int _do_rxlev(Engineering * engineering, unsigned int rxlev,
									GtkTreeIter * iter)
							{
								char buf[32];
								/* FIXME implement with a progress bar */
								snprintf(buf, sizeof(buf), "%u", (rxlev / 2) + 2);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_RXLEV, buf, -1);
								return 0;
							}
							static int _do_bsic(Engineering * engineering, unsigned int bsic,
									GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%u", bsic);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_BSIC, buf, -1);
								return 0;
							}
							static int _do_cell_id(Engineering * engineering, unsigned int cell_id,
									GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%u", cell_id);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_CELL_ID, buf,
										-1);
								return 0;
							}
							static int _do_lac(Engineering * engineering, unsigned int lac,
									GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%u", lac);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_LAC, buf, -1);
								return 0;
							}
							static int _do_frame_offset(Engineering * engineering,
									unsigned int frame_offset, GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%u", frame_offset);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_FRAME_OFFSET,
										buf, -1);
								return 0;
							}
							static int _do_time_alignment(Engineering * engineering,
									unsigned int time_alignment)
							{
								return 0;
							}
							static int _do_cba(Engineering * engineering, unsigned int cba,
									GtkTreeIter * iter)
							{
								char const * buf = cba ? "Yes" : "No";
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_CBA, buf, -1);
								return 0;
							}
							static int _do_cbq(Engineering * engineering, unsigned int cbq,
									GtkTreeIter * iter)
							{
								char const * buf = cbq ? "Yes" : "No";
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_CBQ, buf, -1);
								return 0;
							}
							static int _do_cell_type_ind(Engineering * engineering,
									unsigned int cell_type_ind, GtkTreeIter * iter)
							{
								char const * type = "NA";
								switch(cell_type_ind)
								{
									case 1:
										type = "GSM";
										break;
									case 2:
										type = "GPRS";
										break;
								}
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_CELL_TYPE_IND,
										type, -1);
								return 0;
							}
							static int _do_rac(Engineering * engineering, unsigned int rac,
									GtkTreeIter * iter)
							{
								char buf[32];
								snprintf(buf, sizeof(buf), "%u", rac);
								gtk_list_store_set(engineering->nc_store, iter, NC_COL_RAC, buf, -1);
								return 0;
							}
							static int _do_cell_resel_offset(Engineering * engineering,
									unsigned int cell_resel_offset)
							{
								return 0;
							}
							static int _do_temp_offset(Engineering * engineering, unsigned int temp_offset)
							{
								return 0;
							}
							static int _do_rxlev_acc_min(Engineering * engineering,
									unsigned int rxlev_acc_min)
							{
								return 0;
							}
							