XXT 局域网中控

XXT 局域网中控是 XXTouch 设备批量控制工具,所有的方法使用 XXTouch OpenAPI 来实现。
使用 VB.NET 制作,运行需要 Microsoft .NET Framework 4 基础运行库。



中控为良性软件,不会加入涉及设备数据或PC数据的操作

当设备挂有代理IP时中控可能部分功能无法正常使用

当系统防火墙拦截时中控可能部分功能无法正常使用回

中控API属于额外拓展与中控交互使用,且必须由中控启动脚本才可以生效

中控主要用于批量启动停止脚本,其它额外功能属于拓展功能不能保证在任何情况下都可以正常使用

名字 类型 用途
data 目录 存放与设备进行交互的文件
1ferver 目录 项目目录用于与设备端“1ferver”目录进行同步
CC.mdb 文件 数据库操作API进行访问对应数据库文件
config.ini 文件 缓存中控相关配置文件
CC.lua 提供的方法允许设备访问此目录以便于交互。

针对目录的同步进行适配的文件夹。

脚本选择位置针对 "根目录" 相对的 "\1ferver\lua\scripts\" 文件夹下的脚本文件进行遍历,结构以 "项目名\main.xxt" 或 "项目名\main.lua" , 方可在中控端地址栏中进行点击 "地址栏" 中小三角进行选择为 "项目:项目名" 的脚本文件,启动统一按照 "main.xxt" 或 "main.lua" 为准.

可使用 "发送脚本" 旁小三角内菜单内 "同步目录" 按钮进行触发单向同步目录操作.

当选择 "根目录" 相对的 "\1ferver\lua\scripts\" 文件夹下项目脚本时(以"项目:"为开头的选择脚本的结构), "发送脚本" 按钮即改为 "同步文件" 此时点击也可以触发单向同步目录操作.

当点击发送脚本旁小三角内的同步目录后,此目录会和设备端的 "/1ferver" 目录进行同步,同步以中控端的结构为准.

设备端文件 中控端文件 同步规则
存在 存在 检测 "文件修改时间" 决定是否同步
存在 不存在 不同步且不删除
不存在 存在 同步

数据库是Access 数据库

表结构需要以第一项"ID"为递增值,其他项为字符串。

CC.lua 提供的方法允许设备通讯至此数据库。

根据自己需要进行调整。

默认会隐藏数据表,调整请修改字段。

[Hide]
Db_Tab=true
;用于隐藏数据表
API_Tab=false
;用于隐藏API表
MadeUI_Tab=false
;用于隐藏UI制作
UI=false
;用于隐藏UI功能

储存以下内容为"CC.lua"以"UTF-8"格式至"/var/mobile/Media/1ferver/lua"目录下

删掉最后一句 "return CC" 可嵌入脚本头部

local CC = {}
do
	local _err_msg = {
		timeout = [[与服务端访问超时
1.请确认中控端正常运行
2.中控端防火墙请确保关闭
3.请确保设备与中控端始终处于同一个局域网]],
		server_bug = [[服务端出现错误,请尝试检查 VPN 或 HTTP 代理设置:]],
		submit_ip = [[提交的服务器IP不正确]],
		not_find = [[未找到中控设备
1.中控端防火墙请确保关闭
2.请由中控端或XXTStudio启动
3.请确保设备与中控端始终处于同一个局域网]],
		not_init = [[未初始化中控模块
请使用 'CC.connect()' 进行初始化操作]]
	}
	local _server = {ip = '', port = '', field = {}}
	local post = function(_mode, mode, data)
		if _server.ip == '' then error(_err_msg.not_init, 3) end
		while true do
			local code, header, body = http.post(
					string.format(
						'http://%s:%s/%s/%s',
						_server.ip, _server.port, _mode, mode
					), 30, {}, ((type(data) == 'table' and json.encode(data)) or data)
				)
			if code == 200 then
				return json.decode(body) or body
			elseif code ~= -1 then
				sys.toast(_err_msg.server_bug .. body)
			else
				sys.toast(_err_msg.timeout)
			end
		end
	end
	local encodeURI = function(s)
		return string.gsub(string.gsub(s, '([^%w%.%- ])', function(c) return string.format('%%%02X', string.byte(c)) end), ' ', '+')
	end
	local send_cc = function(db,m,t)
		local r = post('db', m, json.encode({db=db, data=t}))
		if r.state == 0 then return r.data;else sys.alert(r.message,5);return r.data;end
	end
	CC.connect = function(ip, port)
		if ip and port then
			return CC.set_server_ip(ip, port)
		else
			local args = proc_take('spawn_args')
			if args == '' then
				args = proc_take('CC_args')
			end
			proc_put('spawn_args', args)
			proc_put('CC_args', args)
			local _, args_json = pcall(json.decode, args)
			if _ and type(args_json) == 'table' then
				if type(args_json['server_ip']) == 'table' then
					return CC.set_server_ip(
						args_json['server_ip'],
						args_json['server_port']
					)
				else
					return CC.set_server_ip(
						json.decode(args_json['server_ip']),
						args_json['server_port']
					)
				end
			else
				return false
			end
		end
	end
	CC.set_server_ip = function(...)
		local ip_list = {}
		if type(select(1,...)) == 'table' then
			ip_list = select(1, ...)
		elseif type(select(1,...)) == 'string' then
			ip_list = {select(1, ...)}
		else
			error(_err_msg.submit_ip, 2)
		end
		_server.port = select(2, ...) or 27010
		local socket = require('socket')
		local s = socket.tcp()
		s:settimeout(1)
		for k,v in ipairs(ip_list) do
			local s = socket.tcp()
			s:settimeout(1)
			if s:connect(string.format('%s',v), _server.port) == 1 then
				_server.ip = v
				return true
			end
		end
		if _server.ip == '' then
			error(_err_msg.not_find, 2)
		end
	end
	CC.set_sever_ip = CC.set_server_ip
	CC.log = function(t)
		if _server.ip == '' then error(_err_msg.not_init, 2) end
		if type(t) == 'table' then
			for key, value in pairs(t) do
				_server.field[key] = value
			end
		else
			_server.field['日志'] = tostring(t)
		end
		http.post(('http://%s:%s/log'):format(_server.ip, _server.port), 5, {}, json.encode(_server.field))
	end
	CC.getui = function()
		if _server.ip == '' then error(_err_msg.not_init, 2) end
		while true do
			local r = {http.get(string.format('http://%s:%s/getui', _server.ip, _server.port))}
			if r[1] == 200 then return json.decode(r[3]) end
			sys.sleep(2)
			sys.toast(_err_msg.timeout)
		end
	end
	CC.db = {
		add = (function(db,t)
			return send_cc(db,'add',t)
		end),
		del = (function(db,t)
			return send_cc(db,'del',t)
		end),
		edit = (function(db,t)
			return send_cc(db,'edit',t)
		end),
		get = (function(db,t)
			return send_cc(db,'get',t)
		end),
		list = (function(db,t)
			return send_cc(db,'list',t or {})
		end)
	}
	CC.web_file = {
		take_line = (function(path)
			return post('file','take_line',{path=path}).data
		end),
		exists = function(path)
			local r = post('file','exists',{path=path})
			if r.info == json.null then
				return false
			else
				return r.info
			end
		end,
		list = (function(path)
			return post('file','list',{path=path}).list
		end),
		size = (function(path)
			return post('file','size',{path=path}).fsize
		end),
		delete = (function(path)
			return post('file','delete',{path=path}).success
		end),
		reads = (function(path)
			local r = post('file','reads',{path=path})
			if r.success then
				return r.data:base64_decode()
			else
				return nil
			end
		end),
		writes = (function(path,data)
			return post('file','writes',{path=path,data=data:base64_encode()}).success
		end),
		appends = (function(path,data)
			return post('file','appends',{path=path,data=data:base64_encode()}).success
		end),
		line_count = (function(path)
			return post('file','line_count',{path=path}).linecount
		end),
		get_line = (function(path,line_number)
			return post('file','get_line',{path=path,line_number=line_number}).line
		end),
		set_line = (function(path,line_number,data)
			return post('file','set_line',{path=path,line_number=line_number,data=data}).success
		end),
		insert_line = (function(path,line_number,data)
			return post('file','insert_line',{path=path,line_number=line_number,data=data}).success
		end),
		remove_line = (function(path,line_number)
			return post('file','remove_line',{path=path,line_number=line_number}).success
		end),
		get_lines = (function(path)
			return post('file','get_lines',{path=path}).lines
		end),
		insert_lines = (function(path,line_number,lines)
			return post('file','insert_lines',{path=path,line_number=line_number,lines=lines}).success
		end),
		update_file = (function(file,path)
			local f, err = io.open(path,'rb')
			if not f then error(err,2) end
			local s = f:read('*a')
			f:close()
			return post('file-byte','update?file=' .. encodeURI(file), s) == 'ok'
		end),
		down_file = (function(file,path)
			local code, header, body
			while true do
				code, header, body = http.get(('http://%s:%s/file-byte/down?file=%s'):format(_server.ip, _server.port, encodeURI(file)), 30)
				if code == 200 or code == 404 then break end
			end
			if code == 404 then
				error('文件不存在',2)
			else
				local f = io.open(path,'wb')
				f:write(body)
				f:close()
				return true
			end
		end),
	}
	CC.web_directory = {
		exists = function(path)
			local r = post('directory','exists',{path=path})
			if type(r.info) == 'userdata' then
				return false
			else
				return r.info
			end
		end,
		create = (function(path)
			return post('directory','create',{path=path}).success
		end),
		delete = (function(path,data)
			return post('directory','delete',{path=path}).success
		end),
	}
	CC.web_directory.exicts = CC.web_directory.exists
	CC.field = CC.log
end

return CC

储存以下内容为"CC.lua"以"UTF-8"格式至"/var/mobile/Media/1ferver/lua"目录下

删掉最后一句 "return CC" 可嵌入脚本头部

使用一下方法必须引用“CC.lua”

引用方法:


local CC = require('CC')
if not CC.connect() then
	error('连接失败')
end

--[[	固定IP启动方式
local CC = require('CC')
if not CC.connect('10.0.0.88', '27010') then
	error('连接失败')
end
--]]
命令 解释 返回 示例
CC.log(data) 中控日志
CC.log('日志列展示内容')
CC.log(
    {
        ['列标题'] = '对应内容'
    }
)
CC.web_file.update_file(CCfile, localfile) 上传文件
CC.web_file.update_file('timg.jpg','/User/Media/1ferver/res/timg.jpg')
CC.web_file.down_file(CCfile, localfile) 下载文件
CC.web_file.down_file('timg.jpg','/User/Media/1ferver/res/timg.jpg')
data = CC.web_file.take_line(CCfile) 获取中控端文件第一行数据并删除 获取的第一行数据
ret = CC.web_file.take_line('1.txt')
info = CC.web_file.exists(CCfile) 用于判断一个路径是文件还是目录还是不存在 "file" 或 "directory" 或 nil
info = CC.web_file.exists('1.txt')
success = CC.web_file.delete(CCfile) 删除中控端文件 true 或 false
success = CC.web_file.delete('1.txt')
list = CC.web_file.list(CCpath) 获取中控端目录所有文件名列表 顺序表型 或 nil
list  = CC.web_file.list('文件夹1'
fsize = CC.web_file.size(CCfile) 获得中控端一个文件的尺寸 整数型 或 nil
fsize = CC.web_file.size('1.txt')
data = CC.web_file.reads(CCfile) 获得中控端一个文件中所有数据 字符串型 | nil
data = CC.web_file.reads('1.txt')
success = CC.web_file.writes(CCfile, data) 将数据覆盖写入到中控端文件 true 或 false
success = CC.web_file.writes('1.txt', '测试写入')
success = CC.web_file.appends(CCfile, data) 将数据追加写入到中控端文件 true 或 false
success = CC.web_file.appends('1.txt', '测试写入')
linecount = CC.web_file.line_count(CCfile) 统计一个文本文件的总行数 整数型 | nil
linecount = CC.web_file.line_count('1.txt')
line = CC.web_file.get_line(CCfile, line_number) 获取一个文本文件指定行的数据 字符串型 | nil
line = CC.web_file.get_line('1.txt', 2)
success = CC.web_file.set_line(CCfile, line_number, data) 设置文本文件指定行的内容 true 或 false
success = CC.web_file.set_line('1.txt', 2, '123123')
success = CC.web_file.insert_line(CCfile, line_number, data) 在文本文件指定行前插入内容 true 或 false
success = CC.web_file.insert_line('1.txt', 2, '123123')
success = CC.web_file.remove_line(CCfile, line_number) 移除文件中指定行 true 或 false
success = CC.web_file.get_lines('1.txt', 2)
lines = CC.web_file.get_lines(CCfile) 获取一个文本文件的所有行 顺序表型 | nil
lines = CC.web_file.get_lines('1.txt')
success = CC.web_file.appends(CCfile, line_number, lines) 将一个顺序表转换逐行插入到文件指定行前 true 或 false
success = CC.web_file.appends('1.txt', 2, {'被插入的内容'})
info = CC.web_directory.exists(CCdirectory) 判断文件夹是否存在 true 或 false
info = CC.web_directory.exists("文件夹1")
success = CC.web_directory.create(CCdirectory) 在中控端创建一个文件夹 true 或 false
success = CC.web_directory.create("文件夹1")
success = CC.web_directory.delete(CCdirectory) 在中控端删除一个文件夹 true 或 false
success = CC.web_directory.delete("文件夹1")
info = CC.getui() 获取当前中控UI的配置信息 字符串
local data = CC.getui()
local config = json.decode(data)
CC.db.add(table_name, {table_data}) 添加数据库表内数据 table
v = CC.db.add(              
    '账号表',
    {
        {
            ['账号']='新添加项目aaaaa',
            ['密码']='test',
            ['区']='test233'
        }
    }
)
nLog(json.encode(v))
if v[1].state then
    --添加成功
end
CC.db.del(table_name, {table_data}) 删除数据库表内数据 table
v = CC.db.del(
    '账号表',
    {
        {
            ['id'] = 6      --删除id为6的项
        }
    }
)
nLog(json.encode(v))
if v[1].state then
    --删除成功
end
CC.db.edit(table_name, {table_data}) 修改数据库表内项 table
v = CC.db.edit(
    '账号表',
    {
        {                   --修改 id为8项的 密码 成 xxxxxxxxxx
            ['id'] = 8,
            ['密码'] = 'xxxxxxxxxx'
        }
    }
)
nLog(json.encode(v))
if v[1] then
    --账号内容 v[1]
end
CC.db.list(table_name, {select_table}) 获取数据库表内所有数据 table
v = CC.db.list(
    '账号表',
    {
        {
            ['账号'] = '新添加项目'
        }
    }
)
sys.alert(json.encode(v))
CC.db.get(table_name, {select_table}) 获取数据库表内数据(一项)并修改 table
v = CC.db.get(
    '账号表',
    {
        {                   --以此要求索引
            ['密码']='test'
        },
        {                   --索引完毕后及时改掉
            ['密码']='test1'
        }
    }
)
--获取 密码为 test 的一项 并 把密码 改为 test1
--CC.db.get(db,{{过滤要求},{获取后修改为}})
nLog(json.encode(v))
if v[1] then
    --账号内容 v[1]
end

UI 生成

此操作在PC端中控操作实现

生成UI是以 JSON 的 JArray 格式

在菜单中 中控功能 --> 中控UI制作

生成脚本选择脚本会储存在脚本同目录下 脚本名.json 文件

类型 简介 存在的值 注意
Label 内容框 caption 内容默认是居中
Edit 输入框 caption,text
ComboBox 下拉菜单 caption,item,select select属于integer类型
RadioGroup 单选框 caption,item,select select属于integer类型
CheckBoxGroup 多选框 caption,item,select select属于array类型
[
	{
		"type":"Label",
		"caption":"普通展示文字"
	},
	{
		"type":"Edit",
		"caption":"输入框",
		"text":"123"
	},
	{
		"type":"ComboBox",
		"caption":"下拉框",
		"item":["1","2","3","4","5","6","7"],
		"select":1
	},
	{
		"type":"RadioGroup",
		"caption":"单选框",
		"item":["111","222","333","444","555","666"],
		"select":1
	},
	{
		"caption":"多选框",
		"type":"CheckBoxGroup",
		"item":["aaa","bbb","ccc","ddd","eee","fff",],
		"select":[1]
	}
]

以上UI配置获取

local CC = require('CC')
if not CC.connect() then
	error('连接失败')
end

-- 必须由中控启动

args = proc_take('CC_args')
local args = json.decode(args)

local Edit = args["输入框"]

local ComboBox = args["下拉框"]
local ComboBox_t = {"1", "2", "3", "4", "5", "6", "7"}	-- 与中控端内容保持一致


local RadioGroup = args["单选框"]
local RadioGroup_t = {"111", "222", "333", "444", "555", "666"}	-- 与中控端内容保持一致


local CheckBoxGroup = args["多选框"]
local CheckBoxGroup_t = {"aaa", "bbb", "ccc", "ddd", "eee"}	-- 与中控端内容保持一致

local get_checkbox = function(_tab, _tab_val)
	local _ret = {}
	for _, item in ipairs(_tab) do
		table.insert(_ret, _tab_val[item])
	end
	return _ret
end

if Edit and ComboBox and RadioGroup and CheckBoxGroup then	-- 判断是否传入值
	sys.alert(
		"输入框:" .. Edit
	)
	sys.alert(
		"下拉框:" .. ComboBox_t[ComboBox]
	)
	sys.alert(
		"单选框:" .. RadioGroup_t[RadioGroup]
	)
	sys.alert(
		"多选框:" .. table.concat(
			get_checkbox(CheckBoxGroup, CheckBoxGroup_t),
			", "
		)
	)
else
	CC.log("未传入有效内容")
end

提示“无服务端访问超时”或“未找到中控设备”或“服务端出现错误,请尝试检查 VPN 或 HTTP 代理设置”

一般是由中控端电脑的防火墙拦截了数据

请关闭电脑端防火墙

HTTP 代理或 VPN 代理可能会影响正常通讯

设备与中控端设备确保在同一局域网,且路由内没有设置 VLAN(虚拟局域网)

扫描不到设备怎么办?

确保电脑端或路由器的防火墙规则没有拦截

设备与电脑在同一局域网

确保停用了设备上的网络代理及 VPN

确保设备是解锁屏幕状态

确保电脑能 ping 通设备

确保设备打开了远程开关

脚本启动后提示“中控访问超时”?

A:设备端无法连接中控对应的监听的端口引起问题。

R:确保使用中控启动且加入通讯API,且检查电脑端防火墙是否关闭。

中控出现错误提示“基础链接已关闭,无法连接到远程服务器”?

A:一些用户在安装一些软件或是系统做某些修改后,采集器就没无登录或是无法获取到网页。

R:在开始键入 cmd,右键单击“cmd.exe”,单击“以管理员身份运行”,然后按“继续”,在命令提示符处键入 netsh winsock reset,然后按 Enter。

为什么有些电脑无法启动中控?

A:因为中控的开发使用 .NET 基础库,所以需使用中控也要同样的运行库。

R:请下载 Microsoft .NET Framework 4 基础运行库。

点击“搜索设备”特别卡?

A:网络环境搭建有问题。

R:可以考虑点击“搜索设备”旁的小三角“网段扫描”,点选“IP段列表”内对应IP,之后点击“TCP扫描”等待设备刷新出来进行解决。

PS:避免每次启动中控都需要扫描,当扫描妥当后,可勾选所有设备,点击“中控功能”内“导出勾选IP地址”项,下次重启中控后使用虑点击“搜索设备”旁的小三角“IP地址扫描”针对性添加设备。

扫描设备扫描不全?

A:当设备没有点亮屏幕时,设备很有可能 WIFI 处于休眠状态。

R:请确保设备点亮屏幕。

PS:iOS 11 的设备可能还需要主动访问一次网络,例如用 Safari 打开一下百度主页

打开中控出现“配置系统未能初始化”错误提示框?

A:程序相对的配置文件“user.config”损坏引发的问题。

R:删除“C:\Users\[用户名]\AppData\Local\XXTouch团队”或“C:\Documents and Settings\[用户名]\Local Settings\Application Data\XXTouch团队”解决。