signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
232 lines (216 loc) • 45.5 kB
HTML
<html class="default" lang="en" data-base="../../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>Processing Data | Signal K</title><meta name="description" content="Documentation for Signal K"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../../assets/style.css?cache=1783009712609"/><link rel="stylesheet" href="../../assets/highlight.css?cache=1783009712609"/><script defer src="../../assets/main.js?cache=1783009712609"></script><script async src="../../assets/icons.js?cache=1783009712609" id="tsd-icons-script"></script><script async src="../../assets/search.js?cache=1783009712609" id="tsd-search-script"></script><script async src="../../assets/navigation.js?cache=1783009712609" id="tsd-nav-script"></script><script async src="../../assets/hierarchy.js?cache=1783009712609" id="tsd-hierarchy-script"></script><link rel="stylesheet" href="../../assets/theme.css"/><script>if(window.self!==window.top){document.documentElement.classList.add('embedded')}</script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../../index.html" class="title"><img class="title-logo" src="../../assets/logo.svg" alt="Signal K"/><svg class="title-home-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="Documentation home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg></a><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../../assets/icons.svg#icon-search"></use></svg><span class="visible@s">Search</span></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><div id="tsd-toolbar-links"><a href="https://discord.gg/uuZrwz4dCS" target="_blank" rel="noopener" class="toolbar-icon visible@s" aria-label="Discord"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"></path></svg></a><a href="https://github.com/SignalK/signalk-server" target="_blank" rel="noopener" class="toolbar-icon visible@s" aria-label="Discord"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg></a><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../../assets/icons.svg#icon-menu"></use></svg></a></div></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="../../Developing.html">Developing</a></li><li><a href="../Plugins.html">Plugins</a></li><li><a href="" aria-current="page">Processing Data</a></li></ul></div><div class="tsd-panel tsd-typography"><h1 id="processing-data-from-the-server" class="tsd-anchor-link">Processing data from the server<a href="#processing-data-from-the-server" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h1>
<p>A plugin will generally want to:</p>
<ol>
<li>Subscribe to data published by the server <em>(i.e. received from a NMEA 2000 bus, etc)</em></li>
<li>Emit data.</li>
</ol>
<p>In both cases the plugin will use <em>deltas</em> which the server uses to signal changes in the Signal K full data model. Delta messages contain the new value associated with a path (not the amount of change from the previous value.)_</p>
<p><em>See the <a href="http://signalk.org/specification/1.7.0/doc/data_model.html#delta-format">Signal K Delta Specification</a> for details.</em></p>
<p>Using the server API, plugins can either:</p>
<ol>
<li>Get the current value of a path in the full model or</li>
<li>Subscribe to a path and access a stream of <em>deltas</em> that updates every time the value is updated.</li>
</ol>
<p>By specifying a context <em>e.g. 'vessels.self'</em> you can limit the number of delta messages received to those of host vesseel.
To receive all deltas you can specify <code>*</code> as the context.</p>
<p>You can also limit the deltas received by the path you supply.
If you supply a specific path <em>e.g. navigation.position</em>, only updates in the value will be received.
Since paths are hierarchical, paths can contain wildcards <em>e.g._navigation.*</em> which will deliver deltas containing updates to all paths under <code>navigation</code>.</p>
<p>The data received is formatted as per the following example:</p>
<pre><code class="javascript"><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-5">path</span><span class="hl-0">: </span><span class="hl-4">'navigation.position'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-5">value</span><span class="hl-0">: { </span><span class="hl-5">longitude</span><span class="hl-0">: </span><span class="hl-2">24.7366117</span><span class="hl-0">, </span><span class="hl-5">latitude</span><span class="hl-0">: </span><span class="hl-2">59.72493</span><span class="hl-0"> },</span><br/><span class="hl-0"> </span><span class="hl-5">context</span><span class="hl-0">: </span><span class="hl-4">'vessel.self'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-5">source</span><span class="hl-0">: {</span><br/><span class="hl-0"> </span><span class="hl-5">label</span><span class="hl-0">: </span><span class="hl-4">'n2k-sample-data'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-5">type</span><span class="hl-0">: </span><span class="hl-4">'NMEA2000'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-5">pgn</span><span class="hl-0">: </span><span class="hl-2">129039</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-5">src</span><span class="hl-0">: </span><span class="hl-4">'43'</span><br/><span class="hl-0"> },</span><br/><span class="hl-0"> </span><span class="hl-5">$source</span><span class="hl-0">: </span><span class="hl-4">'n2k-sample-data.43'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-5">timestamp</span><span class="hl-0">: </span><span class="hl-4">'2014-08-15T19:00:02.392Z'</span><br/><span class="hl-0"> }</span>
</code><button type="button">Copy</button></pre>
<h2 id="reading-the-current-path-value" class="tsd-anchor-link">Reading the current path value<a href="#reading-the-current-path-value" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>The server API provides the following methods for retrieving values from the full data model.</p>
<ul>
<li><code>getSelfPath(path)</code> returns the value of the supplied <code>path</code> in the <code>vessels.self</code> context.</li>
</ul>
<pre><code class="javascript"><span class="hl-1">const</span><span class="hl-0"> </span><span class="hl-2">value</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> app.</span><span class="hl-7">getSelfPath</span><span class="hl-0">(</span><span class="hl-4">'uuid'</span><span class="hl-0">)</span><br/><span class="hl-0">app.</span><span class="hl-7">debug</span><span class="hl-0">(value) </span><span class="hl-6">// Should output something like urn:mrn:signalk:uuid:a9d2c3b1-611b-4b00-8628-0b89d014ed60</span>
</code><button type="button">Copy</button></pre>
<ul>
<li><code>getPath(path)</code> returns the value of the path (including the context) starting from the <em>root</em> of the full data model.</li>
</ul>
<pre><code class="javascript"><span class="hl-1">const</span><span class="hl-0"> </span><span class="hl-2">baseStations</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> app.</span><span class="hl-7">getPath</span><span class="hl-0">(</span><span class="hl-4">'shore.basestations'</span><span class="hl-0">)</span>
</code><button type="button">Copy</button></pre>
<h2 id="subscribing-to-deltas" class="tsd-anchor-link">Subscribing to Deltas<a href="#subscribing-to-deltas" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>A can subscribe to a stream of updates (deltas) by creating the subscription.</p>
<p>Subcriptions are generally manged in the plugin <code>start()</code> and <code>stop()</code> methods to ensure the subscribtions are <em>unsubscribed</em> prior to the plugin stopping to ensure all resources are freed.</p>
<p>The following example illustrates the pattern using the <a href="../../_signalk/server-api/ServerAPI.html#subscriptionmanager" class="tsd-kind-property"><code>subscriptionmanager</code></a> API method.</p>
<pre><code class="javascript"><span class="hl-1">let</span><span class="hl-0"> unsubscribes </span><span class="hl-1">=</span><span class="hl-0"> []</span><br/><br/><span class="hl-0">plugin.</span><span class="hl-7">start</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> (</span><span class="hl-5">options</span><span class="hl-0">, </span><span class="hl-5">restartPlugin</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> app.</span><span class="hl-7">debug</span><span class="hl-0">(</span><span class="hl-4">'Plugin started'</span><span class="hl-0">)</span><br/><span class="hl-0"> </span><span class="hl-1">let</span><span class="hl-0"> localSubscription </span><span class="hl-1">=</span><span class="hl-0"> {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'*'</span><span class="hl-0">, </span><span class="hl-6">// Get data for all contexts</span><br/><span class="hl-0"> subscribe: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> path: </span><span class="hl-4">'*'</span><span class="hl-0">, </span><span class="hl-6">// Get all paths</span><br/><span class="hl-0"> period: </span><span class="hl-2">5000</span><span class="hl-0"> </span><span class="hl-6">// Every 5000ms</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0"> }</span><br/><br/><span class="hl-0"> app.subscriptionmanager.</span><span class="hl-7">subscribe</span><span class="hl-0">(</span><br/><span class="hl-0"> localSubscription,</span><br/><span class="hl-0"> unsubscribes,</span><br/><span class="hl-0"> (</span><span class="hl-5">subscriptionError</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> app.</span><span class="hl-7">error</span><span class="hl-0">(</span><span class="hl-4">'Error:'</span><span class="hl-0"> </span><span class="hl-1">+</span><span class="hl-0"> subscriptionError)</span><br/><span class="hl-0"> },</span><br/><span class="hl-0"> (</span><span class="hl-5">delta</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> delta.updates.</span><span class="hl-7">forEach</span><span class="hl-0">((</span><span class="hl-5">u</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> app.</span><span class="hl-7">debug</span><span class="hl-0">(u)</span><br/><span class="hl-0"> })</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> )</span><br/><span class="hl-0">}</span><br/><br/><span class="hl-0">plugin.</span><span class="hl-7">stop</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> () </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> unsubscribes.</span><span class="hl-7">forEach</span><span class="hl-0">((</span><span class="hl-5">f</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> </span><span class="hl-7">f</span><span class="hl-0">())</span><br/><span class="hl-0"> unsubscribes </span><span class="hl-1">=</span><span class="hl-0"> []</span><br/><span class="hl-0">}</span>
</code><button type="button">Copy</button></pre>
<p>In the <code>start()</code> method create a subscription definition <code>localSubscription</code> which is then passed to <code>app.subscriptionmanager.subscribe()</code> as the first argument, we also pass the <code>unsubscribes</code> array in the second argument.</p>
<p>The third argument is a function that will be called when there's an error.</p>
<p>The final argument is a function that will be called every time an update is received.</p>
<p>In the <code>stop()</code> method each subcription in the <code>unsubscribes</code> array is <em>unsubscribed</em> and the resources released.</p>
<h3 id="path-discovery-with" class="tsd-anchor-link">Path Discovery with <code>announceNewPaths</code><a href="#path-discovery-with" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<p>When using granular subscriptions (subscribing to specific paths rather than <code>*</code>), you may want to discover what paths are available without receiving continuous updates for all of them. The <code>announceNewPaths</code> option solves this:</p>
<pre><code class="javascript"><span class="hl-1">let</span><span class="hl-0"> localSubscription </span><span class="hl-1">=</span><span class="hl-0"> {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'*'</span><span class="hl-0">,</span><br/><span class="hl-0"> announceNewPaths: </span><span class="hl-2">true</span><span class="hl-0">, </span><span class="hl-6">// Announce all matching paths once</span><br/><span class="hl-0"> subscribe: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> path: </span><span class="hl-4">'navigation.position'</span><span class="hl-0">, </span><span class="hl-6">// Only get continuous updates for this path</span><br/><span class="hl-0"> period: </span><span class="hl-2">1000</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0">}</span>
</code><button type="button">Copy</button></pre>
<p>When <code>announceNewPaths: true</code> is set:</p>
<ol>
<li><strong>On subscribe</strong>: The server sends cached values for ALL existing paths matching the context filter (once each)</li>
<li><strong>On new path</strong>: When a new path appears later (e.g., a new sensor comes online), the server announces it once</li>
<li><strong>Continuous updates</strong>: Only the explicitly subscribed paths receive continuous updates</li>
</ol>
<p>This is useful for:</p>
<ul>
<li><strong>Data browsers</strong> that need to show all available paths but only update visible ones</li>
<li><strong>Discovery tools</strong> that want to know what data is available</li>
<li><strong>Dashboards</strong> that let users select which data to display</li>
</ul>
<p>The announced deltas are regular delta messages - there's no special flag. Your client should track which paths it has seen and can then subscribe to specific ones as needed.</p>
<h3 id="source-policy" class="tsd-anchor-link">Source Policy: <code>sourcePolicy</code><a href="#source-policy" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<p>When the server has <a href="../../Configuration/Source_Priority.html">Source Priority</a> configured, subscriptions receive only the preferred source's data by default. You can override this with the <code>sourcePolicy</code> option:</p>
<pre><code class="javascript"><span class="hl-1">let</span><span class="hl-0"> localSubscription </span><span class="hl-1">=</span><span class="hl-0"> {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'*'</span><span class="hl-0">,</span><br/><span class="hl-0"> sourcePolicy: </span><span class="hl-4">'all'</span><span class="hl-0">,</span><br/><span class="hl-0"> subscribe: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> path: </span><span class="hl-4">'navigation.position'</span><span class="hl-0">,</span><br/><span class="hl-0"> period: </span><span class="hl-2">1000</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0">}</span>
</code><button type="button">Copy</button></pre>
<table>
<thead>
<tr>
<th>Value</th>
<th>Behaviour</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>'preferred'</code></td>
<td>Only deliver values from the preferred source (default)</td>
</tr>
<tr>
<td><code>'all'</code></td>
<td>Deliver values from all sources regardless of priority configuration</td>
</tr>
</tbody>
</table>
<p>Use <code>sourcePolicy: 'all'</code> when your plugin needs to see data from every source — for example, a display that compares readings from multiple sensors, or a data logger that records all sources.</p>
<p><strong>Note:</strong> <code>sourcePolicy</code> is honoured only when subscribing through <code>app.subscriptionmanager.subscribe()</code> — the recommended API documented above. Plugins that read directly from <code>app.streambundle</code> (<code>getBus()</code>, <code>getSelfStream()</code>) always receive the preferred-only stream and have no way to opt into all sources; new plugins should prefer <code>subscriptionmanager.subscribe()</code>.</p>
<h4 id="websocket-connection-query-parameter" class="tsd-anchor-link">WebSocket connection query parameter<a href="#websocket-connection-query-parameter" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h4>
<p>WebSocket clients can apply the same policy to the entire connection by setting the <code>sourcePolicy</code> query parameter on the streaming endpoint:</p>
<pre><code><span class="hl-5">ws</span><span class="hl-0">:</span><span class="hl-6">//localhost:3000/signalk/v1/stream?subscribe=self&sourcePolicy=all</span>
</code><button>Copy</button></pre>
<table>
<thead>
<tr>
<th>Query value</th>
<th>Behaviour</th>
</tr>
</thead>
<tbody>
<tr>
<td><em>(omitted)</em>/<code>preferred</code></td>
<td>Connection delivers preferred-only deltas (default)</td>
</tr>
<tr>
<td><code>all</code></td>
<td>Connection delivers deltas from every source, regardless of priorities</td>
</tr>
</tbody>
</table>
<p>The query-string default applies to the bootstrap cache replay and to per-message subscriptions that don't carry their own <code>sourcePolicy</code>. A subscribe message can still override it on a per-call basis by including <code>sourcePolicy</code> in the message body.</p>
<h4 id="excluding-sources-" class="tsd-anchor-link">Excluding Sources: <code>excludeSources</code> / <code>excludeSelf</code><a href="#excluding-sources-" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h4>
<p>A plugin may want a priority-resolved view of a path with one or more sources removed from the cascade — for example to see the preferred upstream source while ignoring a known-bad device. <code>excludeSources</code> / <code>excludeSelf</code> provide that: the subscription still receives a single priority-resolved value per path, but the cascade runs without the excluded refs.</p>
<blockquote>
<p><strong>Note:</strong> Because the cascade runs on the subscription's feed, this delivers a single priority-resolved value — and if the user ranks the plugin itself above the upstream source, that source is held to the fallback timeout on the plugin's own input. That is the right behaviour for a plugin that wants <em>the preferred remaining upstream value</em>, but <strong>not</strong> for a correction/transform plugin that must see every raw sample at full rate — for that case see <a href="#correction-and-transform-plugins">Correction and transform plugins</a> below, which uses <code>sourcePolicy: 'all'</code>.</p>
</blockquote>
<p>The same fallback semantics the user configured still apply across the remaining sources:</p>
<pre><code class="javascript"><span class="hl-1">let</span><span class="hl-0"> localSubscription </span><span class="hl-1">=</span><span class="hl-0"> {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'vessels.self'</span><span class="hl-0">,</span><br/><span class="hl-0"> excludeSelf: </span><span class="hl-2">true</span><span class="hl-0">,</span><br/><span class="hl-0"> subscribe: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> path: </span><span class="hl-4">'environment.wind.speedTrue'</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0">}</span>
</code><button type="button">Copy</button></pre>
<p>With user ranking <code>myPlugin > sourceB > sourceA</code>:</p>
<table>
<thead>
<tr>
<th>Bus state</th>
<th>Delivered to plugin</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sourceB</code> publishing</td>
<td><code>sourceB</code></td>
</tr>
<tr>
<td><code>sourceB</code> silent past its fallback timeout</td>
<td><code>sourceA</code></td>
</tr>
<tr>
<td><code>sourceB</code> resumes</td>
<td><code>sourceB</code></td>
</tr>
<tr>
<td><code>myPlugin</code> (own output)</td>
<td><em>(never delivered)</em></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Field</th>
<th>Behaviour</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>excludeSources: string[]</code></td>
<td>Drop these <code>$source</code> refs from the cascade. Explicit form; works in both plugin and WebSocket subscriptions.</td>
</tr>
<tr>
<td><code>excludeSelf: true</code></td>
<td>Plugin-only shorthand. The server resolves it to <code>[plugin.id]</code>. Combine with <code>excludeSources</code> to add explicit refs on top.</td>
</tr>
</tbody>
</table>
<p>Both fields take effect only when <code>sourcePolicy</code> is <code>'preferred'</code> (the default). Under <code>sourcePolicy: 'all'</code> they are ignored — <code>'all'</code> already bypasses the priority cascade and partial filtering would be surprising.</p>
<p><code>excludeSelf</code> resolves to the plugin's id only — a single ref, not a prefix match. A plugin that publishes under additional labels (e.g. <code>myPlugin.windFromPolars</code>) should use the explicit <code>excludeSources</code> form to list every ref it produces.</p>
<p>WebSocket subscriptions can use <code>excludeSources</code> directly. <code>excludeSelf</code> is meaningless for them (there is no plugin identity to resolve against) and is silently ignored — WebSocket clients should always use the explicit form.</p>
<p>The match is on the <code>$source</code> of the deltas your plugin emits, and <code>excludeSelf</code> resolves to your bare <code>plugin.id</code>. So your emitted deltas must carry <code>$source === plugin.id</code> for the exclusion to apply. The simplest way is to emit with <strong>no</strong> <code>source</code> object at all — the server then sets <code>$source</code> to your <code>plugin.id</code>:</p>
<pre><code class="javascript"><span class="hl-0">app.</span><span class="hl-7">handleMessage</span><span class="hl-0">(plugin.id, {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'vessels.self'</span><span class="hl-0">,</span><br/><span class="hl-0"> updates: [</span><br/><span class="hl-0"> { values: [{ path: </span><span class="hl-4">'environment.wind.speedTrue'</span><span class="hl-0">, value: corrected }] }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0">})</span>
</code><button type="button">Copy</button></pre>
<p>If you do supply a <code>source</code> object, also set a top-level <code>$source: plugin.id</code> on the update — the server keeps an explicit <code>$source</code> and only derives one from the <code>source</code> object when it is absent, so this guarantees the match. (See <a href="#sending-deltas">Sending Deltas</a> below.)</p>
<h2 id="sending-deltas" class="tsd-anchor-link">Sending Deltas<a href="#sending-deltas" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>A SignalK plugin can not only read deltas, but can also send them. This is done using the <code>handleMessage()</code> API method and supplying:</p>
<ol>
<li>The plugin id</li>
<li>A formatted delta update message</li>
<li>The Signal K version ['v1' or 'v2'] <em>(if omitted the default is 'v1')</em>. See <a href="../REST_APIs.html">REST APIs</a> for details.</li>
</ol>
<p><em>Example:</em></p>
<pre><code class="javascript"><span class="hl-0">app.</span><span class="hl-7">handleMessage</span><span class="hl-0">(</span><br/><span class="hl-0"> plugin.id,</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> updates: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> values: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> path: </span><span class="hl-4">'environment.outside.temperature'</span><span class="hl-0">,</span><br/><span class="hl-0"> value: </span><span class="hl-1">-</span><span class="hl-2">253</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0"> },</span><br/><span class="hl-0"> </span><span class="hl-4">'v1'</span><br/><span class="hl-0">)</span>
</code><button type="button">Copy</button></pre>
<h2 id="setting-default-metadata" class="tsd-anchor-link">Setting Default Metadata<a href="#setting-default-metadata" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>Plugins can suggest default metadata for paths they publish using <code>setDefaultMetadata()</code>. This is useful for setting display units, descriptions, or other metadata that improves the user experience in the data browser without overwriting values the user has already configured.</p>
<p>The method uses <strong>per-field merge</strong> semantics: it only sets metadata fields that don't already exist for the path. If a user has already set <code>displayName</code> for a path, the plugin can still set <code>displayUnits</code> on the same path without overwriting the user's choice.</p>
<pre><code class="javascript"><span class="hl-0">plugin.</span><span class="hl-7">start</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> </span><span class="hl-1">async</span><span class="hl-0"> (</span><span class="hl-5">options</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-6">// Suggest that energy paths display in Wh instead of the SI unit (J)</span><br/><span class="hl-0"> </span><span class="hl-1">await</span><span class="hl-0"> app.</span><span class="hl-7">setDefaultMetadata</span><span class="hl-0">(</span><span class="hl-4">'electrical.batteries.house.energy'</span><span class="hl-0">, {</span><br/><span class="hl-0"> displayUnits: { category: </span><span class="hl-4">'energy'</span><span class="hl-0">, targetUnit: </span><span class="hl-4">'Wh'</span><span class="hl-0"> }</span><br/><span class="hl-0"> })</span><br/><br/><span class="hl-0"> </span><span class="hl-6">// Suggest display units and a description</span><br/><span class="hl-0"> </span><span class="hl-1">await</span><span class="hl-0"> app.</span><span class="hl-7">setDefaultMetadata</span><span class="hl-0">(</span><span class="hl-4">'electrical.solar.panelPower'</span><span class="hl-0">, {</span><br/><span class="hl-0"> displayUnits: { category: </span><span class="hl-4">'power'</span><span class="hl-0">, targetUnit: </span><span class="hl-4">'W'</span><span class="hl-0"> },</span><br/><span class="hl-0"> description: </span><span class="hl-4">'Total solar panel output'</span><br/><span class="hl-0"> })</span><br/><span class="hl-0">}</span>
</code><button type="button">Copy</button></pre>
<p>The method is <strong>idempotent</strong>: on subsequent server starts, fields already persisted from a previous call will not be overwritten. It returns <code>true</code> if any new fields were applied, <code>false</code> if all fields already existed.</p>
<p>The <code>displayUnits.category</code> is validated against the path's SI unit. If the category doesn't match, the method returns <code>false</code> and logs a debug message.</p>
<h2 id="correction-and-transform-plugins" class="tsd-anchor-link">Correction and transform plugins<a href="#correction-and-transform-plugins" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>There are essentially two strategies a plugin can employ to change a value for a path:</p>
<ol>
<li><strong>Correct in place</strong> when the value enters the server</li>
<li><strong>Create a new value</strong> with correction or transform and publish an improved value on the <strong>same</strong> path under its own label</li>
</ol>
<p><strong>In place strategy</strong> is used when the new value is meant to replace the original value completely, for example applying a fixed offset to a heading to compensate for a sensor's mount position. This makes the original value unavailable for other consumers.</p>
<p>With <strong>new value strategy</strong> both values are available, but using the source priority system the plugin's output can be prioritised over the original value. The user ranks the plugin's output above the raw source in <a href="../../Configuration/Source_Priority.html">source priority</a>, so downstream consumers get the corrected value and a user interface like a dashboard can display both if needed.</p>
<p>A correction plugin needs its <strong>input</strong> at full rate, <em>independent</em> of the priority ranking — you correct every reading from the upstream source, not just whichever one a cascade would currently prefer — while the user's ranking decides what <em>consumers</em> see. Subscribe with <strong><code>sourcePolicy: 'all'</code></strong> and skip your own output in the callback:</p>
<ul>
<li><code>sourcePolicy: 'all'</code> delivers every source at full rate with no cascade on your input. Skip updates whose <code>$source</code> is your own <code>plugin.id</code> so you don't reprocess your output. The global priority cascade still applies to consumers, so the user's ranking of your output works as expected.</li>
</ul>
<p>Do <strong>not</strong> use <code>registerDeltaInputHandler</code> for <strong>new value strategy</strong>. An input handler is for modifying a delta in place as it passes through, not for republishing a value under your own source — a correction plugin emits its corrected value under its own <code>$source</code> and lets source priority choose between that and the raw source, which is a different shape from rewriting the incoming delta. Subscribe-and-emit keeps the raw and corrected values as two distinct sources the user can rank.</p>
<p><code>excludeSelf</code> (see <a href="#excluding-sources-excludesources--excludeself">Excluding Sources</a>) is a different tool and <strong>not</strong> what you want for a full-rate correction. It runs the priority cascade on your input feed, so it delivers a single ranked-and-throttled upstream value rather than every reading: with the user ranking your plugin rank-0, the real source is held as a lower-ranked fallback and only reaches you after the fallback timeout. <code>excludeSelf</code> fits a plugin that wants <em>the preferred remaining upstream value</em> (e.g. "show source B, fall back to A, never my own C"), not one that corrects each raw sample.</p>
<p>A complete heel-correction example:</p>
<pre><code class="javascript"><span class="hl-1">let</span><span class="hl-0"> unsubscribes </span><span class="hl-1">=</span><span class="hl-0"> []</span><br/><br/><span class="hl-0">plugin.</span><span class="hl-7">start</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> () </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> app.subscriptionmanager.</span><span class="hl-7">subscribe</span><span class="hl-0">(</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'vessels.self'</span><span class="hl-0">,</span><br/><span class="hl-0"> sourcePolicy: </span><span class="hl-4">'all'</span><span class="hl-0">,</span><br/><span class="hl-0"> subscribe: [{ path: </span><span class="hl-4">'navigation.speedThroughWater'</span><span class="hl-0"> }]</span><br/><span class="hl-0"> },</span><br/><span class="hl-0"> unsubscribes,</span><br/><span class="hl-0"> (</span><span class="hl-5">err</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> app.</span><span class="hl-7">setPluginError</span><span class="hl-0">(err),</span><br/><span class="hl-0"> (</span><span class="hl-5">delta</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> delta.updates.</span><span class="hl-7">forEach</span><span class="hl-0">((</span><span class="hl-5">u</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-6">// $source is a property of the update, not of each value. Skip</span><br/><span class="hl-0"> </span><span class="hl-6">// our own output so we don't reprocess the corrected value.</span><br/><span class="hl-0"> </span><span class="hl-1">if</span><span class="hl-0"> (u.$source </span><span class="hl-1">===</span><span class="hl-0"> plugin.id) </span><span class="hl-1">return</span><br/><span class="hl-0"> u.values.</span><span class="hl-7">forEach</span><span class="hl-0">((</span><span class="hl-5">pv</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-1">if</span><span class="hl-0"> (pv.path </span><span class="hl-1">!==</span><span class="hl-0"> </span><span class="hl-4">'navigation.speedThroughWater'</span><span class="hl-0">) </span><span class="hl-1">return</span><br/><span class="hl-0"> </span><span class="hl-1">const</span><span class="hl-0"> </span><span class="hl-2">corrected</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> </span><span class="hl-7">applyHeelCorrection</span><span class="hl-0">(pv.value)</span><br/><span class="hl-0"> </span><span class="hl-6">// No source object: $source defaults to plugin.id, so the</span><br/><span class="hl-0"> </span><span class="hl-6">// guard above recognises and skips this delta on the way back.</span><br/><span class="hl-0"> app.</span><span class="hl-7">handleMessage</span><span class="hl-0">(plugin.id, {</span><br/><span class="hl-0"> context: </span><span class="hl-4">'vessels.self'</span><span class="hl-0">,</span><br/><span class="hl-0"> updates: [</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> values: [</span><br/><span class="hl-0"> { path: </span><span class="hl-4">'navigation.speedThroughWater'</span><span class="hl-0">, value: corrected }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> ]</span><br/><span class="hl-0"> })</span><br/><span class="hl-0"> })</span><br/><span class="hl-0"> })</span><br/><span class="hl-0"> }</span><br/><span class="hl-0"> )</span><br/><span class="hl-0">}</span><br/><br/><span class="hl-0">plugin.</span><span class="hl-7">stop</span><span class="hl-0"> </span><span class="hl-1">=</span><span class="hl-0"> () </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> unsubscribes.</span><span class="hl-7">forEach</span><span class="hl-0">((</span><span class="hl-5">f</span><span class="hl-0">) </span><span class="hl-1">=></span><span class="hl-0"> </span><span class="hl-7">f</span><span class="hl-0">())</span><br/><span class="hl-0"> unsubscribes </span><span class="hl-1">=</span><span class="hl-0"> []</span><br/><span class="hl-0">}</span>
</code><button type="button">Copy</button></pre>
<h2 id="sending-nmea-2000-data-from-a-plugin" class="tsd-anchor-link">Sending NMEA 2000 data from a plugin<a href="#sending-nmea-2000-data-from-a-plugin" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>A SignalK plugin can not only emit deltas, but can also send data such as NMEA 2000 data.</p>
<p>This is done using the <code>emit()</code> API and specifying the provider as well as the formatted data to send.</p>
<p><em>Example: Send NMEA using Actisense serial format:</em></p>
<pre><code class="javascript"><span class="hl-0">app.</span><span class="hl-7">emit</span><span class="hl-0">(</span><br/><span class="hl-0"> </span><span class="hl-4">'nmea2000out'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-4">'2017-04-15T14:57:58.468Z,0,262384,0,0,14,01,0e,00,88,b6,02,00,00,00,00,00,a2,08,00'</span><br/><span class="hl-0">)</span>
</code><button type="button">Copy</button></pre>
<p><em>Example: Send NMEA using Canboat JSON format:</em></p>
<pre><code class="javascript"><span class="hl-0">app.</span><span class="hl-7">emit</span><span class="hl-0">(</span><span class="hl-4">'nmea2000JsonOut'</span><span class="hl-0">, {</span><br/><span class="hl-0"> pgn: </span><span class="hl-2">130306</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-4">'Wind Speed'</span><span class="hl-0">: speed,</span><br/><span class="hl-0"> </span><span class="hl-4">'Wind Angle'</span><span class="hl-0">: angle </span><span class="hl-1"><</span><span class="hl-0"> </span><span class="hl-2">0</span><span class="hl-0"> </span><span class="hl-1">?</span><span class="hl-0"> angle </span><span class="hl-1">+</span><span class="hl-0"> Math.</span><span class="hl-2">PI</span><span class="hl-0"> </span><span class="hl-1">*</span><span class="hl-0"> </span><span class="hl-2">2</span><span class="hl-0"> </span><span class="hl-1">:</span><span class="hl-0"> angle,</span><br/><span class="hl-0"> Reference: </span><span class="hl-4">'Apparent'</span><br/><span class="hl-0">})</span>
</code><button type="button">Copy</button></pre>
<h3 id="sending-a-message-on-nmea2000-startup" class="tsd-anchor-link">Sending a message on NMEA2000 startup<a href="#sending-a-message-on-nmea2000-startup" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<p>If you need to send an NMEA2000 message out at startup, <em>e.g get current state from a device</em> you will need to wait until the provider is ready before sending your message.</p>
<p><em>Example: Send NMEA after the provider is ready:</em></p>
<pre><code class="javascript"><span class="hl-0">app.</span><span class="hl-7">on</span><span class="hl-0">(</span><span class="hl-4">'nmea2000OutAvailable'</span><span class="hl-0">, () </span><span class="hl-1">=></span><span class="hl-0"> {</span><br/><span class="hl-0"> app.</span><span class="hl-7">emit</span><span class="hl-0">(</span><br/><span class="hl-0"> </span><span class="hl-4">'nmea2000out'</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-4">'2017-04-15T14:57:58.468Z,2,6,126720,%s,%s,4,a3,99,01,00'</span><br/><span class="hl-0"> )</span><br/><span class="hl-0">})</span>
</code><button type="button">Copy</button></pre>
</div></div><div class="col-sidebar"><div class="page-menu"><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#reading-the-current-path-value"><span>Reading the current path value</span></a><a href="#subscribing-to-deltas"><span>Subscribing to <wbr/>Deltas</span></a><ul><li><a href="#path-discovery-with"><span>Path <wbr/>Discovery with </span></a></li><li><a href="#source-policy"><span>Source <wbr/>Policy: </span></a></li></ul><a href="#sending-deltas"><span>Sending <wbr/>Deltas</span></a><a href="#setting-default-metadata"><span>Setting <wbr/>Default <wbr/>Metadata</span></a><a href="#correction-and-transform-plugins"><span>Correction and transform plugins</span></a><a href="#sending-nmea-2000-data-from-a-plugin"><span>Sending <wbr/>NMEA 2000 data from a plugin</span></a><ul><li><a href="#sending-a-message-on-nmea2000-startup"><span>Sending a message on <wbr/>NMEA2000 startup</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="../../modules.html">Signal K</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>