资讯详情

Serial Studio 数据集值变换(Dataset Transforms)完全指南:Lua / JavaScript / Expression 三语言标定、滤波与闭环控制

📅 2026/9/18 22:09:38 | 华诺云谱 👁 阅读
Serial Studio 数据集值变换(Dataset Transforms)完全指南:Lua / JavaScript / Expression 三语言标定、滤波与闭环控制
Serial Studio 数据集值变换Dataset Transforms完全指南Lua / JavaScript / Expression 三语言标定、滤波与闭环控制【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本文是 Serial Studio 开源遥测仪表板中数据集值变换Dataset Value Transforms功能的完整技术指南。该功能允许为每个数据集定义可选的transform(value)函数在原始解析值进入仪表板之前完成标定、单位换算、滤波与信号调理。读完本文你将掌握三种变换语言Lua、JavaScript、Expression的完整语法与函数表、跨帧持久状态、共享表读写 API、deviceWrite()闭环控制、actionFire()动作触发以及 34 个内置模板的选型与使用并能据此在实际项目中落地传感器标定、噪声抑制与自动控制等场景。概述变换在数据管线中的位置帧解析器parse(frame)产出一个原始值数组每个值通过Frame Index映射到对应数据集。在值到达仪表板之前一个可选的变换函数可以修改它parse(frame) → Raw Value → transform(value) → Dashboard变换的典型用途设备发送原始 ADC 计数需要标定斜率 偏移需要单位换算摄氏度到华氏度、弧度到度信号有噪声需要滤波滑动平均、EMA、低通需要派生值变化率、累计值、dB 转换传感器非线性需要多项式校正或查表。变换是可选的未定义变换的数据集直接显示原始解析值。变换还可以读写共享表shared tables——项目期定义的常量与跨帧保持的计算变量这正是把标定值同时传给多个通道、让一个变换计算的值被另一个消费、或为积分器/滤波器/锁存器保持状态的机制。完整参考见 Data-Tables.md即 Variables 文档。transform()函数签名与输入输出Luafunction transform(value) return value * 0.01 273.15 endJavaScriptfunction transform(value) { return value * 0.01 273.15; }输入value参数是已从帧解析并按 Frame Index 映射到本数据集的原始值。数值型解析值以浮点数到达Luanumber/ JSnumber非数值则以字符串到达Luastring/ JSstring。非数值数据集的值仍以字符串形式传入变换若变换返回数字数据集变为数值型若返回字符串则保留字符串值。输出函数返回一个数字或对文本数据集返回字符串返回的字符串被保留为数据集值。返回值会替换所有位置的原始值仪表板控件、绘图、CSV 导出、MDF4 导出以及 API。错误与回退若函数返回nilLua、undefined/NaN/InfinityJS或发生错误原始值保持不变数据流不被中断。JS 异常与 Lua 错误都会向应用日志写入一条带错误信息和数据集 ID 的警告只有非错误的回退情形nil/undefined/NaN/Infinity是静默的。Expression 变换无脚本引擎的算术表达式语言下拉框中还有第三种语言Expression。它没有函数、没有语句、没有脚本引擎编辑器只保存一个算术表达式其值即为数据集的新读数v * 0.01 273.15表达式被编译为一个不分配内存的扁平程序postfix 栈机是标度、限幅与通道组合这类常见场景下成本最低的选项。需要状态、多行分支、表访问或deviceWrite时改用 Lua 或 JavaScript。以#开头的行是注释一直注释到行尾。Expression 输入名称含义v本样本的原始值t帧时间戳秒基于应用单调时钟与 JS 变换的info.timestampMs同源。流启动时不是从零开始计算经过时间请用dt或减去首个样本的tdt距上一个样本的秒数n样本索引从 0 开始pi、e、nan、inf常量同一数据源的其它数据集通过其Script Alias读取——即datasetGetRaw和 API 接受的同一标识符。数据集标题不可用于解析标题是显示文本、可随意更改而别名是稳定的。v * shunt_current没有别名的数据集通过其dataset ID寻址并用花括号包裹因为裸数字是数值常量v * {12}花括号也可以承载包含空格的别名。若项目定义了本身全是数字的别名则别名优先于同数字的 ID。兄弟数据集持有其最新发布值因此在树中较后处理的数据集会贡献其上一帧的样本值——请在 Project Editor 中据此安排数据集顺序。sample(name, k)读取某数据集往前k个样本的值最多回溯 256v - sample(shunt_current, 1)名称可以是裸写或花括号包裹但绝不能加引号。Expression 的共享表访问表达式通过table(name, variable)读取表变量与 Lua/JS 的tableGet访问的是同一批常量与计算变量v * table(calibration, voltage_scale)两个名称均可裸写含空格时用花括号。变量在表达式编译时解析为句柄因此每次样本读取只是一次数组索引查找未知变量是编译错误并会指出是哪个表.变量对。该访问是只读的——从表达式写变量会引入执行顺序依赖那是 Lua/JS 变换配tableSet的用武之地。计算变量带有与兄弟数据集相同的处理顺序警告表达式看到的是最后一次写入的值若该变量在本帧较晚位置才被写入则看到的是上一帧的值。table()仅在帧管线上可用。运行 stream 通道的数据源如音频等密集数据源或强制开启 stream 通道的数据源上使用table()的表达式会编译失败因为变量存储属于另一线程逐样本跨线程读取不在支持范围内。Expression 运算符与函数运算符从最松到最紧||、、!、、-、*/%、^右结合、一元-和!以及条件condition ? a : b。元数函数一元absfloorceilroundsqrtcbrtexploglog10log2sincostanasinacosatansinhcoshtanhdegrad二元minmaxpowatan2hypot三元clamplerplog是自然对数deg与rad在弧度与度之间转换。从源码看这一函数表在 ExpressionTransform.cpp 中以FunctionSpec表的形式实现每个函数直接映射到 IEEE double 实现如clamp(a, lo, hi)即fmin(fmax(a, lo), hi)与文档表格完全一致。Expression 示例# Scale and offset an ADC reading v * 0.01 273.15# Limit to a range clamp(v, 0, 100)# Ratio of two channels, guarding the divide bus_voltage 0 ? 0 : v / bus_voltage# Difference from one sample ago v - sample(flow, 1)Expression 限制一个表达式最多编译 512 个操作expression too long历史最多回溯 256 个样本。编译错误只上报一次此后数据集发布其原始值。这些上限在源码中即 ExpressionTransform.h 的kMaxHistory 256、kMaxNodes 512、kMaxStackDepth 64常量。Expression 的 Template 下拉框被禁用内置模板都是 Lua 与 JavaScript 代码没有 Expression 等价物。跨帧持久状态在变换代码顶层transform()函数之外声明的变量在帧与帧之间持久存在——这正是滤波器、累加器等有状态变换保持状态的机制。关键规则在文件顶部使用localLua或varJavaScript。不要依赖裸全局变量。Serial Studio 隔离每个数据集各自的顶层状态因此两个使用同一模板的数据集例如两条通道上的两个 EMA不会互相覆盖变量。Lua在文件顶部声明locallocal alpha 0.1 local ema function transform(value) if ema nil then ema value end ema alpha * value (1 - alpha) * ema return ema endalpha与ema是 chunk 局部变量。Lua 将它们捕获为transform闭包的上值upvalue因此在调用之间存活且对该数据集私有。同一数据源上另一个带自己local ema的数据集不会看到或覆盖此值。JavaScript在文件顶部声明varvar alpha 0.1; var ema; function transform(value) { if (ema undefined) ema value; ema alpha * value (1 - alpha) * ema; return ema; }Serial Studio 在编译时把每个 JS 变换包裹进一个 IIFE(function(params) { ... })(...)见 TransformCompiler.cpp因此顶层var声明被限定在该数据集的闭包内而不是共享引擎的全局对象上——与 Lua 获得同样的隔离无需额外操作。不要这样做JavaScript 中要避免裸全局隐式全局会写入共享引擎的全局对象与同一数据源上的其它数据集冲突。Lua 中每个数据集 chunk 有自己私有的环境裸全局保持每数据集隔离、不会冲突但为了清晰并遵循 JS 规则仍推荐声明local-- Lua: this works (ema is isolated to this dataset), but local is clearer function transform(value) ema ema or value ema 0.1 * value 0.9 * ema return ema end// WRONG in JavaScript: ema without var is an implicit global // on the shared engine, so another dataset with the same mistake // would clobber it. Always declare var at the top. function transform(value) { if (typeof ema undefined) ema value; ema 0.1 * value 0.9 * ema; return ema; }始终在文件顶部用local/var声明有状态变量这也让代码更易读扫一眼文件顶部即可看出哪些变量携带状态。辅助函数JavaScript 中在文件顶部定义的辅助函数如function clamp(x, lo, hi) { ... }同样被 IIFE 闭包捕获、每数据集私有可以放心使用。Lua 中请用local function定义辅助函数以共享隔离local function clamp(x, lo, hi) if x lo then return lo end if x hi then return hi end return x end function transform(value) return clamp(value, 0, 100) endLua 中在 chunk 顶层写普通function foo() end定义的是全局但该全局仍留在本数据集私有环境中不会与其它数据集冲突不过出于清晰起见仍推荐加local function前缀。计算型数据集Computed datasets数据集可以在 Project Editor 中标记为computed。计算型数据集没有 Frame Index传入帧不为其提供任何数据其值完全由transform()函数计算典型方式是读取其它数据集或表变量。此时传入的value参数恒为0function transform(value) local a datasetGetFinal(10) -- reads the final value of dataset with unique ID 10 local b datasetGetFinal(11) return (a b) / 2 -- average of two channels end计算型数据集用于派生指标平均值、比率、总和、占总量的百分比它们应显示在仪表板上并随原始通道一起导出但不存在于线格式wire format中。自动检测不必手动勾选复选框——项目加载时任何变换代码从不引用自由标识符value区别于something.value或obj:value的数据集都会被自动标记为 computed其 Frame Index 字段随即变灰。若希望数据集保持由传入帧驱动请确保变换至少读取一次value。状态何时重置持久状态包括顶层 Lua/JS 上值以及计算型表变量在以下情况被清除设备断开变换引擎被销毁用户在变换编辑器中点击Apply引擎用全新状态重新编译项目重新加载或保存变更。因此滤波器和累加器在每次新连接会话时从零开始这通常是期望的行为。在连接会话内计算型变量与变换上值可无限期保持值不会在帧之间被清除。共享库与数据集参数大型项目会在许多数据集中重复同一公式例如 40 个 RTD 通道每个都携带一份相同的转换仅三个常量不同。两个项目级特性可消除这种复制。共享库Shared libraries项目可以持有一个 Lua chunk项目内所有 Lua 变换都可调用和一个 JavaScript chunk项目内所有 JS 变换都可调用。两者都位于 Project Editor 树顶部的Project Scripts下、Control Loop 旁边即Lua Library与JavaScript Library变换编辑器也有一个Open Lua Library/Open JavaScript Library按钮可直接跳到当前语言对应的库。API 同样暴露它们project.transformLibrary.get、project.transformLibrary.set、project.transformLibrary.dryRun各带可选的language参数lua或js。-- Shared library function rtd(raw, p) return (raw * p.scale p.offset) / p.r0 end该 chunk 在数据集变换编译之前运行一次写入每个数据源变换状态的全局表。其顶层函数通过普通全局查找对所有 Lua 变换可见变换自己的local值仍对该变换私有。由于库在编译期求值调用库函数与调用本地辅助函数成本相同。JavaScript 库工作原理相同它被求值一次到每个数据源变换引擎的全局对象中其顶层函数对该数据源的所有 JS 变换闭包可见。每种库只被同语言的变换看到Expression 变换没有可调用的函数。若库编译或运行失败变换仍可在没有它的情况下编译调用缺失函数的数数据集回退到其原始值失败出现在 Problem Center 的the shared Lua library或the shared JavaScript library条目下。修复库后引擎会在 Apply 时重新编译无需重新连接。从源码看这一失败-回退路径由 TransformCompiler.cpp 中compileLuaLibrary对kTransformLibraryErrorId的错误记录实现。数据集参数Dataset parameters每个数据集可携带命名参数其变换以params读取。在变换对话框的Parameters表中逐行添加名称与值或通过 APIproject.dataset.update {transformParams: {...}}。值是有类型的数字文本变成数字true与false变成布尔值其它内容保持字符串。-- Dataset transform: the same three lines on every RTD channel function transform(value) return rtd(value, params) end在一个数据集上设置transformParams为{scale: 0.1, offset: -40, r0: 100}、在下一个上设置{scale: 0.1, offset: -40, r0: 1000}两个通道共享一个公式仅表格行不同。params在 Lua 与 JavaScript 中均可用Lua 中它是变换环境中的表params.r0JavaScript 中它是传给变换闭包的对象params.r0。两者都在编译期固定编辑参数会重新编译变换。Expression 变换不读params请改用表变量。共享表 APITable APILua 与 JavaScript 变换有四个内置函数用于读写项目的共享表。Expression 变换改用table(name, variable)读取变量且不能写入。表的完整说明见 Data-Tables.md本节从变换视角记录 API 表面。函数返回用途tableGet(table, reg)number、string 或 nil/undefined读取用户定义变量tableSet(table, reg, value)无写入计算变量常量只读datasetGetRaw(uniqueId \| alias)number、string 或 nil/undefined当前帧中任一数据集的原始变换前值datasetGetFinal(uniqueId \| alias)number、string 或 nil/undefined当前帧中已处理数据集的最终变换后值Lua 与 JavaScript 的 API 完全相同。table与reg是字符串。数据集查找接受整数uniqueIdProject Editor 中每个数据集旁显示的数字或数据集别名字符串Script Alias字段。数字参数总是 uniqueId字符串总是别名因此datasetGetRaw(128)与datasetGetRaw(128)是不同查找。未知别名返回nil/undefined并带一次性控制台警告与未知 uniqueId 相同。对每个值都访问同一批变量的变换可把名字一次性解析为句柄并用tableGetH/tableSetH避免每次调用的名字查找。详见 SerialStudio-SDK.md 的 Fast table access with handles。Lua 示例用项目级标定因子缩放电压读数function transform(value) local k tableGet(calibration, voltage_scale) -- constant return value * (k or 1.0) endJavaScript 示例同样思路。function transform(value) { var k tableGet(calibration, voltage_scale); return value * (k ! undefined ? k : 1.0); }写入计算变量一个变换发布另一个消费-- Dataset 10 (processed first): publish the total current function transform(value) tableSet(runtime, total_current, value) return value end-- Dataset 20 (processed later): compute power from current × voltage function transform(value) local i tableGet(runtime, total_current) or 0 return value * i end处理顺序变换按顺序应用组按帧顺序数据集按组内顺序。在单帧内datasetGetRaw(uid)只对当前数据集及其之前已处理的数据集返回本帧原始值。更靠后的数据集仍持有上一帧的原始值因为原始值与最终值是逐数据集增量写入的而非预扫描一次性写入datasetGetFinal(uid)只对已变换的数据集有效同组内靠前的数据集或更早组的任何数据集计算变量保持其最后写入的值因此一帧中写入的值在下一帧仍可见非常适合积分器、导数与锁存标志。若希望变量每帧从零开始请在较早变换的顶部自行写重置值。如果需要数据集 B 消费数据集 A 的最终值请确保 A 在 Project Editor 树中位于 B 之前。变更驱动变换Change-Driven Transforms可选开启默认情况下每个数据集的变换在每帧都运行。对于大型表驱动项目——帧解析器写表变量、许多计算数据集各读一个变量——意味着一个设备的帧会重新运行每个数据集的变换即使其数据未变化。Change-Driven Transforms开关开启更快模式计算数据集的变换仅在其读取的某个变量或数据集自上次运行以来发生变化时才运行否则该帧保持其之前的输出。Serial Studio 自动发现每个变换读取了什么因此脚本无需任何改动。仪表板两种模式下显示相同的值该选项只是跳过未变化值的重算。在 Project Editor 中开启选择项目根节点点击 Project Title 旁边的Settings按钮扳手图标在弹出框中切换Change-Driven Transforms与 Time Range 和 Point Count 并列。该选项默认关闭按项目保存。对于帧丢失严重的重型表驱动仪表板请开启它。有两点须知逐样本滤波器EMA、死区此后按输入的真实新样本步进一次而非按全局帧步进——这通常正是你想要的若计算数据集的输出依赖 Serial Studio 无法视为输入的东西——直接在变换内读取墙钟时间Date.now()、os.time()而非经表或frameInfo传入的值——请为该项目保持关闭或让这类数据集保持非计算型因为变换可能不会仅因时间流逝而重跑。帧元数据第二个frameInfo参数每个变换都可以声明第二个参数携带该值所属帧的元数据。单参数变换function transform(value)继续工作且无额外成本引擎在编译期检查每个变换的参数个数不使用信息时完全跳过 info 表/对象的构建。这一按参数个数探测在源码中有明确实现——Lua 通过luaTransformAcceptsInfo读取nparamsJS 通过函数length属性判断TransformCompiler.cpp 与 L553。function transform(value, info) -- info.frameNumber : integer, monotonic counter per source (starts at 1) -- info.sourceId : integer, the source the dataset belongs to -- info.timestampMs : integer, monotonic milliseconds (steady clock) return value endfunction transform(value, info) { // info.frameNumber : number // info.sourceId : number // info.timestampMs : number return value; }info.timestampMs是取自 OS 稳定时钟的单调毫秒计数器。它在相邻帧之间递增但不是墙钟时间、也不匹配Date.now()。请用它计算差值info.timestampMs - lastTs而不是现在几点。info.frameNumber按数据源计数断开或项目重载后从 1 重新开始。示例限制控制更新的频率一个调整设备设定点的变换无论帧率多高每秒最多执行十次local lastTs 0 function transform(temperature, info) if info.timestampMs - lastTs 100 then lastTs info.timestampMs local sp tableGet(Control, setpoint) or 25.0 deviceWrite(string.format(SP%.2f\n, sp)) end return temperature end示例每 50 帧请求一次状态推送function transform(value, info) { if (info.frameNumber % 50 0) deviceWrite(STATUS?\n); return value; }写回设备deviceWrite()变换可以通过deviceWrite(data, sourceId?)向已连接设备发送字节。预期用途是闭环控制读取传感器值、计算设定点或修正量并一步推回设备。签名deviceWrite(data, sourceId?) data: string (Lua) / string 或 byte 数组 (JavaScript) sourceId: 可选数字默认是变换所属数据集的源 returns: { ok true } on success { ok false, error ... } on failuredeviceWrite是同步、fire-and-forget的它立即把字节推给驱动。它不阻塞等待回复也不抛异常。任何失败都变成{ ok false, error ... }。每次调用都会记录到应用日志[deviceWrite] sourceid bytesn writtenn便于验证控制命令是否按预期发出。Lua 示例PWM 控制器local kp 4.0 local setpoint 25.0 function transform(sensor_temp) local error setpoint - sensor_temp local pwm math.max(0, math.min(255, kp * error 128)) deviceWrite(string.format(PWM%d\n, math.floor(pwm 0.5))) return sensor_temp end该变换原样返回原始温度仪表板仍显示测量值并在每帧把计算出的 PWM 占空比写回设备。JavaScript 示例报警锁存let triggered false; function transform(value) { if (!triggered value 100) { const r deviceWrite(ALARM1\n); if (r.ok) triggered true; else console.warn(alarm write failed:, r.error); } return value; }triggered上值保证报警条件锁存后deviceWrite不会在后续每帧重复触发。定向到另一数据源传入显式sourceId可写入不同数据源。当遥测来自一个数据源、设备的命令通道在另一个数据源时很有用function transform(value) if value 5 then deviceWrite(REQ_FULL_REPORT\n, 0) -- ask source 0 for a full status frame end return value end失败模式deviceWrite从不抛异常。可能的error值device not connected or write failed目标源没有活动驱动或驱动的write()返回 0/负数deviceWrite: data is empty负载为零字节deviceWrite: data must be a stringLua/... string or byte arrayJSdeviceWrite: sourceId must be a number。何时不该用用户点击的按钮或滑块请用Output Widget。变换每帧都运行Output Widget 在用户操作时运行不要从计算数据集deviceWrite除非你理解其执行顺序。计算数据集像任何其它数据集一样按树顺序处理并非自动最后处理因此计算数据集只能看到它之前数据集的最终值。请把它放在输入之后。它仍然每帧触发不要每帧写大负载。采集管线的变换阶段与数据源内所有数据集共享频繁的deviceWrite会饱和链路。触发动作actionFire()变换还可以触发项目中已定义的任何Action见 Actions.md。复用现有动作连同其预构建负载、编码与定时模式而不是在deviceWrite中硬编码字节。actionFire(actionId) actionId: integer (the actions stable identifier) returns: { ok true } | { ok false, error ... }actionId是动作的actionId字段项目文件存储、API 返回的同一个持久整数。它不是动作列表中的位置。可用project.action.listMCP查找或在 Project Editor 的 Actions 面板读取。local triggered false function transform(value, info) if not triggered and value 100 then local r actionFire(7) if r.ok then triggered true end end return value end行为等同于用户按下动作的按钮包括运行动作的定时器AutoStart、RepeatNTimes等。调用记录为[actionFire] idN indexM ok。actionFire也可在帧解析器与画布脚本中使用。控制仪表板变换还可以驱动一小批仪表板辅助函数clearPlots()、setPlotPoints(n)、setTerminalVisible(bool)、setNotificationLogVisible(bool)、setClockVisible(bool)、setStopwatchVisible(bool)与setActiveWorkspace(idOrName)。它们与在解析器中的行为相同返回相同的{ ok, error }形状。变换中的典型模式是在状态转换时触发一次例如function transform(value) if value 9999 then -- device reboot sentinel clearPlots() return 0 end return value end完整参考包括参数类型与更长的示例GPS 修复重置、模式驱动的工作区切换、专注模式见 JavaScript-API.md 的 Controlling the dashboard。使用变换对话框在 Project Editor 树中选择一个数据集点击数据集工具栏中的Transform按钮对话框打开标题为Dataset Value Transform设置后为Transform — dataset title包含Language selector。Lua、JavaScript 或 Expression。默认使用数据源的帧解析器语言但每个数据集可自选Template dropdown。34 个现成变换模板。Expression 下禁用Code editor。语法高亮带自动补全Parameters table。变换以params.name读取的命名值Test area。输入原始值、点击 Test、查看变换输出。测试会先加载共享库与参数表因此调用库函数的变换测试时与实际运行一致编写或挑选一个transform(value)函数点击Apply将变换保存到数据集。当你在尚无变换的数据集上打开编辑器时它预填了一段多行注释解释transform(value)如何工作以及顶层local/var状态如何被捕获。该占位文本不是真正的变换。若未定义transform()函数就点击 Apply占位文本被丢弃、数据集继续显示原始值。清空代码行为相同不保存任何内容对话框关闭。但写入非空文本或注释却从未定义transform()则不同Apply 会拒绝显示 The value transform must define a transform(value) function.对话框保持打开直到你定义transform()或点击 Clear。切换语言切换语言下拉框时编辑器会自动加载新语言的等价模板若当前代码匹配某个已知模板。自定义代码保持不变仅切换语法高亮器。内置模板34 个变换编辑器包含 34 个现成模板。从 Template 下拉框选择后即载入编辑器供调参。标定与转换模板说明Linear Calibrationy slope × value offset。传感器标定。Polynomial (2nd order)y a×x² b×x c。非线性响应曲线。Map Range从[inMin, inMax]重新缩放到[outMin, outMax]。ADC to Voltage10 位 ADC 计数转电压3.3 V 基准。Calibration from Data Table从表变量读取斜率/偏移并应用。平滑滤波模板说明Moving Average通过环形缓冲区对最近 N 个样本取平均。Exponential Moving Average (EMA)加权平均可调响应度alpha。Low-Pass Filter一阶 IIR可通过 alpha 调节截止。High-Pass Filter一阶 IIR。去除直流漂移与慢速偏移。Median Filter滚动窗口取中值。对尖峰离群值稳健。Kalman Filter (1D)标量卡尔曼滤波可调 Q过程与 R测量噪声。统计模板说明Rolling RMS最近 N 个样本的均方根。适用于交流信号、振动、音频。Running Minimum变换启动以来观察到的最小值。Running Maximum变换启动以来观察到的最大值。Running Accumulator离散积分运行总和。Rate of Change离散微分value - previous。信号整形模板说明Clamp将输出限制在[min, max]。Dead Zone抑制零附近的小值。Slew-Rate Limiter限制相邻样本间值的变化幅度。Auto-Zero / Tare对前 N 个样本取平均再从之后每个值中减去该偏差。Schmitt Trigger滞回比较器。带独立上升/下降阈值输出 0/1。角度数学模板说明Unwrap Angle消除 ±360° 跳变使输出跨边界连续。Integrate Rate to Angle以固定采样率将角速率deg/s积分为绝对角度。Radians to Degreesdeg rad × 180/π。Degrees to Radiansrad deg × π/180。单位换算模板说明Celsius to Fahrenheit°F °C × 9/5 32。Fahrenheit to Celsius°C (°F - 32) × 5/9。Kelvin to Celsius°C K - 273.15。Logarithmic (dB)将线性幅度转换为分贝。逻辑与位运算模板说明Bit Extract从打包状态字中提取单个位0 或 1。Absolute Value无符号幅值。Invert Sign取反。Round to N Decimals精度控制。传感器模板说明Steinhart-Hart ThermistorNTC 电阻转温度°C。模板清单由 ScriptTemplates.h 中的清单机制加载loadScriptTemplateManifesttemplateResourcePath在 DatasetTransformEditor.cpp 中通过buildTemplates()填充下拉框选中后经onTemplateSelected载入对应语言的代码。变换如何融入数据管线Serial Studio 按清晰管线处理数据。知道变换所处位置有助于决定什么放进parse()、什么放进transform()阶段函数作用域目的帧解析器parse(frame)每数据源将原始字节解码为值数组数据集变换transform(value)每数据集将原始值转换为工程单位仪表板无每控件显示最终值经验法则帧解析器负责协议解码字节提取、位操作、CRC 校验、多消息状态机。它处理线格式数据集变换负责值的调理标定、单位换算、滤波、派生计算。它处理物理含义。这一分离让解析器专注于协议逻辑、变换专注于传感器特性。你可以跨项目复用同一个解析器只改每数据集变换即可适配不同标定。从实现层面看变换编译按数据源, 语言聚合成共享引擎每个引擎内再按数据集缓存函数引用TransformCompiler.cpp这正是同源数据集可混用三种语言、共享引擎、但状态互相隔离的底层机制。实战示例示例 1线性标定压力传感器输出原始 ADC 计数0 到 4095。数据手册0 0 PSI4095 100 PSIfunction transform(value) return value * 100 / 4095 end示例 2噪声温度传感器RTD 传感器在真值附近波动 ±0.5°C。用 EMA 平滑local alpha 0.15 local ema function transform(value) if ema nil then ema value end ema alpha * value (1 - alpha) * ema return ema endalpha与ema都在文件顶部声明为local因此 Lua 将它们捕获为transform闭包的上值。状态在帧间持续且对该数据集私有。同一数据源上的另一个数据集可以使用同一 EMA 模板并带自己的local ema而不受干扰。示例 3罗盘航向归一化磁力计以弧度报告航向。转换为度并限制在 0 到 360function transform(value) local degrees value * 180 / math.pi return degrees % 360 end示例 4带死区的电池电压电池监视器在断开连接时围绕 0 V 波动。抑制 0.5 V 以下的噪声function transform(value) if value 0.5 then return 0 end return value end示例 5速度从 m/s 转为 km/hfunction transform(value) return value * 3.6 end规则与限制函数必须命名为transform区分大小写。它接受一个必需参数value加一个可选第二参数info携带帧元数据frameNumber、sourceId、timestampMs。它必须返回数字或对文本数据集返回字符串返回的字符串被保留为数据集值。返回nil、NaN或Infinity时回退到原始值脚本错误同样回退两种语言都会记录日志只有非错误的nil/NaN/Infinity回退是静默的。每个数据集自选变换语言Lua、JavaScript 或 Expression同一数据源上的数据集可以混用。数据源的帧解析器语言只是未选择时的默认值。规则 1-4 描述transform()函数适用于 Lua 与 JavaScriptExpression 数据集没有函数发布其单个表达式的值。同一数据源上的数据集共享一个底层脚本引擎但每个数据集的顶层状态互相隔离。JavaScript 中用var声明有状态变量隐式未声明全局会写入共享引擎并泄漏到其它数据集。Lua 中每个数据集 chunk 有自己的环境因此裸全局以及 chunk 顶层的function foo() end保持每数据集隔离仍推荐local以保持清晰并与 JavaScript 规则一致。引擎是沙箱化的无文件 I/O、无网络、无操作系统命令。从源码看Lua 沙箱只打开_G、table、string、math、bit五个安全库并显式清除dofile、loadfile、load与string.dump且从不打开ffi与jitTransformCompiler.cpp。变换在每个传入帧上运行因此请保持其快速。避免无界循环或重计算。每次调用的看门狗会在 100 ms 后终止变换Lua调用前武装 deadlineJavaScript每帧武装一次 interrupt这一阈值正是源码中的kTransformWatchdogMs 100TransformCompiler.h。超时变换记录警告并回退到原始值同规则 4。相关文档SerialStudio-SDK.md变换可调用的完整辅助函数面tableGet/tableSet、deviceWrite、actionFire、通知、协议编码器。JavaScript-API.md向变换供值的parse(frame)函数。Data-Tables.md所有变换可用的共享常量与计算变量。Data-Flow.md数据如何从设备经解析、变换进入仪表板。Project-Editor.md配置数据集、变换与表的位置。Widget-Reference.md显示变换后值的仪表板控件。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。