ClickZen 可以把自动化方案中的变量通过 TCP 共享给其他程序:外部脚本可以读取变量、修改变量,并在变量变化时收到推送。典型用途是多台电脑/多个程序之间协作(例如一台机器统计结果,另一台根据结果决定下一步)。
服务在 变量 页的「同步服务」卡片中开启,可设置端口(默认 9527)、令牌和最大连接数。
- TCP,UTF-8 编码的 JSON,每条消息一行,以
\n结尾(\r\n也接受)。空行会被忽略。 - 一条消息是一个 JSON 对象,
type字段表示消息类型。 - 任何请求都可以带
id(字符串或数字),服务器在对应回复中原样带回,便于对应请求与回复。 - 单条消息最大 64 KiB,超过会收到
too_large错误并断开。
连接后的第一条消息必须是 hello,否则服务器回复错误并断开:
{"type":"hello","token":"<令牌>","client":"my-script","protocol":2}| 字段 | 说明 |
|---|---|
token |
在 ClickZen 中设置的令牌。服务器未设置令牌时可省略。 |
client |
客户端名称,显示在 ClickZen 的已连接客户端列表中(最长 64 字符)。 |
protocol |
协议版本,当前为 2。省略时视为 2;其他值会被拒绝。 |
成功:
{"type":"welcome","protocol":2,"server":"ClickZen 2.0.0"}失败(随后连接被关闭):
code |
原因 |
|---|---|
hello_required |
第一条消息不是 hello |
auth |
令牌错误 |
protocol |
协议版本不支持 |
bad_json |
不是合法 JSON 对象 |
too_many_clients |
已达到最大连接数(连接时即回复) |
timeout |
10 秒内没有发送 hello |
服务器在 30 秒内没有收到客户端的任何消息就会断开(回复 timeout 错误)。没有其它请求时,请定期发送 ping(建议每 10 秒):
{"type":"ping"}
→ {"type":"pong"}每个变量在方案中有「网络同步」方向,决定外部程序能做什么:
| 方向 | 读取 / 订阅 | 写入 |
|---|---|---|
| 不同步(None) | 否(如同不存在) | 否 |
| 发送(Send) | 是 | 否 |
| 接收(Receive) | 否 | 是 |
| 双向(Both) | 是 | 是 |
变量是强类型的(整数、小数、布尔、字符串)。写入的值会转换成变量声明的类型,例如向整数变量写入 "3" 会得到 3。
{"type":"get","name":"score","id":1}
→ {"type":"value","id":1,"name":"score","value":5}变量不存在或不可读时回复 {"type":"error","code":"not_found",...}。
{"type":"getAll"}
→ {"type":"values","values":{"score":5,"status":"idle"}}{"type":"set","name":"command","value":"go","id":2}
→ {"type":"ok","id":2}value 可以是数字、字符串或布尔值。变量不接受写入时回复 read_only 错误;缺少字段时回复 bad_request。
{"type":"subscribe","names":["score","status"]}
→ {"type":"ok"}
→ {"type":"changed","name":"score","value":5,"source":"reset"}
→ {"type":"changed","name":"status","value":"idle","source":"reset"}- 可以用
names(数组)或name(单个)。"*"表示订阅全部可读变量。 - 订阅后服务器立即为每个(非
*)变量推送一次当前值,保证客户端从一致的状态开始。 unsubscribe用法相同。
已订阅的变量发生变化时,服务器主动发送:
{"type":"changed","name":"score","value":6,"source":"engine"}source |
含义 |
|---|---|
engine |
自动化方案运行时修改 |
user |
用户在变量页手动修改 |
network |
另一个同步客户端写入 |
reset |
方案启动/停止时重置为初值,或订阅时的初始值 |
- 只有值真正改变时才推送;同一个值重复写入不会推送。
- 客户端自己写入的变化不会推送回该客户端。
- 推送与请求的回复共用同一条连接,请按
type区分。
{"type":"error","id":3,"code":"read_only","message":"Variable 'status' does not accept writes."}握手之后的错误(not_found、read_only、bad_request、bad_json、unknown_type)不会断开连接;timeout、too_large 会断开。
import json, socket, threading, time
HOST, PORT, TOKEN = "127.0.0.1", 9527, "your-token"
sock = socket.create_connection((HOST, PORT))
reader = sock.makefile("r", encoding="utf-8")
def send(msg):
sock.sendall((json.dumps(msg) + "\n").encode("utf-8"))
send({"type": "hello", "token": TOKEN, "client": "python-demo", "protocol": 2})
welcome = json.loads(reader.readline())
assert welcome["type"] == "welcome", welcome
def heartbeat():
while True:
time.sleep(10)
send({"type": "ping"})
threading.Thread(target=heartbeat, daemon=True).start()
send({"type": "subscribe", "names": ["score"]})
send({"type": "set", "name": "command", "value": "start"})
for line in reader:
msg = json.loads(line)
if msg["type"] == "changed":
print(f"{msg['name']} = {msg['value']} ({msg['source']})")
elif msg["type"] == "error":
print("error:", msg["code"], msg["message"])using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using var tcp = new TcpClient("127.0.0.1", 9527);
var stream = tcp.GetStream();
var reader = new StreamReader(stream, Encoding.UTF8);
var writeLock = new SemaphoreSlim(1, 1);
async Task SendAsync(object msg)
{
var bytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg) + "\n");
await writeLock.WaitAsync();
try { await stream.WriteAsync(bytes); } finally { writeLock.Release(); }
}
await SendAsync(new { type = "hello", token = "your-token", client = "csharp-demo", protocol = 2 });
var welcome = JsonDocument.Parse((await reader.ReadLineAsync())!).RootElement;
if (welcome.GetProperty("type").GetString() != "welcome")
throw new Exception(welcome.GetRawText());
_ = Task.Run(async () =>
{
while (true) { await Task.Delay(10_000); await SendAsync(new { type = "ping" }); }
});
await SendAsync(new { type = "subscribe", names = new[] { "score" } });
await SendAsync(new { type = "set", name = "command", value = "start" });
while (await reader.ReadLineAsync() is { } line)
{
var msg = JsonDocument.Parse(line).RootElement;
if (msg.GetProperty("type").GetString() == "changed")
Console.WriteLine($"{msg.GetProperty("name")} = {msg.GetProperty("value")}");
}- 旧版连接后发送
auth消息;新版第一条必须是hello,令牌错误会立即断开。 - 旧版按固定间隔轮询比较后推送;新版在变量变化时立即推送,且不会把客户端自己的写入回推。
- 同步方向真正生效:只读变量拒绝写入,只写变量不会被读取或推送。
- 旧版的
sync_variables、broadcast、delete_variable、clear_all不再提供。