UNPKG

node-red-contrib-octocore

Version:

OctoCore implementation for node-red

540 lines (476 loc) 22.2 kB
<script type="text/javascript"> RED.nodes.registerType('uns-publish', { category: 'OctoCore', color: '#e7e9ea', defaults: { name: { value: '', }, server: { value: '', type: 'uns-server', }, debug: { value: false }, enableBuffer: { value: false }, bufferSize: { value: 1000 }, bufferSizeType: { value: 'count' }, bufferSizeBytes: { value: 10485760 }, bufferMode: { value: 'drop-oldest' }, bufferPersistence: { value: 'none' }, bufferAutoSaveInterval: { value: 30 }, message: { value: '', }, dataformat: { value: '', required: true, }, datapointid: { value: '', }, datatypeOverride: { value: 'auto' }, enableBatch: { value: false }, batchSize: { value: 100 }, batchInterval: { value: 1000 }, batchMode: { value: 'size' }, enableAutoReply: { value: false }, replyTimeout: { value: 5000 }, enableRateLimit: { value: false }, rateLimit: { value: 100 }, rateLimitWindow: { value: 1000 }, rateLimitBurst: { value: 20 }, rateLimitAction: { value: 'drop' }, }, inputs: 1, outputs: 1, icon: 'icons/icon-primary-positive.png', label: function () { return this.name || 'uns-publish'; }, oneditprepare: function() { // Set default buffer mode if not set if (!this.bufferMode) { $('#node-input-bufferMode').val('drop-oldest'); } // Show/hide buffer configuration based on checkbox $('#node-input-enableBuffer').on('change', function() { if ($(this).is(':checked')) { $('#buffer-size-type-row').show(); $('#buffer-size-count-row').show(); $('#buffer-size-bytes-row').show(); $('#buffer-mode').show(); $('#buffer-persistence-row').show(); $('#node-input-bufferSizeType').trigger('change'); $('#node-input-bufferPersistence').trigger('change'); } else { $('#buffer-size-type-row').hide(); $('#buffer-size-count-row').hide(); $('#buffer-size-bytes-row').hide(); $('#buffer-mode').hide(); $('#buffer-persistence-row').hide(); $('#buffer-autosave-row').hide(); } }); // Show/hide auto-save interval based on persistence selection $('#node-input-bufferPersistence').on('change', function() { const persistence = $(this).val(); if (persistence === 'none') { $('#buffer-autosave-row').hide(); } else { $('#buffer-autosave-row').show(); } }); // Show/hide buffer size config based on size type selection $('#node-input-bufferSizeType').on('change', function() { if ($(this).val() === 'count') { $('#buffer-size-count-row').show(); $('#buffer-size-bytes-row').hide(); } else { $('#buffer-size-count-row').hide(); $('#buffer-size-bytes-row').show(); } }); // Show/hide datatype override based on dataformat selection $('#node-input-dataformat').on('change', function() { if ($(this).val() === 'uns_value') { $('#datatype-override-row').show(); } else { $('#datatype-override-row').hide(); } }); // Show/hide batch configuration based on checkbox $('#node-input-enableBatch').on('change', function() { if ($(this).is(':checked')) { $('#batch-size-row').show(); $('#batch-interval-row').show(); $('#batch-mode-row').show(); } else { $('#batch-size-row').hide(); $('#batch-interval-row').hide(); $('#batch-mode-row').hide(); } }); // Set default batch mode if not set if (!this.batchMode) { $('#node-input-batchMode').val('size'); } // Show/hide auto-reply configuration based on checkbox $('#node-input-enableAutoReply').on('change', function() { if ($(this).is(':checked')) { $('#reply-timeout-row').show(); } else { $('#reply-timeout-row').hide(); } }); // Show/hide rate limit configuration based on checkbox $('#node-input-enableRateLimit').on('change', function() { if ($(this).is(':checked')) { $('#rate-limit-config').show(); $('#rate-limit-window-row').show(); $('#rate-limit-burst-row').show(); $('#rate-limit-action-row').show(); } else { $('#rate-limit-config').hide(); $('#rate-limit-window-row').hide(); $('#rate-limit-burst-row').hide(); $('#rate-limit-action-row').hide(); } }); // Trigger initial state $('#node-input-enableBuffer').trigger('change'); $('#node-input-dataformat').trigger('change'); $('#node-input-enableBatch').trigger('change'); $('#node-input-enableAutoReply').trigger('change'); $('#node-input-enableRateLimit').trigger('change'); } }); </script> <script type="text/x-red" data-template-name="uns-publish"> <div class="form-row"> <label for="node-input-name"><i class="fa fa-stack-exchange"></i> Name</label> <input type="text" id="node-input-name" placeholder="Name"> </div> <div class="form-row"> <label for="node-input-server"><i class="fa fa-stack-exchange"></i> OctoCore Server</label> <input type="text" id="node-input-server" placeholder="localhost"> </div> <div class="form-row"> <label for="node-input-debug"><i class="fa fa-bug"></i> Debug Logging</label> <input type="checkbox" id="node-input-debug" style="vertical-align: middle;"> </div> <div class="form-row"> <label for="node-input-enableBuffer"><i class="fa fa-database"></i> Message Buffering</label> <input type="checkbox" id="node-input-enableBuffer" style="vertical-align: middle;"> <span style="margin-left: 10px; color: #999;">Queue messages when disconnected</span> </div> <div class="form-row" id="buffer-size-type-row" style="display: none;"> <label for="node-input-bufferSizeType"><i class="fa fa-cog"></i> Buffer Limit Type</label> <select id="node-input-bufferSizeType" style="width:70%"> <option value="count">Message Count</option> <option value="size">Buffer Size (Bytes)</option> </select> </div> <div class="form-row" id="buffer-size-count-row" style="display: none;"> <label for="node-input-bufferSize"><i class="fa fa-list"></i> Buffer Size</label> <input type="number" id="node-input-bufferSize" placeholder="1000" min="10" max="10000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">messages</span> </div> <div class="form-row" id="buffer-size-bytes-row" style="display: none;"> <label for="node-input-bufferSizeBytes"><i class="fa fa-hdd-o"></i> Buffer Size</label> <input type="number" id="node-input-bufferSizeBytes" placeholder="10485760" min="1024" max="104857600" style="width: 150px;"> <span style="margin-left: 10px; color: #999;">bytes</span> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Default: 10 MB (10485760 bytes), Max: 100 MB (104857600 bytes) </div> </div> <div class="form-row" id="buffer-mode" style="display: none;"> <label for="node-input-bufferMode"><i class="fa fa-exchange"></i> Buffer Mode</label> <select id="node-input-bufferMode" style="width:70%"> <option value="drop-oldest">Drop Oldest (FIFO)</option> <option value="drop-newest">Drop Newest (Keep Old)</option> <option value="drop-on-full">Reject New on Full</option> </select> </div> <div class="form-row" id="buffer-persistence-row" style="display: none;"> <label for="node-input-bufferPersistence"><i class="fa fa-floppy-o"></i> Buffer Persistence</label> <select id="node-input-bufferPersistence" style="width:70%"> <option value="none">None (RAM only)</option> <option value="context">Node-RED Context Storage</option> <option value="file">File System</option> <option value="both">Both (Context + File)</option> </select> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Context: Node-RED's persistent storage | File: Disk | Both: Maximum safety </div> </div> <div class="form-row" id="buffer-autosave-row" style="display: none;"> <label for="node-input-bufferAutoSaveInterval"><i class="fa fa-clock-o"></i> Auto-Save Interval</label> <input type="number" id="node-input-bufferAutoSaveInterval" placeholder="30" min="5" max="300" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">seconds</span> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Regular automatic saving (5-300 seconds) </div> </div> <hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;"> <div class="form-row"> <label for="node-input-enableBatch"><i class="fa fa-th-large"></i> Batch Publishing</label> <input type="checkbox" id="node-input-enableBatch" style="vertical-align: middle;"> <span style="margin-left: 10px; color: #999;">Group messages and publish as batch</span> </div> <div class="form-row" id="batch-size-row" style="display: none;"> <label for="node-input-batchSize"><i class="fa fa-list-ol"></i> Batch Size</label> <input type="number" id="node-input-batchSize" placeholder="100" min="1" max="1000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">messages per batch</span> </div> <div class="form-row" id="batch-interval-row" style="display: none;"> <label for="node-input-batchInterval"><i class="fa fa-clock-o"></i> Batch Interval</label> <input type="number" id="node-input-batchInterval" placeholder="1000" min="100" max="60000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">milliseconds</span> </div> <div class="form-row" id="batch-mode-row" style="display: none;"> <label for="node-input-batchMode"><i class="fa fa-gears"></i> Batch Mode</label> <select id="node-input-batchMode" style="width:70%"> <option value="size">Size Only</option> <option value="hybrid">Hybrid (Size OR Time)</option> <option value="time">Time Only</option> </select> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Size: Publish only when batch is full | Hybrid: Size OR Time | Time: At regular intervals </div> </div> <hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;"> <div class="form-row"> <label for="node-input-enableAutoReply"><i class="fa fa-reply"></i> Auto-Reply Handler</label> <input type="checkbox" id="node-input-enableAutoReply" style="vertical-align: middle;"> <span style="margin-left: 10px; color: #999;">Automatically handle request-reply pattern</span> </div> <div class="form-row" id="reply-timeout-row" style="display: none;"> <label for="node-input-replyTimeout"><i class="fa fa-hourglass-half"></i> Reply Timeout</label> <input type="number" id="node-input-replyTimeout" placeholder="5000" min="100" max="60000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">milliseconds</span> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Pass msg from input through to output. Send output msg.payload as reply. </div> </div> <hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;"> <div class="form-row"> <label for="node-input-enableRateLimit"><i class="fa fa-tachometer"></i> Rate Limiting</label> <input type="checkbox" id="node-input-enableRateLimit" style="vertical-align: middle;"> <span style="margin-left: 10px; color: #999;">Limit message throughput (protection against flooding)</span> </div> <div class="form-row" id="rate-limit-config" style="display: none;"> <label for="node-input-rateLimit"><i class="fa fa-line-chart"></i> Rate Limit</label> <input type="number" id="node-input-rateLimit" placeholder="100" min="1" max="10000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">messages per window</span> </div> <div class="form-row" id="rate-limit-window-row" style="display: none;"> <label for="node-input-rateLimitWindow"><i class="fa fa-clock-o"></i> Time Window</label> <input type="number" id="node-input-rateLimitWindow" placeholder="1000" min="100" max="60000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">milliseconds</span> </div> <div class="form-row" id="rate-limit-burst-row" style="display: none;"> <label for="node-input-rateLimitBurst"><i class="fa fa-bolt"></i> Burst Allowance</label> <input type="number" id="node-input-rateLimitBurst" placeholder="20" min="0" max="1000" style="width: 100px;"> <span style="margin-left: 10px; color: #999;">extra messages for bursts</span> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Allows temporary spikes above the rate limit (Token Bucket burst capacity) </div> </div> <div class="form-row" id="rate-limit-action-row" style="display: none;"> <label for="node-input-rateLimitAction"><i class="fa fa-exclamation-triangle"></i> On Limit Exceeded</label> <select id="node-input-rateLimitAction" style="width:70%"> <option value="drop">Drop (Silent)</option> <option value="drop-warn">Drop with Warning</option> <option value="delay">Delay (Queue & Throttle)</option> </select> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Drop: Discard excess messages | Delay: Queue and send at allowed rate </div> </div> <hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;"> <div class="form-row"> <label for="node-input-dataformat"><i class="fa fa-stack-exchange"></i> Dataformat</label> <select id="node-input-dataformat" style="width:70%"> <option value="uns_value">UNS Value</option> <option value="event">UNS Event</option> <option value="reply">Reply</option> <option value="specific_topic">Specific Topic</option> </select> </div> <div class="form-row" id="datatype-override-row" style="display: none;"> <label for="node-input-datatypeOverride"><i class="fa fa-cog"></i> UNS Datatype</label> <select id="node-input-datatypeOverride" style="width:70%"> <option value="auto">Auto-Detect (Default)</option> <option value="1">Integer (1)</option> <option value="2">Float (2)</option> <option value="3">Boolean (3)</option> <option value="4">String (4)</option> <option value="5">Unix Timestamp (5)</option> <option value="6">Object (6)</option> </select> <div style="margin-top: 5px; color: #999; font-size: 11px; margin-left: 110px;"> Leave on "Auto-Detect" for automatic type detection based on payload </div> </div> <div class="form-tips"> <span>Tip: Leave message and/or channel blank if you want to set them via msg properties</span> </div> <div class="form-row"> <label for="node-input-datapointid"><i class="fa fa-stack-exchange"></i>Datapoint / Subject</label> <input type="text" id="node-input-datapointid" placeholder="Datapoint / Subject"> </div> </script> <script type="text/x-red" data-help-name="uns-publish"> <p>Publishes messages to the OctoCore platform using NATS.</p> <h3>Inputs</h3> <dl class="message-properties"> <dt>payload <span class="property-type">any</span></dt> <dd>The data to publish. Format depends on the selected data format.</dd> <dt>id <span class="property-type">string</span></dt> <dd>Event ID (UUID format). Required for events, auto-generated if not provided.</dd> <dt>type <span class="property-type">string</span></dt> <dd>Event type. Options: "point", "interval_start", "interval_end", or custom types.</dd> <dt>startTime <span class="property-type">string</span></dt> <dd>ISO 8601 timestamp for event start. Auto-generated if not provided.</dd> <dt>endTime <span class="property-type">string</span></dt> <dd>ISO 8601 timestamp for event end. Auto-generated if not provided.</dd> </dl> <h3>Data Formats</h3> <h4>UNS Value</h4> <p>Publishes simple values to UNS datapoints.</p> <ul> <li><strong>Subject:</strong> <code>uns.{datapointid}</code></li> <li><strong>Payload:</strong> Simple value (string, number, boolean, object)</li> </ul> <h4>UNS Event</h4> <p>Publishes structured events with automatic ID and timestamp management.</p> <ul> <li><strong>Subject:</strong> <code>event.{datapointid}</code></li> <li><strong>Event Types:</strong> <ul> <li><code>point</code>: Single point event (startTime = endTime)</li> <li><code>interval_start</code>: Interval start event (only startTime)</li> <li><code>interval_end</code>: Interval end event (only endTime, uses same ID as start)</li> </ul> </li> </ul> <h4>Reply</h4> <p>Publishes reply messages to specific reply subjects.</p> <ul> <li><strong>Subject:</strong> Uses <code>msg._unsreply</code></li> <li><strong>Payload:</strong> Reply data</li> </ul> <h3>Event Examples</h3> <h4>Point Event</h4> <pre><code>msg.type = "point"; msg.payload = { temperature: 23.5, humidity: 45 };</code></pre> <h4>Interval Events</h4> <pre><code>// Start event msg.type = "interval_start"; msg.payload = { processId: "batch-001" }; // End event (same ID automatically) msg.type = "interval_end"; msg.payload = { result: "success" };</code></pre> <h4>Custom Event</h4> <pre><code>msg.id = "custom-uuid-123"; msg.type = "alarm"; msg.startTime = "2025-08-15T20:07:10.071Z"; msg.endTime = "2025-08-15T20:07:15.071Z"; msg.payload = { severity: "high", message: "Temperature too high" };</code></pre> <h3>Configuration</h3> <dl class="message-properties"> <dt>Name</dt> <dd>Display name for the node.</dd> <dt>OctoCore Server</dt> <dd>OctoCore server configuration node.</dd> <dt>Data Format</dt> <dd>Format of the data to publish.</dd> <dt>Datapoint / Subject</dt> <dd>Target datapoint ID or subject for publishing.</dd> </dl> <h3>Message Buffering</h3> <p>When enabled, messages are queued when the NATS connection is lost and automatically sent when the connection is restored.</p> <h4>Buffer Limit Types</h4> <ul> <li><strong>Message Count:</strong> Limit by number of messages (default: 1000, max: 10000)</li> <li><strong>Buffer Size (Bytes):</strong> Limit by total size in bytes (default: 10 MB, max: 100 MB)</li> </ul> <h4>Buffer Modes</h4> <ul> <li><strong>Drop Oldest (FIFO):</strong> Remove oldest messages when buffer is full</li> <li><strong>Drop Newest (Keep Old):</strong> Reject new messages when buffer is full</li> <li><strong>Reject New on Full:</strong> Reject new messages with warning when buffer is full</li> </ul> <h4>Buffer Persistence</h4> <p>Messages can be persisted to survive Node-RED restarts:</p> <ul> <li><strong>None (RAM only):</strong> Buffer only in memory (lost on restart)</li> <li><strong>Node-RED Context Storage:</strong> Uses Node-RED's persistent context storage</li> <li><strong>File System:</strong> Saves buffer to disk in <code>~/.node-red/buffer/</code></li> <li><strong>Both (Context + File):</strong> Maximum safety - saves to both storage types</li> </ul> <h4>Auto-Save</h4> <p>When persistence is enabled, the buffer is automatically saved at regular intervals (default: 30 seconds, range: 5-300 seconds). The buffer is also saved when messages are added and when the node is closed.</p> <h4>Timestamp Preservation</h4> <p>When messages are buffered and later flushed, the original timestamp from when the message was buffered is preserved. This ensures accurate time tracking even after connection interruptions.</p> <h3>Features</h3> <ul> <li><strong>Automatic UUID Generation:</strong> Generates valid UUIDs for events</li> <li><strong>UUID Validation:</strong> Validates custom UUIDs against RFC 4122 format</li> <li><strong>Interval Event Pairing:</strong> Automatically pairs start/end events with same ID</li> <li><strong>Timestamp Management:</strong> Auto-generates ISO 8601 timestamps, preserves original timestamps when buffered</li> <li><strong>Performance Optimized:</strong> Cached functions and regex for high throughput</li> <li><strong>Message Buffering:</strong> Queue messages during disconnection with configurable limits and persistence</li> <li><strong>Batch Publishing:</strong> Group messages for efficient transmission</li> <li><strong>Rate Limiting:</strong> Token bucket algorithm to prevent message flooding</li> <li><strong>Auto-Reply Handler:</strong> Process request-reply patterns automatically</li> </ul> <h3>Error Handling</h3> <ul> <li>Invalid UUID format errors with detailed messages</li> <li>Missing reply subject errors for reply format</li> <li>Connection errors with retry logic</li> <li>Buffer overflow warnings with statistics</li> <li>Automatic buffer restoration after restart (if persistence enabled)</li> </ul> </script>