> ## Documentation Index
> Fetch the complete documentation index at: https://devzone.nayax.com/llms.txt
> Use this file to discover all available pages before exploring further.

# C SDK 始终空闲模式

SDK 还可以将外围设备配置为保持空闲状态,这意味着交易流程将从外围设备端开始,通过发送带有相关商品信息的“Vend Request”(出货请求)来启动(而不是由外围设备在商品选择后启用设备的读卡器,然后设备针对消费者出示的卡片响应“Begin Session”(开始会话),之后外围设备才发送“Vend Request”)。

本页将更详细地指导您在 C SDK 中实现始终空闲状态。为了更好地说明此流程,代码实现分为以下几个步骤:

1. 包含库文件
2. 设置宏和全局变量
3. 设置私有函数
4. 添加初始化函数
5. 实现主函数

有关每个步骤的更多详情，请参阅以下各部分。

## 步骤 1：包含库文件

首先包含 SDK 所需的库文件。请参见下方代码块：

```c C theme={null}
#include <string.h>

#include "types.h"
#include "macros.h"
#include "cli/cli.h"
#include "msg_queue/msg_queue.h"
#include "vsnprintf/printk.h"
#include "cpu_platform.h"
#include "board/board.h"
#include "menu.h"

#include "vpos/vmc_link.h"
#include "vpos/vmc_vend.h"

/* Drivers */
#include "drv_usart.h"
```

## 步骤 2：设置宏和全局变量

下方代码定义了用于日志记录/调试的模块名称宏，并设置了用于存储配置和会话相关数据的全局变量。

```c C theme={null}
#define __MODULE__ "marshall-c-sdk-demo"
  
static vmc_config_t   config;
static vend_item_t    session_product;
static vend_session_t session;
static menu_t 		  menu;
static int			  com_id = -1;

```

在哪里：

* `config`：存储自动售货机配置。
* `session_product`：表示单个售卖商品。
* `session`：存储会话详情。
* `menu`：存储菜单相关设置。
* `com_id`：表示通信 ID。

## 步骤 3:设置私有函数

实现用于调试、处理事件和处理交易的辅助函数。以下各节将提供更多详细信息。

### 调试输出函数

此函数确定调试信息的输出流,支持不同的平台:

* Linux/X8：使用 `printf()`。
* STM32 微控制器：使用 `drv_usart_tx_data()`。

请参阅以下代码。

```c C theme={null}
static void terminal_tx_str(char *str)
{
	#if defined(__PLATFORM_X86) || defined(__PLATFORM_LINUX)
		printf("%s", str);
	#elif defined(__PLATFORM_STM32)
		drv_usart_tx_data(&terminal_usart, (uint8_t*)str, strlen(str));
	#endif
}
```

在哪里：

* `str`：包含调试信息的 string。

### CPU 定时器节拍

此函数由平台定时器定期调用（每 5-10 毫秒一次）。

```c C theme={null}
static __bool cpu_timer_tick(void *p)
{
	vmc_link_background();
	return __false;
}
```

在哪里：

* `p`：指向可选参数的指针（此函数中未使用）。

### VMC 链路事件处理程序

此函数处理链路事件并记录相关信息。

```c C theme={null}
static __bool vmc_link_event_handler_cb(int event_id, void *data)
{
	__bool result = __false;

	__log("link event_id: %d", event_id);

	switch (event_id)
	{
		case vmc_link_event_on_ready:
			if (__true)
			{
				vpos_config_t *config = (vpos_config_t*)data;
				__log("link ok! decimal places: %d", config->decimal_place);
			}
			break;
		case vmc_link_event_on_comm_error:
			if (__true)
			{
				__log("lost comm");
			}
			break;
		default:
			break;
	}

	return result;
}
```

在哪里：

* `event_id`：链接事件的类型。
* `data`：指向可选参数的指针（根据事件类型而变化）。

该函数将始终返回布尔值 `__false`，因为未确定成功/失败。

### 出货请求处理程序

此函数用于处理会话及其包含的项目。请参阅下方代码块：

```c C theme={null}
static void triggered_vend_request(void)
{
	if (config.reader_always_on)
		current_session = 2;
	
	if (current_session != -1)
	{
		vend_session_t *session = &sessions[current_session];

		/* a card has been detected. now we can ask the vpos to take money */
		memset(session, 0, sizeof(vend_session_t));

		session_products[0].code = 1;
		session_products[0] .qty = 1;
		session_products[0].price = 10;
		session_products[0].unit = 1;

		session_products[1].code = 2;
		session_products[1].qty = 1;
		session_products[1].price = 20;
		session_products[1].unit = 1;

		if (config.multi_vend_support)
		{
			session->products = session_products;
			session->total_products = 2;
		}
		else
		{
			session->products = &session_products[current_session];
			session->total_products = 1;
		}

		vmc_vend_vend_request(session);

		if (config.mag_card_approved_by_vmc_support || config.mifare_approved_by_vmc_support || config.qr_approved_by_vmc_support)
		{
			vmc_vend_client_gateway_auth(__true);
		}
	}
}
```

### 出货事件处理程序

此函数用于处理出货事件并记录相关信息。

```c C theme={null}
static __bool vmc_vend_event_handler_cb(int event_id, void *data)
{
	__bool result = __false;
	static __bool vend_sent = __false;

	__log("vend event_id: %d", event_id);

	switch (event_id)
	{
		case vmc_vend_event_on_ready:
			break;
		case vmc_vend_event_on_vend_approved:
			/* the vpos approved the payment */
			result = __true;
			break;
		case vmc_vend_event_on_vend_denied:
			/* the vpos declined the payment */
			break;
		case vmc_vend_event_on_settlement:
			break;
		case vmc_vend_event_on_session_canceled:
			if (__true)
			{
				__log("received cancel session");
			}
			break;
		case vmc_vend_event_on_transaction_info:
			if (__true)
			{
				__log("received transaction info");

				vmc_vend_transfer_data_t *info = (vmc_vend_transfer_data_t*)data;

				if (info->encode_bitmap & transfer_data_encode_transaction_id)
					__log("transaction id: %lu", __ptr2uint64_t(info->transaction_id));

				if (info->encode_bitmap & transfer_data_encode_card_bin)
					__log("card bin: %.6s", info->card_bin);

				if (info->encode_bitmap & transfer_data_encode_prop_card_uid)
					__log("prop card uid: %lu", __ptr2uint64_t(info->prop_card_uid));

				if (info->encode_bitmap & transfer_data_encode_card_last_4_digits)
					__log("cc last 4 digits: %.4s", info->cc_last_4_digits);
			}
			break;
		case vmc_vend_event_on_status:
			if (__true)
			{
				uint32_t status = (uint32_t)data;
				__log("received status: %d", status);
			}
			break;
		case vmc_vend_event_on_sessions_rsp:
			if (__true)
			{
				mdb_msg_sessions_status_t *sessions = (mdb_msg_sessions_status_t*)data;
				__log("open sessions: %d\n", sessions->num_opened_sessions);
			}
			break;
		case vmc_vend_event_on_reader_state_rsp:
			if (__true)
			{
				mdb_msg_reader_status_t *state = (mdb_msg_reader_status_t*)data;
				__log("reader state: %d\n", state);
			}
			break;
		case vmc_vend_event_on_qr_code:
			if (__true)
			{
				ereceipt_t *receipt = (ereceipt_t*)data;
				__log("received ereceipt: %s\n", receipt->qr_data);
			}
			break;
		case vmc_vend_event_on_session_timeout:
			if (__true)
			{
				uint32_t *session_id = (uint32_t*)data;
				__log("session timeout: %d\n", session_id);
			}
			break;
		default:
			break;
	}

	return result;
}
```

在哪里：

* `event_id`：售卖事件的类型。
* `data`：指向可选参数的指针。根据事件类型而变化。

此函数将返回一个布尔值：

* 大多数情况下为 `__false`。
* `__true` 用于`vmc_vend_event_on_vend_approved`，表示出货批准成功。

## 步骤 4：设置初始化函数

添加辅助函数后，您需要实现用于初始化 VMC 配置和菜单处理程序的函数。请参见下方各部分了解更多详情。

### 初始化 VMC

此函数用于初始化 VMC 配置并注册事件处理程序。请参见下方代码：

```c C theme={null}
static void vmc_init(void)
{
	/* initialize sdk */
	vmc_link_init();

	/* configuring the sdk */
	memset(&config, 0, sizeof(config));

	__strcpy(config.model, "marshall-c-sdk-demo");
	__strcpy(config.serial, "01234567");
	__strcpy(config.sw_ver, "vmc app version");
	__strcpy(config.vmc_hw_ver, "01234567");
	__strcpy(config.vmc_manuf_code, "manuf");

	config.multi_vend_support = __false;
	config.multi_session_support = __false;
	config.price_not_final_support = __false;
	config.reader_always_on = __false;
	config.always_idle = __true;
	config.explicit_vend_success = __false;
	config.vend_denied_policy = vend_denied_policy_persist_e;

	config.mifare_approved_by_vmc_support = __false;
	config.mag_card_approved_by_vmc_support = __false;
	config.qr_approved_by_vmc_support  = __false;

	config.dump_packets_level = debug_level_dump_moderate;
	config.debug = __true;

	/* register to the link and vend events */
	vmc_link_register_callback((vmc_link_event_handler_cb_t)vmc_link_event_handler_cb);
	vmc_vend_register_callback((vmc_vend_event_handler_cb_t)vmc_vend_event_handler_cb);
}
```

由于您希望使 status 保持为 always idle，请将 `config.always_idle` 设置为 `__true`。

### 初始化菜单

在初始化菜单之前，请添加必要的处理程序函数。请参见下方代码：

```c C theme={null}
static void handler_start_session(void *aux_data)
{
	vmc_vend_session_start(vmc_vend_session_type_credit_e);
	
	triggered_vend_request();
}

static void handler_cancel_session(void *aux_data)
{
	vmc_vend_session_cancel();
}

static void handler_quit(void *aux_data)
{
	menu_stop(&menu);

	exit(0);
}

static void handler_link_stats(void *aux_data)
{
	vmc_stats_t* stats = vmc_link_get_link_stats();

	char buffer[50];
	sprintf(buffer, "\n ------ Stats ------ \n");
	menu_output(buffer);

	sprintf(buffer, "  crc error: %d\n", stats->crc_errors);
	menu_output(buffer);

	sprintf(buffer, "  re-trans: %d\n", stats->retrans);
	menu_output(buffer);

	sprintf(buffer, "  link loss: %d\n", stats->resets);
	menu_output(buffer);

	sprintf(buffer, " ------ Stats ------ \n");
	menu_output(buffer);
}

static void handler_info_session(void *aux_data)
{
	vmc_vend_session_start(vmc_vend_session_type_info_e);
}

static void handler_query_sessions(void *aux_data)
{
	vmc_vend_get_session_status();
}

static void handler_reader_state(void *aux_data)
{
	vmc_vend_get_reader_state();
}

```

现在，您可以通过调用之前创建的处理程序来初始化菜单。请参见以下代码。

```c C theme={null}
static void menu_init(void)
{
	menu.total_items = 0;

	menu_add_item(&menu, "start session 0", &handler_start_session, 0);
	menu_add_item(&menu, "close session 0", &handler_close_session, 0);
	if (config.multi_session_support == __true)
	{
		menu_add_item(&menu, "start session 1", &handler_start_session, 1);
		menu_add_item(&menu, "close session 1", &handler_close_session, 1);
	}
	menu_add_item(&menu, "info session", &handler_info_session, __null);
	menu_add_item(&menu, "cancel session", &handler_cancel_session, __null);
	menu_add_item(&menu, "query sessions", &handler_query_sessions, __null);
	menu_add_item(&menu, "reader state", &handler_reader_state, __null);
	menu_add_item(&menu, "link stats", &handler_link_stats, __null);
	menu_add_item(&menu, "display control", &handler_display_control, __null);
	menu_add_item(&menu, "quit", &handler_quit, __null);

	menu.menu_running = __true;
	menu_show(&menu);
}
```

## 步骤 5：实现主函数

在实现所有其他部分之后，您可以添加程序的主入口点，该入口点会初始化与外围设备的链路及菜单，然后启动一个运行菜单的无限循环。请参见下方代码：

```c C theme={null}
int mymain(int argc, char *argv[])
{
	printk_register_puts(terminal_tx_str);

	/* Modules initialization */
	vmc_init();

	set_command_line_arguments(argc, argv, &config, &com_id);
	
	/* Board initialization */
	board_init(com_id);

	/* Register the timer tick handler */
	cpu_platform_register_timer_tick_cb(2, (platform_timer_tick_cb_t)cpu_timer_tick);

	/* Modules initialization */
#if UTILITIES_CLI_MODE==1
	cli_init();

	cli_register_io(terminal_tx_str);

	printk("welcome to %s, version: %s, build: %s\n\r", __stringify(APP_NAME), __stringify(APP_VER), __stringify(APP_BUILD));
	printk("build data: "__DATE__ " "__TIME__"\n\r");

	/* Start modules and application */
	cli_start();
#endif

	vmc_link_start(&vmc_usart, &config);

	menu_init();

	while (1)
	{
#if UTILITIES_CLI_MODE==1
		if (drv_usart_is_rx_pending(&terminal_usart))
		{
			cli_process_char(drv_usart_rx_byte(&terminal_usart));
		}

#endif
		menu_run(&menu);
	}
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.