node-red-contrib-octocore
Version:
OctoCore implementation for node-red
540 lines (476 loc) • 22.2 kB
HTML
<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>