Mingcedar

所属产品: Mingcedar CONTROL

Python 项目与模块 | Mingcedar

先确认 Script 服务可用。代码管理需超级管理员及 integration:manage;Run Test 另需 integration:execute;Python 模块管理另需 system:view 和 system:manage。按顺序完成计算、依赖安装、事件调用与 Tag 读写练习。

首次发布 最近更新

<div class="manual-doc-article manual-doc-article-section"><h2 id="python-scripting">开发教程: Python 项目与模块</h2>

使用 CPython 3.13 创建项目、复用函数、安装依赖、读取 Tag、配置事件及受控写入。

<div class="doc-card-list doc-tutorial">

<article class="doc-card">

<h3 id="python-scripting-item-1">开始前准备</h3>

<p>先确认 Script 服务可用。代码管理需超级管理员及 integration:manage;Run Test 另需 integration:execute;Python 模块管理另需 system:view 和 system:manage。按顺序完成计算、依赖安装、事件调用与 Tag 读写练习。</p>

<ul>

<li>代码(Code):创建项目、编辑文件;工具栏的“Run Test”执行当前选中的 Python 文件,未保存时显示“Save &amp; Test”。</li>

<li>运行(Run):配置何时触发的事件,以及事件执行的动作。记录(Records):检查正式事件执行结果。</li>

<li>Settings → Python → Python 模块:选择项目并管理模块;高级项目配置管理入口、运行限制、服务器离线目录、环境变量和 Secret。</li>

<li>本教程中的尖括号是待替换项;文件名、函数名和练习用数值则按示例填写。</li>

</ul>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-2">1. 创建项目和两个 Python 文件</h3>

<p>位置:配置中心 → script → 代码(Code)。先创建项目,再添加文件。</p>

<ol>

<li>点击“管理项目(Manage Projects)”,在弹窗内点击“新建(New)”。</li>

<li>在“项目名称”填写 Equipment Integration,点击“创建项目(Create Project)”。关闭弹窗,在代码页项目下拉框选择刚创建的项目。</li>

<li>在 common/ 行点击新建文件的“+”,输入 scaling,点击“创建”。系统补齐 .py。</li>

<li>在 scripts/ 行以相同方式创建 main。若 main.py 已存在,直接选中它,不重复创建。</li>

<li>检查编辑器标题中的完整路径,确认分别是 common/scaling.py 与 scripts/main.py。</li>

</ol>

<div class="manual-code" data-code-language="text"><div class="manual-code-toolbar"><span>Text</span><span class="manual-code-tools"><small>Text</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: Text">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span>Project/</span></span>

<span class="line"><span>├── scripts/</span></span>

<span class="line"><span>│ ├── __init__.py</span></span>

<span class="line"><span>│ └── main.py</span></span>

<span class="line"><span>└── common/</span></span>

<span class="line"><span> ├── __init__.py</span></span>

<span class="line"><span> └── scaling.py</span></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 两个文件均可选中,路径正确。下方目录仅说明文件结构。</p>

<div class="doc-step-troubleshooting"><p><strong>如果结果不一致</strong></p><ul>

<li>没有创建按钮:核对当前账号的脚本管理权限。文件出现在错误目录时,先修正位置再复制代码。</li>

</ul></div>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-3">2. 保存公共函数</h3>

<p>将输入量程 0–100 换算为 0–10。</p>

<ol>

<li>选中 common/scaling.py,确认编辑器路径。</li>

<li>用下方完整代码替换文件内容,点击“保存(Save)”。</li>

<li>等待未保存标记消失,再切换文件。本文件只定义函数,不需要单独运行。</li>

</ol>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>common/scaling.py</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: common/scaling.py">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># common/scaling.py</span></span>

<span class="line"><span style="color:#D73A49">def</span><span style="color:#6F42C1"> scale_linear</span><span style="color:#24292E">(value, input_min, input_max, output_min, output_max):</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> input_max </span><span style="color:#D73A49">==</span><span style="color:#24292E"> input_min:</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> ValueError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Input range must not be zero"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> output_min </span><span style="color:#D73A49">+</span><span style="color:#24292E"> (value </span><span style="color:#D73A49">-</span><span style="color:#24292E"> input_min) </span><span style="color:#D73A49">*</span><span style="color:#24292E"> (output_max </span><span style="color:#D73A49">-</span><span style="color:#24292E"> output_min) </span><span style="color:#D73A49">/</span><span style="color:#24292E"> (input_max </span><span style="color:#D73A49">-</span><span style="color:#24292E"> input_min)</span></span>

<span class="line"></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 保存后无输出;下一步由 main.py 调用。</p>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-4">3. 调用公共函数并核对控制台</h3>

<p>事件调用 main(ctx);代码页通过 __main__ 分支调用同一函数。</p>

<ol>

<li>选中 scripts/main.py,粘贴下方完整代码并保存。</li>

<li>保持选中 main.py,点击工具栏 Run Test。不要运行 scaling.py。</li>

<li>在编辑器下方“控制台(Console)”查看执行状态和输出。</li>

<li>将 main({&quot;raw&quot;: 50}) 中的 50 改为 25,保存并再次运行,核对换算值变成 2.5;完成后改回 50。</li>

</ol>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>scripts/main.py</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: scripts/main.py">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># scripts/main.py</span></span>

<span class="line"><span style="color:#D73A49">from</span><span style="color:#24292E"> ..common.scaling </span><span style="color:#D73A49">import</span><span style="color:#24292E"> scale_linear</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">def</span><span style="color:#6F42C1"> main</span><span style="color:#24292E">(ctx):</span></span>

<span class="line"><span style="color:#24292E"> value </span><span style="color:#D73A49">=</span><span style="color:#24292E"> scale_linear(</span><span style="color:#005CC5">float</span><span style="color:#24292E">(ctx.get(</span><span style="color:#032F62">"raw"</span><span style="color:#24292E">, </span><span style="color:#005CC5">50</span><span style="color:#24292E">)), </span><span style="color:#005CC5">0</span><span style="color:#24292E">, </span><span style="color:#005CC5">100</span><span style="color:#24292E">, </span><span style="color:#005CC5">0</span><span style="color:#24292E">, </span><span style="color:#005CC5">10</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#005CC5"> print</span><span style="color:#24292E">(</span><span style="color:#032F62">"Scaled value:"</span><span style="color:#24292E">, value)</span></span>

<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> {</span><span style="color:#032F62">"scaled"</span><span style="color:#24292E">: value}</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">if</span><span style="color:#005CC5"> __name__</span><span style="color:#D73A49"> ==</span><span style="color:#032F62"> "__main__"</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#24292E"> main({</span><span style="color:#032F62">"raw"</span><span style="color:#24292E">: </span><span style="color:#005CC5">50</span><span style="color:#24292E">})</span></span>

<span class="line"></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 首次输出 Scaled value: 5.0,状态为 Completed;修改输入后输出 2.5。</p>

<div class="doc-step-troubleshooting"><p><strong>如果结果不一致</strong></p><ul>

<li>Completed 但没有输出:检查选中文件,以及底部是否包含 if __name__ == &quot;__main__&quot;。</li>

<li>ImportError:确认两个文件属于同一项目,并保持 scripts/ 和 common/ 的层级;不要从操作系统终端直接运行本示例。</li>

</ul></div>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-5">公共函数与自写包的导入规则</h3>

<p>scripts/main.py 使用 from ..common.scaling import scale_linear。若文件位于 scripts/controls/main.py,应使用三个点返回项目包根。绝对导入必须使用项目清单中的实际 package 名称。</p>

<ul>

<li>同一项目复用:common/ 随项目一起构建和应用,使用相对导入,无需在 Python 模块页面安装。修改公共函数后保存,并确认运行版本已应用。</li>

<li>跨项目复用:将自写代码制作为有明确版本和依赖声明的 Wheel,按下文流程分别安装到需要它的项目。安装后按包内实际模块名普通 import,不使用 Wheel 文件名。</li>

<li>不要修改 sys.path 指向宿主目录,也不要将另一个项目的脚本路径作为导入来源。</li>

</ul>

<p class="doc-step-result"><strong>完成标志</strong> 修改并保存 scaling.py 后,重新运行 main.py 验证结果。跨项目复用须安装依赖,不能直接引用另一项目路径。</p>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-6">4. 记录本项目的 Python 运行目标</h3>

<p>依赖按项目安装,不共享准备机或服务器系统 Python 的包。Python 模块页需上述系统设置权限。</p>

<ul>

<li>标准库 json、csv、sqlite3、datetime、urllib、smtplib 无需安装。</li>

<li>常用内置模块包括 requests、psycopg、PyMySQL、pyodbc、paho-mqtt、websocket-client 和 cryptography。新项目首次构建环境时安装内置模块;已有项目可通过“添加内置模块”补充,已选版本会保留。</li>

<li>Excel 等额外用途可按需安装 openpyxl 等模块。使用 pyodbc 连接数据库时,服务器还需安装对应的 ODBC 驱动。</li>

<li>发行包名和 import 名可能不同:PyMySQL → pymysql,websocket-client → websocket。</li>

</ul>

<ol>

<li>打开 Settings → Python → Python 模块,选择 Equipment Integration。</li>

<li>查看模块列表上方的“运行环境”,记录 Python 版本、操作系统和架构;当前目标为 Python 3.13。</li>

<li>查看模块列表中的名称和版本,确认项目是否已有本练习所需模块;列表标题会随项目状态显示为“项目模块”“已准备模块”或“已安装模块”。</li>

<li>Linux 容器以容器内运行目标为准;Windows、Linux 原生部署使用各自运行目标。</li>

</ol>

<p class="doc-step-result"><strong>完成标志</strong> 按已确认的项目运行目标选择内置模块或兼容 Wheel。</p>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-7">5. 选择内置模块或准备 Wheel 及其依赖</h3>

<p>内置模块无需下载。其他依赖在匹配目标 OS、CPU 架构和 Python 3.13 的准备机上,按批准的精确版本下载。下列命令仅在准备机执行。</p>

<ol>

<li>先查看项目模块列表。若 psycopg 和 PyMySQL 已提供为内置模块,可点击“添加内置模块”,核对预览中全部拟添加的包,再跳到第 7 步;无需重复下载。</li>

<li>需要其他第三方库或指定版本时,在与服务器系统、架构和 Python 版本一致的可联网准备机执行 python --version。本例为后续 SQL 练习准备 psycopg 和 PyMySQL;只使用一种数据库时,可移除另一种包及其导入检查。</li>

<li>在下方命令中分别替换两处 &lt;approved-version&gt; 为选定包的精确版本。引号保留,不要把尖括号原样运行。</li>

<li>先执行 pip download,等待命令成功,再检查 ./wheelhouse 中包含主包及依赖包的 .whl 文件。</li>

<li>自写包也应构建为带明确版本和依赖声明的 .whl;可在临时虚拟环境执行下方离线安装与 import 检查,确认依赖完整和实际导入名后再上传。</li>

</ol>

<div class="manual-code" data-code-language="bash"><div class="manual-code-toolbar"><span>Shell</span><span class="manual-code-tools"><small>Shell</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: Shell">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># Run on a preparation machine matching the target OS and CPU, with Python 3.13.</span></span>

<span class="line"><span style="color:#6A737D"># Replace the version placeholders with your approved exact versions.</span></span>

<span class="line"><span style="color:#6F42C1">python</span><span style="color:#005CC5"> -m</span><span style="color:#032F62"> pip</span><span style="color:#032F62"> download</span><span style="color:#005CC5"> --only-binary=:all:</span><span style="color:#005CC5"> --dest</span><span style="color:#032F62"> ./wheelhouse</span><span style="color:#032F62"> "psycopg[binary]==&#x3C;approved-version>"</span><span style="color:#032F62"> "PyMySQL==&#x3C;approved-version>"</span></span>

<span class="line"><span style="color:#6F42C1">python</span><span style="color:#005CC5"> -m</span><span style="color:#032F62"> pip</span><span style="color:#032F62"> hash</span><span style="color:#032F62"> "&#x3C;wheel-file>"</span></span>

<span class="line"></span>

<span class="line"><span style="color:#6A737D"># Optional local verification in a disposable preparation environment:</span></span>

<span class="line"><span style="color:#6F42C1">python</span><span style="color:#005CC5"> -m</span><span style="color:#032F62"> venv</span><span style="color:#032F62"> ./dependency-check</span></span>

<span class="line"><span style="color:#6A737D"># Use dependency-check/bin/python (Linux) or dependency-check\Scripts\python.exe (Windows):</span></span>

<span class="line"><span style="color:#D73A49">&#x3C;</span><span style="color:#24292E">venv-python</span><span style="color:#D73A49">></span><span style="color:#24292E"> -m pip install --no-index --find-links</span><span style="color:#D73A49">=</span><span style="color:#032F62">./wheelhouse</span><span style="color:#032F62"> "psycopg[binary]==&#x3C;approved-version>"</span><span style="color:#032F62"> "PyMySQL==&#x3C;approved-version>"</span></span>

<span class="line"><span style="color:#D73A49">&#x3C;</span><span style="color:#24292E">venv-python</span><span style="color:#D73A49">></span><span style="color:#24292E"> -c </span><span style="color:#032F62">"import psycopg, pymysql; print('Dependencies ready')"</span></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 内置模块已列入预览,或目录已有所需 .whl。离线检查应输出 Dependencies ready;仍须在目标项目验证。</p>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-8">6. 上传 Wheel 并检查安装预览</h3>

<p>优先使用与目标一致的准备环境。必须交叉下载时,明确指定 --platform、--python-version 3.13、--implementation cp、--abi cp313 和 --only-binary=:all:;平台标签必须匹配目标运行环境,不能仅凭 Linux 名称推定 glibc/musl 兼容性。</p>

<ol>

<li>核对每个 Wheel 的发行包、版本、Python/ABI/平台标签及 SHA-256;同一发行包只保留目标版本,并包含全部间接依赖。</li>

<li>在 Settings → Python → Python 模块选择当前项目,点击“添加”,选中所需 .whl 文件;也可上传仅包含完整 Wheel 依赖集合的 ZIP,或把文件拖入模块列表。</li>

<li>查看“确认安装内容”预览,核对新增或更新的包及版本;若提示缺少依赖,补齐 Wheel 后继续添加,暂不提交安装。</li>

<li>仅在需要从服务器目录导入时,展开“高级项目配置 → 服务器离线目录”,把 .whl 普通文件复制到显示的项目目录并点击“刷新清单”;再回到模块列表展开“离线目录导入”,点击“检查目录中的包”。该目录不接收 ZIP、源码包、子目录或符号链接。</li>

<li>Linux 容器采用容器内路径,并确保服务账号可读取;刷新清单仅检查目录,仍须完成安装预览和下一步的安装。</li>

</ol>

<p class="doc-step-result"><strong>完成标志</strong> 预览列出预期变更,无兼容性、版本或缺失依赖错误。项目运行环境尚未改变。</p>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-9">7. 安装模块并实际导入</h3>

<p>核对预览后提交。新项目可先安装模块供代码页运行;已有运行版本时,检查通过后应用新版本。</p>

<ol>

<li>核对预览后,若所需模块已在当前项目的模块列表中,直接到代码页做导入检查;否则按页面显示点击“安装到项目”或“安装并应用”。前者准备尚无运行版本的项目供手动运行,后者检查并应用新的运行版本。</li>

<li>若提交安装,等待“模块已安装到项目”或“依赖包已安装并应用”的结果;前者可在代码页手动运行,但须完成下节入口配置后才能由事件自动执行。</li>

<li>返回代码页,在 scripts/ 新建 check_dependencies,粘贴下方代码。只使用 PostgreSQL 时,删除 import pymysql 和 PyMySQL 的 version(...) 输出行;只使用 MySQL 时,删除 import psycopg 和 psycopg 的 version(...) 输出行,再保存。</li>

<li>选中 check_dependencies.py,点击 Run Test,在控制台核对 Python 和实际安装包的版本。</li>

</ol>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>scripts/check_dependencies.py</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: scripts/check_dependencies.py">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># scripts/check_dependencies.py</span></span>

<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> sys</span></span>

<span class="line"><span style="color:#D73A49">from</span><span style="color:#24292E"> importlib.metadata </span><span style="color:#D73A49">import</span><span style="color:#24292E"> version</span></span>

<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> psycopg</span></span>

<span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> pymysql</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">if</span><span style="color:#005CC5"> __name__</span><span style="color:#D73A49"> ==</span><span style="color:#032F62"> "__main__"</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#005CC5"> print</span><span style="color:#24292E">(</span><span style="color:#032F62">"Python:"</span><span style="color:#24292E">, sys.version.split()[</span><span style="color:#005CC5">0</span><span style="color:#24292E">])</span></span>

<span class="line"><span style="color:#005CC5"> print</span><span style="color:#24292E">(</span><span style="color:#032F62">"psycopg:"</span><span style="color:#24292E">, version(</span><span style="color:#032F62">"psycopg"</span><span style="color:#24292E">))</span></span>

<span class="line"><span style="color:#005CC5"> print</span><span style="color:#24292E">(</span><span style="color:#032F62">"PyMySQL:"</span><span style="color:#24292E">, version(</span><span style="color:#032F62">"PyMySQL"</span><span style="color:#24292E">))</span></span>

<span class="line"><span style="color:#005CC5"> print</span><span style="color:#24292E">(</span><span style="color:#032F62">"Dependencies ready"</span><span style="color:#24292E">)</span></span>

<span class="line"></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 控制台显示 Python 3.13、实际安装的一个或两个包的版本,以及 Dependencies ready。</p>

<div class="doc-step-troubleshooting"><p><strong>如果结果不一致</strong></p><ul>

<li>ModuleNotFoundError:先核对发行包与实际 import 名,再检查当前项目模块列表及安装结果;缺少依赖时补齐 Wheel、重新检查预览并安装。</li>

<li>wheel_incompatible:按实际运行目标重新准备文件。wheel_version_conflict:上传集合中同一发行包只保留一个版本。</li>

</ul></div>

</article>

<article class="doc-card">

<h3 id="python-package-maintenance">更新或删除项目模块</h3>

<p>模块列表仅影响当前项目。更新、删除均先显示变更预览,确认后才安装或应用。</p>

<ul>

<li>更新模块:点击“添加”,上传该模块的新版本及所需依赖,核对版本变化后安装。每个发行包只保留一个版本。</li>

<li>删除模块:点击该行“删除”,核对清单并点击“确认删除”。其他模块仍依赖它时,先处理依赖提示。</li>

<li>上传接受 .whl 和 Wheel 合集 ZIP;源码压缩包、安装程序、requirements.txt 不能直接导入。一次最多选择 256 个文件,总大小须小于 512 MiB。</li>

<li>安装使用内置或已提供的 Wheel,不自动从互联网补齐缺失依赖。准备机上的 pip install 不会改变宿主机项目环境。</li>

</ul>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-10">8. 配置正式运行入口</h3>

<p>位置:Settings → Python → Python 模块 → 高级项目配置 → 项目运行配置。将已验证的 main(ctx) 登记为事件入口。</p>

<ol>

<li>选择当前项目,查看 hqapp.toml 编辑区。保留系统生成的 id、package、python 与 worker 设置。</li>

<li>找到 [[entrypoints]] 中 id = &quot;main&quot; 的条目。如果已有该条目,修改它,不添加重复 id。</li>

<li>把 target 设置为清单实际 package 值加上 .scripts.main:main。例如 package 为 equipment_integration 时,目标为 equipment_integration.scripts.main:main;将示例包名替换为当前项目的实际 package 值。</li>

<li>本练习保持 class = &quot;normal-python&quot;、timeout_seconds = 30、max_concurrency = 1。点击“保存并应用配置”。</li>

<li>等待应用成功,再到运行页绑定动作。保存失败时保留错误信息并修正,不把源码已保存当成版本已生效。</li>

</ol>

<p class="doc-step-result"><strong>完成标志</strong> 应用后,“选择脚本”应出现该入口;未出现时核对保存结果和 target。</p>

</article>

<article class="doc-card">

<h3 id="python-schedule">9. 建立定时事件并绑定脚本动作</h3>

<p>事件每 60 秒触发一次,动作选择计算脚本。绑定完成前保持事件停用。</p>

<ol>

<li>进入配置中心 → script → 运行(Run)→ 事件(Events),点击“新增事件(New Event)”。</li>

<li>事件名称 / 编码填写 ScaleCheck;来源类型选择“定时循环(Timed Loop)”,循环周期填 60,最小触发间隔填 0。取消事件启用后点击“保存事件”。</li>

<li>选中 ScaleCheck,在“事件绑定的动作”点击“新增动作”。动作编码填写 ScaleValue,类型选择“脚本动作(Script Action)”。</li>

<li>在“选择脚本”选择上一步已登记的 main 脚本,保持动作启用,点击“保存动作绑定(Save Action Binding)”。</li>

<li>回到事件属性,勾选事件启用并保存。等待一个完整周期后打开“记录(Records)”,查看对应事件、动作、时间和执行结果。</li>

<li>练习完成后回到事件属性,取消启用并保存,停止周期运行。</li>

</ol>

<p class="doc-step-result"><strong>完成标志</strong> 记录显示事件执行成功;详情中的返回结果为 {scaled: 5.0},输出包含 Scaled value: 5.0。main(ctx) 默认 raw=50,无需额外输入。</p>

<div class="doc-step-troubleshooting"><p><strong>如果结果不一致</strong></p><ul>

<li>没有记录:检查事件和动作是否都启用、脚本是否选中,以及 Script 服务状态。</li>

<li>记录失败:打开该次执行的错误;若函数路径错误,修正项目配置的 target 后重新应用。</li>

<li>有记录但不是本次结果:按事件、动作和时间核对,避免把之前的代码页运行当成定时执行。</li>

</ul></div>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-11">10. 读取 Tag 并核对质量</h3>

<p>复制本地点位 TagRef,直接替换 tags.read(...) 中的 URI 并运行。Run Test 仅从读取调用的字面量授予范围,变量不适用。对照 Tag 测试核对值与质量。tags.read_many([...]) 每次最多 256 个不重复输入,须逐项检查 ok。</p>

<ol>

<li>准备已在 Tag 测试中显示有效数值的点位,记录其当前值与质量。</li>

<li>从设备目录取得当前本地站点的完整 hq://io/... TagRef;设备名称.Tag 编码与该 URI 是不同表示,不要混填。远端 Tag 不在 Run Test 的本地读取范围内。</li>

<li>在 scripts/ 创建 read_value,把下方 tags.read(...) 引号内的示例 URI 替换成实际 URI。</li>

<li>保存并通过 Run Test 运行 read_value.py,核对 Value 和 Quality。</li>

<li>如果现场值正在变化,以接近的采集时刻比较;先确认质量正常,再判断数值差异。</li>

</ol>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>scripts/read_value.py</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: scripts/read_value.py">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># scripts/read_value.py</span></span>

<span class="line"><span style="color:#D73A49">from</span><span style="color:#24292E"> hqcontrol </span><span style="color:#D73A49">import</span><span style="color:#24292E"> tags</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">def</span><span style="color:#6F42C1"> main</span><span style="color:#24292E">(ctx):</span></span>

<span class="line"><span style="color:#24292E"> response </span><span style="color:#D73A49">=</span><span style="color:#24292E"> tags.read(</span><span style="color:#032F62">"hq://io/&#x3C;io-domain-id>/&#x3C;device-uid>/&#x3C;tag-uid>"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#D73A49"> not</span><span style="color:#24292E"> response </span><span style="color:#D73A49">or</span><span style="color:#24292E"> response.get(</span><span style="color:#032F62">"ok"</span><span style="color:#24292E">) </span><span style="color:#D73A49">is</span><span style="color:#D73A49"> not</span><span style="color:#005CC5"> True</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Tag read failed"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#24292E"> snapshot </span><span style="color:#D73A49">=</span><span style="color:#24292E"> response[</span><span style="color:#032F62">"value"</span><span style="color:#24292E">]</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#D73A49"> not</span><span style="color:#24292E"> tags.is_good_quality(snapshot):</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Tag quality is not good"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#005CC5"> print</span><span style="color:#24292E">(</span><span style="color:#032F62">"Value:"</span><span style="color:#24292E">, snapshot.get(</span><span style="color:#032F62">"value"</span><span style="color:#24292E">), </span><span style="color:#032F62">"Quality:"</span><span style="color:#24292E">, snapshot.get(</span><span style="color:#032F62">"quality"</span><span style="color:#24292E">))</span></span>

<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> {</span><span style="color:#032F62">"value"</span><span style="color:#24292E">: snapshot.get(</span><span style="color:#032F62">"value"</span><span style="color:#24292E">)}</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">if</span><span style="color:#005CC5"> __name__</span><span style="color:#D73A49"> ==</span><span style="color:#032F62"> "__main__"</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#24292E"> main({})</span></span>

<span class="line"></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 输出目标值和 good 质量;缺值或质量异常时保留错误状态。</p>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-12">直接调用类型化方法</h3>

<p>工作台也提供无需导入的 ReadTagBoolean、ReadTagInteger、ReadTagDouble、ReadTagString、ReadTagJson、ReadTagDateTime、ReadTagQuality 和对应 WriteTag 方法。Run Test 中每次读取的首个参数都应直接填写本地点位名称字面量;变量形式不授予读取范围。ReadTagQuality 将状态归为 good/bad;需要详细质量和时间戳时使用 tags.read。</p>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>Python</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: Python">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">if</span><span style="color:#24292E"> ReadTagQuality(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">) </span><span style="color:#D73A49">!=</span><span style="color:#032F62"> "good"</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Tag quality is not good"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#005CC5">print</span><span style="color:#24292E">(ReadTagDouble(</span><span style="color:#032F62">"&#x3C;device-code>.&#x3C;tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">0.0</span><span style="color:#24292E">))</span></span>

<span class="line"></span>

<span class="line"><span style="color:#24292E">calculated </span><span style="color:#D73A49">=</span><span style="color:#24292E"> ReadCalculatedTag(</span><span style="color:#032F62">"&#x3C;calculated-tag-code>"</span><span style="color:#24292E">, </span><span style="color:#005CC5">None</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49">if</span><span style="color:#24292E"> calculated </span><span style="color:#D73A49">is</span><span style="color:#005CC5"> None</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Calculated value is unavailable"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#005CC5">print</span><span style="color:#24292E">(calculated)</span></span></code></pre></div>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-13">11. 保存受控写入方法</h3>

<p>保存为 common/tag_control.py。函数从完整读取响应取得 TagRef 和 expected 快照,写入一次并核对状态。0–100 须改为设备允许范围。Run Test 不授予写权限,仅由具备目标读写范围的正式动作调用。</p>

<ol>

<li>在 common/ 创建 tag_control,粘贴下方代码并保存。此步骤只定义方法,不提交写入。</li>

<li>若要在正式动作中使用,先选择经允许的本地数值设定点,确认示例值 12.5 在设备允许范围内,并记录原值。</li>

<li>确认动作触发上下文授予同一目标 TagRef 的读写范围,目标允许写入且当前采集质量正常。</li>

</ol>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>common/tag_control.py</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: common/tag_control.py">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># common/tag_control.py</span></span>

<span class="line"><span style="color:#D73A49">from</span><span style="color:#24292E"> hqcontrol </span><span style="color:#D73A49">import</span><span style="color:#24292E"> tags</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">def</span><span style="color:#6F42C1"> write_setpoint</span><span style="color:#24292E">(read_response, value):</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#005CC5"> isinstance</span><span style="color:#24292E">(value, </span><span style="color:#005CC5">bool</span><span style="color:#24292E">) </span><span style="color:#D73A49">or</span><span style="color:#D73A49"> not</span><span style="color:#005CC5"> isinstance</span><span style="color:#24292E">(value, (</span><span style="color:#005CC5">int</span><span style="color:#24292E">, </span><span style="color:#005CC5">float</span><span style="color:#24292E">)) </span><span style="color:#D73A49">or</span><span style="color:#D73A49"> not</span><span style="color:#005CC5"> 0</span><span style="color:#D73A49"> &#x3C;=</span><span style="color:#24292E"> value </span><span style="color:#D73A49">&#x3C;=</span><span style="color:#005CC5"> 100</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> ValueError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Setpoint must be a number between 0 and 100"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#D73A49"> not</span><span style="color:#24292E"> read_response </span><span style="color:#D73A49">or</span><span style="color:#24292E"> read_response.get(</span><span style="color:#032F62">"ok"</span><span style="color:#24292E">) </span><span style="color:#D73A49">is</span><span style="color:#D73A49"> not</span><span style="color:#005CC5"> True</span><span style="color:#24292E">:</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Cannot read the target Tag"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#24292E"> tag_ref </span><span style="color:#D73A49">=</span><span style="color:#24292E"> read_response.get(</span><span style="color:#032F62">"tagRef"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#D73A49"> not</span><span style="color:#005CC5"> isinstance</span><span style="color:#24292E">(tag_ref, </span><span style="color:#005CC5">str</span><span style="color:#24292E">) </span><span style="color:#D73A49">or</span><span style="color:#D73A49"> not</span><span style="color:#24292E"> tag_ref.startswith(</span><span style="color:#032F62">"hq://io/"</span><span style="color:#24292E">):</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Read response has no physical TagRef"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#24292E"> snapshot </span><span style="color:#D73A49">=</span><span style="color:#24292E"> read_response[</span><span style="color:#032F62">"value"</span><span style="color:#24292E">]</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#D73A49"> not</span><span style="color:#24292E"> tags.is_good_quality(snapshot):</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Target quality is not good"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#24292E"> result </span><span style="color:#D73A49">=</span><span style="color:#24292E"> tags.write(tag_ref, </span><span style="color:#005CC5">float</span><span style="color:#24292E">(value), </span><span style="color:#E36209">value_type</span><span style="color:#D73A49">=</span><span style="color:#032F62">"number"</span><span style="color:#24292E">,</span></span>

<span class="line"><span style="color:#E36209"> reason</span><span style="color:#D73A49">=</span><span style="color:#032F62">"Approved setpoint update"</span><span style="color:#24292E">, </span><span style="color:#E36209">expected</span><span style="color:#D73A49">=</span><span style="color:#24292E">snapshot)</span></span>

<span class="line"><span style="color:#D73A49"> if</span><span style="color:#24292E"> (result.get(</span><span style="color:#032F62">"accepted"</span><span style="color:#24292E">) </span><span style="color:#D73A49">is</span><span style="color:#D73A49"> not</span><span style="color:#005CC5"> True</span><span style="color:#D73A49"> or</span><span style="color:#24292E"> result.get(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">) </span><span style="color:#D73A49">!=</span><span style="color:#032F62"> "done"</span></span>

<span class="line"><span style="color:#D73A49"> or</span><span style="color:#24292E"> result.get(</span><span style="color:#032F62">"outcomeUncertain"</span><span style="color:#24292E">) </span><span style="color:#D73A49">is</span><span style="color:#005CC5"> True</span></span>

<span class="line"><span style="color:#D73A49"> or</span><span style="color:#24292E"> result.get(</span><span style="color:#032F62">"operatorVerificationRequired"</span><span style="color:#24292E">) </span><span style="color:#D73A49">is</span><span style="color:#005CC5"> True</span></span>

<span class="line"><span style="color:#D73A49"> or</span><span style="color:#24292E"> result.get(</span><span style="color:#032F62">"transportOnly"</span><span style="color:#24292E">) </span><span style="color:#D73A49">is</span><span style="color:#005CC5"> True</span><span style="color:#24292E">):</span></span>

<span class="line"><span style="color:#D73A49"> raise</span><span style="color:#005CC5"> RuntimeError</span><span style="color:#24292E">(</span><span style="color:#032F62">"Verify command "</span><span style="color:#D73A49"> +</span><span style="color:#005CC5"> str</span><span style="color:#24292E">(result.get(</span><span style="color:#032F62">"commandId"</span><span style="color:#24292E">)) </span><span style="color:#D73A49">+</span></span>

<span class="line"><span style="color:#032F62"> "; status="</span><span style="color:#D73A49"> +</span><span style="color:#005CC5"> str</span><span style="color:#24292E">(result.get(</span><span style="color:#032F62">"status"</span><span style="color:#24292E">)) </span><span style="color:#D73A49">+</span></span>

<span class="line"><span style="color:#032F62"> "; accepted="</span><span style="color:#D73A49"> +</span><span style="color:#005CC5"> str</span><span style="color:#24292E">(result.get(</span><span style="color:#032F62">"accepted"</span><span style="color:#24292E">)))</span></span>

<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> result</span></span>

<span class="line"></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 保存完成;调用 write_setpoint 时才写入。</p>

</article>

<article class="doc-card">

<h3 id="python-write-entry">12. 正式动作中的受控写入参考</h3>

<p>此入口仅供正式动作使用,Run Test 不授予写入范围。触发上下文须含目标 TagRef,并满足权限、设备和运行条件。</p>

<ol>

<li>在 scripts/ 创建 write_setpoint,复制下方入口并完成后续替换;仅保存代码不会提交写入。</li>

<li>把 tags.read(...) 引号内的 URI 替换为已批准的本地设定点 TagRef,把 12.5 改为本次批准数值。</li>

<li>在 Run 页配置正式动作前,核对触发规则是否提供同一 TagRef 的读写范围,并排除写入再次触发本动作的循环。不要用 Code 页的 Run Test 验证写入。</li>

<li>正式动作执行后,在 Records 中查看命令结果,并在 Tag 测试核对后续采集值;结果不确定时按 commandId 查记录,确认前不重复写入。</li>

</ol>

<div class="manual-code" data-code-language="python"><div class="manual-code-toolbar"><span>scripts/write_setpoint.py</span><span class="manual-code-tools"><small>Python</small><button type="button" data-copy-code data-copied="已复制" data-failed="请选中代码后复制" aria-label="复制代码: scripts/write_setpoint.py">复制代码</button><span class="manual-code-status" role="status"></span></span></div><pre tabindex="0" class="docs-code-block shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># scripts/write_setpoint.py</span></span>

<span class="line"><span style="color:#D73A49">from</span><span style="color:#24292E"> hqcontrol </span><span style="color:#D73A49">import</span><span style="color:#24292E"> tags</span></span>

<span class="line"><span style="color:#D73A49">from</span><span style="color:#24292E"> ..common.tag_control </span><span style="color:#D73A49">import</span><span style="color:#24292E"> write_setpoint</span></span>

<span class="line"></span>

<span class="line"><span style="color:#D73A49">def</span><span style="color:#6F42C1"> main</span><span style="color:#24292E">(ctx):</span></span>

<span class="line"><span style="color:#24292E"> response </span><span style="color:#D73A49">=</span><span style="color:#24292E"> tags.read(</span><span style="color:#032F62">"hq://io/&#x3C;io-domain-id>/&#x3C;device-uid>/&#x3C;tag-uid>"</span><span style="color:#24292E">)</span></span>

<span class="line"><span style="color:#D73A49"> return</span><span style="color:#24292E"> write_setpoint(response, </span><span style="color:#005CC5">12.5</span><span style="color:#24292E">)</span></span>

<span class="line"></span></code></pre></div>

<p class="doc-step-result"><strong>完成标志</strong> 函数仅在 accepted=true、status=done 且无不确定或人工核查标识时返回,仍须核对后续正常采集值。proxied 等其他状态保留 commandId 核查,不重复写入。</p>

</article>

<article class="doc-card">

<h3 id="python-runtime-status">查看脚本运行状态</h3>

<p>位置:配置中心 → script → 状态。概览与工作进程用于查看服务和执行资源;单次执行详情仍在“记录”中查看。</p>

<ul>

<li>概览:查看服务健康、工作进程摘要、触发器、连接器、调度通道和已采集的 Tag 质量。</li>

<li>工作进程:查看就绪、忙碌、启动中、排空中等状态,以及冷启动、热命中和并发拒绝计数。</li>

<li>服务健康不代表每个业务动作成功。遇到排队或执行失败,结合“记录”中的事件、动作、时间及错误信息排查。</li>

</ul>

</article>

<article class="doc-card">

<h3 id="python-scripting-item-14">运行结果与常见错误</h3>

<p>完成示例后,控制台应输出 5.0,依赖版本与安装清单一致,读取质量为 good。写入结果需结合命令状态和后续采集值确认。</p>

<ul>

<li>ModuleNotFoundError:检查发行包与实际 import 名、间接依赖、当前项目模块列表及安装结果。</li>

<li>wheel_incompatible:重新准备匹配 cp313、操作系统和架构的 Wheel;容器以容器内环境为准。</li>

<li>wheel_version_conflict:上传集合中同一发行包只保留一个版本,再重新检查安装预览。</li>

<li>相对导入失败:检查 scripts/ 层级与点数,或改用清单实际 package 的绝对导入。</li>

<li>Tag 无值:核对授权范围、TagRef、采集质量与服务;fallback 只用于显示备用值,不代表现场数值正常。</li>

<li>写入超时、pending_verification 或 outcome_uncertain:按 commandId 和现场回读核查,不自动重发。</li>

</ul>

</article>

</div>

<div class="docs-note-grid manual-doc-image-grid">

<figure class="docs-note-card manual-doc-image-card" id="python-scripting-image-1" data-after-card-anchor="python-scripting-item-5">

<img src="/WEB_img/HQserver/current-20261025/python-local-import.jpg" alt="Python 模块帮助中的 common 公共函数与 scripts 相对导入示例" loading="lazy" decoding="async" />

<figcaption>

<strong>图示 1: 同项目公共函数导入</strong>

<p>common/ 中的函数由 scripts/ 相对导入,无需作为第三方模块安装;跨项目共享再构建 Wheel。</p>

</figcaption>

</figure>

<figure class="docs-note-card manual-doc-image-card" id="python-scripting-image-2" data-after-card-anchor="python-scripting-item-6">

<img src="/WEB_img/HQserver/current-20261025/python-modules.jpg" alt="Settings 的 Python 模块管理界面,显示项目、运行环境和添加模块入口" loading="lazy" decoding="async" />

<figcaption>

<strong>图示 2: 按项目管理 Python 模块</strong>

<p>选择项目后先核对运行环境;添加内置模块或上传 Wheel 时,都在当前项目中检查并安装。</p>

</figcaption>

</figure>

<figure class="docs-note-card manual-doc-image-card" id="python-scripting-image-3" data-after-card-anchor="python-scripting-item-8">

<img src="/WEB_img/HQserver/current-20261025/python-install-help.jpg" alt="Python 模块帮助中的网页导入与服务器目录导入步骤" loading="lazy" decoding="async" />

<figcaption>

<strong>图示 3: 上传与安装路径</strong>

<p>网页导入是常用入口;服务器离线目录位于高级配置,检查目录后仍须确认安装。</p>

</figcaption>

</figure>

</div>

</div>