int Lua_AddVirtualChannel(lua_State *L)
{
    size_t args = lua_gettop(L);
    if (args < 2) return 0;

    ChannelConfig cc;
    strlcpy(cc.label, lua_tostring(L, 1), DEFAULT_LABEL_LENGTH);
    cc.sampleRate = encodeSampleRate((unsigned short) lua_tointeger(L, 2));
    cc.precision = args >= 3 ? lua_tointeger(L, 3) :
                   DEFAULT_CHANNEL_LOGGING_PRECISION;
    cc.min = args >= 4 ? lua_tointeger(L, 4) : DEFAULT_CHANNEL_MIN;
    cc.max = args >= 5 ? lua_tointeger(L, 5) : DEFAULT_CHANNEL_MAX;
    strlcpy(cc.units, args >=6 ? lua_tostring(L, 6) : DEFAULT_CHANNEL_UNITS,
            DEFAULT_UNITS_LENGTH);
    lua_pushinteger(L, create_virtual_channel(cc));

    return 1;
}
static const jsmntok_t * setChannelConfig(Serial *serial, const jsmntok_t *cfg, ChannelConfig *channelCfg, setExtField_func setExtField, void *extCfg){
	if (cfg->type == JSMN_OBJECT && cfg->size % 2 == 0){
		int size = cfg->size;
		cfg++;
		for (int i = 0; i < size; i += 2 ){
			const jsmntok_t *nameTok = cfg;
			jsmn_trimData(nameTok);
			cfg++;
			const jsmntok_t *valueTok = cfg;
			cfg++;
			if (valueTok->type == JSMN_PRIMITIVE || valueTok->type == JSMN_STRING) jsmn_trimData(valueTok);

			char *name = nameTok->data;
			char *value = valueTok->data;
			unescapeTextField(value);

			if (NAME_EQU("nm",name)) setLabelGeneric(channelCfg->label, value);
			else if (NAME_EQU("ut", name)) setLabelGeneric(channelCfg->units, value);
			else if (NAME_EQU("sr", name)) channelCfg->sampleRate = encodeSampleRate(modp_atoi(value));
			else if (setExtField != NULL) cfg = setExtField(valueTok, name, value, extCfg);
		}
	}
	return cfg;
}