FindNode 案例
更多信息请参考:FindNode 用户指南。
查询示例 I
最基本的查询是 "{}" 或者在 TC 中:
var output = device.sendAai({})
默认的 "template" 是 "more"(除非通过 "setConfig" 修改),默认的动作是 "getIds",与上面相同:
var output = device.sendAai({query:"TP:more", action:"getIds"})
这将返回屏幕上大部分节点,且默认返回的是节点 ID 的列表(动作:"getIds"):
{count: 89, ids: ['80006cbe','7440','7bc2','7f83','8344','8705','8ac6','8e87','1f6e7',…]}
你可以这样做:
{query: "CC:!=0"} 或者 {query:"CC:>0"}:非零子节点数量。
{query:"IT:>10000"}:接受大于一万的自定义输入类型值。
{query:"CC:!=0&&IT:>10000"}:查找子节点数量非零且输入类型大于一万的节点。
{query:"TP:textInput&&IX:2", action:"setText(Hello)"}:查找第三个文本字段并在文本字段中输入 "Hello"。
{query:"T:Input text here", action:"setText(Hello)"}:查找具有初始提示文本的空文本字段,搜索提示并输入 "Hello"。
>> device.sendAai({query:"T:*REAL, \nSANTA CLARA*", action:"getText"})
{retval: '3367 EL CAMINO REAL,
SANTA CLARA, CA 95051'}
>> device.sendAai({actions:["scrollIntoView(T:ci:john)", "click"]})
查询示例 II
示例 1: 查找底部导航栏上的节点:
>> device.sendAai({query:"TP:line,bottom,-1", action:"getText"})
{retval: ['Chats','Calls','Contacts','Today']}
通过使用偏移量(OX/OY)、索引(IX)或诸如“T:”之类的查询,您几乎可以找到上述示例中的每个节点,下面是“联系人”顶部单击图标的 4 个示例:
>> device.sendAai({query:"TP:line,bottom,-1&&IX:2&&OY:-1", action:"click"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-2&&IX:2", action:"click"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-1&&T:Contacts&&OY:-1", action:"click"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-1", action:"click(T:Contacts&&OY:-1)"})
{retval: true}
示例 2: 假设您想从 Yahoo Finance 获取第一页股票代码:
>> device.sendAai({query:"R:.ticker&&T:/[A-Z]{1,4}/", action:"getText"}).retval.forEach(
(p) => {print( p + ":" +
device.sendAai({action:"getDescription(OX:1&&T:" + p + ")"}).retval
)
}
)
TGT:164.76
T:16.76
AAPL:154.48
SBUX:92.14
TSLA:309.07
CSCO:43.30
示例 3: 在 Skype 文本框中输入文本:
输入文字前:
输入文字后,需要点击发送按钮:
这可以通过在一个查询命令中使用来完成:
>> var input = "Hello";
// Use backtick
>> device.sendAai({query:"TP:textInput", actions:[`setText('${input}')`, 'click(OX:2)']});
{count: 2, list: [{retval: true},{retval: true}]}
Another way is using variable substitution along with ";" as separator for multiple actions:
>> device.sendAai({query:"TP:textInput", actions:`setText('${input}');click(OX:2)`})
{count: 2, list: [{retval: true},{retval: true}]}
下面的功能是找人,点击,发送消息,返回主页面。 在这种情况下,需要“waitQuery”以确保在单击返回键之前发送文本。
>> function sendSkype(device, name, text) {
var retval = device.sendAai({actions:[
`scrollIntoView(T:${name})`,
"click",
"newQuery(TP:textInput)",
`setText('${text}')`,
"click(OX:2)",
"waitQuery(T:Type a message)",
"sendKey(Back)"
]});
if (retval == null) {
print("Error: " + lastError());
return false;
}
print(retval);
return true;
}
>> sendSkype(device, "John", "Hello, how are you?")
{count: 7, list: [{retval: true},{retval: true},{count: 1},{retval: true},{retval: true},{retval: true},{retval: true}]}
也可以使用“function”中描述的示例来完成上述操作:
>> device.sendAai({action:"function(sendText(Hello))", sendText:[
"newQuery(TP:textInput)",
"setText(%1)",
"click(OX:2)",
"waitQuery(T:Type a message)"
]})
{retval: true}
>> var input = "Good morning"
>> device.sendAai({action:`sendText(${input})`})
{sendText: [{count: 1},{retval: true},{retval: true},{retval: true}]}
下面的函数会找到一个名字,点击聊天界面,使用刚刚创建的“sendText”函数发送文本,点击返回键返回主界面(如果你使用的是非 Sigma 键盘,使用两个 sendKey(Back)) .
>> device.sendAai({action:"function(findAndSend)", findAndSend:"scrollIntoView(T:%1);click;sendText(%2);sendKey(Back)"})
{retval: true}
function sendSkype(device, name, text) {
var retval = device.sendAai({action:`findAndSend(${name}, '${text}')`});
if (retval == null) {
print("Error: " + lastError());
return false;
}
print(retval);
return true;
}
>> sendSkype(device,"John", "Hello, how are you?");
{findAndSend: [{retval: true},{retval: true},{sendText: [{count: 1},{retval: true},{retval: true},{retval: true}]},{retval: true}]}
将其放入脚本:restartApp(skype) 或 openApp(Skype) 并调用 sendSkype() 发送消息。
示例 4: 这将打开 Samsung 计算器,获取计算器中按钮的节点 ID,从公式创建元素数组并使用“forEach”单击元素数组以获得结果。
var idList = {};
var device;
function sendAai(obj) {
var retval = getDevice().sendAai(obj);
if (retval == null) {
throw "Error on: " + JSON.stringify(obj) + ":" + lastError();
}
print("$ " + JSON.stringify(obj) + "\n" + JSON.stringify(retval));
return retval;
}
function init(device) {
getDevice = () => device;
sendAai({action:"openApp(com.sec.android.app.popupcalculator)"});
var list = sendAai({query:"T:/^[0-9+=-]$/", action:"getNodes(T)"}).list;
list.forEach((p) => idList[p.text+""] = p.id);
}
function calc(formula) {
var fList = formula.split("");
var elemList = [];
fList.forEach((p) => elemList.push(idList[p]));
print(elemList);
elemList.push(idList['=']);
sendAai({elements:elemList, action:"forEach(nsClick)"});
return sendAai({actions:"refresh(*R:.calc_edt_formula);getText"}).list[1].retval;
}
var getDevice = () => device;
init(device);
print(calc("123+456"));
$ {"action":"openApp(com.sec.android.app.popupcalculator)"}
{"retval":true}
$ {"query":"T:/^[0-9+=-]$/","action":"getNodes(T)"}
{"count":12,"list":[{"id":"c734","text":"7"},{"id":"caf5","text":"8"},{"id":"ceb6","text":"9"},{"id":"d638","text":"4"},{"id":"d9f9","text":"5"},{"id":"ddba","text":"6"},{"id":"e53c","text":"1"},{"id":"e8fd","text":"2"},{"id":"ecbe","text":"3"},{"id":"f07f","text":"+"},{"id":"f801","text":"0"},{"id":"ff83","text":"="}]}
$ {"elements":["e53c","e8fd","ecbe","f07f","d638","d9f9","ddba","ff83"],"action":"forEach(nsClick)"}
{"retval":[{"retval":true},{"retval":true},{"retval":true},{"retval":true},{"retval":true},{"retval":true},{"retval":true},{"retval":true}]}
$ {"actions":"refresh(*R:.calc_edt_formula);getText"}
{"count":2,"list":[{"retval":true},{"retval":"579 Calculation result"}]}
579 Calculation result
示例 5: 在第一个示例中,您可以使用"T:<text>"和偏移量来点击图标,某些应用程序没有为底部导航栏提供标签:
由于这显示在最后一行,我们可以使用“TP:line,bottom,-1”返回节点,使用“IX”从节点列表中定位单个节点。例如:点击搜索图标:
device.sendAai({query:"TP:line,bottom,-1&&IX:2", action:"click"})
示例 6: 在某些聊天窗口中,会显示名称(或电话号码),要获取它,使用以下查询:
>> device.sendAai({query:"TP:line,top,1&&IX:1", action:"getText"})
{retval: (999)999-9999}
要返回,点击左箭头:
>> device.sendAai({query:"TP:line,top,1&&IX:0", action:"click"})
{retval: true}
注意:新版本的“Messages”不再适用于“TP:line”,您仍然可以检索第一行或最后一行的信息,使用"VG"获取组信息和"RN"缩小到感兴趣的节点。
第一行:
>> device.sendAai({query:"TP:reduced&&IX:0&&VG&&RN&&ST:x"})
{count: 6, ids: ['1669cf','168f59','16931a','1696db','169a9c','167151']}
要获取名称:
>> device.sendAai({query:"TP:reduced&&VG&&RN&&ST:x&&IX:1", action:"getText"})
{retval: 'Daniel'}
>> device.sendAai({query:"TP:reduced&&VG&&RN&&ST:x", action:"getText(IX:1)"})
{retval: 'Daniel'}
>> device.sendAai({query:"TP:reduced&&VG&&RN&&ST:x", action:["addQuery(IX:1)", "getText"]})
{count: 2, list: [{count: 1},{retval: 'Daniel'}]}
最后一行:
>> device.sendAai({query:"TP:reduced&&IX:-1&&VG&&RN&&ST:x"})
{count: 5, ids: ['16227c','162dbf','15db29','15deea','16570a']}
想要输入文本并点击发送按钮,请使用偏移量2来定位发送按钮:
>> device.sendAai({query:"TP:textInput", action:["setText(Hello)", "click(OX:2)"]})
{count: 2, list: [{retval: true},{retval: true}]}
示例 7: 轻松定位并点击。
要点击箭头,请使用:
>> device.sendAai({query:"T:Channels&&OX:1", action:"click"})
{retval: true}
查询示例 III
示例 1: 考虑以下杂货应用:
"scrollIntoView"将滚动直到找到"*Orange*",并将"Valencia Oranges …"节点存储在ML中,VG将获取ViewGroup。使用UI Explorer查找价格和"+"按钮的资源ID以添加到购物车中。"scrollIntoView"将使找到的节点完全可见,因为节点是水果的名称,因此完全可见,但图像被遮挡。添加一个"Viewgroup"将使其正常工作。
>> device.sendAai({actions:[
"scrollIntoView(T:*Orange*)",
"addQuery(VG)",
"showOnScreen",
"getText(R:.tv_price)",
"click(R:/.iv_edit_right|.tv_num/)"
]})
{count: 5, list: [{retval: true},{count: 13},{retval: true},{retval: '$6.99'},{retval: true}]}
Version 14将允许"scrollIntoView"返回多个节点,更简单:
>> device.sendAai({actions:[
"scrollIntoView(T:*Oranges*&&VG)",
"getText(R:.tv_price)",
"click(R:.iv_edit_right)"
]})
{count: 5, list: [{retval: true},{count: 8},{retval: true},{retval: '$6.99'},{retval: true}]}
示例 2: 使用"checked"来操作复选框。
要展开详细的闹钟信息,使用"\s"来匹配不同类型的空白字符:
>> device.sendAai({query:"T:/3:00\\sAM/&&OX:1", action:"click"})
{retval: true}
要将今天旁边的复选框从true更改为false,使用"checked",仍然需要使用"3:00 AM"作为锚点,因为屏幕上可能还有其他带有"Today"的闹钟。
>> device.sendAai({query:"T:/3:00\\sAM/&&OY:1&&OX:1", actions:["getChecked", "setChecked(false)", "getChecked"]})
{count: 3, list: [{retval: true},{changedCount: 1},{retval: false}]}
接下来:
这是从星期日到星期六的循环闹钟,例如,使用"D:Sunday"和"T:S"来表示第一个复选框。使用intersectX (TX)来获取所有被选中的复选框。在这个示例中,"C:.CheckBox"是不必要的,但作为示例很好:
>> device.sendAai({action:"getDescription(D:Sunday&&C:.CheckBox&&TX)"})
{retval: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']}
要选中所有复选框,setChecked支持查询,它返回已更改的数量。
>> device.sendAai({query:"D:Sunday&&C:.CheckBox&&TX", action:"setChecked(true)"})
{changedCount: 7}
有几种方法可以关闭星期日和星期六:
>> device.sendAai({query:"D:/Sunday|Saturday/", action:"setChecked(false)"})
{changedCount: 2}
>> device.sendAai({query:"T:S", action:"setChecked(false)"})
{changedCount: 2}
要获取这些复选框的值:
>> device.sendAai({query:"D:Sunday&&C:.CheckBox&&TX", action:"getChecked"})
{retval: [false,true,true,true,true,true,false]}
示例 3: OY和OX的顺序很重要,下面是一个开发者选项之一:
"Disable adb authorization timeout"右侧没有节点,因此在X上应用偏移将返回null:
>> device.sendAai({query:"T:Disable adb authorization*&&OX:1&&OY:1"})
null
>> lastError()
Offset out of range
如果将"OX"和"OY"的顺序颠倒,它将起作用,在X之前应用Y的偏移,将得到正确的节点:
>> device.sendAai({query:"T:Disable adb authorization*&&OY:1&&OX:1"})
{count: 1, ids: ['25c8d4']}
>> device.sendAai({query:"T:Disable adb authorization*&&OY:1&&OX:1", action:"getChecked"})
{retval: false}
>> device.sendAai({query:"T:Disable adb authorization*&&OY:1&&OX:1", actions:["setChecked(true)", "getChecked"]})
{count: 2, list: [{changedCount: 1},{retval: true}]}
将所有内容放在一起,使用"openAndroidSetting"来打开开发者选项:
>> device.sendAai({actions:[
"openAndroidSetting(APPLICATION_DEVELOPMENT_SETTINGS)",
"scrollIntoView(T:Disable adb authorization*)",
"addQuery(OY:1&&OX:1)",
"setChecked(true)",
"sendKey(Back)"
]})
{count: 5, list: [{retval: true},{retval: true},{count: 1},{changedCount: 1},{retval: true}]}
示例 4: 将开发者选项 "Window animation scale" 更改为 "2x",需要使用"+"来匹配"T:*2x",因为它是一个新窗口,需要新的查询。
>> device.sendAai({actions:[
"openAndroidSetting(APPLICATION_DEVELOPMENT_SETTINGS)",
"scrollIntoView(T:Window animation scale)",
"click",
"click(+T:*2x)",
"sendKey(back)"
]})
{count: 5, list: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]}
示例 5:获取不可滚动应用程序底部导航栏中的元素。
对于不可滚动的应用程序,"TP:line,bottom" 将返回空值,但底部导航栏的节点并不难获取:
请注意视频被图像边界包围。
您可以使用其中一个标签作为锚点(使用 "ON:last" 或 "IX:-1" 确保查询适用于预期节点(最后一个文本),以防屏幕中其他位置具有相同的文本:
>> device.sendAai({query:"T:Speed&&IX:-1&&TX", action:"getText"})
{retval: ['Speed',{},'Video','Status','Map','VPN']}
请注意,由于 "TX" 将返回没有文本/描述的图像节点,所以存在一个空节点。通过添加后置基本查询 "XT" 来匹配任意字符以将其删除:
>> device.sendAai({query:"T:Speed&&IX:-1&&TX&&XT:/./", action:"getText"})
{retval: ['Speed','Video','Status','Map','VPN']}
如果您不知道底部导航栏中的单词会怎样:
>> device.sendAai({query:"TP:anyText&&IX:-1&&TX&&XT:/./", action:"getText"})
{retval: ['Speed','Video','Status','Map','VPN']}
要点击第二个元素("Video"):
>> device.sendAai({query:"TP:anyText&&IX:-1&&TX&&XT:/./", action:"click(IX:1)"})
{retval: true}
>> device.sendAai({query:"TP:anyText&&IX:-1&&TX&&XT:/./&&IX:-1", action:"click"})
{retval: true}
获取图标很容易,只需在 "TX" 前面添加 "OY:-1":
>> device.sendAai({query:"TP:anyText&&IX:-1&&OY:-1&&TX", action:"getNodes('C,R')"})
{count: 5, list: [{class: '.ImageView', id: '21637d', resourceId: '.navigation_bar_item_icon_view'},{class: '.ImageView', id: '217dc4', resourceId: '.navigation_bar_item_icon_view'},{class: '.ImageView', id: '21980b', resourceId: '.navigation_bar_item_icon_view'},{class: '.ImageView', id: '21b252', resourceId: '.navigation_bar_item_icon_view'},{class: '.ImageView', id: '21cc99', resourceId: '.navigation_bar_item_icon_view'}]}
如果只想点击 VPN 顶部的图标,请使用 OQ:
>> device.sendAai({action:"click(T:VPN&&IX:-1&&OY:-1)"})
{retval: true}
示例 6:编写函数返回 SolarEdge 产品:
要获取今天的产量:
>> device.sendAai({query:"T:Production Today&&OY:1", actions:["getText", "getText(OX:1)"]})
{count: 2, list: [{retval: '38.3'},{retval: 'kWh'}]}
要获取太阳能发电量:
>> device.sendAai({query:"T:*Solar Power Now", action:"getText"})
{retval: '1.17 kW
Solar Power Now'}
>> var production = device.sendAai({query:"T:*Solar Power Now", action:"getText"}).retval.split("\n",1)
1.17 kW
JavaScript 代码获取 SolarEdge 的太阳能产量:
function sendAai(device, obj) {
var retval = device.sendAai(obj);
if (retval == null) {
throw "Error on: " + JSON.stringify(obj) + ":" + lastError();
}
print(">>" + JSON.stringify(obj) + "\n" + JSON.stringify(retval));
return retval;
}
function getSolarProduction(device) {
var retval = sendAai(device, {actions:[
"openApp(mySolarEdge)",
"waitQuery(T:Lifetime,20000)",
"newQuery(R:.pv_power_now)",
"getText",
"sendKey(Back)",
"sendKey(Back)"]});
var prodStr = retval.list[3].retval;
var groups = prodStr.match(/(\d+(\.\d+)?) (W|kW)/);
if (groups == null) {
throw "Cannot find pattern in mySolarEdge";
}
var unit = groups.splice(-1);
var number = groups[1];
if (unit == "kW") {
number = number * 1000;
} else if (unit != "W") {
throw "Unknown unit: " + unit;
}
return number;
}
>> getSolarProduction(device)
>> {"actions":["openApp(mySolarEdge)","waitQuery(T:Lifetime,20000)","newQuery(R:.pv_power_now)","getText","sendKey(Back)","sendKey(Back)"]}
{"count":6,"list":[{"retval":true},{"retval":true},{"count":1},{"retval":"1.17 kW\nSolar Power Now"},{"retval":false},{"retval":true}]}
1170
从 FindNode 用户指南中剪切粘贴:
从 mySolarEdge 应用程序获取 SolarEdge 产品:
>> device.sendAai({actions:"function(getProduction)", getProduction:[
"openApp(mySolarEdge)",
"waitQuery(T:Lifetime,20000)",
"newQuery(R:.pv_power_now)",
"getText",
"sendKey(Back)",
"sendKey(Back)"]});
{retval: true}
>> var info = device.sendAai({action:"getProduction"})
{getProduction: [{retval: true},{retval: true},{count: 1},{retval: '5.89 kW
Solar Power Now'},{retval: false},{retval: true}]}
示例 7:此示例从 SolarEdge 获取太阳能发电量并更改 Tesla 中的充电电流。 目标是通过太阳能发电而不是电网为特斯拉充电。 它将每隔 10 分钟检查一次。 运行它:
/*
这是一个示例,它会检查SolarEdge应用程序中的太阳能产量并调整特斯拉的充电电流,
当产量(减去保留量)低于"minAmp"或达到非零的retriesBeforeStop时,它将停止充电。
其思想是使用太阳能充电特斯拉,而不是使用电网电能。
版本:0.1
*/
var controls = {};
var settings = {
// 每安培的电压
voltagePerAmp: 245,
// 低于此电流时,如果达到retriesBeforeStop则退出
minAmp: 6,
// 特斯拉可接受的最大电流
maxAmp: 32,
// 用于其他用途的保留瓦数
reserve: 500,
// 每隔x分钟检查一次
waitInterval: 10,
// 停止之前的失败重试次数,零表示不停止
retriesBeforeStop: 0,
// 在退出前的零数(例如日落)
retriesOnZero: 3,
// 错误重启次数
retriesRestart: 3,
// 显示交互
display: true
}
function padZero(str, length) {
length = length || 2;
return str.toString().padStart(length, '0');
}
function getLocalDateTimeWithAmPm() {
const now = new Date();
const hours = now.getHours();
const amPm = hours >= 12 ? 'PM' : 'AM';
const hours12 = hours % 12 || 12;
return now.getFullYear() + '-' + padZero(now.getMonth() + 1) + '-' + padZero(now.getDate()) + ' ' +
padZero(hours12) + ':' + padZero(now.getMinutes()) + ":" +
padZero(now.getSeconds()) + ' ' + amPm;
}
function xprint(msg) {
print(getLocalDateTimeWithAmPm(new Date()) + " : " + msg);
}
const fileExists = require('fs').exists;
const removeFile = require('fs').remove;
function xsleep(sleepTime) {
const sleepInterval = 1000;
var curTime = 0;
while (curTime < sleepTime) {
sleep(sleepInterval);
curTime += sleepInterval;
if (fileExists('/tmp/StopNow')) {
removeFile('/tmp/StopNow')
exitNow(0,"用户停止");
}
if (fileExists('/tmp/CheckNow')) {
removeFile('/tmp/CheckNow');
return;
}
}
}
function sendAai(obj, ignoreError) {
ignoreError = ignoreError || false;
var retval = getDevice().sendAai(obj);
if (retval == null) {
errorStr = '错误发生在: ' + JSON.stringify(obj) + ':' + lastError();
if (ignoreError) {
xprint('>> ' + errorStr + '. 已忽略');
return null;
}
throw new Error(errorStr);
}
if (settings.display) {
xprint(">>" + JSON.stringify(obj) + "\n" + JSON.stringify(retval));
}
return retval;
}
function setCharging(newState) {
openTesla();
const pat = "(Start|Stop) Charging";
const retval = sendAai({action:`newQuery('T:/${pat}/', 2000);getIds;getText`}, true);
if (retval == null) {
throw new Error("无法获取充电状态模式");
}
const id = retval.list[1].ids[0];
const chargingString = retval.list[2].retval;
const regExp = new RegExp(pat);
const groups = chargingString.match(regExp);
if (groups == null) {
throw new Error("无效的充电字符串");
}
// 行动是相反的,"Start Charging" 意味着当前未充电。
const curState = groups[1] == "Start" ? false : true;
if (curState == newState) {
return;
}
sendAai({elements:[id], action:"click"});
const text = (newState ? "Start" : "Stop") + " Charging";
return sendAai({action:`waitQuery(T:${text}, 3000)`}, true) != null;
}
var lastErrorMsg = "";
function setError(msg) {
lastErrorMsg = msg;
}
function getError() {
return lastErrorMsg;
}
function getSolarProduction() {
const retval = sendAai({actions:[
"openApp(mySolarEdge)",
"waitQuery(T:Lifetime,25000)",
"newQuery(R:.pv_power_now)",
"getText",
"sendKey(Back)",
"sendKey(Back)"]});
// getText
const prodStr = retval.list[3].retval;
const groups = prodStr.match(/^(\d+(\.?\d+)?) (W|kW)/);
if (groups == null) {
setError("在 mySolarEdge 中找不到匹配模式");
return -1;
}
const unit = groups.splice(-1);
var number = groups[1];
if (unit == "kW") {
number = number * 1000;
} else if (unit != "W") {
setError("未知单位: " + unit);
return -1;
}
return Math.round(number);
}
function startCharging() {
return setCharging(true);
}
// 有时停止充电会在第二次或第三次点击时生效
function stopCharging() {
return setCharging(false);
}
function adjAmp(num) {
openTesla();
var ctrl;
var count;
// 有时 “<” 或 “>” 无法完全识别,运行多次以确保数字匹配
for (var i = 0; i < 5; ++i) {
var amp = sendAai({elements:[controls.amp], action:"getText"}).retval.split(" ")[0];
if (amp == num) {
return true;
} else if (amp > num) {
ctrl = controls.less;
count = amp - num;
} else {
ctrl = controls.more;
num = Math.min(settings.maxAmp, num);
count = num - amp;
}
sendAai({elements:[ctrl], action:`repeat(${count},clickWait)`});
}
}
function init() {
// 可能需要 20-30 秒来显示充电信息
var success = sendAai({action:"restartApp(Tesla, 'T:/(Start|Stop) Charging/', 30000)"}, true);
if (success == null) {
exitNow(2, "找不到充电信息,请确保应用处于充电模式");
}
var retval = sendAai({query:"T:/[0-9]+ A/&&TX"});
if (retval.count != 3) {
exitNow(3, "找不到控件");
}
controls.less = retval.ids[0];
controls.more = retval.ids[2];
controls.amp = retval.ids[1];
var solarProd = getSolarProduction();
if (solarProd == -1) {
exitNow(4, "无法获取太阳能发电量: " + getError());
}
// 创建一个函数“clickWait”
sendAai({action:"function(clickWait)", clickWait:["sleep(200)", "nsClick"]});
return true;
}
function start() {
var retriesCounter = 0;
var retriesOnZero = 0;
while (true) {
var solarProd = getSolarProduction();
if (solarProd == -1) {
throw new Error("无法获取太阳能发电量");
}
if (solarProd == 0 && settings.retriesOnZero != 0) {
retriesOnZero++;
if (retriesOnZero >= settings.retriesOnZero) {
exitNow(1, "零发电量");
}
}
// .7 或以上将会进位到下一个整数
var amp = Math.floor((solarProd - settings.reserve)/settings.voltagePerAmp + 0.3);
amp = Math.min(amp, settings.maxAmp);
amp = amp < 0 ? 0 : amp;
xprint('收到太阳能发电量:' + solarProd + ' 瓦特,调整为 ' + amp + ' 安培');
if (amp < settings.minAmp) {
retriesCounter++;
if (retriesCounter < settings.retriesBeforeStop || settings.retriesBeforeStop == 0) {
stopCharging();
xprint('电流太低,' + amp + ' A。重试下一个间隔。等待中...');
}
} else {
startCharging();
retriesCounter = 0;
adjAmp(amp);
}
xsleep(settings.waitInterval*60*1000);
}
}
function exitNow(code, msg) {
xprint(msg);
xprint("退出代码: " + code);
// 最后尝试停止充电
try {stopCharging()} catch (e) {};
exit(code);
}
if (device == null) {
throw new Error("未连接任何设备");
}
const getDevice = () => device;
const openTesla = () => sendAai({action:"openApp(Tesla);sleep(300)"});
var retriesRestart = 0;
while(true) {
try {
init();
start();
} catch (e) {
bypassExit(e);
retriesRestart++;
if (retriesRestart >= settings.retriesRestart) break;
xprint("发生错误: " + e);
xprint("重新启动脚本");
}
}