@liball/node-red-contrib-opi-gpio
Version:
Orange Pi GPIO. Digital input/output for Orange Pi
273 lines (245 loc) • 13.8 kB
HTML
<script type="text/html" data-template-name="opi_in">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> 名称</label>
<input type="text" id="node-input-name" placeholder="例如:读取按钮状态">
</div>
<div class="form-row">
<label for="node-input-select"><i class="fa fa-microchip"></i> 设备型号</label>
<select id="node-input-select" style="width: 70%;">
<option value="" disabled>-- 选择设备型号 --</option>
<option value="opz2w">Orange Pi Zero 2W</option>
</select>
</div>
<div class="form-row" id="device-pinout-image-container" style="text-align: center; display: none; margin-top: 10px; border: 1px dashed #ccc; padding: 5px;">
<label style="display: block; margin-bottom: 5px;">引脚布局图:</label>
<img id="device-pinout-image" src="" alt="设备引脚图" style="max-width: 95%; height: auto;"/>
</div>
<div id="opz2w-pin-options" style="display: none;"> <div class="form-row">
<label for="node-input-pin-opz2w"><i class="fa fa-tag"></i> GPIO 引脚</label>
<select id="node-input-pin-opz2w" style="width: 70%;">
<option value='' disabled selected style='display:none;'>选择引脚 或 自定义</option>
<option value="264">PI8 (264)</option>
<option value="263">PI7 (263)</option>
<option value="269">PI13 (269)</option>
<option value="224">PH0 (224)</option>
<option value="225">PH1 (225)</option>
<option value="257">PI1 (257)</option>
<option value="226">PH2 (226)</option>
<option value="227">PH3 (227)</option>
<option value="261">PI5 (261)</option>
<option value="270">PI14 (270)</option>
<option value="228">PH4 (228)</option>
<option value="231">PH7 (231)</option>
<option value="232">PH8 (232)</option>
<option value="262">PI6 (262)</option>
<option value="230">PH6 (230)</option>
<option value="229">PH5 (229)</option>
<option value="233">PH9 (233)</option>
<option value="266">PI10 (266)</option>
<option value="265">PI9 (265)</option>
<option value="256">PI0 (256)</option>
<option value="271">PI15 (271)</option>
<option value="267">PI11 (267)</option>
<option value="268">PI12 (268)</option>
<option value="258">PI2 (258)</option>
<option value="76">PC12 (76)</option>
<option value="272">PI16 (272)</option>
<option value="260">PI4 (260)</option>
<option value="259">PI3 (259)</option>
<option value="custom">-- 自定义/环境变量 --</option>
</select>
</div>
<div class="form-row" id="custom-pin-input-row" style="display: none;">
<label for="node-input-pin-custom"><i class="fa fa-pencil"></i> 自定义值</label>
<input type="text" id="node-input-pin-custom" style="width: 70%;">
<input type="hidden" id="node-input-pinType-custom"> </div>
</div>
<div class="form-row" id="node-set-tick">
<label> </label>
<input type="checkbox" id="node-input-enableInterrupt"
style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-enableInterrupt" style="width: 70%;">启用中断?</label>
</div>
<div class="form-row" id="node-set-edge" style="display: none;">
<label for="node-input-edge"><i class="fa fa-bullseye"></i> 触发边沿</label> <select id="node-input-edge" style="width: 70%;">
<option value="rising">上升沿</option>
<option value="falling">下降沿</option>
<option value="both">双边沿</option>
</select>
</div>
<div class="form-row" id="node-set-debounce" style="display: none;">
<label for="node-input-debounce"><i class="fa fa-clock-o"></i> 去抖延迟 (ms)</label> <input type="text" id="node-input-debounce" placeholder="例如:50" style="width: 70%;">
</div>
<div class="form-tips" id="node-tip-debounce" style="display: none;">
去抖会在中断后增加一段死区时间,在此期间忽略新的中断 (0 = 禁用去抖)。
<br><br>
<b>警告:</b> 并非所有引脚都支持硬件中断!请查阅设备文档。
</div>
</script>
<script type="text/html" data-help-name="opi_in">
<p>GPIO <b>输入</b> 节点,用于兼容多种设备(当前主要支持 <b>Orange Pi Zero 2W</b>)。</p>
<p>请先从“设备型号”下拉列表中选择您的设备。根据所选设备,下方会显示引脚布局图和可用的 GPIO 引脚列表。</p>
<p>选择要读取的 GPIO 引脚,或选择“-- 自定义/环境变量 --”来手动输入引脚号、环境变量名 (例如 <code>MY_PIN_VAR</code>) 或其他支持的类型。</p>
<p>如果使用环境变量,请在子流程属性或 Node-RED 运行环境中定义它。</p>
<p>用户有责任确保所选或输入的引脚号有效且不会干扰其他系统功能。</p>
<p>可以通过向节点发送任何消息来轮询引脚当前状态。</p>
<p><b>中断:</b> 您可以选择启用中断以在引脚状态改变时自动发送消息。选择触发边沿(上升沿、下降沿或双边沿)并可选择设置去抖时间(毫秒)以忽略快速的噪声信号。</p>
<p><b>输出消息:</b><br>
<code>msg.payload</code>: 包含引脚当前状态 (0 或 1)。<br>
<code>msg.topic</code>: (可选) 可能包含引脚号或节点名称。<br>
</p>
<p><b>注意:</b> 并非所有引脚都支持中断。请参考您的设备文档。</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('opi_in', {
category: 'Orange Pi', // Or your desired category
color: '#FFCC66', // Orange-ish color
defaults: {
name: {value: ""},
select: {value: "opz2w"}, // Default device
pin: {value: "", required: true}, // Stores final pin value (number or string/var name)
pinSource: {value: "list"}, // Source: 'list' or 'custom'
pinType: {value: "num"}, // Type when pinSource is 'custom' ('num', 'str', 'env'...)
enableInterrupt: {value: false}, // Enable interrupt?
edge: {value: "rising"}, // Interrupt edge ('rising', 'falling', 'both')
debounce: {value: "0", validate: RED.validators.number()} // Debounce time (ms), validate as number
},
inputs: 1, // Allow input messages for polling
outputs: 1, // Output pin state changes
icon: "in.png", // Provide an icon file or use "fa-microchip"
paletteLabel: "GPIO IN",
label: function () {
// Generate a dynamic label for the node
let pinDisplay = this.pin;
if (this.pinSource === 'custom') {
switch (this.pinType) {
case 'env': pinDisplay = '${' + this.pin + '}'; break;
case 'flow': pinDisplay = '#' + this.pin; break;
case 'global': pinDisplay = '#' + this.pin; break;
}
}
const deviceLabel = this.select === 'opz2w' ? 'OPIZ2W' : (this.select || '设备');
const intLabel = this.enableInterrupt ? ` (${this.edge})` : ""; // Add edge info if interrupt enabled
return this.name || (pinDisplay ? `${deviceLabel} GPIO ${pinDisplay}${intLabel}` : "GPIO IN");
},
oneditprepare: function () {
const node = this;
const resourcePath = "resources/@liball/node-red-contrib-opi-gpio";
// ---------------------------------------------------------------------
// Get jQuery elements
const $deviceSelect = $("#node-input-select");
const $imageContainer = $("#device-pinout-image-container");
const $imageElement = $("#device-pinout-image");
const $opz2wOptions = $("#opz2w-pin-options");
const $pinSelect = $("#node-input-pin-opz2w");
const $customPinRow = $("#custom-pin-input-row");
const $customPinInput = $("#node-input-pin-custom");
const $customPinType = $("#node-input-pinType-custom");
const $intEnableCheckbox = $("#node-input-enableInterrupt");
const $intEdgeRow = $("#node-set-edge");
const $intDebounceRow = $("#node-set-debounce");
const $intDebounceTip = $("#node-tip-debounce");
// Function to update UI based on selected device
const updateUIForDevice = () => {
const selectedDevice = $deviceSelect.val();
// Hide all device-specific options and image first
$opz2wOptions.hide();
$imageContainer.hide();
$imageElement.attr("src", "");
// --- Handle Orange Pi Zero 2W ---
if (selectedDevice === 'opz2w') {
// 1. Show OPiZ2W options container
$opz2wOptions.show();
// 2. Show Pinout Image
const imageUrl = `${resourcePath}/gpio-zero2w.png`; // Assumes image is in node dir
$imageElement.attr("src", imageUrl);
$imageContainer.show();
// 3. Handle Pin Input Mode (List vs Custom) based on saved state
if (node.pinSource === 'custom') {
$pinSelect.val('custom');
$customPinRow.show();
if (!$customPinInput.data('typedInput')) {
$customPinInput.typedInput({
default: node.pinType || 'num',
types: ['num', 'str', 'env', 'flow', 'global'],
typeField: $customPinType
});
}
$customPinInput.typedInput('value', node.pin);
$customPinInput.typedInput('type', node.pinType || 'num');
} else {
$pinSelect.val(node.pin);
$customPinRow.hide();
}
}
// --- Handle Other Devices ---
// else if (selectedDevice === 'other_device') { ... }
};
// Function to show/hide interrupt settings
const setInterruptVisibility = function () {
if ($intEnableCheckbox.is(":checked")) {
$intEdgeRow.show();
$intDebounceRow.show();
$intDebounceTip.show();
} else {
$intEdgeRow.hide();
$intDebounceRow.hide();
$intDebounceTip.hide();
}
};
// --- Event Handlers ---
// 1. Device selection changes
$deviceSelect.change(() => {
updateUIForDevice();
});
// 2. Pin dropdown selection changes
$pinSelect.change(function () {
const selectedValue = $(this).val();
if (selectedValue === 'custom') {
$customPinRow.show();
if (!$customPinInput.data('typedInput')) {
$customPinInput.typedInput({
default: 'num',
types: ['num', 'str', 'env', 'flow', 'global'],
typeField: $customPinType
});
}
} else {
$customPinRow.hide();
// Optionally update name based on selection
// const selectedText = $(this).find('option:selected').text();
// $("#node-input-name").val(selectedText);
}
});
// 3. Interrupt checkbox changes
$intEnableCheckbox.change(setInterruptVisibility);
// --- Initialization on Dialog Open ---
// Set initial values from node properties
$deviceSelect.val(node.select || 'opz2w');
$intEnableCheckbox.prop('checked', node.enableInterrupt); // Set interrupt checkbox
$("#node-input-edge").val(node.edge || 'rising'); // Set edge dropdown
$("#node-input-debounce").val(node.debounce || '0'); // Set debounce input
// Update the device/pin/image UI based on loaded device and pin source
updateUIForDevice();
// Update visibility of interrupt settings
setInterruptVisibility();
},
oneditsave: function () {
const node = this;
// Save device selection
node.select = $("#node-input-select").val();
// Determine pin source and save pin/pinType accordingly
const pinSelectionMode = $("#node-input-pin-opz2w").val(); // Check the dropdown
if (pinSelectionMode === 'custom') {
node.pinSource = 'custom';
node.pin = $("#node-input-pin-custom").typedInput('value');
node.pinType = $("#node-input-pin-custom").typedInput('type');
} else {
node.pinSource = 'list';
node.pin = pinSelectionMode; // Get value directly from dropdown
node.pinType = 'num'; // Pins from list are always numbers
}
// Note: enableInterrupt, edge, debounce defaults are automatically saved by Node-RED
}
});
</script>