Product: Mingcedar CONTROL
Python Projects and Packages | Mingcedar
Confirm the Script service is available. Code management requires super-admin and integration:manage; Run Test also requires integration:execute. Python Packages requires system:view and system:manage. Follow the exercises for calculations, dependencies, events, and Tag access.
<div class="manual-doc-article manual-doc-article-section"><h2 id="python-scripting">Developer tutorial: Python Projects and Packages</h2>
Use CPython 3.13 to create projects, reuse functions, install dependencies, read Tags, configure events, and authorize writes.
<div class="doc-card-list doc-tutorial">
<article class="doc-card">
<h3 id="python-scripting-item-1">Before you start</h3>
<p>Confirm the Script service is available. Code management requires super-admin and integration:manage; Run Test also requires integration:execute. Python Packages requires system:view and system:manage. Follow the exercises for calculations, dependencies, events, and Tag access.</p>
<ul>
<li>Code creates projects and edits files. The toolbar's Run Test executes the selected Python file; unsaved changes change the button to Save & Test.</li>
<li>Run configures when an event fires and which actions it executes. Records shows event execution results.</li>
<li>Settings → Python → Python Packages selects a project and manages its packages. Advanced Project Settings manages entrypoints, runtime limits, the server offline directory, environment variables, and Secrets.</li>
<li>Replace angle-bracket placeholders. Keep the tutorial filenames, function names, and practice values as shown.</li>
</ul>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-2">1. Create the project and two Python files</h3>
<p>Location: Configuration Center → script → Code. Create the project, then add its files.</p>
<ol>
<li>Click Manage Projects, then New in the dialog.</li>
<li>Enter Equipment Integration as the Project name and click Create Project. Close the dialog and select this project in the Code page dropdown.</li>
<li>Click the new-file + beside common/, enter scaling, and click Create. The editor adds .py.</li>
<li>Create main under scripts/ in the same way. If main.py already exists, select it instead.</li>
<li>Check the editor title: the two paths must be common/scaling.py and 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="Copied" data-failed="Select and copy the code" aria-label="Copy code: Text">Copy code</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>Expected result</strong> Both files are selectable with the expected paths. The tree shows the file layout.</p>
<div class="doc-step-troubleshooting"><p><strong>If the result differs</strong></p><ul>
<li>If creation controls are unavailable, check script management permission. Correct a misplaced file before copying code.</li>
</ul></div>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-3">2. Save the shared function</h3>
<p>Convert the input range 0–100 to 0–10.</p>
<ol>
<li>Select common/scaling.py and verify the editor path.</li>
<li>Replace its contents with the complete code below and click Save.</li>
<li>Wait for the Unsaved marker to disappear before changing files. This module defines a function and does not need to run separately.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: common/scaling.py">Copy code</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>Expected result</strong> No output is expected until main.py calls the saved function.</p>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-4">3. Call the function and inspect the console</h3>
<p>Events call main(ctx); the Code page calls the same function through the __main__ block.</p>
<ol>
<li>Select scripts/main.py, paste the complete code below, and save.</li>
<li>Keep main.py selected and click Run Test in the toolbar. Do not run scaling.py.</li>
<li>Inspect the execution status and output in Console below the editor.</li>
<li>Change 50 to 25 in main({"raw": 50}), save, and run again. Verify 2.5, then restore 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="Copied" data-failed="Select and copy the code" aria-label="Copy code: scripts/main.py">Copy code</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>Expected result</strong> Expect Scaled value: 5.0 with status Completed, then 2.5 after changing the input.</p>
<div class="doc-step-troubleshooting"><p><strong>If the result differs</strong></p><ul>
<li>Completed with no output: verify the selected file and its if __name__ == "__main__" block.</li>
<li>ImportError: keep both files in the same project under scripts/ and common/. Do not run this relative-import example directly from an OS terminal.</li>
</ul></div>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-5">Import rules for shared functions and custom packages</h3>
<p>scripts/main.py uses from ..common.scaling import scale_linear. A file at scripts/controls/main.py needs three leading dots. Absolute imports must use the actual package value in the project manifest.</p>
<ul>
<li>Within a project, common/ is built and applied with the calling scripts. Use a relative import; no installation in Python Packages is needed. Save changes and verify the applied runtime version.</li>
<li>Across projects, build custom code as a Wheel with an explicit version and dependency declarations. Install it in each consuming project using the workflow below. Then use a normal import with the package's actual module name, not its Wheel filename.</li>
<li>Do not extend sys.path to host folders or import another project through its storage path.</li>
</ul>
<p class="doc-step-result"><strong>Expected result</strong> Save changes to scaling.py and rerun main.py. Cross-project reuse requires an installed dependency, not another project’s path.</p>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-6">4. Identify this project’s Python runtime</h3>
<p>Dependencies are installed per project, not inherited from the preparation machine or system Python. The Packages page requires the system permissions listed above.</p>
<ul>
<li>json, csv, sqlite3, datetime, urllib, and smtplib are standard-library modules and need no installation.</li>
<li>Bundled packages include requests, psycopg, PyMySQL, pyodbc, paho-mqtt, websocket-client, and cryptography. New projects install them when their environment is first built. Existing projects can use Add Bundled Packages; previously selected versions are retained.</li>
<li>Install additional packages such as openpyxl for Excel as needed. A pyodbc database connection also requires the corresponding ODBC driver on the server.</li>
<li>Distribution names can differ from imports: PyMySQL → pymysql; websocket-client → websocket.</li>
</ul>
<ol>
<li>Open Settings → Python → Python Packages and select Equipment Integration.</li>
<li>Read Runtime above the package list and record the Python version, operating system, and architecture. The current target is Python 3.13.</li>
<li>Check the package list for the names and versions needed in this exercise. Its heading may read Project Packages, Prepared Packages, or Installed Packages depending on the project's state.</li>
<li>For a Linux container, use the target inside the container. Native Windows and Linux deployments use their respective targets.</li>
</ol>
<p class="doc-step-result"><strong>Expected result</strong> Use the confirmed project runtime target to select bundled packages or compatible Wheels.</p>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-7">5. Choose bundled packages or prepare Wheels and dependencies</h3>
<p>Bundled packages need no download. Download other dependencies at approved exact versions on a preparation machine matching the target OS, CPU and Python 3.13. Run these commands only there.</p>
<ol>
<li>Check the project package list first. If psycopg and PyMySQL are available as bundled packages, click Add Bundled Packages, check every package proposed in the preview, and continue at step 7 without downloading them again.</li>
<li>For another third-party package or a specific version, run python --version on an online preparation machine matching the server's OS, architecture, and Python version. This example prepares psycopg and PyMySQL for the SQL tutorial; omit one package and its import check if using only one database.</li>
<li>Replace each <approved-version> with the exact version of its package. Keep the quotes; do not execute placeholders literally.</li>
<li>Run pip download, wait for success, and inspect ./wheelhouse for both the main packages and their dependency Wheels.</li>
<li>Build custom packages as .whl files with explicit versions and dependency declarations. Optionally run the offline installation and import check below in a temporary virtual environment to verify complete dependencies and actual import names before upload.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: Shell">Copy code</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]==<approved-version>"</span><span style="color:#032F62"> "PyMySQL==<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"> "<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"><</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]==<approved-version>"</span><span style="color:#032F62"> "PyMySQL==<approved-version>"</span></span>
<span class="line"><span style="color:#D73A49"><</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>Expected result</strong> Confirm bundled packages in the preview or the required .whl files in the download folder. The offline probe prints Dependencies ready; verify again in the target project.</p>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-8">6. Upload Wheels and review the installation preview</h3>
<p>Prefer a matching preparation environment. If cross-downloading is necessary, specify --platform, --python-version 3.13, --implementation cp, --abi cp313, and --only-binary=:all:. The platform tag must match the actual runtime, including glibc/musl compatibility.</p>
<ol>
<li>Check each Wheel's distribution, version, Python/ABI/platform tags, and SHA-256. Keep one intended version per distribution and include all transitive dependencies.</li>
<li>In Settings → Python → Python Packages, select the project and click Add to select the required .whl files. You can also upload a ZIP containing only the complete Wheel dependency set, or drop files onto the package list.</li>
<li>Review the Review Installation preview for added or updated packages and versions. If it reports missing dependencies, add the missing Wheels before submitting the installation.</li>
<li>Use the server directory only when needed: expand Advanced Project Settings → Server Offline Directory, copy regular .whl files to the displayed Project directory, and click Refresh Inventory. Then return to the package list, expand Offline Directory Import, and click Check Directory Packages. Do not place ZIPs, source archives, folders, or symlinks in that directory.</li>
<li>For Linux containers, use the path inside the container and ensure the service account can read it. Refresh Inventory only scans the directory; complete the preview and installation in the next step.</li>
</ol>
<p class="doc-step-result"><strong>Expected result</strong> The preview shows the intended changes without compatibility, version, or missing-dependency errors. The runtime is unchanged.</p>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-9">7. Install packages and verify imports</h3>
<p>Submit after reviewing the preview. New projects can install packages for Code-page runs; projects with a running version apply the new version after checks pass.</p>
<ol>
<li>After reviewing the preview, if the required packages are already in this project's package list, proceed to the Code-page import check. Otherwise click Install in Project or Install and Apply as shown. The former prepares a project without a running version for manual runs; the latter checks and applies a new runtime version.</li>
<li>If you submitted an installation, wait for the “Packages are installed in the project” or “Packages installed and applied” result. The first result permits manual runs from Code, but an entrypoint must be configured below before an event can run automatically.</li>
<li>Return to Code and create check_dependencies under scripts/. Paste the code below. For PostgreSQL only, remove import pymysql and the PyMySQL version(...) print line; for MySQL only, remove import psycopg and the psycopg version(...) print line. Then save.</li>
<li>Select check_dependencies.py, click Run Test, and compare Python and installed-package versions in Console.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: scripts/check_dependencies.py">Copy code</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>Expected result</strong> Console reports Python 3.13, the versions of the one or two packages installed, and Dependencies ready.</p>
<div class="doc-step-troubleshooting"><p><strong>If the result differs</strong></p><ul>
<li>ModuleNotFoundError: match the distribution to its actual import name, then check this project's package list and installation result. Supply missing Wheels, review the preview again, and install.</li>
<li>wheel_incompatible: prepare Wheels for the actual runtime target. wheel_version_conflict: retain one version per distribution in the upload set.</li>
</ul></div>
</article>
<article class="doc-card">
<h3 id="python-package-maintenance">Update or remove project packages</h3>
<p>The package list applies to the selected project. Updates and removals show a preview before installation or application.</p>
<ul>
<li>To update, choose Add and upload the new package version with its dependencies. Review the version changes before installing. Keep one version per distribution.</li>
<li>To remove, choose Delete beside a package, review the list, then Confirm Delete. Resolve dependency warnings if other packages still require it.</li>
<li>Uploads accept .whl files and Wheel bundle ZIPs, not source archives, installers, or requirements.txt. Select at most 256 files with a combined size below 512 MiB.</li>
<li>Installation uses bundled or supplied Wheels; missing dependencies are not downloaded automatically. Running pip install on a preparation computer does not change the host project environment.</li>
</ul>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-10">8. Configure the runtime entrypoint</h3>
<p>Location: Settings → Python → Python Packages → Advanced Project Settings → Project Runtime Configuration. Register the verified main(ctx) as an event entrypoint.</p>
<ol>
<li>Select the project and inspect hqapp.toml. Keep the generated id, package, python, and worker settings.</li>
<li>Locate the [[entrypoints]] item with id = "main". Edit an existing item rather than adding a duplicate id.</li>
<li>Set target to the actual package value followed by .scripts.main:main. For package equipment_integration, that is equipment_integration.scripts.main:main; do not copy a package name absent from your manifest.</li>
<li>For this exercise use class = "normal-python", timeout_seconds = 30, and max_concurrency = 1. Click Save and Apply Configuration.</li>
<li>Wait for successful application before binding the action on Run. Source persistence alone does not establish that the runtime version is active.</li>
</ol>
<p class="doc-step-result"><strong>Expected result</strong> After application, the entrypoint appears in the action’s Script selector. If absent, check the save result and target.</p>
</article>
<article class="doc-card">
<h3 id="python-schedule">9. Create a scheduled event and bind a script action</h3>
<p>The event triggers every 60 seconds; its action selects the calculation script. Keep it disabled until binding is complete.</p>
<ol>
<li>Open Configuration Center → script → Run → Events and click New Event.</li>
<li>Enter ScaleCheck as Event Name / Code. Choose Timed Loop, set Interval (seconds) to 60 and Minimum Trigger Interval to 0. Uncheck Event Enabled and click Save Event.</li>
<li>Select ScaleCheck, then New Action under Actions Bound to Event. Enter ScaleValue as Action Code and choose Script Action.</li>
<li>Select the registered main script in Script, keep the action enabled, and click Save Action Binding.</li>
<li>Edit Event Properties, enable the event, and save. After a full interval, inspect the matching event, action, timestamp, and result in Records.</li>
<li>After the exercise, disable the event in Event Properties and save to stop the schedule.</li>
</ol>
<p class="doc-step-result"><strong>Expected result</strong> Records shows successful execution. The details show {scaled: 5.0} as the return value and Scaled value: 5.0 in the output. main(ctx) defaults raw to 50, so no extra input is needed.</p>
<div class="doc-step-troubleshooting"><p><strong>If the result differs</strong></p><ul>
<li>No record: check that both event and action are enabled, a script is selected, and the Script service is available.</li>
<li>Failed record: inspect that execution’s error. Correct an invalid target in Project Configuration and apply it again.</li>
<li>Wrong result: match the event, action, and timestamp; a previous Code-page run does not verify the schedule.</li>
</ul></div>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-11">10. Read a Tag and verify quality</h3>
<p>Replace the URI directly inside tags.read(...) with a local TagRef. Run Test grants read scope from literal arguments, not variables. Compare value and quality with Tag Test. tags.read_many([...]) accepts up to 256 unique inputs; check each item’s ok.</p>
<ol>
<li>Choose a Tag showing a valid number in Tag Test and record its value and quality.</li>
<li>Obtain the complete hq://io/... TagRef for the current local Site. A device-code.tag-code string and this URI are different representations. Remote Tags are outside Run Test's local read scope.</li>
<li>Create read_value under scripts/ and replace the example URI inside tags.read(...) with the actual URI.</li>
<li>Save and run read_value.py with Run Test; inspect Value and Quality.</li>
<li>For a changing signal, compare near the same acquisition time. Establish good quality before evaluating value differences.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: scripts/read_value.py">Copy code</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/<io-domain-id>/<device-uid>/<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>Expected result</strong> Expect the target value with good quality; missing or poor-quality data remains an error.</p>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-12">Direct typed methods</h3>
<p>The workbench also provides ReadTagBoolean, ReadTagInteger, ReadTagDouble, ReadTagString, ReadTagJson, ReadTagDateTime, ReadTagQuality, and matching WriteTag methods without an import. For Run Test, put a literal local Tag name in the first argument of each read; a variable does not grant read scope. ReadTagQuality maps status to good/bad; use tags.read for detailed quality and timestamps.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: Python">Copy code</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">"<device-code>.<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">"<device-code>.<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">"<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. Save a controlled write function</h3>
<p>Save as common/tag_control.py. The function takes TagRef and expected from the read response, writes once, and checks status. Replace 0–100 with the equipment range. Run Test grants no write scope; call from a formal action with target read/write scope.</p>
<ol>
<li>Create tag_control under common/, paste the code below, and save. Defining this function does not submit a write.</li>
<li>Before using it in a formal action, choose an approved local numeric setpoint, confirm that 12.5 is within its allowed range, and record the original value.</li>
<li>Verify that the action trigger grants read and write scope for the same target TagRef, the target is writable, and current acquisition quality is good.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: common/tag_control.py">Copy code</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"> <=</span><span style="color:#24292E"> value </span><span style="color:#D73A49"><=</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>Expected result</strong> Saved; writing occurs only when write_setpoint is called.</p>
</article>
<article class="doc-card">
<h3 id="python-write-entry">12. Controlled write pattern for a formal action</h3>
<p>Use this entrypoint in a formal action; Run Test grants no write scope. The trigger context must include the target TagRef and satisfy permission, equipment, and runtime conditions.</p>
<ol>
<li>Create write_setpoint under scripts/, copy the entrypoint below, and complete the replacements before saving. Saving source alone does not submit a write.</li>
<li>Replace the URI inside tags.read(...) with the approved local setpoint TagRef, and replace 12.5 with the approved value.</li>
<li>Before configuring the formal action in Run, confirm that its trigger grants read/write scope for this same TagRef and that the write cannot retrigger the action in a loop. Do not use Code-page Run Test to verify writing.</li>
<li>After the formal action runs, inspect command results in Records and subsequent acquisition in Tag Test. If uncertain, reconcile the commandId before submitting another write.</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="Copied" data-failed="Select and copy the code" aria-label="Copy code: scripts/write_setpoint.py">Copy code</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/<io-domain-id>/<device-uid>/<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>Expected result</strong> The function returns only for accepted=true, status=done, and no uncertainty or operator-verification flags. Verify later normal collection. For proxied or other states, retain commandId for reconciliation without repeating the write.</p>
</article>
<article class="doc-card">
<h3 id="python-runtime-status">Inspect Script runtime status</h3>
<p>Location: Configuration Center → script → Status. Overview and Workers show service health and execution resources; use Records for individual executions.</p>
<ul>
<li>Overview shows service health, worker summaries, triggers, connectors, scheduler lanes, and collected Tag quality.</li>
<li>Workers shows ready, busy, starting, and draining states, along with cold starts, warm hits, and concurrency-rejection counts.</li>
<li>A healthy service does not mean every business action succeeded. For queued or failed work, inspect the event, action, timestamp, and error in Records.</li>
</ul>
</article>
<article class="doc-card">
<h3 id="python-scripting-item-14">Expected results and troubleshooting</h3>
<p>After the examples, the console should show 5.0, dependency versions should match the package list, and read quality should be good. Confirm writes through command status and later collected values.</p>
<ul>
<li>ModuleNotFoundError: check distribution and actual import names, transitive dependencies, the current project's package list, and the installation result.</li>
<li>wheel_incompatible: obtain matching cp313, OS, and CPU Wheels. For containers, match the environment inside the container.</li>
<li>wheel_version_conflict: retain one version per distribution in the upload set, then review a new installation preview.</li>
<li>Relative import error: check script depth and leading dots, or use the actual manifest package name.</li>
<li>Missing Tag data: verify scope, TagRef, collection quality, and service state. A fallback is a display value, not proof that field data is healthy.</li>
<li>Write timeout, pending_verification, or outcome_uncertain: reconcile the commandId and field readback; do not resend automatically.</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 package help showing common functions and relative imports from scripts" loading="lazy" decoding="async" />
<figcaption>
<strong>Figure 1: Import shared functions within a project</strong>
<p>Import common/ functions relatively from scripts/ without installing a package. Build a Wheel to reuse code across projects.</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 Packages page showing the project, runtime and package controls" loading="lazy" decoding="async" />
<figcaption>
<strong>Figure 2: Manage Python packages per project</strong>
<p>Select the project and check its runtime before adding bundled packages or uploading Wheels.</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 package help showing web upload and server directory workflows" loading="lazy" decoding="async" />
<figcaption>
<strong>Figure 3: Upload and installation paths</strong>
<p>Web upload is the usual path. The server offline directory is under advanced settings and still requires an installation review.</p>
</figcaption>
</figure>
</div>
</div>