Compare commits

..

1112 Commits

Author SHA1 Message Date
Nick O'Leary
a3c4f12764 Bump 0.10.4 2015-02-26 21:39:30 +00:00
Nick O'Leary
393fc349b9 Fix saving for node-library content 2015-02-26 17:08:20 +00:00
dceejay
dfed4963ed fix big labelling issue with Pi + pins ... 2015-02-26 14:18:49 +00:00
dceejay
131adb6f4e let email node mark mail as read
(for the ones it reads)
2015-02-26 14:18:49 +00:00
dceejay
a8b3cbb683 remove unecessary require from serial node 2015-02-26 14:18:49 +00:00
Nick O'Leary
e97d5c7354 Rename node-red wrapper to node-red-pi 2015-02-26 13:41:01 +00:00
Nick O'Leary
061c44f958 Move shebang to the correct red.js 2015-02-26 13:24:38 +00:00
Nick O'Leary
f5d8433341 Add node-red-pi command 2015-02-26 11:38:05 +00:00
Nick O'Leary
f78a71e8ed Load flows file from userDir when appropriate 2015-02-26 11:30:20 +00:00
Nick O'Leary
4d48c72146 Add node-red script
Needed to allow arguments to be passed to the node
engine, which isn't possible if red.js is run with
a #! line.
2015-02-25 22:37:56 +00:00
dceejay
71ff828947 Tidy up all console.log util.log from core nodes.
Try to make log,warn,error more consistent behaviour.

Especially make sure any existing catches produce errors
2015-02-25 19:10:59 +00:00
Nick O'Leary
b6245bdef7 Remove console.log from XML test spec 2015-02-25 14:25:37 +00:00
Nick O'Leary
ce1cd1ab9c Change default data dir
Changes the default location for user data to $HOME/.node-red.
2015-02-25 14:25:01 +00:00
dceejay
54b0debb3b Re-order palette categories, move subflows to top of list. 2015-02-25 10:10:10 +00:00
Nick O'Leary
1876b56022 Fix jshint error 2015-02-24 23:06:07 +00:00
Nick O'Leary
d148a23ed6 Handle config nodes appearing out of order in flow
The editor ensures config nodes appear first in the flow file. The
code in the runtime and editor assumes this to be the case, so that
when a node is instantiated that requires a config node, it can assume
the config node already exists.

This change allows a config node to appear in the flow file after a
node that wants to use it. In both the editor and runtime, the code
now scans for config nodes and handles them first.
2015-02-24 23:04:55 +00:00
dceejay
049a5f1be6 revert small whitespace change to server start messages. 2015-02-24 22:22:16 +00:00
Nick O'Leary
f3880b7601 Fix credential pruning and start/stop log messages 2015-02-24 22:03:04 +00:00
dceejay
fbb45a8961 make udp node consistent with it's info.... (re params passed out) 2015-02-24 13:22:48 +00:00
dceejay
b8c460b825 pass original url request through http request node
(will be useful when we handle errors... ;-)
2015-02-24 13:20:33 +00:00
Nick O'Leary
63191bc641 Bump 0.10.3 2015-02-23 22:18:14 +00:00
dceejay
9f012c261a Make parser nodes errors actual errors.
(more cleanup will probably be necessary - but this is a start)
2015-02-23 19:30:29 +00:00
dceejay
dc7701ad70 Add node.js version to startup log msgs for debug. 2015-02-23 19:30:29 +00:00
Nick O'Leary
e8666827e6 Restore httpAdminAuth with deprecation warning 2015-02-23 11:39:38 +00:00
Nick O'Leary
5e2c51a741 Handle deleted tab when diffing flows 2015-02-22 22:59:26 +00:00
dceejay
51421ce657 clone msg more correctly for CSV node multiple line output 2015-02-22 21:57:06 +00:00
Nick O'Leary
339e6039e1 Add engine restriction against node 0.12 2015-02-22 21:28:28 +00:00
dceejay
43054906dc preserve other msg properties when passing through CSV node 2015-02-22 19:23:36 +00:00
Nick O'Leary
57dedcf816 Add files to .gitignore 2015-02-21 00:28:29 +00:00
Nick O'Leary
4dc21c43fa Handle strings for limit/skip args to mongo node 2015-02-20 20:02:25 +00:00
Nick O'Leary
1c86908b90 Bump 0.10.2 2015-02-20 10:07:10 +00:00
Nick O'Leary
9b26973883 Ensure activeWorkspace is set when a flow contains no tabs 2015-02-20 10:05:42 +00:00
Nick O'Leary
2585e983a5 Add test-* grunt tasks 2015-02-15 23:35:24 +00:00
Nick O'Leary
a808cb44c2 Merge pull request #563 from emiloberg/master
Fixes bug that prevented unsubscribe from WebSockets, browser side. Fixes node-red/node-red/#562
2015-02-15 21:54:48 +00:00
Nick O'Leary
edd9d2cb9c Fix Inject node handling of day selection
Fixes #564
2015-02-15 21:53:14 +00:00
Emil Öberg
33d24f79ce Fixes bug that prevented unsubscribe from WebSockets, browser side. Fixes node-red/node-red/#562 2015-02-14 22:25:25 +01:00
dceejay
cc095e4edf edit HTML node info to remove ref to jQuery. Link to CSSselect instead. 2015-02-14 19:06:35 +00:00
dceejay
07641d57ab recorrect debug logging level colour class names so sidebar is as-was.
in light of new logging levels
2015-02-14 12:14:06 +00:00
dceejay
5643c51507 Let debug node show "topic" correctly for errors in functions. 2015-02-13 21:14:54 +00:00
Nick O'Leary
ad6254c0b8 Add oneditdelete handler for config nodes
Already had the undocumented ondelete handler. Adding
this to be consistent with the other oneditXYZ handlers.
2015-02-13 11:20:08 +00:00
Nick O'Leary
d6ca421d59 Remove unecessary argument to oneditcancel 2015-02-13 11:05:45 +00:00
Nick O'Leary
79bd01f810 Remove X button on dialogs
Fixes #561

Also, oneditcancel not being honoured for regular nodes, only config nodes
2015-02-13 10:06:28 +00:00
Nick O'Leary
e357352240 Tidy up info tab handling of subflows and comments 2015-02-10 21:29:27 +00:00
dceejay
0accfade02 catch internal subflow info error in tab-info... 2015-02-10 20:15:00 +00:00
dceejay
00b7afe3ae spelling pedant alert in debug node... its not it's 2015-02-10 20:14:33 +00:00
dceejay
2e76541fa5 Update Debug node test to "unbreak" build... oops 2015-02-10 17:31:26 +00:00
dceejay
e2911078e3 tidy up tab-info so subflows show more useful information
(was broken before but no-one noticed ;-)
(and reorder fields in HTTP and XML nodes so name comes out first - ocd)
2015-02-10 16:56:07 +00:00
dceejay
c6157687c9 Move payload type label in Debug window to meta data row
rather than (object) etc at start of actual payload.
2015-02-10 16:56:07 +00:00
Nick O'Leary
87a1818486 Add missing FA font file 2015-02-09 09:48:31 +00:00
Nick O'Leary
d6c5d91e2c Remove package.json from gitignore because it breaks everything 2015-02-08 23:14:37 +00:00
Nick O'Leary
e042d9e8cd Bump version 0.10.0 2015-02-08 23:03:48 +00:00
dceejay
07c8c4bb45 Let Pi GPIO node show pins in use to help avoid clashes. 2015-02-08 19:40:23 +00:00
dceejay
2dd572f5bd Allow msg.description to add to the email body text
(for feed from Fluickr node)
2015-02-08 15:02:02 +00:00
dceejay
d4a27f88a1 Allow Email node to send html, and add attachments.
(outbound only at present)
2015-02-08 11:33:04 +00:00
dceejay
1513dbfcdd Add strict flag to debug (for consistency with others) 2015-02-08 11:32:26 +00:00
Nick O'Leary
8451c29a25 Add line/col report test 2015-02-07 19:56:22 +00:00
Nick O'Leary
ae7f1b38a8 Add line/col reporting to Function runtime errors 2015-02-07 19:52:14 +00:00
Nick O'Leary
83dad88ad3 Fix debug reporting of warning/errors 2015-02-07 19:29:43 +00:00
Nick O'Leary
8eb1a02407 Fix unit test failures following mocha upgrade 2015-02-06 17:27:43 +00:00
Nick O'Leary
1e6f45852f Update package dependencies 2015-02-06 17:17:44 +00:00
Nick O'Leary
3849855b57 Rename _id to _msgid to avoid mongo clash 2015-02-06 16:36:32 +00:00
Nick O'Leary
f2712f296b Update to font-awesome 4.3 2015-02-06 13:57:33 +00:00
Nick O'Leary
c7f13e3d0c Add needsPermission protection to core nodes 2015-02-06 13:57:15 +00:00
Nick O'Leary
d1fe997bf7 Doc updates 2015-02-06 13:16:46 +00:00
Nick O'Leary
86c8a5de81 Allow palette categories to be predefined 2015-02-06 13:16:19 +00:00
Nick O'Leary
010abbd3d5 Remove deprecated nodes 2015-02-06 11:33:04 +00:00
Nick O'Leary
3123aa6279 Merge pull request #558 from knolleary/auth
Add bearer token authentication to Editor
2015-02-06 11:28:12 +00:00
Nick O'Leary
dedda19626 Restyle user menu 2015-02-06 11:27:21 +00:00
Nick O'Leary
c31ffb98b0 Tie auth middleware to needsPermission api 2015-02-05 23:43:35 +00:00
Nick O'Leary
3ef6f29d6e Add Log.trace/debug/error functions 2015-02-05 17:05:39 +00:00
Nick O'Leary
26c42e500f Allow user.default to be an api function 2015-02-05 13:01:00 +00:00
Nick O'Leary
53a515176b Remove unused token expiry code 2015-02-05 13:01:00 +00:00
Nick O'Leary
fbf7ee50eb Increase unit test coverage of auth code 2015-02-05 13:00:56 +00:00
Nick O'Leary
b2aae93fa6 Hide user profile menu 2015-02-05 13:00:55 +00:00
Nick O'Leary
a494954275 Add permissions and user menu 2015-02-05 13:00:55 +00:00
Nick O'Leary
f5d7903ecb Stop lost connection message bouncing when not authed 2015-02-05 13:00:55 +00:00
Nick O'Leary
9bbe0799bd Allow adminAuth setting to provide functions 2015-02-05 13:00:55 +00:00
Nick O'Leary
f3eb85c449 Move over to settings.adminAuth 2015-02-05 13:00:54 +00:00
Nick O'Leary
74e1ef0823 Add auth awareness to comms channel 2015-02-05 13:00:49 +00:00
Nick O'Leary
982997c3df Add auth awareness to ui 2015-02-05 13:00:49 +00:00
Nick O'Leary
66005a2688 Use jquery ajax rather than d3 2015-02-05 13:00:49 +00:00
Nick O'Leary
42cd6f94a7 fix settings 2015-02-05 13:00:37 +00:00
Nick O'Leary
dfc20f3cc9 Add node-red logo for login screen 2015-02-05 13:00:29 +00:00
Nick O'Leary
2b5a1ce6d4 Add settings changes 2015-02-05 13:00:16 +00:00
Nick O'Leary
28823802ea Prompt login if auth enabled 2015-02-05 13:00:16 +00:00
Nick O'Leary
2128b57ab2 Add oauth grant 2015-02-05 13:00:07 +00:00
Nick O'Leary
c8ccacb035 Switch node sortable entries diable inputs in FF 2015-02-05 10:46:01 +00:00
dceejay
3334c795e0 Correct spelling in Pi error messages, fix mouse icon file type 2015-02-04 23:27:51 +00:00
Nick O'Leary
b97d251787 Add on-headers dependency to package.json 2015-02-04 22:32:27 +00:00
Nick O'Leary
482c4e9c5e Add memory metric reporting 2015-02-04 22:28:17 +00:00
Nick O'Leary
1712146836 Clear loghandlers on init and supress output in tests 2015-02-04 21:29:11 +00:00
Nick O'Leary
c8d2d690f0 Rename HTTP In content-length metric 2015-02-04 21:10:18 +00:00
Nick O'Leary
f2d4648384 Support of HTTP Node metrics 2015-02-04 20:48:46 +00:00
dceejay
86ca75bcd5 Pi Node status not displaying 0 fixed. 2015-02-04 19:02:32 +00:00
Nick O'Leary
31aa3901cc Fix global leak in localfilesystem 2015-02-04 15:41:18 +00:00
Nick O'Leary
ab831c34f3 Enable node status by default 2015-02-04 15:23:28 +00:00
Nick O'Leary
8443e48240 Place flows backup alongside flow file
Also backup credentials file in the same manner.
2015-02-04 15:23:28 +00:00
dceejay
a08b29dbd1 Add info to Twitter out node to make it obvious how to do DMs. 2015-02-04 11:36:43 +00:00
Nick O'Leary
97621b41b9 Ensure deleted core nodes are removed from config list 2015-02-04 10:27:02 +00:00
Nick O'Leary
f462446213 Make Function duration status optional 2015-02-04 10:01:46 +00:00
hbeeken
6b96c1876a changing function node metrics to new infrastructure 2015-02-04 09:46:54 +00:00
Nick O'Leary
0aaea1ec40 Update logging/metric system 2015-02-03 22:02:26 +00:00
hbeeken
7d6ce1ec12 Changing metric logging to take a primative rather than an object 2015-02-03 19:12:09 +00:00
hbeeken
b052324d36 Adding logging & metric recording configuration via settings 2015-02-03 19:12:09 +00:00
dceejay
a22f819f40 Bit more consistent error handling / status updates for Pi GPIO node 2015-02-03 17:22:44 +00:00
Nick O'Leary
07acc6642f Merge pull request #460 from hindessm/fix-lastSent-initial-value
Fix node.lastSent initial value after refactoring.
2015-02-03 13:27:48 +00:00
Nick O'Leary
950611ed31 Merge pull request #554 from hbeeken/Node-spec-fix
Fixing test where part wasn't exercised
2015-02-03 13:26:50 +00:00
dceejay
84e6417877 Change inject node to use tick boxes for days of week instead of select. 2015-02-03 11:21:29 +00:00
hbeeken
a87548a991 Fixing test where part wasn't exercised 2015-02-02 13:21:19 +00:00
dceejay
5e5a220f68 Better ref link for CSS selectors list for HTTP parser node. 2015-01-30 10:00:20 +00:00
dceejay
f451d0644a Allow msg.PATCH method on http request node
Simple fix  to Close PR #524
(no CLA)
2015-01-30 10:00:20 +00:00
Nick O'Leary
e9f0877da8 Skip .info call for subflow IO nodes 2015-01-30 09:53:11 +00:00
dceejay
f6c6301733 Add done() async close to tcp node(s) 2015-01-29 21:43:23 +00:00
dceejay
0709a118e3 more fun trying to close Pi and Arduin nodes async style with done()
(more consistent use of call done().)
2015-01-29 21:43:23 +00:00
Nick O'Leary
7576878ba5 Filter comments when generating palette tooltips
Fixes #549
2015-01-29 21:40:21 +00:00
Nick O'Leary
083e253e45 Configure marked once on start-up 2015-01-29 20:47:30 +00:00
Nick O'Leary
da67e69544 Add drag handle to switch node rules 2015-01-29 20:38:19 +00:00
dceejay
0ed8d28342 Add Markdown capability to Comment node
body is rendered in the info tab and can be styled with Markdown
2015-01-29 18:53:59 +00:00
dceejay
27f9056360 Add status to file node when you overide filename
(so you can see what it writes to... )
2015-01-29 18:53:59 +00:00
dceejay
8c075bfde3 Make HTML select node point to Cheerio select docs. 2015-01-29 18:53:59 +00:00
dceejay
cae755d948 Cleanup closing down of Pi gpio nodes 2015-01-29 18:53:58 +00:00
Nick O'Leary
ca9d84b1b7 Allow Switch rules to be drag-ordered 2015-01-29 16:28:18 +00:00
Nick O'Leary
f983e4da9f Renable unit tests following logging api changes 2015-01-29 09:57:09 +00:00
Nick O'Leary
109270b437 Merge pull request #541 from hbeeken/logging-infrastructure
Adding metric logging mechanism
2015-01-27 17:50:26 +00:00
hbeeken
0bfbb12211 Adding metric logging mechanism 2015-01-27 14:41:20 +00:00
dceejay
69998243cc Add fs.notify, feedparser and serialport to default install packages
To close #518  Pull request 9 by TJ
We want to keep arduino, mongo and redis out of the default install for now.
need for js2xmlparser will go in v0.10
2015-01-21 15:05:35 +00:00
dceejay
8b61c121e6 Small changes to info box language for file watch node 2015-01-21 15:05:35 +00:00
Nick O'Leary
56ef982345 Revert settings.js 2015-01-18 21:24:19 +00:00
Nick O'Leary
8389df9729 Merge branch 'master' of github.com:node-red/node-red 2015-01-18 09:39:05 +00:00
Nick O'Leary
a8f1a6df2c Update sidemenu links to nodered.org 2015-01-18 09:38:47 +00:00
dansu
68e51bb886 added tests for websocket-client
cleanup and prettify
2015-01-18 09:38:47 +00:00
Nick O'Leary
462c259f3a Allow node to provide dynamic content to Info tab
Closes #492

The node definition can now include an `info` property. This property can be either a string or a Function. Whenever the info tab is refreshed, such as the node is selected, the value of this property, or the result of the Function, will be appended to the Info tab.
2015-01-18 09:38:47 +00:00
Nick O'Leary
f6f4b0784b Make subflow delete option more obvious
Fixes #514
2015-01-18 09:38:47 +00:00
Nick O'Leary
57b39022d0 Merge pull request #537 from dsundberg/websocket-client-unittests
added tests for websocket-client
2015-01-17 22:45:22 +00:00
Nick O'Leary
110f0bf169 Allow node to provide dynamic content to Info tab
Closes #492

The node definition can now include an `info` property. This property can be either a string or a Function. Whenever the info tab is refreshed, such as the node is selected, the value of this property, or the result of the Function, will be appended to the Info tab.
2015-01-17 21:36:16 +00:00
Nick O'Leary
3f0fcb70a4 Make subflow delete option more obvious
Fixes #514
2015-01-17 21:02:28 +00:00
Nick O'Leary
d8c7ea8144 Tidy sidebar menu
- remove inconsistent ellipsis
- add Subflow submenu
2015-01-17 20:36:18 +00:00
dansu
44db2c28a3 added tests for websocket-client
cleanup and prettify
2015-01-17 21:10:38 +01:00
Nick O'Leary
8d5f99640e Merge pull request #533 from knolleary/deploy
Add smarter deployment options
2015-01-16 16:00:35 +00:00
Nick O'Leary
ffe417976c Merge pull request #532 from hindessm/random-delay-fixes
Random delay fixes
2015-01-16 15:46:53 +00:00
Nick O'Leary
d04ac00732 Add more Flow_spec tests 2015-01-16 15:43:47 +00:00
Nick O'Leary
dd5e851339 Better sidemenu style 2015-01-16 10:25:57 +00:00
Dave Conway-Jones
2890575b3d Merge pull request #531 from dsundberg/websocket-unittest
Websocket unit tests
2015-01-15 17:18:34 +00:00
Dave Conway-Jones
8b3fbae3f6 Merge pull request #516 from dsundberg/websocket_ext
Added option to specify remote WebSocket URL in web socket node.
2015-01-15 17:17:21 +00:00
Nick O'Leary
c97ab18e62 Add Flow spec 2015-01-15 17:12:50 +00:00
Nick O'Leary
b0ffc12142 Restyle sidebar menu 2015-01-15 10:28:12 +00:00
Nick O'Leary
687a66344e Deploy menu style 2015-01-15 10:28:11 +00:00
Nick O'Leary
a5afc258b1 Update unit tests 2015-01-15 10:28:11 +00:00
Nick O'Leary
afb5e8cbce Fix jshint errors 2015-01-15 10:28:11 +00:00
Nick O'Leary
83b40a7ba6 Fix credential update 2015-01-15 10:28:11 +00:00
Nick O'Leary
cf1371bfdf Add deployment types in runtime
- removes ui option as it needs work
2015-01-15 10:28:11 +00:00
Nick O'Leary
89fff339d5 Add deploy dropdown button 2015-01-15 10:28:10 +00:00
Nick O'Leary
e11abd2508 Identify modified nodes on deploy 2015-01-15 10:28:10 +00:00
dansu
9a0177b900 Added support for websocket-client in 22-wesocket node, selectable in ui
as Listen to/Connect to drop down.
2015-01-15 08:57:42 +01:00
Mark Hindess
5510dffe18 Fix random delay mode to be random delay not random rate-limited stack.
Documentation says "Introduces a delay into a flow or rate limits
messages." but this node was doing delay and rate limit in random mode
which doesn't seem that useful. Worse it was a stack not a queue. I
can't think of any sane use cases for that behaviour.
2015-01-14 14:19:21 +00:00
Mark Hindess
7909ca24d3 Fix random delay in milliseconds case and change test to reproduce bug.
Because there was no multiplier the node.randomFirst was a string so
the later '+' was a concatentation. The test failed to catch this because
it uses integers not strings to configure the node.
2015-01-14 10:18:47 +00:00
dceejay
eee2996e8a Make Pi GPIO node wait for close before restarting
Looking to address Issue #530
2015-01-14 09:55:30 +00:00
dansu
2577631334 Added unittests for websocket node 2015-01-13 10:38:53 +01:00
dansu
9f91493cd1 rename 'send' to 'reply' to not collide with internal node function names, a config node has no wires so the 'send' function gets overwritten with a noop (nodes/red/Node.js:42, introduced in 57ae297) 2015-01-13 08:48:59 +01:00
dceejay
fcec704b7b Reset some changes to sample to be in line with how most of our nodes
actually are.
2015-01-12 19:11:07 +00:00
dceejay
2066d53d3f Handle quotes in JSON inside CSV files in CSV node...
so they come out as real JSON rather than being stripped.
2015-01-09 20:32:05 +00:00
Dave Conway-Jones
8822335700 Merge pull request #522 from motiooon/patch-1
adding skip to the mongodb input node
Great - looks good - many thanks @motiooon
2015-01-09 20:27:09 +00:00
Gabriel Baciu
f461b121e1 mongodb node take a skip property so pagination can be achieved 2015-01-09 14:32:21 -05:00
dceejay
92b393d3df really enforce binary for exec node stdout when required. 2015-01-08 12:35:42 +00:00
dceejay
4fb2a44d74 correct exec binary stdout to be consistent with spawn stdout.
Binary = buffer
String = utf8
2015-01-07 21:33:15 +00:00
dceejay
00429ebe70 Update exec node to handle binary stdout 2015-01-07 21:12:01 +00:00
dceejay
25537e01d4 Add byte mode and mouse buttons to Pi node 2015-01-06 22:06:28 +00:00
Nick O'Leary
65e4d83625 Merge pull request #526 from anna2130/mongo-db-aggregate
Fixed check for array in MongoDB aggregate function
2015-01-05 11:14:18 +00:00
Anna Thomas
ed6272ce12 Fixed check for array in MongoDB aggregate function
Fixes #525
2015-01-05 10:57:49 +00:00
dceejay
9e4187d6a8 New Pi GPIO node based on RPI.GPIO library.
Adds PWM support of outputs and easier access to interrupts for inputs.
2014-12-27 13:11:44 +00:00
Dave C-J
b4dc66944a Make sample node more representaive of a real node...
(OK not really real but at least it now doesn't fail if you try to run it)
2014-12-26 15:28:31 +00:00
Dave C-J
a51056a91f Be a bit more relaxed about IRC node reconnect - it does happen eventually.
Trying to nail down Issue #447
2014-12-18 23:22:36 +00:00
Nick O'Leary
a3692944a2 Merge pull request #515 from anna2130/nr-cli-enhancements
No-op and return success on enabling/disabling node by type name
2014-12-17 14:03:38 +00:00
Anna Thomas
03765afefa Updated nodes_spec test to no-op when already enabled/disabled by type name 2014-12-17 13:58:49 +00:00
Anna Thomas
66213d151d No-op and return success on enabling/disabling node by type name 2014-12-17 13:35:57 +00:00
Nick O'Leary
0c699ae57f Merge pull request #511 from anna2130/nr-cli-enhancements
Updates to match cli changes
2014-12-17 13:10:49 +00:00
Dave C-J
bf8d549cf7 Add "advanced" options to XML parsing node
Allows setting of attrkey and charkey
Push to close #348
2014-12-15 17:05:18 +00:00
Dave C-J
1261bf97ea Remove : from inject node label - people didn't like it.
Claim they can tell the difference between topic and payload as they wrote
the flow...
2014-12-15 17:03:18 +00:00
Dave C-J
41552625e0 remove spurious line of debug from http node 2014-12-15 17:01:58 +00:00
Dave C-J
27ef7d972f Add Binary paylaod option to Http request node
Also add JSON parse option to output
Allow user to select override using msg.method to stop getting warning.
Fix to close #399
2014-12-09 14:40:03 +00:00
Dave C-J
6fc3aab907 Make Palette search Case InSeNsItIve
fix to close #496
2014-12-09 14:37:32 +00:00
Anna Thomas
10681f97d9 Updated tests to reflect addition of version getSet 2014-12-08 16:53:06 +00:00
Anna Thomas
475d9e110e Updated to match cli changes 2014-12-08 16:53:06 +00:00
Nick O'Leary
8c5fab61e6 Merge pull request #509 from anna2130/nr-cli-enhancements
nr-cli enhancements
2014-12-08 13:08:58 +00:00
Anna Thomas
e7ccff5a4b Get version from pkg 2014-12-08 10:15:21 +00:00
Anna Thomas
81e08e06e4 Updated test to reflect saving settings in nodes 2014-12-08 10:10:16 +00:00
Anna Thomas
b006ccf610 saveNodeList saves settings in nodes 2014-12-08 09:57:17 +00:00
Anna Thomas
bb0e48f271 Store version in settings 2014-12-08 09:55:51 +00:00
Nick O'Leary
6d0dffcdf7 Migrate node config to new format 2014-12-05 20:43:41 +00:00
Nick O'Leary
14b84f0c7b Merge pull request #498 from anna2130/nr-cli-enhancements
nr-cli enhancements - server side api
2014-12-05 20:39:42 +00:00
Anna Thomas
8b6e287a74 Updated remove modules tests to reflect getNodeModuleInfo changes 2014-12-05 16:51:26 +00:00
Anna Thomas
1a5751ff1d Update removeModule to reflect getNodeModuleInfo changes 2014-12-05 16:45:32 +00:00
Anna Thomas
851048077c Remove new lines from version 2014-12-04 13:22:42 +00:00
Dave C-J
5f74a1d237 Update file, http and email nodes to only show deprecation warning
if msg property is a real overide  to node property ( !== ).

Fix for comment by @drJeckyll  to Issue #399
2014-12-01 22:58:25 +00:00
Dave Conway-Jones
e9b1e287ba Merge pull request #500 from jacktech24/master
Fixed bug, in Arduino output node
2014-11-28 10:52:12 +00:00
Nick O'Leary
da7b3ce9e4 Incorrect reference to routes in http in node
Fixes #503
2014-11-28 10:09:38 +00:00
Anna Thomas
35b3912808 Removed CLI from repo 2014-11-28 09:21:39 +00:00
Anna Thomas
e28f933f64 Refactor saveNodeList 2014-11-28 09:17:46 +00:00
Anna Thomas
57bc83b2a7 Enabling and disabling non-existent nodes throws an error 2014-11-27 16:42:45 +00:00
Anna Thomas
ec43fc4fe2 Removed unused code 2014-11-27 13:12:47 +00:00
Anna Thomas
8f2a0b63d9 Changed cleanNodeList to cleanModuleList 2014-11-26 16:46:51 +00:00
Anna Thomas
bb6e27f662 Store node list as module list 2014-11-26 16:25:37 +00:00
jacktech24
4e28a308b0 fixed bug, replaced servoWrite with analogWrite where it should be 2014-11-25 20:42:17 +01:00
Anna Thomas
04ffaeb2b8 Refactor nodeModules.nodes to moduleNodes 2014-11-24 15:44:11 +00:00
Anna Thomas
d7f249eac4 Added version number to modules 2014-11-21 16:35:29 +00:00
Anna Thomas
a5064b3ab6 Reloads module info after enabling/disabling module 2014-11-21 16:34:57 +00:00
Anna Thomas
dd5821ee1b Installing a module returns module info
Removing a module checks module exists and checks type is not in use
2014-11-21 15:15:24 +00:00
Anna Thomas
4c9d53388c Removed plugins references 2014-11-21 11:31:07 +00:00
Anna Thomas
70f101497d Replaced delete response with 204 2014-11-21 11:25:51 +00:00
Anna Thomas
56cb985de9 Separated put response into /nodes/:mod and /nodes/:mod/:set
Updated put tests
2014-11-21 10:36:32 +00:00
Anna Thomas
d614b7c39f Moved get node set response to /nodes/:mod/:set
Updated tests
Changed plugin back to module
2014-11-20 15:18:16 +00:00
Anna Thomas
0ff65f6805 Updated registry tests to reflect id changes 2014-11-20 13:08:27 +00:00
Anna Thomas
591b5f3f91 Replaced hex id with 'module/set' id 2014-11-20 12:15:15 +00:00
Anna Thomas
50fddf474b Local nodes loaded with node-red as their module 2014-11-20 09:58:42 +00:00
Nick O'Leary
3a78a2fedd Tab name changes not persisting
Fixes #495
2014-11-19 16:03:12 +00:00
Anna Thomas
9552055b08 loadNodeConfig assumed to always be called with module and name parameters 2014-11-19 13:54:00 +00:00
Nick O'Leary
71bd5cd9e9 Merge pull request #485 from anna2130/nr-cli-enhancements
WIP: Command Line Tool API
2014-11-17 13:34:24 +00:00
Nick O'Leary
7cff7ed297 Merge pull request #491 from anna2130/mongo-msg-collection
Fixes collection only being set by first message
2014-11-17 11:15:45 +00:00
Anna Thomas
6ba0d83778 Fixes collection only being set by first message 2014-11-17 10:33:31 +00:00
Dave C-J
ca2ef7e71f Pi GPIO node was not displaying Model B+ pin numbers correctly
on subsequent edit.
2014-11-16 18:23:24 +00:00
Dave C-J
977a9e1c83 Better attempt at making Inject node label more "useful".
Addresses Issue #489
2014-11-16 18:22:33 +00:00
Dave C-J
7da108e129 Reverting fix for #489 while we debate the issue more fully.... 2014-11-14 23:50:49 +00:00
Dave C-J
7b14e753cd Let Twitter node save place as a location property rather than text. 2014-11-14 23:42:35 +00:00
Dave C-J
9863b6e178 Add payload to Inject label to make more helpful
Close #489
2014-11-14 17:28:08 +00:00
Nick O'Leary
0789b82c15 Restore dialog size on reopn properly 2014-11-13 22:14:05 +00:00
Nick O'Leary
a477c0b827 Fix serial config node edit layout 2014-11-13 20:53:15 +00:00
Nick O'Leary
3cb423a0b4 Fix func/temp/comment editor resizing 2014-11-13 17:21:12 +00:00
Nick O'Leary
9c8d9550a7 Do not assume subflows exist when deleting nodes 2014-11-13 16:00:46 +00:00
Anna Thomas
8d16f3c8be Registry tests for plugins and enable/disable in CLI 2014-11-13 15:14:20 +00:00
Nick O'Leary
b4c92b457a Flatten the dialog box style 2014-11-13 13:55:12 +00:00
Nick O'Leary
426fcc2fdd Rework subflow edit process 2014-11-13 12:59:28 +00:00
Nick O'Leary
5cb9a5b7eb All subflow input to be deleted by selection 2014-11-13 00:02:41 +00:00
Nick O'Leary
64a6fe11da Add subflow outputs to be delete by selection 2014-11-12 23:51:42 +00:00
Nick O'Leary
f72f7cdaa7 Merge pull request #469 from Belphemur/ui-localstorage
Ui localstorage
2014-11-11 10:43:06 +00:00
Antoine Aflalo
01f0d5390f Adding support to LocalStorage on client side
Save the state of the Menu Item between session (like activation of
node-status and sidebar)
2014-11-11 09:04:57 +02:00
Dave C-J
ed9951f065 Add PWM support to Pi GPIO Node - pin 12 (GPIO1)
(only pin that has hardware pwm support)

Note: It will interfere with any other audio output as they share 
same hardware/timers.
2014-11-10 20:03:51 +00:00
Nick O'Leary
4249cf5d69 Fix debugMaxLength description
Closes #484
2014-11-09 20:55:13 +00:00
Dave C-J
5da45b404c Add delete option to File node
to replace msg.delete option - now deprecated but not removed.
Addresses some of the confusion  for Issue #399
2014-11-08 15:34:54 +00:00
Dave C-J
8b7e367416 tweak CSV parser to better handle GSM style phone numbers. 2014-11-08 15:34:54 +00:00
Nick O'Leary
28da2dc38a Subflow palette node outputs not updating
part of #479
2014-11-07 16:12:27 +00:00
Dave C-J
5c5de028da Add remote server name to page title / tab
Makes selecting one of several servers easier.
2014-11-07 12:28:35 +00:00
Dave C-J
b861f490c6 tiny tidy-up on TCP request node 2014-11-07 12:28:34 +00:00
Nick O'Leary
e7dccf04d2 Changing subflow in/outs leaving wires behind
Fixes #477
2014-11-07 11:22:00 +00:00
Nick O'Leary
3e235ecc0b Move cloneMessage to RED.util.cloneMessage 2014-11-06 11:39:30 +00:00
Anna Thomas
deeaa09360 Renamed modules to plugins for CLI 2014-11-06 10:59:34 +00:00
Anna Thomas
2e7a97fb88 Get plugins tests 2014-11-06 10:59:06 +00:00
Dave C-J
9c92eeb9f5 Allow tcp request node to accept msg.host and msg.port as inputs
Overrides only allowed if edit setting left blank.
2014-11-06 10:21:14 +00:00
Anna Thomas
3e24601518 Functionality to get installed module info from the cli 2014-11-06 10:00:25 +00:00
Nick O'Leary
266a644ca6 Preserve querystring when ensuring path ends with slash 2014-11-06 00:01:01 +00:00
Nick O'Leary
def93214de Merge pull request #476 from knolleary/auth
Reorganise how adminApp is setup
2014-11-05 23:18:32 +00:00
Nick O'Leary
a520240b25 Ensure application/json on library flows reqs 2014-11-05 23:08:23 +00:00
Nick O'Leary
e7eb02fcb7 Add unit tests for refactored API modules 2014-11-05 23:07:50 +00:00
Nick O'Leary
72f9471f2b Reorganise how adminApp is setup 2014-11-05 22:45:18 +00:00
Nick O'Leary
67449eb65a Merge pull request #470 from anna2130/msg-property-overrides
Message properties overriding set node properties
2014-11-05 22:05:34 +00:00
Anna Thomas
069a47f35a Added node warnings when message properties override set node properties 2014-11-05 17:23:27 +00:00
Nick O'Leary
53b07581cf Merge pull request #474 from zobalogh/twitter-location
Adding location support to Twitter
2014-11-05 15:40:47 +00:00
zobalogh
11a29b4633 Adding location support to Twitter 2014-11-05 12:00:37 +00:00
Dave C-J
bdb46cbbe4 Add "is-utf8" npm to package - part of fix #435 2014-11-04 22:00:58 +00:00
Dave C-J
273acc0ec4 Let MQTT input node receive binary packets
Try to auto select output type to be string or buffer to be backwards compatible
Fixes #435
2014-11-04 21:56:15 +00:00
Nick O'Leary
1153619a03 Handle uninitialised node in single-wire fastpath 2014-11-04 11:36:28 +00:00
Nick O'Leary
f89ddb5f7a Merge pull request #463 from njh/redis-object
Added support for storing a msg.payload of type object in a Redis hash
2014-11-01 21:19:45 +00:00
Nick O'Leary
4494c11b03 Subflow name property getting dropped on import
Closes #468
2014-10-31 22:46:26 +00:00
Dave C-J
694649e8f9 Update feedparse in line with underlying npm. 2014-10-31 18:59:57 +00:00
Anna Thomas
9f925140c9 Updated MongoDB node info 2014-10-31 15:41:13 +00:00
Nick O'Leary
6a37a823df Update orion to 7.0 release
Closes #467

Version: http://download.eclipse.org/orion/drops/R-7.0-201410282256/index.html
2014-10-31 14:28:33 +00:00
Nick O'Leary
f0e9a0279f Ignore comms heartbeat messages in comms test 2014-10-31 13:06:08 +00:00
Nick O'Leary
863b85714d localfilesystem storage must fsync writes
Closes #465
2014-10-31 11:40:10 +00:00
Nick O'Leary
dfc79e3122 Preserve unknown node type properties across deploys
Closes #5
2014-10-30 21:41:42 +00:00
Nicholas Humfrey
afde3d0ab8 Added support for storing a msg.payload of type object in a Redis hash 2014-10-30 10:09:40 +00:00
Nick O'Leary
7419f62a20 Merge pull request #461 from hindessm/fix-tweet-post-error-reporting
Fix tweet post error reporting.
2014-10-30 08:44:08 +00:00
Mark Hindess
f06b52625f Fix tweet post error reporting. 2014-10-30 08:38:49 +00:00
Nick O'Leary
ccfbd69f24 Merge pull request #459 from hindessm/fix-info-id-refactoring-bug
Fix refactoring error; rename info to id.
2014-10-30 08:35:10 +00:00
Mark Hindess
d859412785 Fix node.lastSent initial value after refactoring. 2014-10-30 08:19:52 +00:00
Mark Hindess
3840bd117c Fix refactoring error; rename info to id. 2014-10-30 08:08:41 +00:00
Nick O'Leary
aff8a7802a Merge pull request #458 from knolleary/subflows
Add Subflows
2014-10-29 22:04:48 +00:00
Nick O'Leary
6169e4299a Add initial subflow tests 2014-10-29 21:44:33 +00:00
Nick O'Leary
d9648ca76b Add subflow support 2014-10-29 20:26:25 +00:00
Dave C-J
348b642d25 Return sensible name to display for Delay node Queue mode 2014-10-29 18:26:43 +00:00
Dave C-J
a1830def8e Fix http in node close wrinkle if cors enabled but not used. 2014-10-29 18:26:09 +00:00
Dave C-J
98b875c4a0 Neater fix for Mac meta key - thanks to Nick. 2014-10-29 09:05:48 +00:00
Nick O'Leary
d242f67be3 Merge pull request #457 from anna2130/change-node-multi-level-properties
Change node can set msg property to another msg property. Closes #456
2014-10-28 16:07:42 +00:00
Anna Thomas
ce6513e7f7 Change node can set msg property to another msg property. Closes #456 2014-10-28 15:50:50 +00:00
Dave C-J
bd75c1c753 Better attempt at adding Apple meta key 2014-10-28 09:03:00 +00:00
Nick O'Leary
48d3b8f37a Inject node interval error
part two of #455
2014-10-27 19:41:29 +00:00
Nick O'Leary
4c573b208c Merge branch 'master' of github.com:node-red/node-red 2014-10-27 19:36:43 +00:00
Nick O'Leary
a5228875a6 Inject node calculating hour interval period incorrectly
Fixes #455
2014-10-27 19:36:26 +00:00
Dave C-J
c51866c2c5 Add ⌘ Cmd key as alternative to Ctrl key to make Mac users happy. 2014-10-27 15:07:34 +00:00
Dave C-J
bbaf7bf247 correcting typos in trigger node... oops 2014-10-27 08:54:21 +00:00
Nick O'Leary
04673c65f4 Node drag start threshold incorrectly calculated 2014-10-26 22:23:56 +00:00
Nick O'Leary
57ae297efd Clone messages before any node.receive call 2014-10-25 23:12:30 +01:00
Dave C-J
0bb78ae491 Try to ensure TCP node closes server connections on redeploy
(to clean up properly - especially in case of port changes)

Closes Issue #454
2014-10-25 17:52:24 +01:00
Dave C-J
d9363f4974 Reduce logging for exec node
(can be re-enabled by using node -v red.js ... )
2014-10-25 17:50:55 +01:00
Dave C-J
b54e9edfa6 Add "topic based fair queue" option to delay node 2014-10-24 20:00:25 +01:00
Dave C-J
cf81de415a Minor UI tweaks to RPi node 2014-10-24 20:00:25 +01:00
Nick O'Leary
4cd78692e2 Handle debug boolean complete properties 2014-10-23 15:28:47 +01:00
Nick O'Leary
5fd1ff5137 Add hint on requiring fs module in settings.js 2014-10-22 21:44:01 +01:00
Nick O'Leary
fe95a98339 Merge pull request #441 from anna2130/reduce-message-cloning-overhead
Reduces message cloning overhead for single recipients
2014-10-22 15:09:59 +01:00
Nick O'Leary
da73972c41 Merge pull request #449 from hbeeken/update-test-helper
Adding capability to the test helper to load more than one node at once
2014-10-22 15:08:17 +01:00
Nick O'Leary
014389c55f Merge pull request #450 from Belphemur/patch-1
Correcting a Typo
2014-10-22 15:07:47 +01:00
Antoine Aflalo
ff4edccbbc Correcting a Typo 2014-10-22 16:33:48 +03:00
hbeeken
1e7ce2cfe7 Adding capability to the test helper to load more than one node at once 2014-10-22 11:29:54 +01:00
Anna Thomas
bc8e459ae6 Node does not clone first message sent
Tests updated to mirror this behaviour
    Annotated algorithm
2014-10-21 14:08:35 +01:00
Nick O'Leary
53a9a5fe93 Merge pull request #422 from anna2130/debug
Updated debug node to output other msg properties
2014-10-20 16:35:24 +01:00
Anna Thomas
17e4bf1a11 Tidy up node label and edit dialog 2014-10-20 12:52:07 +01:00
Nick O'Leary
bec4e429f9 Merge pull request #433 from anna2130/change-node-multi-level-properties
Change node: Multi-level properties
2014-10-20 11:12:59 +01:00
Dave C-J
472fdc65a9 Allow Raspberry Pi node to set initial output level.
Fix for #443
Also allow (optional) initial read of input pins on deploy.
Moved to Category Raspberry Pi 
(other Pi related nodes will be updated to match soon).
2014-10-19 13:54:21 +01:00
Dave C-J
28a4ba1aad Don't let IRC node try to join channels multiple times...
Addresses #447
2014-10-18 20:38:58 +01:00
Dave Conway-Jones
dd9fc6a250 Merge pull request #437 from hindessm/file-query-node-usability
Minor fixes to messages sent from "file in" nodes.
2014-10-14 17:54:47 +01:00
Dave C-J
7802939bb0 fix IRC node (once again)
refactoring created incorrect object references - which weren't picked up.
(sorry) -  Addresses Issue 439
2014-10-09 20:50:46 +01:00
Nick O'Leary
d4a21be666 Clone settings to avoid modifying original values 2014-10-09 14:21:53 +01:00
Mark Hindess
0d9abbb8b6 Minor fixes to messages sent from "file in" nodes.
Specifically:

* in the error case, set msg.filename to be the name of the file used (as
  is done in the non-error case),

* in the error case, delete msg.payload so that subsequent nodes only need
  check for a msg.payload to act upon if they don't care about error cases,
  and

* in the non-error case, delete msg.error to avoid passing through errors
  from earlier nodes to a subsequent node that does care about error cases

Messages sent will now always have well-defined behaviour with respect to
the payload, filename, and error in both error and non-error cases.
2014-10-09 11:19:14 +01:00
Nick O'Leary
9a32e79603 Merge pull request #436 from anna2130/calculate-text-width
Refactored palette.js and view.js to reuse calculateTextWidth
2014-10-09 10:21:42 +01:00
Anna Thomas
206b8ac34a Refactored to reuse calculateTextWidth 2014-10-09 10:07:17 +01:00
Nick O'Leary
da1321f1de Bump 0.9.1
Commit 1000! \o/
2014-10-08 13:56:05 +01:00
Nick O'Leary
748c7aedee Add nodes to provide custom palette labels 2014-10-08 13:54:48 +01:00
Nick O'Leary
483c4352d3 Merge pull request #434 from anna2130/palette-multi-line-nodes
Node expands for multi-line node names
2014-10-08 13:23:54 +01:00
Anna Thomas
1afd4e7acc Node expands for multi-line node names splitting on spaces only 2014-10-08 13:20:56 +01:00
Nick O'Leary
21e349c22a Ensure httpNodePath is handled properly in HTTP/WS nodes
Cope with httpNodePath not ending with a / when it comes to
displaying the node details. The runtime is not affected.
2014-10-08 11:03:18 +01:00
Dave Conway-Jones
da4446c20f Merge pull request #432 from hindessm/avoid-abbreviation-and-be-consistent
Don't use abbreviation 'mins' and 'minutes' in the same sentence.
2014-10-07 22:23:06 +01:00
Anna Thomas
986ce8163f Added tests to check functionality when using multi-level properties 2014-10-07 16:12:35 +01:00
Anna Thomas
97e5c2e571 Added multi-level property functionality 2014-10-07 16:12:35 +01:00
Anna Thomas
2a753c9d22 Updated debug node test 2014-10-07 10:25:56 +01:00
Anna Thomas
f96b40cff2 Updated debug node to output other msg properties 2014-10-07 10:25:56 +01:00
Mark Hindess
e0927d190f Don't use abbreviation 'mins' and 'minutes' in the same sentence.
I don't think saving three letters justifies the use of a non-standard
abbreviation.  If we really need to save letters, then the SI units would
be better and for most people to read.
2014-10-07 08:40:06 +01:00
Dave C-J
0c636ca707 Wind down excessive logging on IRC node
now only logs connects/reconnects, errors and "raw" input commands
(so you can debug them when you get it wrong)
All other messages appear on the node output anyway.
2014-10-06 10:20:40 +01:00
Dave C-J
d49ed69a0a Remove listener limit on IRC node server connection.
Fixes #431
2014-10-05 21:36:42 +01:00
Dave C-J
4b73a92f71 Cross platform fix for Arduino Serial port not there.
Fix for #428
2014-10-04 22:56:52 +01:00
Dave C-J
e95d4a9010 Update Ardunio node slightly in line with latest Windows Serialport patches.
(But there is still a possibly bad underlying problem 
- error not being caught -  in the upstream npm - reported.)
2014-10-04 22:26:02 +01:00
Dave C-J
d950ceceea Tweak IRC fix #430 to not create multiple clients on extended reconnects 2014-10-03 22:27:58 +01:00
Dave C-J
43f55c6038 Add IRC server link check and retry after timeout
Fix for #430
2014-10-03 21:15:09 +01:00
Nick O'Leary
489c552dbe Reset settings between tests 2014-10-03 15:05:37 +01:00
Nick O'Leary
c223e7f58f Avoid writing unchanged settings 2014-10-03 14:47:23 +01:00
Nick O'Leary
86d4179039 Merge pull request #421 from hindessm/fix-supertest-usage
Fix supertest usage to correctly throw errors.
2014-10-01 22:48:00 +01:00
Nick O'Leary
b0e2b3d525 Merge pull request #429 from hindessm/fix-build-without-nock
Make nock an optional dependency.
2014-10-01 20:40:57 +01:00
Mark Hindess
ea6313c9a1 Make nock an optional dependency. 2014-10-01 18:14:40 +01:00
Nick O'Leary
c631b393e9 Merge pull request #427 from hindessm/add-nock-to-node-test-helper
Add nock to node test helper.
2014-10-01 14:59:23 +01:00
Mark Hindess
8df9eee6d2 Add nock to node test helper. 2014-10-01 14:35:51 +01:00
Nick O'Leary
322a6ebed3 Merge pull request #419 from hindessm/add-credentials-to-test-helper
Add credentials support to node test helper.
2014-10-01 11:13:28 +01:00
Mark Hindess
669f3d9ba1 Fix supertest usage to correctly throw errors. 2014-09-30 14:59:37 +01:00
Mark Hindess
059ec3b50c Add credentials support to node test helper. 2014-09-30 08:01:40 +01:00
Dave C-J
4f496c37be Force file node to write "binary" - so as not to expand binary like chars
into utf encoding. Helps with writing strings that happen to be binary
encoded rather than buffers.
2014-09-26 21:19:32 +01:00
Nick O'Leary
d3956f9816 Ensure valid mongo selector object 2014-09-26 11:30:09 +01:00
Nick O'Leary
a5265784e8 Bump 0.9.0 2014-09-24 13:14:19 +01:00
Nick O'Leary
dbaa1ed59c Don't cache api requests in the editor
Fixes #413
2014-09-24 09:57:45 +01:00
Nick O'Leary
8742bf354b Missing brackets in sample node
Closes #414
2014-09-23 23:15:34 +01:00
Nick O'Leary
9152daa13b Fix race condition on saving config 2014-09-23 17:09:23 +01:00
Nick O'Leary
6305c5b55f Use request.del in cli request module 2014-09-23 16:35:17 +01:00
Nick O'Leary
723a3e628e Add cli tests 2014-09-23 00:14:30 +01:00
Nick O'Leary
af0b740fbd Rework nr-cli
- better componentisation
- add 'search' option to look for npm modules
- add 'target' option to point at non-default installs
2014-09-22 22:59:42 +01:00
Nick O'Leary
31255bd66b Handle blank config file 2014-09-22 21:35:30 +01:00
Nick O'Leary
1f95071a37 Only normalise known headers in http node 2014-09-22 20:48:06 +01:00
Nick O'Leary
5f77531a39 Merge pull request #410 from anna2130/mongo
Updates to MongoDB nodes
2014-09-22 16:45:19 +01:00
Anna Thomas
23b5ac4582 Allow user to select collection in mongo nodes using msg.collection 2014-09-22 16:16:21 +01:00
Anna Thomas
4f2e4b58e4 Added update functionality to mongodb out node 2014-09-22 16:09:56 +01:00
Nick O'Leary
abd3d752f5 Add deprecation warnings to nodes 2014-09-22 15:57:16 +01:00
Nick O'Leary
8d54126127 Pretty-print the .config.json file for ease of debug 2014-09-22 14:34:53 +01:00
Nick O'Leary
55c830b812 Add disableEditor option
Closes #409
2014-09-22 14:34:17 +01:00
Nick O'Leary
e48cbafbd6 Fix jshint test 2014-09-22 13:22:23 +01:00
Nick O'Leary
9bfc6d376b Use module:node name to generate set id 2014-09-22 13:15:19 +01:00
Nick O'Leary
c98b9dfaa3 Don't filter errored nodes in result list 2014-09-20 22:42:48 +01:00
Nick O'Leary
ea89aff3db Handle node-load errors properly
Fixes #403
Certain load errors were causing start-up to fail. In particular,
an npm module with a missing .js file.
2014-09-20 22:25:00 +01:00
Nick O'Leary
e2be5c6383 Don't assume errors have stacks associated with them 2014-09-20 21:29:46 +01:00
Nick O'Leary
f939d52551 Restore node ids from config list 2014-09-20 21:29:46 +01:00
Dave C-J
ed1da5cf7b Add try/catch to websocket to fix #402
Also allow buffer out as binary if required. (ie don't try and stringify a buffer)
2014-09-18 17:10:01 +01:00
Dave C-J
a718f34c58 Expand the non-functional sample to have an input to make it more useful 2014-09-18 17:06:35 +01:00
Nick O'Leary
da61fe12d0 Add dynamic node api
Closes #322
- nodes modules can be installed/removed dynamically at runtime
- nodes can be enabled/disabled
- onpaletteadd/onpaletteremove api added to node definitions
- initial implementation of nr-cli
2014-09-17 23:57:29 +01:00
Nick O'Leary
00cb8d5bce Update package dependencies
Closes #345

Also fixes a resulting test failure with the HTML parse node
2014-09-17 23:49:28 +01:00
Nick O'Leary
ebb0d1a46d Handle invalid property values in Switch node
Fixes #404
2014-09-17 22:31:26 +01:00
Dave C-J
f04e42e4da Fix bug in number handling in CSV parser
Thanks Anupam !
2014-09-16 19:05:16 +01:00
Dave C-J
22329ca106 Add hourly option to Inject Node time interval mode,
Fix a few UI wrinkles when editing.
2014-09-15 12:32:28 +01:00
Nick O'Leary
c4932e3cf9 Merge pull request #401 from anna2130/master
Added count and aggregate operations to MongoDB In node
2014-09-15 09:37:56 +01:00
Anna Thomas
bc8acd24ae Added count and aggregate operations to MongoDB In node 2014-09-15 09:33:36 +01:00
Dave C-J
a0ac79384d Tidy up some little wrinkles in Inject node html labels etc 2014-09-14 18:38:30 +01:00
Dave C-J
7e2dbb13e4 Major Update to CSV node.
now handles lines, files, column names in first row, etc etc
2014-09-12 16:50:01 +01:00
Nick O'Leary
429a87f88a Merge pull request #400 from hindessm/add-ensure-buffer-helper
Add ensureBuffer helper for nodes.
2014-09-10 13:33:23 +01:00
Mark Hindess
c0fcc20f23 Add ensureBuffer helper for nodes. 2014-09-10 12:46:56 +01:00
Nick O'Leary
a3497a5fc7 Merge pull request #398 from anna2130/master
Fixed _spec.js test
2014-09-10 09:53:02 +01:00
Anna Thomas
54a2923c65 Fixed _spec.js test 2014-09-10 09:48:27 +01:00
Dave C-J
400f51d921 Allow IRC name changes when node chanel not set.
Trying to f ix #397
2014-09-09 22:14:52 +01:00
Nick O'Leary
50f1a17920 Merge pull request #396 from hindessm/delete-should-not-require-defined-payload
Fix logic so defined msg.payload is not prereq for delete.
2014-09-09 20:47:54 +01:00
Mark Hindess
72dcb53d23 Fix logic so defined msg.payload is not prereq for delete. 2014-09-09 20:01:54 +01:00
Nick O'Leary
0111ab8901 Merge pull request #393 from hindessm/remove-unused-vars-in-nodes
Remove unused variables.
2014-09-08 22:26:16 +01:00
Mark Hindess
fd04b5851b Remove unused variables. 2014-09-08 21:53:15 +01:00
Nick O'Leary
94b196bfcf Merge pull request #392 from hindessm/remove-redundant-null-checks
Remove redundant msg != null checks.
2014-09-08 21:31:55 +01:00
Mark Hindess
5d9c16ffbf Remove redundant msg != null checks. 2014-09-08 21:10:06 +01:00
Nick O'Leary
7b63680be1 Upgrade FontAwesome to 4.2
Part of #377
2014-09-08 21:01:49 +01:00
Nick O'Leary
d30f1e639b Merge pull request #391 from hindessm/remove-undefined-reference-to-node
Remove reference to undefined variable 'node'.
2014-09-08 20:38:00 +01:00
Nick O'Leary
27149fe10f Merge pull request #390 from hindessm/fix-base64-udp-send
Fix base64 udp send
2014-09-08 20:20:22 +01:00
Nick O'Leary
3880bd7ca4 Merge pull request #389 from hindessm/remove-spurious-dollar
Remove spurious dollar.
2014-09-08 20:20:12 +01:00
Nick O'Leary
7ec7354755 Merge pull request #388 from hindessm/fix-emailkey-variable
Fix variable name emailkey which should be globalkeys.
2014-09-08 20:20:03 +01:00
Nick O'Leary
afa201790d Merge pull request #387 from hindessm/fix-scope-of-data
Avoid wasting time preparing the payload if operation is a delete.
2014-09-08 20:19:54 +01:00
Dave C-J
cba4791b65 MQTT node better check for topic existing 2014-09-08 20:19:25 +01:00
Mark Hindess
3ec07a9161 Remove reference to undefined variable 'node'. 2014-09-08 20:19:20 +01:00
Mark Hindess
6368a43a02 Fix base64 encoding of msg.payload in UDP out node. 2014-09-08 20:13:08 +01:00
Mark Hindess
39d0419b65 Remove spurious dollar. 2014-09-08 20:04:11 +01:00
Dave C-J
ab4d3c025e MQTT node should only send message if topic actually specified (string) 2014-09-08 20:01:28 +01:00
Mark Hindess
19c3a7f36c Fix variable name, emailkey should be globalkeys.
Also, remove unused reference to util.
2014-09-08 19:59:22 +01:00
Mark Hindess
22b32b1684 Avoid wasting time preparing the payload if operation is a delete. 2014-09-08 19:53:03 +01:00
Nick O'Leary
1a5a548fbf Incorrect credentials in mongo node
Closes #386
2014-09-08 19:25:12 +01:00
Nick O'Leary
c03db99a21 Merge pull request #363 from kylegordon/patch-1
Provide a process.title
2014-09-08 14:58:22 +01:00
Nick O'Leary
141c8a1faf Merge pull request #385 from zobalogh/delay-test-fix
Adding drop check to Delay tests
2014-09-08 14:48:06 +01:00
Nick O'Leary
3e3b388ca7 Prevent adding empty twitter creds
Closes #372
2014-09-08 14:41:39 +01:00
zobalogh
4706b814dd Adding drop check to Delay tests 2014-09-08 14:26:35 +01:00
Nick O'Leary
b3fa866385 Remove last remnants of bootstrap icons from core
Closes #376
2014-09-08 14:18:34 +01:00
Nick O'Leary
e3f72a697e Ignore coveralls upload errors
Part of #299
2014-09-08 14:02:53 +01:00
Dave Conway-Jones
148fbefbbb Merge pull request #377 from hindessm/fa-icon-migration
Fa icon migration
2014-09-08 13:54:32 +01:00
Mark Hindess
3f054bdf7e Migrate to fa icons. 2014-09-08 12:07:39 +01:00
Nick O'Leary
995268c5df Handle node imports containing tab nodes
Closes #172
2014-09-08 10:54:06 +01:00
Dave C-J
a03861cb6d Indent range node UI more in style with other nodes. 2014-09-07 22:13:10 +01:00
Dave C-J
426453d925 make tcp and udp node fa icons more consistent 2014-09-07 22:12:25 +01:00
Dave C-J
ef9255a87c set defaults in trigger node rather than confusing placeholders 2014-09-07 22:11:58 +01:00
Dave C-J
5872541b1b Tweak info of watch and file nodes to be more style consistent with others. 2014-09-07 22:11:00 +01:00
Nick O'Leary
6076be011a Merge pull request #379 from hindessm/fix-undefined-class-on-labels
Correct logic to avoid 'undefined' label style being inserted.
2014-09-05 23:12:55 +01:00
Nick O'Leary
67d5bc2399 Merge pull request #380 from hindessm/trivial-html-fixes
Fix two trivial HTML bugs.
2014-09-05 23:12:32 +01:00
Nick O'Leary
c91a3f3f78 Delay node drop immediate not working
Fixes #381
2014-09-05 16:04:35 +01:00
Dave C-J
083dc213f0 Check to see serialport exists before opening in Arduino node
Fixes #382

now warns if chosen serial port doesn't exist - rather than crashing.
So is now recoverable without manually editing flows.

(Also moved to fa-icons and Arduino category)
2014-09-05 15:10:40 +01:00
Nick O'Leary
56768fbf2c HTTP In delete configuration missing errorHandler
Fixes #384
2014-09-05 13:19:05 +01:00
Nick O'Leary
293725afcd Reuse vm context to speed up Function nodes
If the NODE_RED_FUNCTION_TIME environment variable is set, the
function nodes publish a status of how long the function took
to run, in ms.
2014-09-05 10:40:57 +01:00
Mark Hindess
e90054c93c Fix two trivial HTML bugs. 2014-09-04 19:59:30 +01:00
Mark Hindess
553935ecc8 Correct logic to avoid 'undefined' label style being inserted. 2014-09-04 19:56:53 +01:00
Dave C-J
06542d95f2 fix email html fa icons... seemed to get lost in merge conflict 2014-09-04 08:20:13 +01:00
Dave C-J
5bf9819bd1 add comment to main.js - how to enable auto clear of debug on deploy 2014-09-04 08:19:37 +01:00
Dave C-J
fc679adefb Allow TCP node option to break connections per message (and auto
reconnect) - eg for file trnasfer to indicate EOF.

Change to FA icons.
2014-09-03 20:06:29 +01:00
Dave C-J
e136080888 Tiny wrinkle (non critical) in httpin node 2014-09-03 20:04:38 +01:00
Dave C-J
a05c0f4bb2 Fix RPi node - gpio path as per @Croady suggestion 2014-09-03 20:03:52 +01:00
Dave C-J
fa8d31609e fix merge email.js 2014-09-03 19:39:26 +01:00
Dave C-J
7da7887ed2 Add info to IRC node re RAW commands 2014-09-03 19:37:19 +01:00
Nick O'Leary
5a81ce8569 Merge pull request #375 from hindessm/rename-red-utils
Rename RED.utils to RED.util
2014-09-03 13:54:31 +01:00
Mark Hindess
0a009e2a15 Rename RED.utils to RED.util. Fixes #364. 2014-09-03 13:20:45 +01:00
Nick O'Leary
8e78df09da Don't clear selection on ctrl-click
Fixes #373
2014-09-02 16:00:01 +01:00
Nick O'Leary
16048112c6 Merge pull request #370 from jhr007/patch-2
Add checkbox support so config dialogs can save checkbox values
2014-09-01 20:31:17 +01:00
jhr007
5c39e330cb Add checkbox support so config dialogs can save checkbox values 2014-09-01 11:50:29 -05:00
Nick O'Leary
0138eb3f5f Merge pull request #343 from zobalogh/range-tests
Adding range tests
2014-09-01 10:23:55 +01:00
Nick O'Leary
4f36435628 Merge branch 'zobalogh-delay-test' 2014-09-01 10:22:50 +01:00
Nick O'Leary
9876570189 Merge delay node test
Fixes #346
Closes #347
2014-09-01 10:22:29 +01:00
Nick O'Leary
961c9f3fa9 Add ability to tweet images
Closes #117
2014-09-01 10:16:37 +01:00
Nick O'Leary
27e34f20fb Merge pull request #368 from jhr007/patch-1
Update index.html
2014-09-01 09:00:42 +01:00
jhr007
eb1e967be6 Update index.html
Closing head tag
2014-08-31 20:18:12 -05:00
Dave C-J
06e48e9cb1 make IRC node handle RAW messages more cleanly. 2014-08-31 23:50:56 +01:00
Dave C-J
8c8c9b13b9 Addressing the first message bug in #346.
Not closing the Issue (yet) as there still seems to be some discussion re timing.
2014-08-31 13:29:38 +01:00
Dave C-J
42c3ee430f Slight tweak to IRC status updates to clear on start. 2014-08-30 13:24:09 +01:00
Dave C-J
dfc427c192 Downplay twitter geo-area capability (from warn to log) 2014-08-30 13:23:24 +01:00
Dave C-J
c3926f6513 Better template handling in CSV node - (can cope with quoted column names etc) 2014-08-30 13:22:22 +01:00
Dave C-J
64c95f1d1b Make IRC node channel check case-insensitive. Closes #365
Thanks _jhr007_ for the heads up.

Also now handle multiple output channels from a node. 
And add status indicators for connectied/joined.
2014-08-30 10:41:12 +01:00
Nick O'Leary
fae8b35961 Allow spaces in node category names
Fixes #361
2014-08-29 20:37:30 +01:00
Dave C-J
0a3c889cc6 Allow IRC to listen on multiple channels from one or multiple nodes...
Issue #362
2014-08-29 16:41:32 +01:00
Kyle Gordon
98338508d2 Provide a process.title
Set the process.title to 'node-red'.
Helps identify node-red amongst a plethora of other nodejs processes
Aids monitoring systems in identifying running processes
2014-08-29 16:22:01 +01:00
Nick O'Leary
44ec551ed9 Tweak example settings for functionGlobalContext
Make it more obvious there should be a single hash, not multiple
2014-08-29 09:35:14 +01:00
Dave C-J
e256110eac Slight tweak to text spacing in IRC node
(Thanks to _jhr007_ for spotting)

Also taking chance to update to fa icons.

Closes #359
2014-08-28 21:05:00 +01:00
Nick O'Leary
8849f11eb5 Change how hidden input is appended
Fixes #360
2014-08-28 20:56:04 +01:00
Nick O'Leary
6f888068d2 Merge pull request #358 from hindessm/utils-ensure-string
Add ensure string helper for nodes.
2014-08-28 16:53:34 +01:00
Mark Hindess
a9e72858df Add ensure string helper for nodes. 2014-08-28 14:25:41 +01:00
Nick O'Leary
e09ac859d3 Merge pull request #354 from hindessm/redis-string-fix
Make more effort to stringify sensibly when storing in redis.
2014-08-28 14:15:53 +01:00
Nick O'Leary
e1ba2f7254 Merge pull request #357 from hindessm/fix-help
Fix --help so it doesn't crash.
2014-08-28 14:15:23 +01:00
Mark Hindess
7abae51b42 Fix --help so it doesn't crash. 2014-08-28 13:59:56 +01:00
Nick O'Leary
8febfbe329 Disable tail truncated test until it behaves on Travis 2014-08-28 00:22:12 +01:00
Nick O'Leary
bac288fa52 Add more delays to tail node test 2014-08-28 00:14:25 +01:00
Nick O'Leary
691ef12e04 Add more tail node tests
- add 'file doesn't exist initially' text
- remove resource file as it is programmatically modified by the test
- tweak test checking in truncated file test
2014-08-27 23:29:26 +01:00
Nick O'Leary
96a0a9d2d2 Update credential in edit form when left unchanged
Related to #355
2014-08-27 22:06:45 +01:00
Nick O'Leary
2dac28a421 Add hidden input to prevent edit form submit on enter
closes #349
2014-08-27 21:48:27 +01:00
Mark Hindess
025e4ab6d1 Make more effort to stringify sensibly when storing in redis. 2014-08-27 12:41:56 +01:00
Dave C-J
2d92b44f52 Tidy up tail node tests 2014-08-27 10:41:25 +01:00
Dave C-J
6b04d512ae add consistent timeouts to tail file write tests 2014-08-27 10:08:44 +01:00
Dave C-J
df751be331 Make all tail node test writres are sync 2014-08-27 09:51:30 +01:00
Dave C-J
c38aa91a4d put Debug node active flag back to boolean. (mea culpa) 2014-08-25 15:42:12 +01:00
Dave C-J
a74009caa9 more consistent use of "true"/"false" for options in Debug node 2014-08-25 15:37:31 +01:00
Dave C-J
40c87ab14c Make defaults into strings rather than boolean.
Fixes #350
2014-08-25 15:09:52 +01:00
Dave C-J
ecbf4add6b Modify Tail node to only send new messages
Now with test that works with it.
Part of Issue #326
Now need to redo with libs that support windows.
2014-08-22 12:44:55 +01:00
Dave C-J
a8ade083d4 Add QoS and Retain to the MQtt node options 2014-08-22 12:44:54 +01:00
Dave C-J
f1d2b7ffa0 change file and mongodb nodes to use fa icons 2014-08-22 12:44:54 +01:00
Nick O'Leary
ec5aaa5bfb Update header buttons 2014-08-22 11:07:32 +01:00
Nick O'Leary
7a9696526a Fix header css and tidy up 2014-08-21 21:48:08 +01:00
Nick O'Leary
7176f3ee2b Add declarative menu structure 2014-08-20 21:58:54 +01:00
Nick O'Leary
47b4ebc92f Check for null when undoing history actions 2014-08-20 17:05:01 +01:00
Nick O'Leary
44a51e849d Info sidebar tweaks 2014-08-19 23:46:18 +01:00
Nick O'Leary
1e33843798 Move main ui over to font-awesome 2014-08-19 22:58:52 +01:00
zobalogh
3823150e46 Adding range node tests 2014-08-19 11:45:17 +01:00
Nick O'Leary
7042d87444 Handle missing file/modules in Add node api 2014-08-18 21:59:19 +01:00
Nick O'Leary
4d6846047f Dynamic node add not listing added types properly 2014-08-18 21:12:15 +01:00
Nick O'Leary
8ddfa9eb29 Migrate email node to credentials system 2014-08-18 17:15:14 +01:00
Nick O'Leary
c0842455b9 Update email node to use nodemailer 1.x api
Fixes #334
2014-08-18 15:47:37 +01:00
Nick O'Leary
e8c4caaf74 Remove cheerio parse of node html files on start-up 2014-08-18 14:34:47 +01:00
Nick O'Leary
76e8512869 Temporarily remove Mongo Update option
Part of #335
2014-08-18 11:30:48 +01:00
Nick O'Leary
907ce5c079 MQTT Node: allow publishing of Buffer payloads
Closes #341
2014-08-18 11:27:52 +01:00
Nick O'Leary
3c31ef4643 Merge pull request #336 from zobalogh/switch-test
Adding Switch Node tests
2014-08-18 11:15:04 +01:00
Nick O'Leary
9734c755ae Merge pull request #342 from zobalogh/change-node-tests
Adding Change Node tests
2014-08-18 11:14:04 +01:00
zobalogh
89825dae04 Adding Change Node tests 2014-08-15 12:19:07 +01:00
zobalogh
ce4d44ea48 Adding Switch Node tests 2014-08-14 11:27:21 +01:00
Nick O'Leary
105b25287f Reverting tail node change to fix build
Fixes #337

A useful change but need the corresponding tests to be updated and also properly publicised on the mailing list as it is a change in behaviour.
2014-08-13 22:43:15 +01:00
Dave Conway-Jones
b3c3bffeaa tweak tail.js to only send changed/new lines. 2014-08-12 19:33:41 +01:00
Nick O'Leary
972e6fc6b3 Tidy editor code to pass jshint 2014-08-08 00:01:35 +01:00
Nick O'Leary
fb2f307a26 Remove unnecessary semicolon 2014-08-07 22:24:02 +01:00
Nick O'Leary
58c2f5dd3d Add remove node api 2014-08-07 22:20:06 +01:00
Nick O'Leary
43ad8706aa Update tail node tests to use resource file 2014-08-07 14:55:34 +01:00
Nick O'Leary
fde77cec5d Add rest endpoint for add/remove and send updates to editor 2014-08-07 13:46:38 +01:00
Nick O'Leary
15494dda84 Merge pull request #332 from zobalogh/tail-done-fix
Fixing test termination
2014-08-07 13:34:46 +01:00
Nick O'Leary
c6a98da256 Merge pull request #331 from hbeeken/issue-326
Proposed fix for Issue #326
2014-08-07 13:33:57 +01:00
Nick O'Leary
2e2b5ad13e Merge pull request #327 from hbeeken/html-node-tests
Tests for HTML node
2014-08-07 13:30:13 +01:00
zobalogh
ea22ffa2bf Fixing test termination 2014-08-07 13:23:20 +01:00
hbeeken
fadd2167f5 Closing file handles to fix test failures on windows 2014-08-07 12:03:03 +01:00
hbeeken
82fb15896c Tests for HTML node 2014-08-06 11:34:30 +01:00
Nick O'Leary
960d15491d Add node add/remove/enable/disable apis to registry 2014-08-04 17:12:54 +01:00
Nick O'Leary
495dd3f2e0 Merge pull request #325 from hbeeken/fix-registry-tests-for-windows
Using path.sep rather than "/" so tests run on windows as well as linux
2014-08-04 11:35:32 +01:00
hbeeken
8c0c843367 Using path.sep rather than "/" so tests run on windows as well as linux 2014-08-04 11:27:26 +01:00
Nick O'Leary
4ac4995762 Merge pull request #320 from hbeeken/xmlparser-node-tests
Adding tests for XML parser node
2014-08-04 11:14:21 +01:00
hbeeken
118c50ce8e Fixing tests by clearing out the node registry between tests. 2014-08-04 10:59:08 +01:00
hbeeken
38c541361e Removing unreachable catch blocks 2014-08-04 10:57:00 +01:00
hbeeken
8eb8ac0f88 Adding tests for XML parser node 2014-08-04 10:56:49 +01:00
Nick O'Leary
f7f58a2347 Add registry test for node module loading 2014-08-03 21:17:24 +01:00
Nick O'Leary
1697aee9f6 Add loadNode api to registry.js 2014-08-01 23:42:01 +01:00
Nick O'Leary
3ee98e730d Ensure test resource icons directory exists 2014-08-01 22:23:32 +01:00
Nick O'Leary
d6c5f9b57f Add debug for eventEmitSpy
Debugging a travis-only build failure
2014-08-01 22:19:33 +01:00
Nick O'Leary
882b593ba4 Add missing registry test resources 2014-08-01 22:09:40 +01:00
Nick O'Leary
79e9641c09 Big rework of registry.js
Part of #322

Loads node.html files before node.js files

Adds helper.unload which must be called by node tests
to clear the registery of nodes
2014-08-01 22:05:49 +01:00
Nick O'Leary
a869642705 Split up tests in gruntfile 2014-08-01 22:02:12 +01:00
Nick O'Leary
8ee5be7031 Restore stubs in credentials_spec 2014-08-01 21:56:27 +01:00
Nick O'Leary
8e8e13a3a2 Add comms.stop to tidy up keepalive timer 2014-08-01 21:55:05 +01:00
Nick O'Leary
6c464ed3a4 Merge pull request #324 from zobalogh/tail-test
Adding tail node tests
2014-08-01 16:30:04 +01:00
zobalogh
716d0c1135 Adding tail node tests 2014-08-01 16:22:02 +01:00
Nick O'Leary
989f3459d5 Merge pull request #323 from zobalogh/further-registry-tests
Adding test for registry cheerio
2014-07-31 17:25:31 +01:00
Nick O'Leary
b8f40d4e39 Merge pull request #319 from hindessm/restrict-library-entry-names
Prohibit library entry names from containing '../'.
2014-07-31 17:24:57 +01:00
zobalogh
401afcbadd Adding test for registry cheerio 2014-07-31 16:49:19 +01:00
Nick O'Leary
a170623b4c Merge pull request #315 from zobalogh/registry-test-delivery
Adding registry node load tests
2014-07-31 14:54:56 +01:00
zobalogh
d9544e4ac5 Adding registry node load tests 2014-07-31 14:43:26 +01:00
Nick O'Leary
530a1fce81 Merge pull request #321 from hbeeken/remove-unnecessary-require
Remove duplicate require("util")
2014-07-31 14:19:53 +01:00
Mark Hindess
b7428ab627 Prohibit library entry names from containing '../'. 2014-07-31 13:59:24 +01:00
hbeeken
5a714242a4 Remove duplicate require("util") 2014-07-31 13:53:11 +01:00
Nick O'Leary
bc8ea998bb Merge pull request #318 from hbeeken/json-node-tests
JSON node tests
2014-07-31 12:44:17 +01:00
hbeeken
e179753853 Error path tests for 70-JSON.js 2014-07-31 11:02:55 +01:00
hbeeken
80676da300 Adding test for JSON node 2014-07-31 11:02:43 +01:00
Nick O'Leary
f4ffdce3d0 Merge pull request #317 from hindessm/fix-flows-missing-types-logic
Fix missing types logic.
2014-07-31 10:01:27 +01:00
Nick O'Leary
32d14ddbcf Merge pull request #316 from hindessm/fix-server-http-response
Fix HTTP response; 204 should be code not body.
2014-07-31 10:00:00 +01:00
Nick O'Leary
3e76e73839 Merge pull request #312 from hbeeken/test-credentials-registerEndpoint
Adding unit tests for credentials.registerEndpoint
2014-07-31 09:59:42 +01:00
hbeeken
9c0b65a4c2 Adding tests for credentials.registerEndpoint 2014-07-31 08:41:43 +01:00
Mark Hindess
2f882913f0 Fix missing types logic.
missingTypes.length was greater than zero so the only way it can be
zero immediately afterwards is if splice is called so move the zero
check after the splice.

Despite what istanbul reports this function is covered by the tests.
2014-07-31 08:36:54 +01:00
Mark Hindess
e407fc857e Fix HTTP response; 204 should be code not body. 2014-07-31 07:04:10 +01:00
Nick O'Leary
30a94bdaf5 Merge pull request #313 from hindessm/more-comms-tests
More comms tests
2014-07-30 17:03:10 +01:00
Mark Hindess
195f581da7 Remove redundant assignment.
The publish immediately overwrites this time so there isn't much
point setting it.
2014-07-30 14:33:31 +01:00
Mark Hindess
a8c491bf2b Add comms keep alive tests. 2014-07-30 14:33:31 +01:00
Mark Hindess
8e30910065 Add webSocketKeepAliveTime setting to aid testing. 2014-07-30 14:33:31 +01:00
Nick O'Leary
47f7cb52d8 Merge pull request #311 from hbeeken/test-credentials-extract
Adding unit tests for credentials.extract
2014-07-30 12:51:49 +01:00
Nick O'Leary
14c36687e0 Merge pull request #310 from hindessm/more-node-tests
More tests for the Node base class.
2014-07-30 12:51:41 +01:00
hbeeken
a3cb0e996d Adding unit tests for credentials.extract 2014-07-30 10:56:42 +01:00
Mark Hindess
bbdfa03687 Test Node status method. 2014-07-30 10:39:48 +01:00
Mark Hindess
d7b3e86096 Test Node send for req/res behaviour. 2014-07-30 10:29:37 +01:00
Nick O'Leary
1ce4765ed8 Merge pull request #309 from hindessm/more-function-node-tests
Add more tests for function node.
2014-07-30 09:54:36 +01:00
Nick O'Leary
a9ee417c1f Merge pull request #308 from hindessm/avoid-debug-payload-corruption
Avoid corrupting message payload while logging.
2014-07-30 09:54:10 +01:00
Mark Hindess
53d9eab94e Add more tests for function node. 2014-07-30 09:32:17 +01:00
Mark Hindess
96ea81894d Avoid corrupting message payload while logging. 2014-07-30 08:17:40 +01:00
Nick O'Leary
b1f14031a9 Merge pull request #303 from hbeeken/test-registerType-with-credentials
Adding test for index.registerType with credentials defined
2014-07-29 23:56:52 +01:00
Nick O'Leary
575d166947 Merge pull request #307 from hindessm/start-testing-nodes
Some initial tests for core nodes.
2014-07-29 23:55:54 +01:00
hbeeken
796810f460 Test to ensure credential definition is defined after index.registerType 2014-07-29 14:58:49 +01:00
Mark Hindess
74ae2eca3e Start adding tests for nodes. 2014-07-29 12:59:42 +01:00
Nick O'Leary
ac1e750f14 Merge pull request #304 from hbeeken/test-credentials
Add test for case if loading credentials from storage has an error
2014-07-29 11:24:18 +01:00
Nick O'Leary
a396bca06a Merge pull request #306 from hindessm/simplify-debug-node
Simplify debug node message processing.
2014-07-29 10:24:42 +01:00
Mark Hindess
deff93f95e Simplify debug node message processing.
Make processing more consistent irrespective of complete flag.
Avoid processing when node is inactive.
Avoid duplicate code.
2014-07-29 09:41:47 +01:00
Dave C-J
78edf89e52 Allow File node to write out buffers of binary data 2014-07-29 08:57:27 +01:00
Nick O'Leary
00a9542edf Fix typo in example file 2014-07-29 08:55:29 +01:00
Nick O'Leary
31d72b2193 Merge pull request #305 from hbeeken/test-index-remove-redundant-test
Remove redundant "can be required without errors" test
2014-07-28 20:50:16 +01:00
hbeeken
d09c992ecc Remove redundant "can be required without errors" test 2014-07-28 20:24:01 +01:00
Nick O'Leary
e07a523c3b Fix node html compression and refactor registry 2014-07-28 16:37:39 +01:00
Nick O'Leary
3d31a0abca Merge pull request #302 from hbeeken/fix-ui-windows-test-failure
Removing assumption that /tmp exists so test runs on Windows
2014-07-28 15:47:12 +01:00
hbeeken
132dcdeb43 Add test for case if loading credentials from storage has an error 2014-07-28 14:29:35 +01:00
hbeeken
50b28842a4 Removing assumption that /tmp exists so test runs on Windows 2014-07-28 12:10:13 +01:00
Nick O'Leary
536a0c9142 Only trigger dbl click on single node
Fixes #301
2014-07-27 22:08:27 +01:00
Nick O'Leary
76a6730ef4 Merge pull request #289 from zobalogh/storage-interface-dev
Defining storage interface
2014-07-25 15:00:40 +01:00
Nick O'Leary
0c8b54253a Merge pull request #300 from zobalogh/ui-caller-delivery
Removing accidental requires
2014-07-25 15:00:22 +01:00
zobalogh
1f09c29aeb Removing accidental requires 2014-07-25 14:50:54 +01:00
zobalogh
d58788f721 Defining storage interface. 2014-07-25 14:44:15 +01:00
Nick O'Leary
e299baf1a0 Refactor inner workings of registry.js 2014-07-24 23:16:41 +01:00
Nick O'Leary
af1bcf33c1 Merge pull request #297 from zobalogh/ui-caller-delivery
Adding UI unit tests
2014-07-24 16:26:45 +01:00
Nick O'Leary
7abda9bc52 Merge pull request #298 from hindessm/test-comms
Add comms tests
2014-07-24 15:29:06 +01:00
Mark Hindess
2c1da2d546 Add comms tests. 2014-07-24 14:47:45 +01:00
Mark Hindess
8506fd0c4b Do not crash on malformed json message on websocket. 2014-07-24 14:46:04 +01:00
zobalogh
6c093eef99 Fixing settings usage in UI 2014-07-24 14:35:01 +01:00
zobalogh
12a06cacce Adding UI unit tests 2014-07-24 14:35:01 +01:00
Nick O'Leary
b50b23318b Merge pull request #295 from hbeeken/test-node-index
Adding unit test for nodes/index.js
2014-07-24 12:36:52 +01:00
Nick O'Leary
166a798c09 Merge pull request #293 from hindessm/remove-some-unused-variables
Remove some unused variables.
2014-07-24 12:35:54 +01:00
Nick O'Leary
4096589d21 Fix README link and add badges
Closes #296
2014-07-24 12:33:57 +01:00
hbeeken
2e86a41944 Adding unit test for nodes/index.js 2014-07-24 09:41:47 +01:00
Nick O'Leary
6bc4b235bb Merge pull request #291 from hindessm/test-library
Add library tests
2014-07-23 23:06:12 +01:00
Nick O'Leary
38390c6285 Merge pull request #292 from hindessm/handle-storage-error
Fail with error rather than silently on storage init problems.
2014-07-23 23:05:39 +01:00
Mark Hindess
e96fff573d Remove some unused variables. 2014-07-23 23:04:08 +01:00
Nick O'Leary
16a5aa7368 Merge pull request #294 from hindessm/trivial-whitespace-fixes
Trivial whitespace fixes
2014-07-23 23:03:37 +01:00
Mark Hindess
f331d906b4 Fail with error rather than silently on storage init problems. 2014-07-23 22:54:16 +01:00
Mark Hindess
d7445a5bb9 Replace tabs with 4 space indent. 2014-07-23 22:10:27 +01:00
Mark Hindess
6c4611a934 Fix EOL to be dos or unix not both in same file. 2014-07-23 22:07:02 +01:00
Mark Hindess
5b5b7d2be1 Add library tests 2014-07-23 21:38:57 +01:00
Nick O'Leary
55679694c9 HTTP In - fix header iterator 2014-07-23 21:28:53 +01:00
Nick O'Leary
c079576e49 Remove websocket listeners on close
Fixes #285
2014-07-23 21:18:07 +01:00
Nick O'Leary
5179cc77f5 Merge pull request #290 from hindessm/unused-red-vars
Remove unused references to red/red.js
2014-07-23 20:50:38 +01:00
Mark Hindess
406581d522 Remove unused references to red/red.js 2014-07-23 20:02:28 +01:00
Nick O'Leary
9a60294813 Merge pull request #286 from zobalogh/master
Adding a unit test that checks if every .js file in the core
2014-07-23 13:07:11 +01:00
Dave C-J
82036dd84a Simple fix to check/force http: to close #288
Checks url starts http: or https: - if not add http: - at least then it breaks gently.
Also add resultant url to any error payload if request  fails in order to make debug easier.
2014-07-23 12:09:34 +01:00
zobalogh
b29062a931 Add test that every source file has a test. 2014-07-23 11:40:46 +01:00
Nick O'Leary
14a1899c23 Merge pull request #287 from hindessm/spurious-require-of-events-singleton
Remove spurious require of the events singleton.
2014-07-23 10:19:05 +01:00
Mark Hindess
d51eadb6c2 Remove spurious require of the events singleton. 2014-07-22 12:43:02 +01:00
Nick O'Leary
bead24e760 HTTP In node: Check credentials exist before using 2014-07-22 11:33:52 +01:00
Dave C-J
0b308deb79 Slight tidy to MQTT - move to fa icons - use built in close method. 2014-07-21 20:29:10 +01:00
Nick O'Leary
d1300c0632 Remove unnecessary semi-colon 2014-07-21 16:18:22 +01:00
Nick O'Leary
014fca2d61 Add some function-level docs 2014-07-21 16:07:28 +01:00
Nick O'Leary
fea6280bff Tidy up runtime credentials 2014-07-21 15:56:38 +01:00
Nick O'Leary
7687cf6661 Merge pull request #283 from hindessm/missing-require-util
Add missing util require.
2014-07-21 15:22:15 +01:00
Mark Hindess
63ebee22dd Add missing require. 2014-07-21 13:29:54 +01:00
Nick O'Leary
4f8ddad935 Merge pull request #282 from hindessm/more-node-tests
Test that wires to non-existent nodes are ignored.
2014-07-21 10:08:49 +01:00
Dave Conway-Jones
bd70c4b6e7 Merge pull request #281 from hindessm/trivial-switch-spelling-fix
Trivial spelling fix.
2014-07-21 09:57:59 +01:00
Mark Hindess
1414308179 Test that wires to non-existent nodes are ignored. 2014-07-21 09:26:48 +01:00
Mark Hindess
fd6682cfce Trivial spelling fix. 2014-07-21 08:54:37 +01:00
Nick O'Leary
9d481858a0 Update twitter node to new credential api 2014-07-20 22:00:02 +01:00
Nick O'Leary
4302deb5a6 Change credential boolean prefix 2014-07-20 20:42:41 +01:00
Nick O'Leary
d67a54a66a Update HTTP Request node to new credentials api 2014-07-19 00:26:46 +01:00
Nick O'Leary
b604db83f6 Tweak the credential handling
- use node.credentials rather than node._creds for consistency with the runtime
 - simplify the structure of node.credentials
2014-07-19 00:25:22 +01:00
Nick O'Leary
760dd022dc Migrate MQTT nodes to new credentials api 2014-07-19 00:25:22 +01:00
Nick O'Leary
c379f1b197 Migrate mongo node to new credential api 2014-07-19 00:25:22 +01:00
Dave C-J
4770a06679 yet more JShint cleaning. 2014-07-18 21:49:48 +01:00
Dave C-J
252135532d more JShint cleaning and fa icons added 2014-07-18 21:49:20 +01:00
Dave C-J
aaab0d0d0b Add B+ GPIO support to Pi Node 2014-07-18 15:08:16 +01:00
Nick O'Leary
aa35484a30 Prepopulate node credentials if present 2014-07-18 14:23:32 +01:00
Nick O'Leary
a6a0352b70 Add credential registration to RED.nodes.registerType 2014-07-18 14:20:49 +01:00
Nick O'Leary
f80c41058c Properly escape html entities in debug
Fixes #276
2014-07-17 22:13:04 +01:00
Nick O'Leary
84a0e8ceff Add version information to /settings 2014-07-17 21:32:30 +01:00
Dave C-J
8b6c14b05d update mongodb to fa-icons and fix name position in edit config... 2014-07-17 17:21:35 +01:00
Nick O'Leary
cb854c40ba Merge pull request #278 from hindessm/fix-jshint-of-tests
Fix jshint 'tests' definition.
2014-07-17 16:12:16 +01:00
Mark Hindess
bf4369a01e Fix jshint 'tests' definition. 2014-07-17 15:51:21 +01:00
Nick O'Leary
8f3de41049 Merge pull request #277 from hindessm/realistic-test-coverage
Make test coverage figure more realistic.
2014-07-17 12:21:51 +01:00
Dave C-J
fea68d4eda add strict to redisout and fa-icons 2014-07-17 10:25:52 +01:00
Mark Hindess
2778c38b55 Add trivial tests to make coverage figure more realistic. 2014-07-17 09:16:43 +01:00
Mark Hindess
67bd4f373f Move tests to match source hierarchy. 2014-07-17 09:16:43 +01:00
Nick O'Leary
2b423fe9be Merge pull request #274 from hindessm/redis-status
Add status support to the redisout node.
2014-07-17 09:07:29 +01:00
Nick O'Leary
3bc4e7125b Merge pull request #275 from hindessm/add-missing-license-header
Add missing license header.
2014-07-17 09:06:45 +01:00
Mark Hindess
8cedf76b70 Add missing license header. 2014-07-17 08:08:24 +01:00
Mark Hindess
79853d8583 Add status support to the redisout node. 2014-07-16 23:10:34 +01:00
Nick O'Leary
17a83436b3 Merge pull request #273 from hindessm/trivial-mqtt-var-cleanup
Trivial mqtt var cleanup
2014-07-16 22:10:28 +01:00
Mark Hindess
5073cf8dd7 Reduce scope of variable declaration to be consistent with MQTTInNode code. 2014-07-16 21:45:02 +01:00
Mark Hindess
77f6652fa9 Remove spurious variable declaration. 2014-07-16 21:45:02 +01:00
Nick O'Leary
3756e41522 Merge pull request #271 from Belphemur/test-creds
Correcting setFlow test
2014-07-16 12:15:50 +01:00
Antoine Aflalo
ac889e18d2 Correcting setFlow test
Because of the change made into the credentials, the saveCredentials needs to be defined for the setFLow to work
2014-07-16 13:25:43 +03:00
Nick O'Leary
9650b3ed38 Merge pull request #270 from hindessm/ignore-coverage
Ignore the test coverage directory.
2014-07-16 10:57:44 +01:00
Nick O'Leary
b7a83383c9 Merge pull request #268 from hbeeken/test-coverage
Adding unit test for credentials.clean
2014-07-16 10:53:25 +01:00
Nick O'Leary
d4919e27bd Merge pull request #251 from Belphemur/credentials-pr
Rewrite credentials API to work with save on deploy.
2014-07-16 10:41:01 +01:00
Mark Hindess
4f6e76cace Ignore the test coverage directory. 2014-07-16 10:19:39 +01:00
Antoine Aflalo
4d97d9d300 Rewrite credentials API to work with save on deploy. 2014-07-16 11:25:40 +03:00
hbeeken
1419a79933 Adding unit test for credentials.clean 2014-07-16 09:25:08 +01:00
Nick O'Leary
cfd590a397 Merge pull request #267 from hindessm/flows-tests
Flows tests
2014-07-15 17:03:16 +01:00
Nick O'Leary
d67074f4b7 Merge pull request #269 from zobalogh/master
Removing orphaned function: localfilesystem.writeFile(root,path,meta,body,res)
2014-07-15 17:01:27 +01:00
zobalogh
4a514b9060 Removing orphaned function. 2014-07-15 15:32:56 +01:00
Mark Hindess
8e32427109 Add setFlows test. 2014-07-14 22:00:09 +01:00
Mark Hindess
fe9ff0a297 Fix jshint complaints. 2014-07-14 21:46:36 +01:00
Mark Hindess
7281d273a1 Remove unused code. 2014-07-14 21:45:03 +01:00
Mark Hindess
d4548deeb3 Add node flows tests. 2014-07-14 21:44:34 +01:00
Dave Conway-Jones
debd5c4496 Spelling correctin http node info, close #266 2014-07-14 09:26:09 +01:00
Dave C-J
62496d80d5 tray/catch error in Websocket node to close #264 2014-07-13 17:39:56 +01:00
Dave C-J
bc53d302ff and add fa icons to websocket 2014-07-11 21:32:51 +01:00
Dave C-J
1a1c6a73b6 Update icon to fa icons for parser nodes and sentiment 2014-07-11 21:01:16 +01:00
Dave Conway-Jones
9158766297 Merge pull request #262 from hindessm/improve-node-coverage
Improve red/nodes/Node.js coverage and minor refactoring
2014-07-11 14:04:27 +01:00
Dave Conway-Jones
b84aba5c98 minor spelling correction 2014-07-11 08:20:47 +01:00
Mark Hindess
66459f1bd6 Short circuit the null message case. 2014-07-10 14:25:32 +01:00
Mark Hindess
74335990e3 Add test for the null message case. 2014-07-10 14:25:32 +01:00
Mark Hindess
d371511d1d Refactor common log code. 2014-07-10 14:25:32 +01:00
Mark Hindess
0a7bd848c6 Add more Node tests. 2014-07-10 14:25:32 +01:00
Dave C-J
de352dcdc2 Change template node to allow templating of any property of msg 2014-07-10 08:23:44 +01:00
Nick O'Leary
242fe1ef86 Merge pull request #260 from hindessm/test-coverage
Test coverage on coveralls.io
2014-07-09 10:00:10 +01:00
Nick O'Leary
3db84d5bf1 HTTP Out payload: relax null checking 2014-07-09 09:51:43 +01:00
Nick O'Leary
4d031891e5 Http Out: remove excess debug 2014-07-09 09:48:09 +01:00
Nick O'Leary
a7e7254317 Handle null response payloads 2014-07-09 09:46:10 +01:00
Nick O'Leary
8e24a958b7 HTTP Out - stringed the wrong bit 2014-07-09 09:43:02 +01:00
Nick O'Leary
bd80cf4f83 HTTP Out - header properties must be strings 2014-07-09 09:08:43 +01:00
Mark Hindess
abf5944316 Report coverage to build log and coveralls. 2014-07-09 07:45:58 +01:00
Mark Hindess
85a8e7283f Have travis generate test coverage using istanbul. 2014-07-09 07:45:58 +01:00
Dave C-J
ac7448759b Revert Alt zoom change as it doesn't work in FF... 2014-07-08 14:17:37 +01:00
Dave C-J
c10c687653 Add TCP request node to TCP node "family" 2014-07-08 13:45:00 +01:00
Dave C-J
2c5d5148b8 tweak tail node to use -F (thanks to tip from Mike Smerekov)
pending other changes
2014-07-08 12:28:02 +01:00
Dave C-J
6fb9739245 Add strict to function node 2014-07-08 12:27:09 +01:00
Dave C-J
9bc9994354 Fix for missingType(s) - to close #259
(thanks to toksea for spotting it)
2014-07-08 08:49:48 +01:00
Dave C-J
35965e55b5 Add on error method to exec node... (should have been there before :-) 2014-07-08 08:48:46 +01:00
Dave C-J
4532cadb14 Add Alt zoom to help page (to be complete) 2014-07-08 08:47:44 +01:00
Nick O'Leary
6e956ef03a Merge pull request #254 from TJKoury/master
Added dynamic category capability
2014-07-02 22:23:52 +01:00
tjkoury
4afda2f53b Added deprecated to exclusion list 2014-07-02 17:20:16 -04:00
Nick O'Leary
9db1166ce0 Fix logHandler array handling
Fixes #257
2014-07-02 16:31:02 +01:00
Nick O'Leary
3de2f3d1da Add jshint:core to default build 2014-07-01 23:50:17 +01:00
Nick O'Leary
9f3233175b Fix node html generation 2014-07-01 23:48:26 +01:00
Nick O'Leary
649c82f7d7 Fix all jshint warnings in core code 2014-07-01 23:46:25 +01:00
Nick O'Leary
c18119f26e Debug pane should use local timezone 2014-07-01 23:14:44 +01:00
tjkoury
ef6a0e5947 Added default sub-categories to the core categories; 2014-06-30 23:31:06 -04:00
Dave C-J
045f658ef9 More defensive handling of missing filename in file node.
(in line with jshint)
2014-06-30 23:23:01 +01:00
Dave C-J
c7f0f9639a tidy up jsHint errors in exec and trigger nodes
Add "use strict" to the sample node
2014-06-30 20:44:35 +01:00
Dave C-J
52779ac518 Tidy up jsHinst errors in parser nodes 2014-06-30 20:44:01 +01:00
Dave C-J
790ad8eb68 Tidy up jsHint errors in parser nodes 2014-06-30 20:43:31 +01:00
tjkoury
e43b342048 Removed unnecessary console.log 2014-06-30 11:33:46 -04:00
tjkoury
5959da2d37 Added dynamic category capability 2014-06-30 11:31:02 -04:00
Nick O'Leary
e15de8cf37 Bump 0.8.1 2014-06-29 22:55:08 +01:00
Nick O'Leary
13da5e7327 Fix 0.8 build 2014-06-29 22:48:35 +01:00
Nick O'Leary
3bbf2fa737 Disable Travis 0.8 build until upstream bug fixed
Cannot npm install grunt-contrib-jshint due to this
https://github.com/Raynos/console-browserify/issues/6
2014-06-28 22:11:36 +01:00
Nick O'Leary
cfe7c372d3 Add jshint to grunt file 2014-06-28 21:40:46 +01:00
Nick O'Leary
3fcc1b5680 Missing variable declaration in tail node 2014-06-27 12:29:13 +01:00
Dave C-J
6d54050b4f Tweak colour of new trigger node to match delay node. 2014-06-26 13:05:34 +01:00
Nick O'Leary
83b8c46b28 Bump 0.8 2014-06-25 21:39:27 +01:00
Nick O'Leary
1602d9496f Add trigger node 2014-06-25 21:36:28 +01:00
Nick O'Leary
479a02cc16 Enable npm nodes to provide custom icons 2014-06-25 10:30:52 +01:00
Dave C-J
7c7f030aa8 Tweak serial to outpur correct number of chars from buffer.
Having counted the correct number into the buffer it seems a shame
not to send them all out to the next node...
(Also allow separator char to be specified as hex (eg  0x0a))
2014-06-24 22:27:02 +01:00
Dave C-J
cf70fee8c7 Add new HTML (cheerio/jquery like) parser node ... 2014-06-24 13:22:16 +01:00
Nick O'Leary
842e7cf5f5 Allow retained status message to be cleared 2014-06-21 22:43:48 +01:00
Dave C-J
ed3be7f82e Add italic style to name for exec function...
been missing for ages... who knows why :-)
2014-06-17 20:07:29 +01:00
Dave C-J
6f6ecfc7a8 Make sure buffers are nulled to ensure cleanup. 2014-06-17 20:06:54 +01:00
Dave C-J
c61d4d3209 revert logging of every url request from http reg node. 2014-06-12 21:16:41 +01:00
Nick O'Leary
dded87c134 Log close errors rather than pass up the stack 2014-06-08 23:28:46 +01:00
Dave C-J
fc94429266 Add missing name field to file (in) node
(and tweak icons to new style)
2014-06-08 22:54:21 +01:00
Dave C-J
2d9e72816f Update comment node to fa icons - plus matching node icon. 2014-06-08 22:13:12 +01:00
Dave C-J
fe1b7c54ff Update serial info to match new capabilities. 2014-06-08 15:39:38 +01:00
Dave C-J
7d6f70545a and honour text (utf8) mode in single char mode. aaargh. 2014-06-08 15:27:15 +01:00
Dave C-J
12b8fa36dc Serial port - missed no split char option. If no split char then send every
character in char mode.
2014-06-08 15:21:02 +01:00
Dave C-J
00f87cbcd6 Add strict mode and latest icons to http node(s) 2014-06-08 15:05:08 +01:00
Dave C-J
0cbc277a2c Add binary capabilities to Serial node - along with timeout, fixed
buffer size and binary output options. Also updated icons and added strict
checking.
2014-06-08 14:58:19 +01:00
Dave C-J
c10ed13322 Add flash of status to exec node 2014-06-08 14:58:19 +01:00
Dave C-J
346ca21803 Make sure file name gets passed on in file node 2014-06-08 14:58:19 +01:00
Nick O'Leary
8e48251f26 Disable max event listener warning on server
Fixes #238
2014-06-08 00:01:29 +01:00
Nick O'Leary
72476cc8a7 Set content-length in HTTP response node
Fixes #233
2014-06-07 23:18:50 +01:00
Nick O'Leary
749eaa2181 Add flowFilePretty option 2014-06-07 22:33:29 +01:00
Nick O'Leary
fce00b2f4b Update debug icon 2014-06-04 23:13:01 +01:00
Nick O'Leary
e296635b57 Icon rework 2014-06-04 22:44:01 +01:00
Dave Conway-Jones
9d2864088b Merge pull request #243 from TJKoury/change_file_node
Change to error handling in 50-file.js to pass on msg.error.
2014-06-02 21:08:18 +01:00
tjkoury
785d4a66f0 Added error handling to 50-file.js 2014-06-02 15:25:48 -04:00
Dave C-J
4e1d45b508 Tweak to Watch Node info to close #236 2014-06-01 18:02:25 +01:00
Nick O'Leary
32cad7a627 Icon refresh 2014-05-31 22:20:06 +01:00
Dave C-J
8c8f75df69 Catch more errors in file watcher node. 2014-05-31 19:44:02 +01:00
Nick O'Leary
a4d27e4cb5 Update some icons 2014-05-31 00:32:00 +01:00
Nick O'Leary
c4b00de48b Add font-awesome 2014-05-31 00:01:22 +01:00
Nick O'Leary
70147d0b6b Remove 'retined' property from node.status calls 2014-05-30 20:30:26 +01:00
Dave C-J
2cdaed1325 Add "use strict" to most core nodes.
(skipping ones that may have other work in progress)
2014-05-29 22:13:21 +01:00
Dave C-J
7ad28de52a Add use strict to mqtt node, allow will parm to be passed. 2014-05-29 09:00:28 +01:00
Nick O'Leary
1d5e8de6f6 Add function documentation to editor.js 2014-05-27 16:06:25 +01:00
Nick O'Leary
7333eb80b4 Merge branch 'master' of github.com:node-red/node-red 2014-05-27 10:54:45 +01:00
Nick O'Leary
d8df592ea6 Merge pull request #228 from Belphemur/credentials
Credentials Validation
2014-05-27 10:53:10 +01:00
Nick O'Leary
b995f70d36 Do not reset mouse mode when trigging edit 2014-05-25 22:24:08 +01:00
Dave C-J
b67e70e09f Fix Delay node milliseconds label to read ms.
(and add use strict)
2014-05-23 23:15:28 +01:00
Dave C-J
dcc0adf2f7 Update Arduino to a better supported npm, that supports callbacks
for inputs... (less processor load). Also adds connected status to
visualisation.
2014-05-23 15:35:55 +01:00
Dave C-J
db125974c0 Tidy up inject tabs/spaces one more time. 2014-05-19 22:08:50 +01:00
Dave Conway-Jones
69405296ba Merge pull request #230 from TJKoury/master
Fixed spinner issue in inject node
2014-05-19 22:06:15 +01:00
TJKoury
21a72336fd Update 20-inject.html
Tabs got wonky, fixed it.
2014-05-19 16:43:30 -04:00
tjkoury
57e5f52d02 Fixed spinner issue in inject node with non-hardcoded offset 2014-05-19 16:42:36 -04:00
tjkoury
804dd67b4a Fixed spinner issue in inject node with non-hardcoded offset 2014-05-19 16:40:37 -04:00
tjkoury
4f56f36cde Fixed spinner issue in inject node 2014-05-19 15:38:07 -04:00
Nick O'Leary
74007f2ef4 Fix wire corruption across tabs
Fixes #229
2014-05-16 23:28:02 +01:00
Nick O'Leary
b7f8c92b13 Add chrome rendering workaround 2014-05-16 20:43:22 +01:00
Antoine Aflalo
364f44451f Validation of credentials inputs. 2014-05-16 11:20:36 +03:00
Antoine Aflalo
f822827454 Credentials into RED 2014-05-16 10:07:43 +03:00
Nick O'Leary
2fa82d9f1f Touch: don't trigger radialMenu on pinch zoom 2014-05-15 22:56:12 +01:00
Nick O'Leary
e524393d87 Touch: add long-touch menu 2014-05-15 22:49:07 +01:00
Nick O'Leary
863ceb065a Make links a bigger target 2014-05-15 22:44:07 +01:00
Nick O'Leary
525321ec7f Fix node emitter 2014-05-15 20:55:01 +01:00
Nick O'Leary
820ca4475d Add tests for async close 2014-05-14 21:46:07 +01:00
Nick O'Leary
8f1dd62515 Allow Serial nodes to close asynchonously 2014-05-14 21:34:17 +01:00
Nick O'Leary
c317ccc36d Allow nodes to close asynchronously 2014-05-14 21:18:47 +01:00
Dave C-J
3ba6ad07b7 Finally get Firefox and Chrome to wheel zoom in the same manner... 2014-05-14 17:41:04 +01:00
Nick O'Leary
e0971d96c6 add grip handle icon 2014-05-14 15:38:54 +01:00
Dave C-J
a99b41a101 Self inflicted type - apologies to all... 2014-05-14 15:14:53 +01:00
Dave C-J
1bbd6297ba Actually make xml2js and json2xml deprecated
as well as just moving them
2014-05-14 14:32:44 +01:00
Dave C-J
7ed10d631e Reverse mouse wheel - so canvas (alt) zoom consistent with page (ctrl) zoom 2014-05-14 14:22:28 +01:00
Dave C-J
a76f48f50b Add alt key hook to keyboard event handler (just in case :-) 2014-05-14 14:18:12 +01:00
Dave C-J
0e35b65afd Deprecate xml2js and jsn2xml nodes
Replaced by single XML node
2014-05-14 14:18:11 +01:00
Nick O'Leary
789b86b122 Prevent node label selection on drag in FF 2014-05-14 13:49:16 +01:00
Dave C-J
6f981d29ec Patch to suppoer use strict
Fixes Issue #205
Manual merge due to other changes
2014-05-14 11:44:19 +01:00
Dave C-J
55245610dd Patch to Support use strict
Fixes Issue #206
(manual merge due ot other changes)
2014-05-14 11:43:51 +01:00
Dave Conway-Jones
7e4fa5fa45 Merge pull request #204 from tedgoddard/patch-1
support for use_strict - patch for server.js file
Removes redundant variable declaration.
(now handled in storage)
2014-05-14 11:25:38 +01:00
Nick O'Leary
a3ce04e9a5 Increase double-click to edit time 2014-05-14 09:50:06 +01:00
Dave C-J
6f0e619611 add Status to email, http nodes (update copyright on tcp) 2014-05-13 11:39:59 +01:00
Nick O'Leary
0e8f0735cc Touch: long-press initiate lasso 2014-05-13 00:42:24 +01:00
Nick O'Leary
aa4bf4d640 Add sidebar grip and make drag-openable 2014-05-13 00:01:05 +01:00
Nick O'Leary
59eccb116a Add RED._debug 2014-05-13 00:00:17 +01:00
Nick O'Leary
b29e434449 Touch: add meta tags for chrome/ios save to homescreen 2014-05-12 23:59:27 +01:00
Nick O'Leary
2ee78b73fb Touch: pinch-to-zoom from touchpoint not origin 2014-05-12 23:57:14 +01:00
Nick O'Leary
4abbe483a0 Merge pull request #227 from TJKoury/mobile_PR
Add inital pinch-to-zoom implementation
2014-05-12 23:51:41 +01:00
Dave C-J
7e71a118eb Add status indicators to serial and tcp (client) nodes 2014-05-12 16:32:19 +01:00
tjkoury
cf7339dc6d Merged with latest from node-RED master 2014-05-12 09:19:54 -04:00
Nick O'Leary
e15a0d545d Touch: unable to scroll after drawing link 2014-05-11 23:55:11 +01:00
Nick O'Leary
bf49485d4c Handle null TouchEvent (FF/Safari) 2014-05-11 13:57:54 +01:00
Nick O'Leary
7e85eb297d Make status display toggleable and add to MQTT nodes 2014-05-10 23:33:02 +01:00
Nick O'Leary
ec5985eaa3 Touch: double tap to edit rather than long-touch 2014-05-09 23:46:36 +01:00
Nick O'Leary
a3dea6ec0d Touch: enable wiring nodes together 2014-05-09 23:30:00 +01:00
Nick O'Leary
a5cf4b17bf New config nodes not picking up defaults 2014-05-09 15:23:33 +01:00
Nick O'Leary
87410621c7 Merge pull request #222 from Belphemur/node-credentials
Node credentials
2014-05-09 15:11:55 +01:00
Dave C-J
69873bacb6 Tiny EFL tweaks to IRC. Thanks again Belphemur 2014-05-09 15:03:41 +01:00
Dave C-J
d1aff364e4 let nodemon ignore backup file - or it restarts on every deploy. 2014-05-09 14:56:02 +01:00
Nick O'Leary
bd6ac11c43 Unable to click-select link 2014-05-09 14:22:10 +01:00
Antoine Aflalo
6a7b3cf62c REST API for Credentials
Adding RED.nodes.registerCredentials to register the credentials definition server sided.
Adding the property credentials in the template definition client-side.

Connecting the editor to the credential API.
I added a TODO for the validation of Credentials field.
As the other field, the developer should be able to set the credentials as required and also give a validation function.
2014-05-09 13:47:21 +03:00
Dave Conway-Jones
848e5a9824 Merge pull request #225 from Belphemur/node-irc
Adding a listener to "names" and improve documentation of IRC Node
2014-05-09 10:54:13 +01:00
Antoine Aflalo
0ea2c92d23 Adding a listener to "names" and improve documentation
Being able to get the list of connected user when join the channel can be interesting, that why I added the listener.
I improved the documentation by adding all the possible status and their description.
2014-05-09 11:48:41 +03:00
Nick O'Leary
034f17a8e8 Touch ui - deselect nodes when touch on canvas 2014-05-08 23:00:11 +01:00
Nick O'Leary
da3fbd3b62 Initialise Node earlier in registry life-cycle 2014-05-08 22:58:30 +01:00
Nick O'Leary
9524ef726a Change status icon look 2014-05-08 22:56:17 +01:00
Nick O'Leary
e061b2559c Remove touch-undo 2014-05-08 21:54:20 +01:00
Nick O'Leary
1d2fcc6fa1 Disable native pinch zoom of page 2014-05-08 21:50:19 +01:00
Nick O'Leary
17ad6f94dd Touch enable jQuery UI components 2014-05-08 21:47:58 +01:00
Nick O'Leary
c99f0d895f Add comms back to RED interface 2014-05-08 16:55:08 +01:00
Nick O'Leary
7ecb80bf40 Add node status updates 2014-05-08 14:15:54 +01:00
Nick O'Leary
8e7fc011f0 File In node loses message properties 2014-05-08 11:13:35 +01:00
Nick O'Leary
ba126e90d9 TypeError on debug node
Fixes #223
2014-05-08 11:12:13 +01:00
Nick O'Leary
16f8673ec0 Add comms module 2014-05-07 20:47:25 +01:00
Nick O'Leary
e6794a0c75 Twitter: add rate limit warning 2014-05-07 20:45:59 +01:00
Nick O'Leary
3c176d0b94 Handle parse errors in template file 2014-05-07 20:45:26 +01:00
Nick O'Leary
7b0a1b2463 Fix flow backup for first run, add tests 2014-05-07 00:33:50 +01:00
Nick O'Leary
0d6525623d Refactor editor.js to reduce duplication of editor setup
Part of #93

This reduces the duplication in code that creates the both the normal-
and the config-node dialogs.

It also identifies the key points that credentials handling should
be introduced.
2014-05-06 23:44:35 +01:00
Dave C-J
fb0cae0935 Reduce GPIO setup on Pi node to try to make it play nice with other peripherals 2014-05-06 20:58:36 +01:00
Dave C-J
d1318d215c change preferred name of backup file to flows.backup
(even if it is the previous version :-)
relates to Issue #184
2014-05-06 16:02:18 +01:00
Dave C-J
67d1e2acd8 Create a copy of the previous flow file (flow.previous) before each deploy
closes #184
In case of emergency you can now find the previous flow file before you hit 
deploy in a file called flow.previous . As indicated it is the flow that was 
running prior to you hitting deploy...
2014-05-06 15:22:58 +01:00
Nick O'Leary
44e8aeaae4 Remove console.log in credentials 2014-05-06 14:25:53 +01:00
Nick O'Leary
4b160dc3a9 Function error not recognised as TypeError 2014-05-06 13:49:22 +01:00
Nick O'Leary
5e5d3d3000 Log.addHandler missing its implementation 2014-05-06 13:06:06 +01:00
Dave C-J
e47839b7e7 Tweaks to shorten Serialport html names.
and remove redundant code.
2014-05-06 11:12:56 +01:00
Nick O'Leary
d982d02810 Prevent accidental edit of nodes
Part of #217
2014-05-06 10:14:18 +01:00
Nick O'Leary
baaf8167d3 Delay node dropping intermediate messages incorrectly 2014-05-06 10:07:57 +01:00
Nick O'Leary
7c5baac192 Don't show edit dialog for dblclick-drag
Closes #217
2014-05-05 23:28:24 +01:00
Nick O'Leary
14e882f250 Remove rogue console.log 2014-05-05 21:44:04 +01:00
Nick O'Leary
b64b1f2956 Serial port select combo-box 2014-05-05 21:42:01 +01:00
Dave C-J
671723374d Allow serial port to be manual entry or pick from list. 2014-05-05 11:39:59 +01:00
Dave C-J
b806854867 Revert serial port config to a text box - while we look at how best to
provide a list - but allow free input. (for Pi that doesn't enumerate tyAMA0)
2014-05-05 09:02:01 +01:00
Dave C-J
bc1fb3b404 Tiny formatting tidy ups. 2014-05-05 09:02:01 +01:00
Nick O'Leary
7674492819 Fix node loading order 2014-05-05 00:10:50 +01:00
Nick O'Leary
da8ef7acc6 Minify node .html files before sending 2014-05-04 17:30:55 +01:00
Nick O'Leary
ff49d2b217 Migrate to new node function style 2014-05-03 23:32:04 +01:00
Nick O'Leary
5afc5857c4 Fix localfilesystem_spec 2014-05-03 22:38:33 +01:00
Nick O'Leary
7eed375111 Split up nodes.js into components 2014-05-03 22:28:17 +01:00
Dave C-J
df9744084c Tweaks to email error rmessages 2014-05-02 15:16:11 +01:00
Dave C-J
1309b9a72d Add new/updated Email node including credentials per node.
Deprecating old IMAP node as now part of this general Email node
2014-05-02 14:37:41 +01:00
Dave C-J
a9e07f8b78 Updated credentials storage so there is a .._cred.... file per flow.
Allows swapping flows more easily without having to re-enter credentials.
Thus also added *_cred* to .gitignore
2014-05-02 14:35:51 +01:00
Nick O'Leary
84093bcb6e Make node loading more asynchronouse 2014-05-02 11:16:07 +01:00
Nick O'Leary
4dda4aeef2 Remove sentiment tip to be consistent 2014-05-01 23:12:19 +01:00
Nick O'Leary
d0b6fd078c Add File In node 2014-04-30 22:48:30 +01:00
Nick O'Leary
1df963e0ed Generate content-length on HTTP Request node 2014-04-30 22:05:27 +01:00
Dave C-J
e49eb3c685 Tweak layout of serial port config (Thanks Frank) 2014-04-30 14:09:08 +01:00
Dave Conway-Jones
f60430305e Merge pull request #207 from fvdpol/master
Add configuration of serial protocol settings (25-serial node)
2014-04-30 13:53:36 +01:00
Dave C-J
0b49b2cdda Add parser function nodes for XML, JSON and CSV.
Each is dual function - pass in (for example) and get out a js object, pass in a js object and get back out the xml string.
The CSV node must be configured with a column template that specifys the required property names for that column (csv->js), or the properties of the object that should be made into the csv (js->csv)
2014-04-29 17:01:30 +01:00
Dave C-J
f2e9b43866 Functional Fix for Inject node
addresses Issue #209
Interval is now wholly timeout based.
Now works on Sundays
2014-04-29 16:20:45 +01:00
Nick O'Leary
02eb1d9a64 Import unknown config node breaks layout
Fixes #211
2014-04-28 21:40:32 +01:00
Nick O'Leary
b2f4bc915e Bump 0.7.2 2014-04-26 22:43:29 +01:00
Nick O'Leary
13deef189d Add ws heartbeat to keep connection alive through firewall 2014-04-24 23:42:44 +01:00
Nick O'Leary
b5a8a7288b Tidy up ajax usage 2014-04-21 22:42:46 +01:00
Frank van de Pol
c62a42169f Typo 2014-04-21 23:42:07 +02:00
Frank van de Pol
9d1bb39018 Add configuration of serial protocol settings
- Add support for configuration of protocol parameters (data bits,
party, stop bits)
- Set serial device/port using select list (discovered ports)
- Provide default settings for existing flows that do not yet have
serial protocol settings
2014-04-21 23:35:28 +02:00
Nick O'Leary
b6fd103b37 /nodes end-point should be text/html not json 2014-04-21 22:17:52 +01:00
Nick O'Leary
6a17a7d4c2 Add version information to log output 2014-04-21 21:55:28 +01:00
Nick O'Leary
c39f4f9738 Handle port-in-use error on start-up 2014-04-21 21:42:59 +01:00
Nick O'Leary
c20128b80f MQTT Client - missing null check 2014-04-21 21:14:03 +01:00
Nick O'Leary
0b7fa1ab5c Fix MQTT client reconnect logic 2014-04-21 20:40:56 +01:00
Nick O'Leary
775297d625 Fix library ui 2014-04-20 23:07:54 +01:00
Nick O'Leary
d00624f9e3 Tidy up REST interface
- Ensure application/json where appropriate
 - Use jquery api rather than d3
2014-04-20 22:35:38 +01:00
Nick O'Leary
d702caa5be Bump 0.7.1 2014-04-20 20:52:16 +01:00
Nick O'Leary
729036ec0b Fix HTTTP Request url template 2014-04-20 20:50:20 +01:00
Nick O'Leary
eee8f89146 Clear MQTT Connection watchdog on error 2014-04-19 22:19:06 +01:00
Nick O'Leary
4ae5f34d2e Make Template node help clearer 2014-04-18 15:33:29 +01:00
Ted Goddard
440d649cd6 support for use_strict
variable appears unused but results in

ReferenceError: flowfile is not defined

when node is invoked with --use_strict
2014-04-17 10:54:55 -06:00
Nick O'Leary
18ae7108f5 Add Grunt-cli dependency and fix Travis 2014-04-16 23:28:02 +01:00
Nick O'Leary
b8bcd57cda Bump 0.7.0 2014-04-16 22:42:26 +01:00
Nick O'Leary
0a2dab67c7 Remove deprecated nodes
As announced here https://groups.google.com/forum/#!topic/node-red/-2nG6nKaxFI
2014-04-16 22:21:50 +01:00
Nick O'Leary
fa275646a1 Allow use of arrow keys to move selected node
Closes #176
2014-04-16 13:39:16 +01:00
Nick O'Leary
4219681cfa Single-click on debug message reveals node
Closes #192
2014-04-16 12:59:23 +01:00
Nick O'Leary
a386c028b0 Handle null debug messages
Fixes #200
2014-04-16 11:37:41 +01:00
Nick O'Leary
9ad4d50442 Merge pull request #187 from monteslu/sentiment
Add support for word overrides feature in sentiment node
2014-04-16 11:07:29 +01:00
Nick O'Leary
653c02bb15 Add drop-intermediate option to Delay rate node 2014-04-15 23:08:02 +01:00
Nick O'Leary
d5b36fcadc Add mustache template support to the HTTP Request url 2014-04-15 22:49:39 +01:00
Nick O'Leary
e9c6501771 Add onadd node edit hook 2014-04-15 22:31:34 +01:00
Dave C-J
7290512794 Allow File out node to handle objects more usefully. 2014-04-14 15:49:49 +01:00
Dave C-J
be5f6762f7 Add Array detection to Debug node output 2014-04-14 15:49:17 +01:00
Nick O'Leary
1ea023e8ef Update README.md 2014-04-14 09:51:55 +01:00
Nick O'Leary
896b52ed9e Fix node tests 2014-04-13 22:32:33 +01:00
Nick O'Leary
92d10384ba Avoid adding null req/res properties to messages 2014-04-13 22:12:57 +01:00
Nick O'Leary
de9ee37b42 Fix 'cannot set property boolean of undefined' 2014-04-12 23:09:47 +01:00
Dave C-J
ae02cf8d71 Make 0's appear in switch node dialog (were seen as nulls) 2014-04-11 10:56:59 +01:00
Dave C-J
df0ecbaf3a Disable unselectable option in Raspberry Pi node 2014-04-11 10:56:20 +01:00
Dave C-J
96ed3055bf tweak sample nodes - slightly 2014-04-11 10:55:14 +01:00
Nick O'Leary
10d9dee4aa Escape html chars in Inject/Debug and Info pane 2014-04-09 14:50:53 +01:00
Nick O'Leary
8a646f73b3 Config node validation should define to node-defined func 2014-04-08 16:19:58 +01:00
Dave C-J
a08789a086 Change parseXML node to no longer have special debug options
(as can now be done in the debug node... so more consistent)
2014-04-08 16:06:58 +01:00
Dave C-J
e38b321c33 Tweak to Pi GPIO node to throw exceptions more in line with latest thinking 2014-04-08 16:06:58 +01:00
Nick O'Leary
d784889b75 Validate properties after calling oneditprepare 2014-04-08 15:32:58 +01:00
Nick O'Leary
43073de10b Add nodesExcludes option 2014-04-08 13:33:09 +01:00
Dave C-J
069f42f0c5 Add console.log option to debug node
Fixes #194
2014-04-08 11:31:35 +01:00
Nick O'Leary
0b8e8de260 Allow nodesDir to be an array of paths 2014-04-07 22:01:33 +01:00
Dave C-J
61285a0ee8 Fixes to spawn behaviour - (well more to catching bad behaviour)
It needs a redo - but patched so it doesn't die horribly.
2014-04-06 23:25:40 +01:00
Dave C-J
789d64f7ed Add Ctrl-x (cut) to shortcuts 2014-04-03 00:05:16 +01:00
Dave C-J
572a6156d6 Add .npm dir to .gitignore 2014-04-02 23:59:51 +01:00
Nick O'Leary
214338eb62 Fix Function node
accidental leakage of in-progress work
2014-04-02 23:56:46 +01:00
Nick O'Leary
bcefa6c9ef Properly deprecate httpget node 2014-04-02 21:21:51 +01:00
Nick O'Leary
c52db897b3 Remove boilerplate help from Function node
Fixes #180
2014-04-02 21:20:58 +01:00
Nick O'Leary
fa2d2771a7 Add websocket error handlers
Fixes #190
2014-04-01 23:18:34 +01:00
Nick O'Leary
6cc0df75a6 Better spinner graphic
Fixes #189
2014-04-01 22:53:48 +01:00
Nick O'Leary
c1502663b4 Update node_registry_spec 2014-03-31 14:41:51 +01:00
Nick O'Leary
ea2c0da163 Make localfs test async aware 2014-03-31 14:37:38 +01:00
Nick O'Leary
1bdfd920cd Add debug to storage_localfilesystem test 2014-03-31 14:15:10 +01:00
Nick O'Leary
22db06046b Fix mocha dependency 2014-03-31 13:38:35 +01:00
Nick O'Leary
9a7042b8dc Add grunt build and travis-ci 2014-03-31 13:31:31 +01:00
Nick O'Leary
e7dcdb075a Update udp node bind local options 2014-03-31 13:06:57 +01:00
Dave C-J
1d23cdad9f Improve labels for Pi GPIO pins 2014-03-31 11:18:37 +01:00
Dave C-J
1f6155f118 Fix to allow blank password for Basic Auth in httprequest node 2014-03-31 10:28:37 +01:00
Nick O'Leary
a3fa6dada5 Rework local port UI in udp node 2014-03-30 23:05:59 +01:00
Nick O'Leary
e2bac40b17 Update range node ui 2014-03-29 23:05:46 +00:00
Nick O'Leary
99ab6eaafd Tidy-up language in change node 2014-03-29 21:57:20 +00:00
Luis Montes
8b138ff2c8 add support for word overrides in sentiment node 2014-03-29 14:27:09 -07:00
Nick O'Leary
c24cf9c1c2 Fix config node usage counts 2014-03-28 21:34:26 +00:00
Nick O'Leary
cefa0ae5b6 Node defaults of 0 not showing in edit form
Fixes #186
2014-03-28 21:00:11 +00:00
Dave C-J
79f8d057a1 tweak "leave blank for random" text for outgoing port
as per Nick's suggestion
2014-03-28 08:53:25 +00:00
Dave C-J
e5e457a410 UDP node fixes. Allow fixing of outbound port.
Fixes #178
Also fixed multicast binding to work more correctly.
Note: - if you fix the outbound port it will then be unvailable for input - as we are not setting up a pool.
2014-03-27 23:27:43 +00:00
Nick O'Leary
0409a14a84 Merge pull request #183 from hardillb/master
fix delay node name when using random setting
2014-03-25 23:24:22 +00:00
Nick O'Leary
4e41db15e9 Add explicit CLA links 2014-03-25 17:41:33 +00:00
Ben Hardill
4e00ab3b2d fix delay node name when using random setting 2014-03-25 11:09:38 +00:00
Dave C-J
2a0491542d Fix for IMAP node - inbox 0 error. 2014-03-23 11:43:34 +00:00
Nick O'Leary
0b516a83db Fix snap-to-grid
Fixes #177
2014-03-22 13:47:47 +00:00
Nick O'Leary
bc8683f40a Skip non-existent node directories 2014-03-21 14:05:27 +00:00
Nick O'Leary
4dce130acb Hide palette until nodes loaded 2014-03-17 16:26:15 +00:00
Nick O'Leary
f666b0e6f9 TCP Node port in use not caught properly 2014-03-17 16:09:07 +00:00
Nick O'Leary
015adb3dfd Clear palette filter box on reload (firefox) 2014-03-17 16:01:47 +00:00
Nick O'Leary
15d642c55d Unexpected error if uiPort already in use 2014-03-17 15:58:44 +00:00
Dave C-J
d87cc471a0 Tiny change to presets for range node - more useful example irl. 2014-03-11 16:38:52 +00:00
Nick O'Leary
d5ad113d1b Remove console.log debug 2014-03-09 22:34:45 +00:00
Nick O'Leary
15002f6872 Add headless mode
closes #2

httpAdminRoot / httpNodeRoot can be set to false to disable their respective bits. If both are set to false, (or httpRoot is set to false), and httpStatic is not defined, then it will not start the http server.
2014-03-08 22:35:35 +00:00
Nick O'Leary
5e58cc9fc1 HTTP In help text should use httpNodeRoot 2014-03-08 21:54:13 +00:00
Nick O'Leary
77f1ee9f64 Tidy up nodes module exports 2014-03-06 22:44:34 +00:00
Nick O'Leary
f7792c66b4 Add cmd-line help and reduce node error output by default
Added nopt package dependency - npm update required!

Added -v cmdline option to show node module load errors, which are otherwise hidden by default with only a summary shown
2014-03-06 22:32:23 +00:00
Nick O'Leary
68d5ebf388 Allow multiple config types in a node
Fixes #170
2014-02-28 20:58:04 +00:00
Nick O'Leary
1c655b5945 Missed sidebar api changes 2014-02-27 16:47:28 +00:00
Nick O'Leary
ebcf539795 Update CONTRIBUTING.md 2014-02-27 16:38:35 +00:00
Nick O'Leary
e403924d2b Only refresh info if editing_node not null 2014-02-27 16:28:12 +00:00
Nick O'Leary
325600ea61 Update info sidebar on node edit
Fixes #168

Moves info sidebar to its own file so it can be refreshed from other parts of the UI
2014-02-26 22:59:53 +00:00
Nick O'Leary
fb5b45c655 Tidy up tab switching 2014-02-24 23:42:24 +00:00
Nick O'Leary
6d4a7c73b5 Move switch-workspace menu up a level 2014-02-24 23:21:23 +00:00
Nick O'Leary
3ac0ea75f4 Update orion editor to 5.0RC1 2014-02-24 23:07:23 +00:00
Nick O'Leary
a0aec3f8f1 Fix config node tab entry for blank label 2014-02-23 20:58:24 +00:00
Nick O'Leary
f81ebf0e64 Fix too specific wildcard matching in MQTT node 2014-02-23 20:14:27 +00:00
Nick O'Leary
a44104a7e4 Bump to 0.6.0 2014-02-21 21:48:31 +00:00
Nick O'Leary
715fb6e7f4 Tidy-up tab ui api 2014-02-21 09:54:50 +00:00
Nick O'Leary
f7a72a48ea Improve socket error handling in MQTT client
Fixes #155
2014-02-20 21:56:29 +00:00
Dave C-J
58774c366d Add .delete property to File output node
Fix to close #162
2014-02-20 21:00:54 +00:00
Dave C-J
0bc4a3bbb1 update UDP node to not bind output port if not required to do so.
Fix to Close #165
2014-02-20 20:59:05 +00:00
Dave C-J
46765d5737 Turn down console.log-ing in udp node 2014-02-20 18:11:08 +00:00
Nick O'Leary
309e5f4921 Add deploy-in-action spinner gif 2014-02-20 17:32:16 +00:00
Nick O'Leary
ed9ce1bb3c Add deploy-in-action spinner 2014-02-20 17:31:40 +00:00
Nick O'Leary
d97e23947d Add uid to mqtt client log messages
This is a semi-temporary change to help debug the multiple-connection issue seen with the MQTT client
2014-02-19 22:17:20 +00:00
Nick O'Leary
b4ef1d354d Catch mqtt socket write error after disconnect
Part of #155
2014-02-19 21:30:46 +00:00
Nick O'Leary
d8f2f24b44 Add optional basic-auth to HTTP Request node
Closes #160
2014-02-19 20:31:42 +00:00
Nick O'Leary
ec0b5da29c Import dragging off-node failed to set dirty flag 2014-02-19 20:08:25 +00:00
Nick O'Leary
19d5709e2a Allow Escape to clear the droptarget 2014-02-19 10:38:46 +00:00
Nick O'Leary
af50fe876f Merge pull request #164 from hardillb/master
Fix delay node singleton issues
2014-02-18 18:40:03 +00:00
Ben Hardill
da167c8607 remove un needed commented out line 2014-02-18 10:28:46 +00:00
Ben Hardill
560d106ba2 Merge remote-tracking branch 'upstream/master' 2014-02-18 10:25:21 +00:00
Ben Hardill
7af88f63f5 Fix delay node singleton issue
Fixes #154
2014-02-18 10:23:44 +00:00
Nick O'Leary
b8953abb28 Add clientid to mqtt-broker label
Fixes #161
2014-02-17 22:42:31 +00:00
Nick O'Leary
6b278fdceb Add httpNodeCors setting
Adds a dependency on the 'cors' npm module
2014-02-17 22:32:53 +00:00
Nick O'Leary
09f162d933 Fix overriding method in HTTP Req node 2014-02-17 22:16:42 +00:00
Nick O'Leary
b7e3e2d739 Add username/password to Mongo nodes
Fixes #159
2014-02-16 23:17:15 +00:00
Nick O'Leary
7c24d4d760 Separate out httpAdmin and httpNode 2014-02-16 00:39:30 +00:00
Nick O'Leary
e6cf783d52 HTTP In GET error handler fix 2014-02-15 22:36:01 +00:00
Nick O'Leary
eb90d96d65 Fix drag'n'drop of flows on FF 2014-02-12 22:33:07 +00:00
Nick O'Leary
35fb4bb47a Add rawBody middleware
Fixes #151
2014-02-12 21:30:49 +00:00
Nick O'Leary
62389b487f Merge pull request #153 from andypiper/mime-fix
Fix for bad MIME Content-Type
2014-02-12 20:15:33 +00:00
Andy Piper
c9374532a9 Fix for bad MIME Content-Type (per #152) 2014-02-11 13:21:30 +00:00
Nick O'Leary
75fc46c05d Improve node info properties table 2014-02-09 15:24:05 +00:00
Nick O'Leary
79d4b32e3f Update README.md 2014-02-08 15:33:09 +00:00
Nick O'Leary
6756e964fd Add CONTRIBUTING.md 2014-02-08 15:31:01 +00:00
Nick O'Leary
7c24c7465a HTTP Request: allow message to override https
Fixes #147
2014-02-06 22:14:27 +00:00
Nick O'Leary
20de0c7c89 Move to twitter-ng module
Fixes #136

This will require an `npm update` to keep things working
2014-02-05 20:26:56 +00:00
Nick O'Leary
cef652eef7 File node: allow filename to be overridden 2014-02-05 10:26:17 +00:00
Dave C-J
ae03562f86 Slight UI tweak to Inject node - move fire once below options 2014-02-05 08:09:16 +00:00
Dave C-J
f3f52fa586 Inject - cleanup messing with crontab... just don't do it. 2014-02-04 22:31:51 +00:00
Dave C-J
40232f95ed Clear inject once flag properly. Issue #145 2014-02-04 22:19:55 +00:00
Dave C-J
b1de42b297 Tweak Inject Fire at Once options as per Issue #145 2014-02-04 21:50:35 +00:00
Dave C-J
bdd9d901ec inject node - Remove couple of lines of excess console.log 2014-02-03 19:06:49 +00:00
Nick O'Leary
45cb1016cc Merge pull request #144 from hardillb/master
Fix random wait in delay node
2014-02-03 06:40:45 -08:00
Ben Hardill
ad1f967a8d Fix random wait in delay node
Fixes #143
2014-02-03 13:08:20 +00:00
Nick O'Leary
2afe474ec8 Merge pull request #139 from skynetim/storage
Remove some sync calls from localfilesystem.js.
2014-02-02 12:22:23 -08:00
Dave C-J
e19f2956a8 tiny tweak to inject to fix name and hide un-needed text box.
tweak exec node text to be less alarmist
2014-02-02 18:12:28 +00:00
Dave C-J
b882846516 Quick fix for serial hangs (with serialport1.3.1)
While we investigate further.
2014-02-02 16:52:32 +00:00
Nick O'Leary
c47c72cf48 Improve inject node payload options 2014-02-02 15:37:34 +00:00
Nick O'Leary
d52cd1ce00 Merge branch 'master' of github.com:node-red/node-red 2014-01-27 21:55:58 +00:00
Nick O'Leary
f79fdc66e0 Fix Firefox palette filter CSS 2014-01-27 21:55:31 +00:00
Dave C-J
3dee0f1e20 Add small bit of extra node.log to exec node to make it obvious what it is doing. 2014-01-27 19:24:15 +00:00
Dave C-J
3a2ed39b51 Add new Range Node 2014-01-27 19:23:35 +00:00
Nick O'Leary
cfd8d137cf Add config-node sidebar tab
Accessed from the drop-down menu
2014-01-25 22:31:43 +00:00
Nick O'Leary
1bdbd6a5b0 Cannot toggle an empty category 2014-01-19 19:23:25 +00:00
Nick O'Leary
8178ab3415 Add palette filter (again) 2014-01-19 12:12:07 +00:00
Nick O'Leary
a6d5d6ca82 Add palette filter 2014-01-19 00:01:27 +00:00
Nick Niemeir
288b129ec3 Make more promisey 2014-01-16 06:28:10 +00:00
Nick Niemeir
3c41b2624a First pass at removing sync calls 2014-01-16 05:59:13 +00:00
Dave C-J
aa044970c9 Add extra labels to extra Pi GPIO pins 2014-01-14 19:23:50 +00:00
Nick Niemeir
bdef2a5b96 Add npm test 2014-01-14 06:13:07 +00:00
Dave C-J
ad675c00d8 enhance fs.notify help text
windows paths need double slashes \\
2014-01-13 20:25:58 +00:00
Dave C-J
82f58393c7 enhance labels for Pi GPIO pins 2014-01-13 20:24:57 +00:00
Dave Conway-Jones
08559838cc Merge pull request #133 from cpswan/master
Enable Pi to use of all 17 WiringPi pins
2014-01-13 12:04:31 -08:00
Nick O'Leary
79aeeea640 Handle duplicate PUBREL
Fixes #138

If the connection to a broker is lost mid qos 2 flow, there is a window where we have processed the PUBREL, released the message and deleted it from our store, but not sent the PUBCOMP. When the connection is re-established, and the PUBREL is resent by the broker, we assume the message still exists - and hit the error reported.

The fix is to check the message is valid before trying to process it. We send the PUBCOMP to complete the flow regardless.
2014-01-13 11:32:16 +00:00
Nick O'Leary
6b3010f95b Guard against null client in MQTT nodes
Fixes #130

There was a timing window where a client could connect to a broker just as new flows were deployed that would cause the on-connect callback to be called after client has been set to null. This caused an NPE.

The fix is to check client isn't null in the event handler.
2014-01-13 11:27:09 +00:00
Dave C-J
10b7f402c3 Update MQTT node Icon color to be different from Twitter one
Fixes #132
2014-01-08 16:44:47 +00:00
Dave C-J
cbad188be8 Update to fs.watch node to use new fs.notify API
requires npm update fs.notify
2014-01-08 16:44:05 +00:00
Dave C-J
b652d26b6b Add invite event to irc node 2014-01-03 10:57:49 +00:00
Dave C-J
e5536b848a Changes to IRC node
addresses fix for #106
moved channel to node properties (won't break but will flag as undeployed changes)
changed defaults to be irc.freenode.net
allow input of a msg.raw as an array of a raw irc command (undocumented)
2014-01-02 11:15:03 +00:00
Nick O'Leary
5219d08cb8 Support drag/drop of flow json onto the canvas
Closes #43
2013-12-31 21:32:50 +00:00
Chris Swan
9839e87580 pintable + tablepin maps for all 17 WiringPi pins 2013-12-31 08:54:06 +00:00
Chris Swan
982ad91581 Drop down options for all 17 pins in WiringPi 2013-12-31 08:50:24 +00:00
Nick O'Leary
1c010c568d Fix keepalive handling in MQTT client
Fixes #124
2013-12-29 20:16:27 +00:00
Nick O'Leary
0046164689 Prevent nodes being dragged off canvas - top-left only
Fixes #120

Doesn't prevent dragging off the right/bottom sides, but that should be less likely to occur
2013-12-28 20:03:43 +00:00
Nick O'Leary
4e3594d617 Disable keyboard handler whilst workspace dialogs are open
Fixes #128
2013-12-28 17:59:45 +00:00
Nick O'Leary
24c373ecc2 Tidying up the Change node after a lot of churn 2013-12-24 23:16:36 +00:00
Nick O'Leary
74f43f4059 Merge pull request #127 from fvdpol/master
Add validation to the Change node 'from' field
2013-12-24 14:53:48 -08:00
Frank van de Pol
44e920fde2 Disable the notification for the Change node editor
Disable the notification on the Change node.
Once the infrastructure for validation error messages is implemented
this can be re-enabled and retrofitted to the
new structure.
2013-12-24 23:29:40 +01:00
Frank van de Pol
1ebc5979aa Add validation to Change node editor for invalid regex
This change adds input validation to the gui of Change Nodes to prevent
the user from unintentionally entering an invalid regular expression
(in case the ‘use regular expressions’  option is enabled).

The user will be notified (using the RED notification mechanism) on the
specific error code to help resolve the issue.
2013-12-24 20:28:08 +01:00
Frank van de Pol
b411d59d43 Make log message in invalid regular expressions more verbose
Make logging of erratic regular expressions more verbose to help
identification and resolving of the configuration issue:

eg.:
old: 24 Dec 18:40:09 - [error] [change:Strip kW] Invalid regex: *kW
new: 24 Dec 18:40:09 - [error] [change:Strip kW] Invalid regular
expression: /*kW/: Nothing to repeat

old: 24 Dec 20:15:57 - [error] [change:Strip kW] Invalid regex: *kW
new: 24 Dec 20:15:57 - [error] [change:Strip kW] Invalid regular
expression: /[kW/: Unterminated character class
2013-12-24 20:17:42 +01:00
Frank van de Pol
231f8b6a4d undo my changes to the Change node; revert to original
undo the local changes to the Change node to get back aligned with the
master tree
2013-12-24 20:02:30 +01:00
Frank van de Pol
b81f251023 merge from upstream 2013-12-24 18:53:38 +01:00
Dave C-J
00202a3930 wrong syntax for checking check box... in change node 2013-12-24 17:07:07 +00:00
Dave C-J
e0921f84c4 still flattening wrinkles in change node...
(I blame the Xmas "spirit" ;-)
2013-12-24 17:01:53 +00:00
Dave C-J
dafb2f1d38 Removed excess debugging from change node (sorry) 2013-12-24 16:54:19 +00:00
Dave C-J
6b2e666600 Make sure old flows with change node - work as-is/was.
Signed-off-by: Dave C-J <dave@conway-jones.co.uk>
2013-12-24 16:24:31 +00:00
Dave C-J
b7531bae4d Added proper choice for regex support to change node
(and better error catching)
Addresses Issue #121
2013-12-24 16:18:18 +00:00
Dave C-J
2ba5e0fe3e Add socketTimeout to settings.js for TCP server sockets
Fixes #125
adds an optional socketTimeout param to settings.js file to add a TCP server
socket timeout. Default is no timeout.
2013-12-24 13:12:17 +00:00
Nick O'Leary
a9668a1999 Better error message if twitter auth flow fails
Closes #123
2013-12-23 14:44:35 +00:00
Nick O'Leary
c6264e8040 Add newline when wrapping function to allow comments
Fixes #122
2013-12-22 22:22:47 +00:00
Frank van de Pol
a03b4e4dd4 Added validation logic to Change editor for validity of regular expressions
This change adds input validation to the gui of Change Nodes to prevent
the user from unintentionally entering an invalid regular expression.
The user will be notified on the specific error code to help resolve
the issue.
2013-12-22 17:46:25 +01:00
Frank van de Pol
655e777a3e Add exception handing to Change node
Adding exception handling to the change node to prevent node-RED from
crashing on invalid regular expressions eg. “*kW” (missing escape
before the asterix)
2013-12-22 14:00:25 +01:00
Nick O'Leary
5d43334b1c Bump version 0.5.0 2013-12-21 19:31:50 +00:00
Dave C-J
15669b7f1f Only declare node variables inside node declaration.
Fix for Issue #119
(also reformatted with spaces hence what looks like massive changes...)
2013-12-21 17:31:05 +00:00
Nick O'Leary
95b8600da7 Automatically follow 301-redirects in HTTP request node
Fixes #30
2013-12-20 11:11:50 +00:00
Nick O'Leary
48d37df199 Track node.changed state over undo
Part of #33
2013-12-19 21:34:25 +00:00
Nick O'Leary
73f3ea52a5 Add session awareness to TCP nodes
Closes #63,#65
2013-12-19 21:16:25 +00:00
Nick O'Leary
02df584af6 Allow storage module to be set explicitly
Rather than just by name
2013-12-19 11:05:03 +00:00
Nick O'Leary
751ac7b9ee Handle Buffer objects properly in Serial out node
Fixes #115
The serial out node does a JSON.stringify if the payload is an object. This was incorrectly being applied to Buffer objects, causing the output seen in issue #115.

The Buffer is now passed through as-is (with the newline appended if so configured).
2013-12-13 10:27:52 +00:00
Nick O'Leary
344660dfee Refinements to Unknown-node handling
Refinement to #113
2013-12-12 15:51:15 +00:00
Dave C-J
4fff3ce448 Make missing node type error popup non-permanent
Fixes Issue #113
2013-12-11 22:22:33 +00:00
Dave C-J
ac884bfdf3 Add "Unknown node" type to core.
Relates to Issue #5
not the best/complete solution - but a start.
2013-12-08 21:55:34 +00:00
Nicholas O'Leary
3984b6b702 Add websocket node docs
Part of #105
2013-12-08 20:41:57 +00:00
Nicholas O'Leary
cce5f33a97 Allow JSON sending/receiving in websocket node
Fixes #105
2013-12-08 20:31:56 +00:00
Nicholas O'Leary
f22cd381ee Add JSONP support to http response node
Fixes #102
2013-12-08 19:40:27 +00:00
Dave C-J
fae34f8244 Let serialport retry reconnects etc
Partial fix to Issue #111

This doesn't fix the screaming loop issue if you try to write to an unplugged
serial port - but does attempt to fix the not retrying to reconnect part of 
the issue.

Both were introduced by changes to the underlying serialport npm.
2013-12-08 16:59:36 +00:00
Dave C-J
67e16adfd0 Added output "New Line" char to Serial
Addresses Issue #104
adds the option to re-use the character used the split input into lineson input as an append to every line sent out to the serial port.
2013-12-06 21:04:35 +00:00
Nicholas O'Leary
d2ce6af486 Switch node: infer intent to compare numbers
Fixes #108
2013-12-06 20:26:55 +00:00
Nicholas O'Leary
4475e74187 Overlapping mqtt subs get duplicate messages 2013-12-06 14:19:21 +00:00
Nicholas O'Leary
ce7bf78349 Non-Cloning of req/res properties caused them to be lost
The previous fix to not clone the req/res objects introduced a bug where the req/res objects were lost from all but the first clone made out of a particular node.
2013-12-05 14:39:26 +00:00
Nicholas O'Leary
5767478871 Switch node null/not null tests don't always work
a === null / a !=== null

is different to

    typeof a == "undefined" / typeof a != "undefined"
2013-12-03 01:12:29 +00:00
Nick O'Leary
7eae669a34 Merge pull request #101 from hardillb/master
Fix typo in the delay node
2013-11-30 10:14:20 -08:00
Ben Hardill
f44272877e Merge remote-tracking branch 'upstream/master' 2013-11-30 18:10:03 +00:00
Ben Hardill
4b3f26bed5 Fix milliseconds in delay 2013-11-30 18:08:44 +00:00
Nicholas O'Leary
a4a3322048 Merge branch 'master' of github.com:node-red/node-red 2013-11-29 19:59:02 +00:00
Nicholas O'Leary
0507578c98 Always clone messages to ensure no cross-pollution
Part of #85

A function that returns the same message to multiple outputs, where each output is wired to at most one node was not having its messages cloned due to the change I made in #85 to be slightly more efficient. By returning the same message to each output, cross pollution was possible.
2013-11-29 19:56:46 +00:00
Dave C-J
399617dc58 Make Imap node always read once on start
Closes Issue #96

Imap node now logs (in console) if it finds duplicate email on read or not.
It doesn't send on duplicates. It also now DOES fire once on startup AND re-deploy to populate anything downstream... at least that is the excuse for the change.

Signed-off-by: Dave C-J <dave@conway-jones.co.uk>
2013-11-29 19:16:11 +00:00
Nicholas O'Leary
0bc0dc3a2b Cannot clone http.req/http.res properties
Temporary fixes #97
2013-11-28 16:06:17 +00:00
Nicholas O'Leary
9690ebe9c1 Add session awareness to WebSocket node
This allows a websocket-in node to receive data, process it in a flow
and then send it back to the originating websocket client via a
websocket-out node.
2013-11-26 23:33:57 +00:00
Dave C-J
ab04fcf7c0 Update IMAP node to use new 0.8.x API
Fixes Issue #96

this necessitates an update to the underlying npm
npm install --force imap
2013-11-26 19:55:40 +00:00
Nicholas O'Leary
7040aaa179 Add clientid/username/password to MQTT nodes
Alternative implementation, closes #42

The username/password as not stored in the main flow file for security reasons;
they are stored in the adjacent credentials file. This does mean an extra step
to importing an MQTT node, as the user has to manually edit it to re-add username
and password if needed.
2013-11-25 22:50:08 +00:00
Nicholas O'Leary
796080471d Twitter: add help text about rate limits 2013-11-25 21:46:15 +00:00
Nicholas O'Leary
83072dcda4 Twitter node: tags field not required if DM's selected
Fixes #91
2013-11-25 10:32:21 +00:00
Dave C-J
3982dcdaf1 Add change node to core/logic - allows simple(r) manipulation of some payloads and msg.properties to save having to write functions for these basic tasks. You can replace contents (regex based or simple swap), and add, modify or delete proerties. 2013-11-24 22:25:35 +00:00
Dave C-J
0a78838c71 Add default name to switch node (for when name not specified - to be more consistent with other nodes) 2013-11-24 22:22:35 +00:00
Nicholas O'Leary
873974478a Allow settings.js be specified on command line
Closes #79
2013-11-24 21:49:32 +00:00
Nicholas O'Leary
c1d495b62a Twitter node: DM's come in with a different structure 2013-11-24 20:54:10 +00:00
Nicholas O'Leary
cb8a3f064e Twitter doc updates and rate limit fixes 2013-11-24 16:48:24 +00:00
Nicholas O'Leary
9104b4200a Update feedparser node for underlying module api changes 2013-11-24 16:03:30 +00:00
Dave C-J
f051fbd1e1 Make imap node check for email right away on start/restart. Add some more console logging for re-assurance of things happening - or not. 2013-11-24 13:10:48 +00:00
Nicholas O'Leary
f2ed2365cd Twitter: monitor direct messages 2013-11-23 21:48:17 +00:00
Nicholas O'Leary
8176506d72 Twitter node: incorrect default user setting
caused by #80
2013-11-22 23:09:49 +00:00
Nicholas O'Leary
e88dcd4aba Get tweets from specific people
fixes #80
2013-11-22 23:07:08 +00:00
Nicholas O'Leary
88be896f1c Add WebSocket nodes 2013-11-22 21:28:05 +00:00
Nicholas O'Leary
7463ef92cb Add httpRoot tip to http node 2013-11-22 21:26:18 +00:00
Nicholas O'Leary
0aa17662f5 Posting to /flows should block until successfully saved, or fail
Mentioned in #76
2013-11-22 21:07:29 +00:00
Dave C-J
ff8db09fd9 Add "otherwise" option to switch node - also added ability to stop checking after first match or keep matching (as-is today). Fixes Issue #88 2013-11-22 16:25:02 +00:00
Nicholas O'Leary
3054b04378 Storage object null when adding twitter credentials as first ever node 2013-11-22 13:54:10 +00:00
Dave C-J
1967046cc8 Slight tweak to sentiment info to reflect reality. 2013-11-21 17:14:05 +00:00
Nicholas O'Leary
e1dbb95396 Deep-clone messages when there are multiple recipients
Fixes #85

As well as adding deep-clone (via the new dependency on the 'clone' module), we no longer clone the message if there is a single recipient. This makes simple node-to-node flows more efficient.

I've done some simple profiling using process.hrtime to time how long the Node.send function takes, and at best, this change is neutral to performance.
2013-11-21 14:03:17 +00:00
Nicholas O'Leary
6a4aa1ff21 Disable escape-to-close on edit dialog
Closes #84
2013-11-21 11:02:31 +00:00
Nicholas O'Leary
a0aed93c69 Make Debug topic slightly darker for better contrast
Fixes #82
2013-11-20 22:17:21 +00:00
Nicholas O'Leary
48c4786d66 Ignore node_modules dir when scanning for nodes 2013-11-20 13:55:21 +00:00
Nicholas O'Leary
2028880b48 Debug activation message the wrong way around
As reported under #47
2013-11-19 15:51:33 +00:00
Dave C-J
620af84088 Auto select node on drop onto canvas so info panel shows relevant info (if visible) 2013-11-19 08:48:44 +00:00
Nicholas O'Leary
72f72e8a50 Less intruisive node-changed icon
Part of #33
2013-11-18 23:02:27 +00:00
Nicholas O'Leary
f5284f5e1f Twitter Oauth path needs to account for httpRoot
Fixes #78
2013-11-18 21:20:50 +00:00
Nicholas O'Leary
1fc4a65307 Handle un-validated config nodes on open
Part of #33
2013-11-18 21:17:29 +00:00
Nicholas O'Leary
cbe57aa96c Validate config node properties
Fixes #74
2013-11-17 17:49:32 +00:00
Nicholas O'Leary
3797ace89b Display node-changed icon when there are unsaved changes
Part of #33
2013-11-17 15:52:34 +00:00
Nicholas O'Leary
7d2195d95c Add node-changed icon
Part of #33
2013-11-16 18:38:30 +00:00
Nicholas O'Leary
e703fa1b6b Add 'changed' property to nodes to track undeployed changes
Part of #33
2013-11-15 23:40:36 +00:00
Dave C-J
f7fc0760ca Fix to Close #72 . remove drag back to left palette to delete. 2013-11-15 17:46:57 +00:00
Dave C-J
3c32186a9d force parsexml node to be async (just in case) 2013-11-15 09:15:26 +00:00
Dave C-J
f2b7fada9d Tweaks to email and imap info now that the relative paths no longer make sense. 2013-11-15 08:59:55 +00:00
Dave C-J
c17687e5db allow twitter multipe sech terms... space for AND and , for OR. (not sure why I had knobbled it in first place...) 2013-11-15 08:59:06 +00:00
Nicholas O'Leary
e700a11647 Bump version: 0.4.0 2013-11-14 20:45:34 +00:00
Nicholas O'Leary
48dabffefc Move all nodes into core subdirectory
This makes it easier to distinguish core nodes from those added later
2013-11-14 15:52:19 +00:00
Nicholas O'Leary
affcc8ae65 Update all core nodes to use NODE_RED_HOME rather than relative paths 2013-11-14 15:44:54 +00:00
Nicholas O'Leary
82b863805d Move Debug ws endpoint to /debug/ws
fixes #61
2013-11-14 14:40:34 +00:00
Nicholas O'Leary
d2208fae83 Collapse TCP nodes into single pair of files 2013-11-14 14:39:26 +00:00
Nicholas O'Leary
2a5f4abd49 Document msg.tweet property of Twitter In node 2013-11-14 14:38:59 +00:00
Dave C-J
11523a6ced duplicate http get or post params into msg.payload to make life easier for debug etc. other properties remain as-is 2013-11-14 13:25:12 +00:00
Nicholas O'Leary
510fab7b8f IMAP node should not try to load settings file directly 2013-11-13 22:20:36 +00:00
Nicholas O'Leary
3a52397744 Add nodesDir property to all nodes files to exist elsewhere 2013-11-13 17:02:29 +00:00
Nicholas O'Leary
851c2ab089 Merge branch 'master' of github.com:node-red/node-red 2013-11-13 15:42:58 +00:00
Nicholas O'Leary
e4353a22bc Add some more comments to settings.js 2013-11-13 15:42:38 +00:00
Nick O'Leary
5b69dfb2f2 Merge pull request #70 from hardillb/master
Add Random delay to Delay node
2013-11-13 07:37:37 -08:00
Ben Hardill
ca72e187f9 and another typo 2013-11-13 15:36:54 +00:00
Ben Hardill
e2a532434e Added missing () to validator 2013-11-13 15:25:29 +00:00
Nicholas O'Leary
8426c9802b Allow unlimited event listeners on mqttConnectionPool
Fixes #71
2013-11-13 15:00:55 +00:00
Nicholas O'Leary
848a69dc26 Make storage.init return a promise to async initialisation
part of #62
2013-11-12 17:13:06 +00:00
Nicholas O'Leary
1536dcdf1e Add httpStatic setting 2013-11-11 21:16:57 +00:00
Nicholas O'Leary
07a5d3626e Typo in HTTP Request Node prevents POSTs
fixes #68
2013-11-11 14:25:50 +00:00
Nicholas O'Leary
29734dd994 Add unit test for localfilestorage
Stage 2 of #62
2013-11-10 22:19:01 +00:00
Ben Hardill
f3a84eacf3 Added some limits to the spinners, and updated the image 2013-11-10 19:49:16 +00:00
Ben Hardill
e9a64f7bdf Added random delay to delay node 2013-11-10 17:25:55 +00:00
Nicholas O'Leary
89dc15567d Remove stray setting from testing 2013-11-10 15:15:37 +00:00
Nicholas O'Leary
95bef6b6ca Abstract all file-system operations
Stage 1 of  #62
2013-11-10 00:05:58 +00:00
Nicholas O'Leary
22f46a4317 Inject Node: Interval between times not sticking
fixes #67
2013-11-09 17:07:57 +00:00
Nicholas O'Leary
a2e77471a9 Add settings.uiHost - to allow binding to a specific interface
Closes #58
2013-11-08 14:50:54 +00:00
Nicholas O'Leary
cb6fbf29a8 Palette popover defined title twice
fixes #64
2013-11-07 16:08:35 +00:00
Nicholas O'Leary
3b49c85a8e Remove extra slash in Debug WS connection url and respect http/https
Fixes #59,#60
2013-11-06 21:33:25 +00:00
Nicholas O'Leary
8f71ee4631 Unable to pass custom headers to HTTP Response node
fixes #54
2013-11-06 21:08:00 +00:00
Dave C-J
25596b06b1 Add json2xml node - to complement the xml2json one... 2013-11-04 20:29:33 +00:00
Dave C-J
d9ed5b46c4 Various little tweaks - less console.log more util.log, add exra try/catch to serial, add wiring-pi url to Pi "error message". 2013-11-03 19:10:35 +00:00
Dave Conway-Jones
2e92b9a120 Added sentiment and irc to base dependencies 2013-11-02 16:03:32 +00:00
Dave C-J
09348eb353 Improve IRC UI options to Fix Issue #53. Now a three way select - either send whole msg object to channel, just msg.payload to channel or msg.payload to users specified in msg.topic 2013-11-02 12:12:47 +00:00
321 changed files with 33301 additions and 19052 deletions

7
.gitignore vendored
View File

@@ -1,4 +1,11 @@
node_modules
credentials.json
flows*.json
*.backup
*_cred*
nodes/node-red-nodes/
.npm
/coverage
.config.json
.sessions.json

View File

@@ -1,3 +1,4 @@
.git/*
*.json
lib/*
*.backup

11
.travis.yml Normal file
View File

@@ -0,0 +1,11 @@
language: node_js
before_install:
- npm install -g npm@~1.4.18
node_js:
- "0.10"
- "0.8"
script:
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage
before_script:
- npm install -g istanbul
- npm install coveralls

71
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,71 @@
# Contributing to Node-RED
We welcome contributions, but request you follow these guidelines.
- [Raising issues](#raising-issues)
- [Feature requests](#feature-requests)
- [Pull-Requests](#pull-requests)
- [Contributor License Agreement](#contributor-license-agreement)
## Raising issues
Please raise any bug reports on the project's
[issue tracker](https://github.com/node-red/node-red/issues?state=open). Be sure to
search the list to see if your issue has already been raised.
A good bug report is one that make it easy for us to understand what you were
trying to do and what went wrong.
Provide as much context as possible so we can try to recreate the issue.
If possible, include the relevant part of your flow. To do this, select the
relevant nodes, press Ctrl-E and copy the flow data from the Export dialog.
At a minimum, please include:
- Version of Node-RED - either release number if you downloaded a zip, or the first few lines of `git log` if you are cloning the repository directly.
- Version of node.js - what does `node -v` say?
## Feature requests
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red).
## Pull-Requests
If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it may well get rejected if you haven't discussed it on
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
### Contributor License Agreement
In order for us to accept pull-requests, the contributor must first complete
a Contributor License Agreement (CLA). This clarifies the intellectual
property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not
change your rights to use your own Contributions for any other purpose.
You can download the CLAs here:
- [individual](http://nodered.org/cla/node-red-cla-individual.pdf)
- [corporate](http://nodered.org/cla/node-red-cla-corporate.pdf)
If you are an IBMer, please contact us directly as the contribution process is
slightly different.
### Coding standards
Please ensure you follow the coding standards used through-out the existing
code base. Some basic rules include:
- all files must have the Apache license in the header.
- indent with 4-spaces, no tabs. No arguments.
- opening brace on same line as `if`/`for`/`function`/etc, closing brace on its
own line.

95
Gruntfile.js Normal file
View File

@@ -0,0 +1,95 @@
/**
* Copyright 2013, 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
simplemocha: {
options: {
globals: ['expect'],
timeout: 3000,
ignoreLeaks: false,
ui: 'bdd',
reporter: 'spec'
},
all: { src: ['test/**/*_spec.js'] },
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]}
},
jshint: {
options: {
// http://www.jshint.com/docs/options/
"asi": true, // allow missing semicolons
"curly": true, // require braces
"eqnull": true, // ignore ==null
"forin": true, // require property filtering in "for in" loops
"immed": true, // require immediate functions to be wrapped in ( )
"nonbsp": true, // warn on unexpected whitespace breaking chars
//"strict": true, // commented out for now as it causes 100s of warnings, but want to get there eventually
"loopfunc": true, // allow functions to be defined in loops
"sub": true // don't warn that foo['bar'] should be written as foo.bar
},
all: [
'Gruntfile.js',
'red.js',
'red/**/*.js',
'nodes/**/*.js',
'public/red/**/*.js'
],
core: {
files: {
src: [
'Gruntfile.js',
'red.js',
'red/**/*.js'
]
}
},
nodes: {
files: {
src: [ 'nodes/**/*.js' ]
}
},
editor: {
files: {
src: [ 'public/red/**/*.js' ]
}
},
tests: {
files: {
src: ['test/**/*.js']
},
options: {
"expr": true
}
}
}
});
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['test-core','test-editor','test-nodes']);
grunt.registerTask('test-core', ['jshint:core','simplemocha:core']);
grunt.registerTask('test-editor', ['jshint:editor']);
grunt.registerTask('test-nodes', ['simplemocha:nodes']);
};

View File

@@ -1,5 +1,10 @@
# Node-RED
http://nodered.org
[![Build Status](https://travis-ci.org/node-red/node-red.png)](https://travis-ci.org/node-red/node-red) [![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.png?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
A visual tool for wiring the Internet of Things.
![Screenshot](http://nodered.org/images/node-red-screenshot.png "Node-RED: A visual tool for wiring the Internet of Things")
@@ -14,70 +19,32 @@ Check out [INSTALL](INSTALL.md) for full instructions on getting started.
4. node red.js
5. Open <http://localhost:1880>
## Documentation
## Getting Help
More documentation can be found [here](http://nodered.org/docs).
For further help, or general discussion, there is also a [mailing list](https://groups.google.com/forum/#!forum/node-red).
For further help, or general discussion, please use the [mailing list](https://groups.google.com/forum/#!forum/node-red).
## Browser Support
The Node-RED editor runs in the browser. We routinely develop and test using
Chrome and Firefox. We have anecdotal evidence that it works in IE9.
Chrome and Firefox. We have anecdotal evidence that it works in recent versions of IE.
We do not yet support mobile browsers, although that is high on our priority
list.
We have basic support for using in mobile/tablet browsers.
## Contributing
### Reporting issues
Please raise any bug reports on the project's [issue tracker](https://github.com/node-red/node-red/issues?state=open).
Be sure to search the list to see if your issue has already been raised.
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red)
first.
### Creating new nodes
The plugin nature of Node-RED means anyone can create a new node to extend
its capabilities.
We want to avoid duplication as that can lead to confusion. Many of our existing
nodes offer a starting point of functionality. If they are missing features,
we would rather extend them than add separate 'advanced' versions. But the key
to that approach is getting the UX right to not lose the simplicity.
We are also going to be quite selective over what nodes are included in the main
repository - enough to be useful, but not so many that new user is overwhelmed.
To contribute a new node, please raise a pull-request against the
`node-red-nodes` repository.
Eventually, the nodes will be npm-installable, but we're not there yet. We'll
also have some sort of registry of nodes to help with discoverability.
### Pull-Requests
In order for us to accept pull-requests, the contributor must first complete
a Contributor License Agreement (CLA). This clarifies the intellectual
property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not
change your rights to use your own Contributions for any other purpose.
Once you have created a pull-request, we'll provide a link to the appropriate
CLA document.
If you are an IBMer, please contact us directly as the contribution process is
slightly different.
Before raising a pull-request, please read our [contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
## Authors
Node-RED is a creation of the IBM Emerging Technology Services team.
Node-RED is a creation of [IBM Emerging Technology](http://ibm.com/blogs/et).
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
For more open-source projects from IBM, head over [here](http://ibm.github.io).
## Copyright and license
Copyright 2013 IBM Corp. under [the Apache 2.0 license](LICENSE).
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).

43
bin/node-red-pi Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
#
# Copyright 2015 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Separate out node/v8 options from node-red ones
OPTIONS=""
ARGS=""
for arg in "$@"
do
case $arg in
--userDir|--settings|--help) ARGS="$ARGS $arg";;
--*) OPTIONS="$OPTIONS $arg";;
*) ARGS="$ARGS $arg";;
esac
done
# Find the real location of this script
CURRENT_PATH=`pwd`
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
cd "`dirname "${SCRIPT_PATH}"`"
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
cd $CURRENT_PATH
# Run Node-RED
/usr/bin/env node $OPTIONS $SCRIPT_PATH/../red.js $ARGS

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
<!-- Sample html file that corresponds to the 99-sample.js file -->
<!-- This creates and configures the onscreen elements of the node -->
<!-- If you use this as a template, replace IBM Corp. with your own name. -->
<!-- If you use this as a template, update the copyright with your own name. -->
<!-- First, the content of the edit dialog is defined. -->
@@ -28,16 +28,17 @@
<!-- Generally, there should be an input for each property of the node. -->
<!-- The for and id attributes identify the corresponding property -->
<!-- (with the 'node-input-' prefix). -->
<!-- The available icon classes are defined in Twitter Bootstrap -->
<!-- The available icon classes are defined Twitter Bootstrap glyphicons -->
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<br/>
<!-- By convention, most nodes have a 'name' property. The following div -->
<!-- provides the necessary field. -->
<!-- provides the necessary field. Should always be the last option -->
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
@@ -64,10 +65,11 @@
name: {value:""}, // along with default values.
topic: {value:"", required:true}
},
inputs:0, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
icon: "arrow-in.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
// set the icon (held in icons dir below where you save the node)
icon: "myicon.png", // saved in icons/myicon.png
label: function() { // sets the default label contents
return this.name||this.topic||"sample";
},
labelStyle: function() { // sets the class to apply to the label

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,39 +14,55 @@
* limitations under the License.
**/
// If you use this as a template, replace IBM Corp. with your own name.
// If you use this as a template, update the copyright with your own name.
// Sample Node-RED node file
// Require main module
var RED = require(process.env.NODE_RED_HOME+"/red/red");
// The main node definition - most things happen in here
function SampleNode(n) {
// Create a RED node
RED.nodes.createNode(this,n);
module.exports = function(RED) {
"use strict";
// require any external libraries we may need....
//var foo = require("foo-library");
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic;
// The main node definition - most things happen in here
function SampleNode(n) {
// Create a RED node
RED.nodes.createNode(this,n);
// Do whatever you need to do in here - declare callbacks etc
// Note: this sample doesn't do anything much - it will only send
// this message once at startup...
// Look at other real nodes for some better ideas of what to do....
var msg = {};
msg.topic = this.topic;
msg.payload = "Hello world !"
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic;
// send out the message to the rest of the workspace.
this.send(msg);
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
// Do whatever you need to do in here - declare callbacks etc
// Note: this sample doesn't do anything much - it will only send
// this message once at startup...
// Look at other real nodes for some better ideas of what to do....
var msg = {};
msg.topic = this.topic;
msg.payload = "Hello world !"
// send out the message to the rest of the workspace.
// ... this message will get sent at startup so you may not see it in a debug node.
this.send(msg);
// respond to inputs....
this.on('input', function (msg) {
node.warn("I saw a payload: "+msg.payload);
// in this example just send it straight on... should process it here really
node.send(msg);
});
this.on("close", function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: node.client.disconnect();
});
}
// Register the node by name. This must be called before overriding any of the
// Node functions.
RED.nodes.registerType("sample",SampleNode);
this.on("close", function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: this.client.disconnect();
});
}
// Register the node by name. This must be called before overriding any of the
// Node functions.
RED.nodes.registerType("sample",SampleNode);

View File

@@ -1,53 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="xml2js">
<div class="form-row">
<label>Use Console</label>
<input type="checkbox" id="node-input-useEyes" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useEyes" style="width: 70%;">Debug output in console ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Uses xml2js to process xml into javascript object.</div>
</script>
<script type="text/x-red" data-help-name="xml2js">
<p>A function that parses the <b>msg.payload</b> using the xml2js library. Places the result in the payload.</p>
<p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('xml2js',{
category: 'advanced-function',
color:"#E6E0F8",
defaults: {
useEyes: {value:false},
name: {value:""},
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"xml2js";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,49 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require("../../red/red");
var util = require("util");
var parseString = require('xml2js').parseString;
var gotEyes = false;
try {
var eyes = require("eyes");
gotEyes = true;
} catch(e) {
util.log("[73-parsexml.js] Note: Module 'eyes' not installed. (not needed, but useful)");
}
function Xml2jsNode(n) {
RED.nodes.createNode(this,n);
this.useEyes = n.useEyes;
var node = this;
this.on("input", function(msg) {
try {
parseString(msg.payload, function (err, result) {
if (err) { node.error(err); }
else {
msg.payload = result;
node.send(msg);
if (node.useEyes == true) {
if (gotEyes == true) { eyes.inspect(msg); }
else { node.log(JSON.stringify(msg)); }
}
}
});
}
catch(e) { console.log(e); }
});
}
RED.nodes.registerType("xml2js",Xml2jsNode);

View File

@@ -1,371 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="inject">
<div class="form-row node-input-payload">
<label for="node-input-payload"><i class="icon-envelope"></i> Payload</label>
<input type="text" id="node-input-payload" placeholder="Payload">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-once" placeholder="once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
</div>
<div class="form-row">
<label for=""><i class="icon-repeat"></i> Repeat</label>
<select id="inject-time-type-select"><option value="none">None</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
<input type="hidden" id="node-input-repeat" placeholder="Payload">
<input type="hidden" id="node-input-crontab" placeholder="Payload">
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
on <select disabled id="inject-time-interval-days" class="inject-time-days"></select>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
every <input id="inject-time-interval-time-units" class="inject-time-count" value="1"></input> minutes<br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
on <select id="inject-time-interval-time-days" class="inject-time-days"></select>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/>
on <select id="inject-time-time-days" class="inject-time-days"></select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: Injects Date.now() if no payload set</div>
<script>
{
$("#inject-time-type-select").change(function() {
var id = $("#inject-time-type-select option:selected").val();
$(".inject-time-row").hide();
$("#inject-time-row-"+id).show();
});
var days = [
{v:"*",t:"every day"},
{v:"1-5",t:"Mondays to Fridays"},
{v:"6-7",t:"Saturdays and Sundays"},
{v:"1",t:"Mondays"},
{v:"2",t:"Tuesdays"},
{v:"3",t:"Wednesdays"},
{v:"4",t:"Thursdays"},
{v:"5",t:"Fridays"},
{v:"6",t:"Saturdays"},
{v:"7",t:"Sundays"}
];
$(".inject-time-days").each(function() {
for (var d in days) {
$(this).append($("<option></option>").val(days[d].v).text(days[d].t));
}
});
$(".inject-time-times").each(function() {
for (var i=0;i<24;i++) {
var l = (i<10?"0":"")+i+":00";
$(this).append($("<option></option>").val(i).text(l));
}
});
$(".inject-time-count").spinner({
min:1,
max:60
});
$("#inject-time-interval-units").change(function() {
var units = $("#inject-time-interval-units option:selected").val();
$("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false);
$(".inject-time-count").spinner("option","max",(units == "h")?24:60);
});
$.widget( "ui.injecttimespinner", $.ui.spinner, {
options: {
// seconds
step: 60 * 1000,
// hours
page: 60,
min:0,
max:(23*60+59)*60*1000
},
_parse: function( value ) {
if ( typeof value === "string" ) {
// already a timestamp
if ( Number( value ) == value ) {
return Number( value );
}
var p = value.split(":");
return ((p[0]*60)+Number(p[1]))*60*1000;
}
return value;
},
_format: function( value ) {
var d = new Date(value);
var h = d.getHours();
var m = d.getMinutes();
return ((h<10)?"0":"")+h+":"+((m<10)?"0":"")+m;
}
});
$("#inject-time-time").injecttimespinner();
};
</script>
</script>
<style>
.inject-time-row {
padding-left: 110px;
}
.inject-time-row select {
margin: 3px 0;
}
.inject-time-days {
width: 225px;
}
.inject-time-times {
width: 90px;
}
.inject-time-row > .ui-spinner {
height: 28px;
margin: 3px 0;
border-color: rgb(204, 204, 204);
}
#inject-time-time {
margin-top: 3px;
width: 75px;
}
.inject-time-count {
width: 30px !important;
}
.
</style>
<script type="text/x-red" data-help-name="inject">
<p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p>
<p>If no payload is specified the payload is set to the current time in millisecs since 1970. This allows subsequent functions to perform time based actions.</p>
<p>The repeat function does what it says on the tin and continuously sends the payload every x seconds.</p>
<p>The Fire once at start option actually waits 50mS before firing to give other nodes a chance to instantiate properly.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('inject',{
category: 'input',
color:"#a6bbcf",
defaults: {
name: {value:""},
topic: {value:""},
payload: {value:""},
repeat: {value:""},
crontab: {value:""},
once: {value:false}
},
inputs:0,
outputs:1,
icon: "inject.png",
label: function() {
return this.name||this.topic||this.payload||"inject";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var repeattype = "none";
if (this.repeat != "") {
repeattype = "interval";
$("#inject-time-interval-units option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
$("#inject-time-interval-count").val(this.repeat);
$("#inject-time-interval-days").prop("disabled","disabled");
} else if (this.crontab) {
var cronparts = this.crontab.split(" ");
var days = cronparts[4];
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = "time";
// Fixed time
var time = cronparts[1]+":"+cronparts[0];
$("#inject-time-time").val(time);
$("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
$("#inject-time-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
} else if (cronparts[0] == "0") {
// interval - hours
var hours = cronparts[1].slice(2);
repeattype = "interval";
$("#inject-time-interval-days").prop("disabled",false);
$("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
$("#inject-time-interval-count").val(hours)
$("#inject-time-interval-units option").filter(function() {return $(this).val() == "h";}).attr('selected',true);
} else if (cronparts[1] == "*") {
// interval - minutes
var minutes = cronparts[0].slice(2);
repeattype = "interval";
$("#inject-time-interval-days").prop("disabled",false);
$("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
$("#inject-time-interval-count").val(minutes)
$("#inject-time-interval-units option").filter(function() {return $(this).val() == "m";}).attr('selected',true);
} else {
repeattype = "interval-time";
// interval - time period
var minutes = cronparts[0].slice(2);
$("#inject-time-interval-time-units").val(minutes);
$("#inject-time-interval-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
var time = cronparts[1];
var timeparts = time.split(",");
var start;
var end;
if (timeparts.length == 1) {
// 0 or 0-10
var hours = timeparts[0].split("-");
if (hours.length == 1) {
start = hours[0];
end = Number(hours[0])+1;
} else {
start = hours[0];
end = (Number(hours[1])+1)%24;
}
} else {
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
var startparts = timeparts[0].split("-");
start = startparts[0];
var endparts = timeparts[1].split("-");
if (endparts.length == 1) {
end = Number(endparts[0])+1;
} else {
end = Number(endparts[1])+1;
}
}
$("#inject-time-interval-time-start option").filter(function() {return $(this).val() == start;}).attr('selected',true);
$("#inject-time-interval-time-end option").filter(function() {return $(this).val() == end;}).attr('selected',true);
}
} else {
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
}
$(".inject-time-row").hide();
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
$("#inject-time-row-"+repeattype).show();
},
oneditsave: function() {
var repeat = "";
var crontab = "";
var type = $("#inject-time-type-select option:selected").val();
if (type == "none") {
// nothing
} else if (type == "interval") {
var count = $("#inject-time-interval-count").val();
var units = $("#inject-time-interval-units option:selected").val();
var days = $("#inject-time-interval-days option:selected").val();
if (units == "s") {
repeat = count;
} else {
if (units == "m") {
crontab = "*/"+count+" * * * "+days;
} else if (units == "h") {
crontab = "0 */"+count+" * * "+days;
}
}
} else if (type == "interval-time") {
var count = $("#inject-time-interval-time-units").val();
var startTime = Number($("#inject-time-interval-time-start option:selected").val());
var endTime = Number($("#inject-time-interval-time-end option:selected").val());
var days = $("#inject-time-interval-time-days option:selected").val();
var timerange = "";
if (startTime == endTime) {
//TODO: invalid
repeat = 0;
crontab = "";
} else if (endTime == 0) {
timerange = startTime+"-23";
} else if (startTime+1 < endTime) {
timerange = startTime+"-"+(endTime-1);
} else if (startTime+1 == endTime) {
timerange = startTime;
} else {
var startpart = "";
var endpart = "";
if (startTime == 23) {
startpart = "23";
} else {
startpart = startTime+"-23";
}
if (endTime == 1) {
endpart = "0";
} else {
endpart = "0-"+(endTime-1);
}
timerange = startpart+","+endpart;
}
repeat = 0;
crontab = "*/"+count+" "+timerange+" * * "+days;
} else if (type == "time") {
var time = $("#inject-time-time").val();
var days = $("#inject-time-time-days option:selected").val();
var parts = time.split(":");
repeat = 0;
crontab = parts[1]+" "+parts[0]+" * * "+days;
}
$("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab);
},
button: {
onclick: function() {
var label = this.name||this.payload;
d3.xhr("inject/"+this.id).post(function(err,resp) {
if (err) {
if (err.status == 404) {
RED.notify("<strong>Error</strong>: inject node not deployed","error");
} else if (err.status == 500) {
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
} else if (err.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
}
} else if (resp.status == 200) {
RED.notify("Successfully injected: "+label,"success");
} else {
RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
}
});
}
}
});
</script>

View File

@@ -1,93 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require("../../red/red");
try {
var cron = require("cron");
} catch(err) {
require("util").log("[inject] Warning: cannot find module 'cron'");
}
function InjectNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.payload = n.payload;
this.repeat = n.repeat;
this.crontab = n.crontab;
this.once = n.once;
var node = this;
this.interval_id = null;
this.cronjob = null;
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000;
this.log("repeat = "+this.repeat);
this.interval_id = setInterval( function() {
node.emit("input",{});
}, this.repeat );
} else if (this.crontab) {
if (cron) {
this.log("crontab = "+this.crontab);
this.cronjob = new cron.CronJob(this.crontab,
function() {
node.emit("input",{});
},
null,true);
} else {
this.error("'cron' module not found");
}
}
if (this.once) {
setTimeout( function(){ node.emit("input",{}); }, 50);
}
this.on("input",function(msg) {
var msg = {topic:this.topic,payload:this.payload};
if (msg.payload == "") { msg.payload = Date.now(); }
this.send(msg);
msg = null;
});
}
RED.nodes.registerType("inject",InjectNode);
InjectNode.prototype.close = function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
this.log("inject: repeat stopped");
} else if (this.cronjob != null) {
this.cronjob.stop();
this.log("inject: cronjob stopped");
delete this.cronjob;
}
}
RED.app.post("/inject/:id", function(req,res) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {
try {
node.receive();
res.send(200);
} catch(err) {
res.send(500);
node.error("Inject failed:"+err);
console.log(err.stack);
}
} else {
res.send(404);
}
});

View File

@@ -1,247 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="debug">
<div class="form-row">
<label for="node-input-complete"><i class="icon-list"></i> Output</label>
<select type="text" id="node-input-complete" style="display: inline-block; width: auto; vertical-align: top;">
<option value=false>Payload only</option>
<option value=true>Complete msg object</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="debug">
<p>The Debug node can be connected to the output of any node. It will display the timestamp, <b>msg.topic</b> and <b>msg.payload</b> fields of any messages it receives in the debug tab of the sidebar.
<br/>The sidebar can be accessed under the options drop-down in the top right corner.</p>
<p>The button to the right of the node will toggle it's output on and off so you can de-clutter the debug window.</p>
<p>If the payload is an object it will be stringified first for display and indicate that by saying "(Object) ".</p>
<p>If the payload is a buffer it will be stringified first for display and indicate that by saying "(Buffer) ".</p>
<p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
<p>Optionally can show the complete msg object - but the screen can get messy.</p>
<p>In addition any calls to node.warn or node.error will appear here.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('debug',{
category: 'output',
defaults: {
name: {value:""},
active: {value:true},
complete: {value:false}
},
label: function() {
return this.name||"debug";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
color:"#87a980",
inputs:1,
outputs:0,
icon: "debug.png",
align: "right",
button: {
toggle: "active",
onclick: function() {
var label = this.name||"debug";
d3.xhr("debug/"+this.id+"/"+(this.active?"enable":"disable")).post(function(err,resp) {
if (err) {
if (err.status == 404) {
RED.notify("<strong>Error</strong>: debug node not deployed","error");
} else if (err.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
}
} else if (resp.status == 200) {
RED.notify("Successfully activated: "+label,"success");
} else if (resp.status == 201) {
RED.notify("Successfully deactivated: "+label,"success");
} else {
RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
}
});
}
}
});
var a = function() {
var content = document.createElement("div");
content.id = "tab-debug";
var toolbar = document.createElement("div");
toolbar.id = "debug-toolbar";
content.appendChild(toolbar);
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="icon-trash"></i></a></div> ';
var messages = document.createElement("div");
messages.id = "debug-content";
content.appendChild(messages);
RED.sidebar.addTab("debug",content);
function getTimestamp() {
var d = new Date();
return d.toUTCString().substring(5,25)+"."+d.getMilliseconds();
}
var sbc = document.getElementById("debug-content");
var errornotification = null;
var messageCount = 0;
function debugConnect() {
//console.log("debug ws connecting");
var ws = new WebSocket("ws://"+location.hostname+":"+location.port+document.location.pathname+"/debug");
ws.onopen = function() {
if (errornotification) {
errornotification.close();
errornotification = null;
}
//console.log("debug ws connected");
}
ws.onmessage = function(event) {
var o = JSON.parse(event.data);
//console.log(msg);
var msg = document.createElement("div");
msg.onmouseover = function() {
msg.style.borderRightColor = "#999";
RED.nodes.eachNode(function(node) {
if( node.id == o.id) {
node.highlighted = true;
node.dirty = true;
}
});
RED.view.redraw();
};
msg.onmouseout = function() {
msg.style.borderRightColor = "";
RED.nodes.eachNode(function(node) {
if( node.id == o.id) {
node.highlighted = false;
node.dirty = true;
}
});
RED.view.redraw();
};
var name = (o.name?o.name:o.id).toString().replace(/</g,"&lt;").replace(/>/g,"&gt;");
var topic = (o.topic||"").toString().replace(/</g,"&lt;").replace(/>/g,"&gt;");
var payload = (o.msg||"").toString().replace(/</g,"&lt;").replace(/>/g,"&gt;");
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'')
msg.innerHTML = '<span class="debug-message-date">'+getTimestamp()+'</span>'+
'<span class="debug-message-name">['+name+']</span>'+
(o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+
'<span class="debug-message-payload">'+payload+'</span>';
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
messageCount++;
$(messages).append(msg);
if (messageCount > 200) {
$("#debug-content .debug-message:first").remove();
messageCount--;
}
if (atBottom) {
$(sbc).scrollTop(sbc.scrollHeight);
}
};
ws.onclose = function() {
if (errornotification == null) {
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true);
}
setTimeout(debugConnect,1000);
}
}
debugConnect();
$("#debug-tab-clear").click(function() {
$(".debug-message").remove();
messageCount = 0;
RED.nodes.eachNode(function(node) {
node.highlighted = false;
node.dirty = true;
});
RED.view.redraw();
});
}();
</script>
<style>
#debug-content {
position: absolute;
top: 30px;
bottom: 0px;
left:0px;
right: 0px;
overflow-y: scroll;
}
#debug-toolbar {
padding: 3px 10px;
height: 24px;
background: #f3f3f3;
}
.debug-message {
cursor: pointer;
border-bottom: 1px solid #eee;
border-left: 8px solid #eee;
border-right: 8px solid #eee;
padding: 2px;
}
.debug-message-date {
background: #fff;
font-size: 9px;
color: #aaa;
padding: 1px 5px 1px 1px;
}
.debug-message-topic {
display: block;
background: #fff;
padding: 1px 5px;
font-size: 9px;
color: #caa;
}
.debug-message-name {
background: #fff;
padding: 1px 5px;
font-size: 9px;
color: #aac;
}
.debug-message-payload {
display: block;
padding: 2px;
background: #fff;
}
.debug-message-level-log {
border-left-color: #eee;
border-right-color: #eee;
}
.debug-message-level-warn {
border-left-color: #ffdf9d;
border-right-color: #ffdf9d;
}
.debug-message-level-error {
border-left-color: #f99;
border-right-color: #f99;
}
</style>

View File

@@ -1,119 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require("../../red/red");
var util = require("util");
var ws = require('ws');
var events = require("events");
var debuglength = RED.settings.debugMaxLength||1000;
function DebugNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.complete = n.complete;
this.active = (n.active == null)||n.active;
this.on("input",function(msg) {
if (this.active) {
if (msg.payload instanceof Buffer) {
msg.payload = "(Buffer) "+msg.payload.toString();
}
if (this.complete == "true") {
DebugNode.send({id:this.id,name:this.name,topic:msg.topic,msg:msg,_path:msg._path});
} else {
if (typeof msg.payload !== "undefined") {
DebugNode.send({id:this.id,name:this.name,topic:msg.topic,msg:msg.payload,_path:msg._path});
}
}
}
});
}
RED.nodes.registerType("debug",DebugNode);
DebugNode.send = function(msg) {
if (msg.msg instanceof Error) {
msg.msg = msg.msg.toString();
}
else if (typeof msg.msg === 'object') {
var seen = [];
msg.msg = "(Object) " + JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.indexOf(value) !== -1) { return "[circular]"; }
seen.push(value);
}
return value;
}," ");
seen = null;
}
else if (typeof msg.msg === "boolean") msg.msg = "(boolean) "+msg.msg.toString();
else if (msg.msg === 0) msg.msg = "0";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substr(0,debuglength) +" ....";
}
for (var i in DebugNode.activeConnections) {
var ws = DebugNode.activeConnections[i];
try {
var p = JSON.stringify(msg);
ws.send(p);
} catch(err) {
util.log("[debug] ws error : "+err);
}
}
}
DebugNode.activeConnections = [];
DebugNode.wsServer = new ws.Server({server:RED.server});
DebugNode.wsServer.on('connection',function(ws) {
DebugNode.activeConnections.push(ws);
ws.on('close',function() {
for (var i in DebugNode.activeConnections) {
if (DebugNode.activeConnections[i] === ws) {
DebugNode.activeConnections.splice(i,1);
break;
}
}
});
});
DebugNode.logHandler = new events.EventEmitter();
DebugNode.logHandler.on("log",function(msg) {
if (msg.level == "warn" || msg.level == "error") {
DebugNode.send(msg);
}
});
RED.nodes.addLogHandler(DebugNode.logHandler);
RED.app.post("/debug/:id/:state", function(req,res) {
var node = RED.nodes.getNode(req.params.id);
var state = req.params.state;
if (node != null) {
if (state === "enable") {
node.active = true;
res.send(201);
} else if (state === "disable") {
node.active = false;
res.send(200);
} else {
res.send(404);
}
} else {
res.send(404);
}
});

View File

@@ -1,75 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require("../../red/red");
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
function ExecNode(n) {
RED.nodes.createNode(this,n);
this.cmd = n.command;
this.append = n.append || "";
this.useSpawn = n.useSpawn;
var node = this;
this.on("input", function(msg) {
if (msg != null) {
if (this.useSpawn == true) {
// make the extra args into an array
// then prepend with the msg.payload
var arg = node.append.split(",");
if (msg.payload != " ") { arg.unshift(msg.payload); }
//console.log(arg);
var ex = spawn(node.cmd,arg);
ex.stdout.on('data', function (data) {
//console.log('[exec] stdout: ' + data);
msg.payload = data;
node.send([msg,null,null]);
});
ex.stderr.on('data', function (data) {
//console.log('[exec] stderr: ' + data);
msg.payload = data;
node.send([null,msg,null]);
});
ex.on('close', function (code) {
//console.log('[exec] result: ' + code);
msg.payload = code;
node.send([null,null,msg]);
});
}
else {
var cl = node.cmd+" "+msg.payload+" "+node.append;
var child = exec(cl, function (error, stdout, stderr) {
msg.payload = stdout;
var msg2 = {payload:stderr};
//console.log('[exec] stdout: ' + stdout);
//console.log('[exec] stderr: ' + stderr);
if (error !== null) {
var msg3 = {payload:error};
//console.log('[exec] error: ' + error);
}
node.send([msg,msg2,msg3]);
});
}
}
});
}
RED.nodes.registerType("exec",ExecNode);

View File

@@ -1,71 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require("../../red/red");
var util = require("util");
var vm = require("vm");
var fs = require('fs');
var fspath = require('path');
function FunctionNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.func = n.func;
var functionText = "var results = (function(msg){"+this.func+"})(msg);";
this.topic = n.topic;
this.context = {global:RED.settings.functionGlobalContext || {}};
try {
this.script = vm.createScript(functionText);
this.on("input", function(msg) {
if (msg != null) {
var sandbox = {msg:msg,console:console,util:util,Buffer:Buffer,context:this.context};
try {
this.script.runInNewContext(sandbox);
var results = sandbox.results;
if (results == null) {
results = [];
} else if (results.length == null) {
results = [results];
}
if (msg._topic) {
for (var m in results) {
if (results[m]) {
if (util.isArray(results[m])) {
for (var n in results[m]) {
results[m][n]._topic = msg._topic;
}
} else {
results[m]._topic = msg._topic;
}
}
}
}
this.send(results);
} catch(err) {
this.error(err.message);
}
}
});
} catch(err) {
this.error(err.message);
}
}
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");

View File

@@ -1,41 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require("../../red/red");
var mustache = require("mustache");
var util = require("util");
var fs = require('fs');
function TemplateNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.template = n.template;
this.on("input", function(msg) {
if (msg != null) {
try {
msg.payload = mustache.render(this.template,msg)
this.send(msg);
} catch(err) {
this.error(err.message);
}
}
});
}
RED.nodes.registerType("template",TemplateNode);
RED.library.register("templates");

View File

@@ -1,117 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="delay">
<div class="form-row">
<label for="node-input-pauseType"><i class="icon-tasks"></i> Action</label>
<select id="node-input-pauseType" style="width:270px !important">
<option value="delay">Delay message</option>
<option value="rate">Limit message rate</option>
</select>
</div>
<div id="delay-details" class="form-row">
<label for="node-input-timeout"><i class="icon-time"></i> For</label>
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
<select id="node-input-timeoutUnits" style="width:200px !important">
<option value="milliseconds">Milliseconds</option>
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
</select>
</div>
<div id="rate-details" class="form-row">
<label for="node-input-rate"><i class="icon-time"></i> To</label>
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:40px !important">
<label for="node-input-rateUnits" style="width:120px !important">message(s) per</label>
<select id="node-input-rateUnits" style="width:140px !important">
<option value="second">Second</option>
<option value="minute">Minute</option>
<option value="hour">Hour</option>
<option value="day">Day</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="delay">
<p>Introduces a delay into a flow or rate limits messages</p>
<p>Default delay is 5 seconds or rate limit of 1 message/second, but both can be configured</p>
<p>The rate limiter will delay messages by buffering in a time released queue. A warning is generated if the buffer is larger than 1000 items.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('delay',{
category: 'function',
color:"#E6E0F8",
defaults: {
name: {value:""},
pauseType: {value:"delay", required:true},
timeout: {value:"5", required:true, validate:RED.validators.number()},
timeoutUnits: {value:"seconds"},
rate: {value:"1", required:true, validate:RED.validators.number()},
rateUnits: {value: "second"}
},
inputs:1,
outputs:1,
icon: "timer.png",
label: function() {
if (this.pauseType == "delay") {
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
return this.name||"delay "+this.timeout+" " + units;
} else if (this.pauseType == "rate") {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name||"limit "+this.rate+" msg/"+ units;
}
return "";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-timeout" ).spinner({ min:1, max:60 });
$( "#node-input-rate" ).spinner({ min:1});
if (this.pauseType == "delay") {
$("#delay-details").show();
$("#rate-details").hide();
} else if (this.pauseType == "rate") {
$("#delay-details").hide();
$("#rate-details").show();
}
if (!this.timeoutUnits) {
$("#node-input-timeoutUnits option").filter(function() {
return $(this).val() == 'seconds';
}).attr('selected', true);
}
$("#node-input-pauseType").on("change",function() {
if (this.value == "delay") {
$("#delay-details").show();
$("#rate-details").hide();
} else if (this.value == "rate") {
$("#delay-details").hide();
$("#rate-details").show();
}
});
}
});
</script>

View File

@@ -1,102 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
// Simple node to introduce a pause into a flow
var RED = require("../../red/red");
function DelayNode(n) {
RED.nodes.createNode(this,n);
this.pauseType = n.pauseType;
this.timeoutUnits = n.timeoutUnits;
this.rateUnits = n.rateUnits;
if (n.timeoutUnits == "milliseconds") {
this.timeout = n.timout;
} else if (n.timeoutUnits == "seconds") {
this.timeout = n.timeout * 1000;
} else if (n.timeoutUnits == "minutes") {
this.timeout = n.timeout * (60 * 1000);
} else if (n.timeoutUnits == "hours") {
this.timeout = n.timeout * (60 * 60 * 1000);
} else if (n.timeoutUnits == "days") {
this.timeout = n.timeout * (24 * 60 * 60 * 1000);
}
if (n.rateUnits == "second") {
this.rate = 1000/n.rate;
} else if (n.rateUnits == "minute") {
this.rate = (60 * 1000)/n.rate;
} else if (n.rateUnits == "hour") {
this.rate = (60 * 60 * 1000)/n.rate;
} else if (n.rateUnits == "day") {
this.rate = (24 * 60 * 60 * 1000)/n.rate;
}
this.name = n.name;
this.idList = [];
this.buffer = [];
this.intervalID = -1;
var node= this;
if (this.pauseType == "delay") {
this.on("input", function(msg) {
var node= this;
var id;
id = setTimeout(function(){
node.idList.splice(node.idList.indexOf(id),1);
node.send(msg);
}, node.timeout);
this.idList.push(id);
});
this.on("close", function() {
for (var i=0; i<this.idList.length; i++ ) {
clearTimeout(this.idList[i]);
}
this.idList = [];
});
} else if (this.pauseType == "rate") {
this.on("input", function(msg) {
if ( node.intervalID != -1) {
node.buffer.push(msg);
if (node.buffer.length > 1000) {
node.warn(this.name + " buffer exceeded 1000 messages");
}
} else {
node.send(msg);
node.intervalID = setInterval(function() {
if (node.buffer.length == 0) {
clearInterval(node.intervalID);
node.intervalID = -1;
}
if (node.buffer.length > 0) {
node.send(node.buffer.shift());
}
},node.rate);
}
});
this.on("close", function() {
clearInterval(this.intervalID);
this.buffer = [];
});
}
}
RED.nodes.registerType("delay",DelayNode);

View File

@@ -16,18 +16,17 @@
<script type="text/x-red" data-template-name="sentiment">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Adds <b>msg.sentiment.score</b> as the anaylsis result.
</div>
</script>
<script type="text/x-red" data-help-name="sentiment">
<p>Analyses the <b>msg.payload</b> and adds a <b>msg.sentiment</b> object that contains the resulting AFINN-111 sentiment score as <b>msg.sentiment.score</b>.</p>
<p>A score greater than zero is positive and less than zero is negative.</p>
<p>Score can range from -5 to +5.</p>
<p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_new">the Sentiment docs here</a>.</p>
<p>Analyses the <b>msg.payload</b> and adds a <b>msg.sentiment</b> object that contains the resulting AFINN-111 sentiment score as <b>msg.sentiment.score</b>.</p>
<p>A score greater than zero is positive and less than zero is negative.</p>
<p>The score typically ranges from -5 to +5, but can go higher and lower.</p>
<p>An object of word score overrides can be supplied as <b>msg.overrides</b>.</p>
<p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_new">the Sentiment docs here</a>.</p>
</script>
<script type="text/javascript">
@@ -47,5 +46,4 @@
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -14,19 +14,20 @@
* limitations under the License.
**/
var RED = require("../../red/red");
var sentiment = require('sentiment');
module.exports = function(RED) {
"use strict";
var sentiment = require('sentiment');
function SentimentNode(n) {
RED.nodes.createNode(this,n);
function SentimentNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.on("input", function(msg) {
var node = this;
sentiment(msg.payload, function (err, result) {
this.on("input", function(msg) {
sentiment(msg.payload, msg.overrides || null, function (err, result) {
msg.sentiment = result;
node.send(msg);
});
});
});
}
RED.nodes.registerType("sentiment",SentimentNode);
}
RED.nodes.registerType("sentiment",SentimentNode);

View File

@@ -0,0 +1,500 @@
<!--
Copyright 2013,2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="inject">
<div class="form-row node-input-payload">
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
<select id="node-input-payloadType" style="width:73%">
<option value="date">timestamp</option>
<option value="string">string</option>
<option value="none">blank</option>
</select>
</div>
<div class="form-row" id="node-input-row-payload">
<label for="node-input-payload"></label>
<input type="text" id="node-input-payload" placeholder="payload" style="width:70%">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="topic" style="width: 70%x">
</div>
<div class="form-row">
<label for=""><i class="fa fa-repeat"></i> Repeat</label>
<select id="inject-time-type-select" style="width: 73%"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
<input type="hidden" id="node-input-repeat" placeholder="payload">
<input type="hidden" id="node-input-crontab" placeholder="payload">
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="10">10</option>
<option value="12">12</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="0">60</option>
</select> minutes<br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
<div id="inject-time-interval-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> Monday</label>
<label><input type='checkbox' checked value='2'/> Tuesday</label>
<label><input type='checkbox' checked value='3'/> Wednesday</label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> Thursday</label>
<label><input type='checkbox' checked value='5'/> Friday</label>
<label><input type='checkbox' checked value='6'/> Saturday</label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> Sunday</label>
</div>
</div>
</div>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/>
<div id="inject-time-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> Monday</label>
<label><input type='checkbox' checked value='2'/> Tuesday</label>
<label><input type='checkbox' checked value='3'/> Wednesday</label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> Thursday</label>
<label><input type='checkbox' checked value='5'/> Friday</label>
<label><input type='checkbox' checked value='6'/> Saturday</label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> Sunday</label>
</div>
</div>
</div>
</div>
<div class="form-row" id="node-once">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name">
</div>
<div class="form-tips"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div>
</script>
<style>
.inject-time-row {
padding-left: 110px;
}
.inject-time-row select {
margin: 3px 0;
}
.inject-time-days label {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
vertical-align: top;
width: 100px;
}
.inject-time-days input {
width: auto;
}
.inject-time-times {
width: 90px;
}
.inject-time-row > .ui-spinner {
height: 28px;
margin: 3px 0;
border-color: rgb(204, 204, 204);
}
#inject-time-time {
margin-top: 3px;
width: 75px;
}
.inject-time-count {
width: 40px !important;
}
</style>
<script type="text/x-red" data-help-name="inject">
<p>Pressing the button on the left side of the node allows a message on a topic
to be injected into the flow. This is mainly for test purposes.</p>
<p>The payload defaults to the current time in millisecs since 1970, but can
also be set to a String or left blank.</p>
<p>The repeat function allows the payload to be sent on the required schedule.</p>
<p>The Fire once at start option actually waits a short interval before firing
to give other nodes a chance to instantiate properly.</p>
<p><b>Note: </b>"Interval between times" and "at a specific time" uses cron.
This means that 20 minutes will be at the next hour, 20 minutes past and
40 minutes past - not in 20 minutes time. If you want every 20 minutes
from now - use the "interval" option.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('inject',{
category: 'input',
color:"#a6bbcf",
defaults: {
name: {value:""},
topic: {value:""},
payload: {value:""},
payloadType: {value:"date"},
repeat: {value:""},
crontab: {value:""},
once: {value:false}
},
inputs:0,
outputs:1,
icon: "inject.png",
label: function() {
if (this.payloadType === "string") {
if ((this.topic !== "") && ((this.topic.length + this.payload.length) <= 32)) {
return this.name||this.topic + ":" + this.payload;
}
else if (this.payload.length < 24) {
return this.name||this.payload;
}
}
if ((this.topic.length < 24) && (this.topic.length > 0)) {
return this.name||this.topic;
}
else { return this.name||(this.payloadType==="date"?"timestamp":null)||"inject"; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#inject-time-type-select").change(function() {
$("#node-input-crontab").val('');
var id = $("#inject-time-type-select option:selected").val();
$(".inject-time-row").hide();
$("#inject-time-row-"+id).show();
if ((id == "none") || (id == "interval")) {
$("#node-once").show();
}
else {
$("#node-once").hide();
$("#node-input-once").prop('checked', false);
}
});
$(".inject-time-times").each(function() {
for (var i=0;i<24;i++) {
var l = (i<10?"0":"")+i+":00";
$(this).append($("<option></option>").val(i).text(l));
}
});
$("#inject-time-interval-time-start").change(function() {
var start = Number($("#inject-time-interval-time-start option:selected").val());
var end = Number($("#inject-time-interval-time-end option:selected").val());
$("#inject-time-interval-time-end option").remove();
for (var i=start+1;i<25;i++) {
var l = (i<10?"0":"")+i+":00";
if (i==24) {
l = "00:00";
}
var opt = $("<option></option>").val(i).text(l).appendTo("#inject-time-interval-time-end");
if (i === end) {
opt.attr("selected","selected");
}
}
});
$(".inject-time-count").spinner({
//max:60,
min:1
});
$("#inject-time-interval-units").change(function() {
var units = $("#inject-time-interval-units option:selected").val();
});
$.widget( "ui.injecttimespinner", $.ui.spinner, {
options: {
// seconds
step: 60 * 1000,
// hours
page: 60
},
_parse: function( value ) {
if ( typeof value === "string" ) {
// already a timestamp
if ( Number( value ) == value ) {
return Number( value );
}
var p = value.split(":");
var offset = new Date().getTimezoneOffset();
return (((Number(p[0])+1)*60)+Number(p[1])+offset)*60*1000;
}
return value;
},
_format: function( value ) {
var d = new Date(value);
var h = d.getHours();
var m = d.getMinutes();
return ((h < 10)?"0":"")+h+":"+((m < 10)?"0":"")+m;
}
});
$("#inject-time-time").injecttimespinner();
var repeattype = "none";
if (this.repeat != "" && this.repeat != 0) {
repeattype = "interval";
var r = "s";
var c = this.repeat;
if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
if (this.repeat % 1440 === 0) { r = "h"; c = c/60; }
$("#inject-time-interval-count").val(c);
$("#inject-time-interval-units").val(r);
$("#inject-time-interval-days").prop("disabled","disabled");
} else if (this.crontab) {
var cronparts = this.crontab.split(" ");
var days = cronparts[4];
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = "time";
// Fixed time
var time = cronparts[1]+":"+cronparts[0];
$("#inject-time-time").val(time);
$("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
if (days == "*") {
$("#inject-time-time-days input[type=checkbox]").prop("checked",true);
} else {
$("#inject-time-time-days input[type=checkbox]").removeAttr("checked");
days.split(",").forEach(function(v) {
$("#inject-time-time-days [value=" + v + "]").prop("checked", true);
});
}
} else {
repeattype = "interval-time";
// interval - time period
var minutes = cronparts[0].slice(2);
if (minutes === "") { minutes = "0"; }
$("#inject-time-interval-time-units").val(minutes);
if (days == "*") {
$("#inject-time-interval-time-days input[type=checkbox]").prop("checked",true);
} else {
$("#inject-time-interval-time-days input[type=checkbox]").removeAttr("checked");
days.split(",").forEach(function(v) {
$("#inject-time-interval-time-days [value=" + v + "]").prop("checked", true);
});
}
var time = cronparts[1];
var timeparts = time.split(",");
var start;
var end;
if (timeparts.length == 1) {
// 0 or 0-10
var hours = timeparts[0].split("-");
if (hours.length == 1) {
if (hours[0] === "") {
start = "0";
end = "0";
}
else {
start = hours[0];
end = Number(hours[0])+1;
}
} else {
start = hours[0];
end = (Number(hours[1])+1)%24;
}
} else {
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
var startparts = timeparts[0].split("-");
start = startparts[0];
var endparts = timeparts[1].split("-");
if (endparts.length == 1) {
end = Number(endparts[0])+1;
} else {
end = Number(endparts[1])+1;
}
}
$("#inject-time-interval-time-start option").filter(function() {return $(this).val() == start;}).attr('selected',true);
$("#inject-time-interval-time-end option").filter(function() {return $(this).val() == end;}).attr('selected',true);
}
} else {
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
}
$(".inject-time-row").hide();
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
$("#inject-time-row-"+repeattype).show();
if (this.payloadType == null) {
if (this.payload == "") {
this.payloadType = "date";
} else {
this.payloadType = "string";
}
}
$("#node-input-payloadType").change(function() {
var id = $("#node-input-payloadType option:selected").val();
if (id === "string") {
$("#node-input-row-payload").show();
} else {
$("#node-input-row-payload").hide();
}
});
$("#node-input-payloadType").val(this.payloadType);
$("#node-input-payloadType").change();
$("#inject-time-type-select").change();
$("#inject-time-interval-time-start").change();
},
oneditsave: function() {
var repeat = "";
var crontab = "";
var type = $("#inject-time-type-select option:selected").val();
if (type == "none") {
// nothing
} else if (type == "interval") {
var count = $("#inject-time-interval-count").val();
var units = $("#inject-time-interval-units option:selected").val();
if (units == "s") {
repeat = count;
} else {
if (units == "m") {
//crontab = "*/"+count+" * * * "+days;
repeat = count * 60;
} else if (units == "h") {
//crontab = "0 */"+count+" * * "+days;
repeat = count * 60 * 60;
}
}
} else if (type == "interval-time") {
repeat = "";
var count = $("#inject-time-interval-time-units").val();
var startTime = Number($("#inject-time-interval-time-start option:selected").val());
var endTime = Number($("#inject-time-interval-time-end option:selected").val());
var days = $('#inject-time-interval-time-days input[type=checkbox]:checked').map(function(_, el) {
return $(el).val()
}).get();
if (days.length == 0) {
crontab = "";
} else {
if (days.length == 7) {
days="*";
} else {
days = days.join(",");
}
var timerange = "";
if (endTime == 0) {
timerange = startTime+"-23";
} else if (startTime+1 < endTime) {
timerange = startTime+"-"+(endTime-1);
} else if (startTime+1 == endTime) {
timerange = startTime;
} else {
var startpart = "";
var endpart = "";
if (startTime == 23) {
startpart = "23";
} else {
startpart = startTime+"-23";
}
if (endTime == 1) {
endpart = "0";
} else {
endpart = "0-"+(endTime-1);
}
timerange = startpart+","+endpart;
}
if (count === "0") {
crontab = count+" "+timerange+" * * "+days;
} else {
crontab = "*/"+count+" "+timerange+" * * "+days;
}
}
} else if (type == "time") {
var time = $("#inject-time-time").val();
var days = $('#inject-time-time-days input[type=checkbox]:checked').map(function(_, el) {
return $(el).val()
}).get();
if (days.length == 0) {
crontab = "";
} else {
if (days.length == 7) {
days="*";
} else {
days = days.join(",");
}
var parts = time.split(":");
repeat = "";
crontab = parts[1]+" "+parts[0]+" * * "+days;
}
}
$("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab);
},
button: {
onclick: function() {
var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
if (this.payloadType === "date") { label = "timestamp"; }
if (this.payloadType === "none") { label = "blank"; }
$.ajax({
url: "inject/"+this.id,
type:"POST",
success: function(resp) {
RED.notify("Successfully injected: "+label,"success");
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: inject node not deployed","error");
} else if (jqXHR.status == 500) {
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
} else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+jqXHR.status+") "+textStatus,"error");
}
}
});
}
}
});
</script>

View File

@@ -0,0 +1,97 @@
/**
* Copyright 2013, 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var cron = require("cron");
function InjectNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.payload = n.payload;
this.payloadType = n.payloadType;
this.repeat = n.repeat;
this.crontab = n.crontab;
this.once = n.once;
var node = this;
this.interval_id = null;
this.cronjob = null;
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000;
if (RED.settings.verbose) { this.log("repeat = "+this.repeat); }
this.interval_id = setInterval( function() {
node.emit("input",{});
}, this.repeat );
} else if (this.crontab) {
if (cron) {
if (RED.settings.verbose) { this.log("crontab = "+this.crontab); }
this.cronjob = new cron.CronJob(this.crontab,
function() {
node.emit("input",{});
},
null,true);
} else {
this.error("'cron' module not found");
}
}
if (this.once) {
setTimeout( function(){ node.emit("input",{}); }, 100);
}
this.on("input",function(msg) {
var msg = {topic:this.topic};
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null || this.payloadType === "string") {
msg.payload = this.payload;
} else {
msg.payload = "";
}
this.send(msg);
msg = null;
});
}
RED.nodes.registerType("inject",InjectNode);
InjectNode.prototype.close = function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log("inject: repeat stopped"); }
} else if (this.cronjob != null) {
this.cronjob.stop();
if (RED.settings.verbose) { this.log("inject: cronjob stopped"); }
delete this.cronjob;
}
}
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {
try {
node.receive();
res.send(200);
} catch(err) {
res.send(500);
node.error("Inject failed:"+err);
}
} else {
res.send(404);
}
});
}

View File

@@ -0,0 +1,296 @@
<!--
Copyright 2013, 2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="debug">
<div class="form-row">
<label for="node-input-select-complete"><i class="fa fa-list"></i> Output</label>
<select type="text" id="node-input-select-complete" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">message property</option>
<option value="true">complete msg object</option>
</select>
</div>
<div class="form-row" id="node-prop-row">
<label for="node-input-complete">&nbsp;</label>msg.<input type="text" style="width:208px" id="node-input-complete">
</div>
<div class="form-row">
<label for="node-input-console"><i class="fa fa-random"></i> to</label>
<select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">debug tab</option>
<option value="true">debug tab and console</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="debug">
<p>The Debug node can be connected to the output of any node. It can be used to display the output of any message property in the debug tab of the sidebar. The default is to display <b>msg.payload</b>.</p>
<p>Each message will also display the timestamp, <b>msg.topic</b> and the property chosen to output.</p>
<p>The sidebar can be accessed under the options drop-down in the top right corner.</p>
<p>The button to the right of the node will toggle its output on and off so you can de-clutter the debug window.</p>
<p>If the payload is an object or buffer it will be stringified first for display and indicate that by saying "(Object)" or "(Buffer)".</p>
<p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
<p>Optionally can show the complete <b>msg</b> object.</p>
<p>In addition any calls to node.warn or node.error will appear here.</p>
</script>
<script type="text/javascript">
function oneditprepare() {
if (this.complete === "true" || this.complete === true) {
// show complete message object
$("#node-input-select-complete").val("true");
$("#node-prop-row").hide();
} else {
// show msg.[ ]
var property = (!this.complete||(this.complete === "false")) ? "payload" : this.complete+"";
$("#node-input-select-complete").val("false");
$("#node-input-complete").val(property);
$("#node-prop-row").show();
}
$("#node-input-select-complete").change(function() {
var v = $("#node-input-select-complete option:selected").val();
$("#node-input-complete").val(v);
if (v !== "true") {
$("#node-input-complete").val("payload");
$("#node-prop-row").show();
$("#node-input-complete").focus();
} else {
$("#node-prop-row").hide();
}
});
}
RED.nodes.registerType('debug',{
category: 'output',
defaults: {
name: {value:""},
active: {value:true},
console: {value:"false"},
complete: {value:"false", required:true}
},
label: function() {
if (this.complete === true || this.complete === "true") {
return this.name||"msg";
} else {
return this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete);
}
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
color:"#87a980",
inputs:1,
outputs:0,
icon: "debug.png",
align: "right",
button: {
toggle: "active",
onclick: function() {
var label = this.name||"debug";
$.ajax({
url: "debug/"+this.id+"/"+(this.active?"enable":"disable"),
type: "POST",
success: function(resp, textStatus, xhr) {
if (xhr.status == 200) {
RED.notify("Successfully activated: "+label,"success");
} else if (xhr.status == 201) {
RED.notify("Successfully deactivated: "+label,"success");
}
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: debug node not deployed","error");
} else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+") "+err.response,"error");
}
}
});
}
},
onpaletteadd: function() {
var content = document.createElement("div");
content.id = "tab-debug";
var toolbar = document.createElement("div");
toolbar.id = "debug-toolbar";
content.appendChild(toolbar);
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
var messages = document.createElement("div");
messages.id = "debug-content";
content.appendChild(messages);
RED.sidebar.addTab("debug",content);
function getTimestamp() {
var d = new Date();
return d.toLocaleString();
}
var sbc = document.getElementById("debug-content");
var messageCount = 0;
var that = this;
RED._debug = function(msg) {
that.handleDebugMessage("",{
name:"debug",
msg:msg
});
}
this.handleDebugMessage = function(t,o) {
var msg = document.createElement("div");
msg.onmouseover = function() {
msg.style.borderRightColor = "#999";
var n = RED.nodes.node(o.id);
if (n) {
n.highlighted = true;
n.dirty = true;
}
RED.view.redraw();
};
msg.onmouseout = function() {
msg.style.borderRightColor = "";
var n = RED.nodes.node(o.id);
if (n) {
n.highlighted = false;
n.dirty = true;
}
RED.view.redraw();
};
msg.onclick = function() {
var node = RED.nodes.node(o.id);
if (node) {
RED.view.showWorkspace(node.z);
}
};
var name = (o.name?o.name:o.id).toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var topic = (o.topic||"").toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var property = (o.property?o.property:'').replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var payload = (o.msg||"()").toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var typ = payload.substring(0,payload.indexOf(')')+1);
payload = payload.substring(payload.indexOf(')')+1);
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'');
msg.innerHTML = '<span class="debug-message-date">'+
getTimestamp()+'</span><span class="debug-message-name">['+name+']'+
'</span>';
// NOTE: relying on function error to have a "type" that all other msgs don't
if (o.hasOwnProperty("type") && (o.type === "function")) {
msg.className = 'debug-message debug-message-level-20';
msg.innerHTML += '<span class="debug-message-topic">[function] : (error)</span>';
} else {
msg.innerHTML += '<span class="debug-message-topic">'+(o.topic?topic+' : ':'')+
(o.property?'[msg.'+property+']':'[msg]')+" : "+typ+'</span>';
}
msg.innerHTML += '<span class="debug-message-payload">'+ payload+ '</span>';
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
messageCount++;
$(messages).append(msg);
if (messageCount > 200) {
$("#debug-content .debug-message:first").remove();
messageCount--;
}
if (atBottom) {
$(sbc).scrollTop(sbc.scrollHeight);
}
};
RED.comms.subscribe("debug",this.handleDebugMessage);
$("#debug-tab-clear").click(function() {
$(".debug-message").remove();
messageCount = 0;
RED.nodes.eachNode(function(node) {
node.highlighted = false;
node.dirty = true;
});
RED.view.redraw();
});
},
onpaletteremove: function() {
RED.comms.unsubscribe("debug",this.handleDebugMessage);
RED.sidebar.removeTab("debug");
delete RED._debug;
},
oneditprepare: oneditprepare
});
</script>
<style>
#debug-content {
position: absolute;
top: 30px;
bottom: 0px;
left:0px;
right: 0px;
overflow-y: scroll;
}
#debug-toolbar {
padding: 3px 10px;
height: 24px;
background: #f3f3f3;
}
.debug-message {
cursor: pointer;
border-bottom: 1px solid #eee;
border-left: 8px solid #eee;
border-right: 8px solid #eee;
padding: 2px;
}
.debug-message-date {
background: #fff;
font-size: 9px;
color: #aaa;
padding: 1px 5px 1px 1px;
}
.debug-message-topic {
display: block;
background: #fff;
padding: 1px 5px;
font-size: 10px;
color: #a66;
}
.debug-message-name {
background: #fff;
padding: 1px 5px;
font-size: 9px;
color: #aac;
}
.debug-message-payload {
display: block;
padding: 2px;
background: #fff;
}
.debug-message-level-log {
border-left-color: #eee;
border-right-color: #eee;
}
.debug-message-level-30 {
border-left-color: #ffdf9d;
border-right-color: #ffdf9d;
}
.debug-message-level-20 {
border-left-color: #f99;
border-right-color: #f99;
}
</style>

142
nodes/core/core/58-debug.js Normal file
View File

@@ -0,0 +1,142 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var util = require("util");
var events = require("events");
var debuglength = RED.settings.debugMaxLength||1000;
var useColors = false;
// util.inspect.styles.boolean = "red";
function DebugNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.complete = n.complete||"payload";
if (this.complete === "false") {
this.complete = "payload";
}
if (this.complete === true) {
this.complete = "true";
}
this.console = n.console;
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
var node = this;
this.on("input",function(msg) {
if (this.complete === "true") {
// debug complete msg object
if (this.console === "true") {
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
}
if (this.active) {
sendDebug({id:this.id,name:this.name,topic:msg.topic,msg:msg,_path:msg._path});
}
} else {
// debug user defined msg property
var property = "payload";
var output = msg[property];
if (this.complete !== "false" && typeof this.complete !== "undefined") {
property = this.complete;
var propertyParts = property.split(".");
try {
output = propertyParts.reduce(function (obj, i) {
return obj[i];
}, msg);
} catch (err) {
output = undefined;
}
}
if (this.console === "true") {
if (typeof output === "string") {
node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
} else if (typeof output === "object") {
node.log("\n"+util.inspect(output, {colors:useColors, depth:10}));
} else {
node.log(util.inspect(output, {colors:useColors}));
}
}
if (this.active) {
sendDebug({id:this.id,name:this.name,topic:msg.topic,property:property,msg:output,_path:msg._path});
}
}
});
}
RED.nodes.registerType("debug",DebugNode);
function sendDebug(msg) {
if (msg.msg instanceof Error) {
msg.msg = msg.msg.toString();
} else if (msg.msg instanceof Buffer) {
msg.msg = "(Buffer) "+msg.msg.toString('hex');
} else if (typeof msg.msg === 'object') {
var seen = [];
var ty = "(Object) ";
if (util.isArray(msg.msg)) { ty = "(Array) "; }
msg.msg = ty + JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.indexOf(value) !== -1) { return "[circular]"; }
seen.push(value);
}
return value;
}," ");
seen = null;
} else if (typeof msg.msg === "boolean") {
msg.msg = "(boolean) "+msg.msg.toString();
} else if (typeof msg.msg === "number") {
msg.msg = "(number) "+msg.msg.toString();
} else if (msg.msg === 0) {
msg.msg = "0";
} else if (msg.msg === null || typeof msg.msg === "undefined") {
msg.msg = "(undefined)";
} else { msg.msg = "(string) "+msg.msg; }
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substr(0,debuglength) +" ....";
}
RED.comms.publish("debug",msg);
}
DebugNode.logHandler = new events.EventEmitter();
DebugNode.logHandler.on("log",function(msg) {
if (msg.level === RED.log.WARN || msg.level === RED.log.ERROR) {
sendDebug(msg);
}
});
RED.log.addHandler(DebugNode.logHandler);
RED.httpAdmin.post("/debug/:id/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
var node = RED.nodes.getNode(req.params.id);
var state = req.params.state;
if (node !== null && typeof node !== "undefined" ) {
if (state === "enable") {
node.active = true;
res.send(200);
} else if (state === "disable") {
node.active = false;
res.send(201);
} else {
res.send(404);
}
} else {
res.send(404);
}
});
};

View File

@@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="exec">
<div class="form-row">
<label for="node-input-command"><i class="icon-file"></i> Command</label>
<label for="node-input-command"><i class="fa fa-file"></i> Command</label>
<input type="text" id="node-input-command" placeholder="command">
</div>
<div class="form-row">
<label for="node-input-append"><i class="icon-list"></i> Append</label>
<label for="node-input-append"><i class="fa fa-list"></i> Append</label>
<input type="text" id="node-input-append" placeholder="extra input">
</div>
<div class="form-row">
@@ -29,17 +29,20 @@
<label for="node-input-useSpawn" style="width: 70%;">Use spawn() instead of exec() ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div>
</script>
<script type="text/x-red" data-help-name="exec">
<p>Call to a system command.<br/>This can be very dangerous...</p>
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
<p>By default uses exec() which calls the command, waits for completion and then returns the complete result in one go. Along with any errors.</p>
<p>Optionally can use spawn() instead, which returns output from stdout and stderr as the command runs (ie one line at a time). On completion then returns a return code (on the 3rd output).</p>
<p>The optional append gets added to the command after the <b>msg.payload</b> (so you can do things like pipe etc.)</p>
<p>Calls out to a system command.<br/></p>
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
<p>By default uses exec() which calls the command, blocks while waiting for completion, and then returns the complete result in one go, along with any errors.</p>
<p>Optionally can use spawn() instead, which returns output from stdout and stderr as the command runs (ie one line at a time). On completion it then returns a return code (on the 3rd output).</p>
<p>Spawn only expect one command word, with all extra parameters to be comma separated and passed as the append.</p>
<p>The optional append gets added to the command after the <b>msg.payload</b> - so you can do things like pipe the result to another command.</p>
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
</script>
<script type="text/javascript">
@@ -58,6 +61,9 @@
align: "right",
label: function() {
return this.name||this.command;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,88 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var isUtf8 = require('is-utf8');
function ExecNode(n) {
RED.nodes.createNode(this,n);
this.cmd = n.command.trim();
this.append = n.append.trim() || "";
this.useSpawn = n.useSpawn;
var node = this;
this.on("input", function(msg) {
node.status({fill:"blue",shape:"dot"});
if (this.useSpawn === true) {
// make the extra args into an array
// then prepend with the msg.payload
if (typeof(msg.payload !== "string")) { msg.payload = msg.payload.toString(); }
var arg = [];
if (node.append.length > 0) { arg = node.append.split(","); }
if (msg.payload.trim() !== "") { arg.unshift(msg.payload); }
if (RED.settings.verbose) { node.log(node.cmd+" ["+arg+"]"); }
if (node.cmd.indexOf(" ") == -1) {
var ex = spawn(node.cmd,arg);
ex.stdout.on('data', function (data) {
//console.log('[exec] stdout: ' + data);
if (isUtf8(data)) { msg.payload = data.toString(); }
else { msg.payload = data; }
node.send([msg,null,null]);
});
ex.stderr.on('data', function (data) {
//console.log('[exec] stderr: ' + data);
if (isUtf8(data)) { msg.payload = data.toString(); }
else { msg.payload = new Buffer(data); }
node.send([null,msg,null]);
});
ex.on('close', function (code) {
//console.log('[exec] result: ' + code);
msg.payload = code;
node.status({});
node.send([null,null,msg]);
});
ex.on('error', function (code) {
node.error(code);
});
}
else { node.error("Spawn command must be just the command - no spaces or extra parameters"); }
}
else {
var cl = node.cmd+" "+msg.payload+" "+node.append;
if (RED.settings.verbose) { node.log(cl); }
var child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
msg.payload = new Buffer(stdout,"binary");
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
var msg2 = {payload:stderr};
var msg3 = null;
//console.log('[exec] stdout: ' + stdout);
//console.log('[exec] stderr: ' + stderr);
if (error !== null) {
msg3 = {payload:error};
//console.log('[exec] error: ' + error);
}
node.status({});
node.send([msg,msg2,msg3]);
});
}
});
}
RED.nodes.registerType("exec",ExecNode);
}

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013, 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,25 +16,40 @@
<script type="text/x-red" data-template-name="function">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-func"><i class="icon-wrench"></i> Function</label>
<input type="hidden" id="node-input-func" autofocus="autofocus">
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
<input type="hidden" id="node-input-func" autofocus="autofocus">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div>
<div class="form-row">
<label for="node-input-outputs"><i class="icon-random"></i> Outputs</label>
<label for="node-input-outputs"><i class="fa fa-random"></i> Outputs</label>
<input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
</div>
<div class="form-tips">See the Info tab for help writing functions.</div>
</script>
<script type="text/x-red" data-help-name="function">
<p>The generic function block where you can write code to do more interesting things.</p>
<p>You can have multiple outputs - in which case the function expects you to return an array of messages... <pre>return [msg,msg2,...];</pre></p>
<p>You may return null for any or all outputs if you want to.</p>
<p>You can save your functions to a library (button to right of name field) - and likewise pick from that library.</p>
<p>A function block where you can write code to do more interesting things.</p>
<p>The message is passed in as a JavaScript object called <code>msg</code>.</p>
<p>By convention it will have a <code>msg.payload</code> property containing
the body of the message.</p>
<p>The function should return the messages it wants to pass on to the next nodes
in the flow. It can return:</p>
<ul>
<li>a single message object - passed to nodes connected to the first output</li>
<li>an array of message objects - passed to nodes connected to the corresponding outputs</li>
</ul>
<p>If any element of the array is itself an array of messages, multiple
messages are sent to the corresponding output.</p>
<p>If null is returned, either by itself or as an element of the array, no
message is passed on.</p>
<p>See the <a target="_new" href="http://nodered.org/docs/writing-functions.html">online documentation</a> for more help.</p>
</script>
<script type="text/javascript">
@@ -43,7 +58,7 @@
category: 'function',
defaults: {
name: {value:""},
func: {value:"// The received message is stored in 'msg'\n// It will have at least a 'payload' property:\n// console.log(msg.payload);\n// The 'context' object is available to store state\n// between invocations of the function\n// context = {};\n\n\nreturn msg;"},
func: {value:"\nreturn msg;"},
outputs: {value:1}
},
inputs:1,
@@ -57,15 +72,24 @@
min:1
});
function functionDialogResize(ev,ui) {
$("#node-input-func-editor").css("height",(ui.size.height-235)+"px");
function functionDialogResize() {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
functionDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
functionDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

View File

@@ -0,0 +1,93 @@
/**
* Copyright 2013,2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var util = require("util");
var vm = require("vm");
function FunctionNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.func = n.func;
var functionText = "var results = null; results = (function(msg){\n"+this.func+"\n})(msg);";
this.topic = n.topic;
var sandbox = {
console:console,
util:util,
Buffer:Buffer,
context: {
global:RED.settings.functionGlobalContext || {}
}
};
var context = vm.createContext(sandbox);
try {
this.script = vm.createScript(functionText);
this.on("input", function(msg) {
try {
var start = process.hrtime();
context.msg = msg;
this.script.runInContext(context);
var results = context.results;
if (results == null) {
results = [];
} else if (results.length == null) {
results = [results];
}
if (msg._topic) {
for (var m in results) {
if (results[m]) {
if (util.isArray(results[m])) {
for (var n=0; n < results[m].length; n++) {
results[m][n]._topic = msg._topic;
}
} else {
results[m]._topic = msg._topic;
}
}
}
}
this.send(results);
var duration = process.hrtime(start);
var converted = Math.floor((duration[0]* 1e9 + duration[1])/10000)/100;
this.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
this.status({fill:"yellow",shape:"dot",text:""+converted});
}
} catch(err) {
var errorMessage = err.toString();
var stack = err.stack.split(/\r?\n/);
if (stack.length > 0) {
var m = /at undefined:(\d+):(\d+)$/.exec(stack[1]);
if (m) {
var line = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+line+", col "+cha+")";
}
}
this.error(errorMessage);
}
});
} catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
this.error(err);
}
}
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");
}

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,22 +16,35 @@
<script type="text/x-red" data-template-name="template">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-template"><i class="icon-wrench"></i> Template</label>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
<input type="hidden" id="node-input-template" autofocus="autofocus">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
</div>
<div class="form-row">
<label for="node-input-field"><i class="fa fa-edit"></i> Property</label>
msg.<input type="text" id="node-input-field" placeholder="payload" style="width: 64%;">
</div>
</script>
<script type="text/x-red" data-help-name="template">
<p>Creates new messages based on a template.</p>
<p>Very useful for creating boilerplate web pages, emails, tweets and so on.</p>
<p>Uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">Mustache</a></i> format.</p>
<p>Creates a new message based on the provided template.</p>
<p>This uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i> format.</p>
<p>For example, when a template of:
<pre>Hello {{name}}. Today is {{date}}</pre>
<p>receives a message containing:
<pre>{
name: "Fred",
date: "Monday"
payload: ...
}</pre>
<p>The resulting payload will be:
<pre>Hello Fred. Today is Monday</pre>
</script>
<script type="text/javascript">
@@ -40,6 +53,7 @@
category: 'function',
defaults: {
name: {value:""},
field: {value:"payload"},
template: {value:"This is the payload: {{payload}}!"},
},
inputs:1,
@@ -50,15 +64,24 @@
},
oneditprepare: function() {
function templateDialogResize(ev,ui) {
$("#node-input-template-editor").css("height",(ui.size.height-200)+"px");
function templateDialogResize() {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", templateDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-template');
if (size) {
templateDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
templateDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
@@ -77,7 +100,7 @@
url:"templates", // where to get the data from
type:"template", // the type of object the library is for
editor:that.editor, // the field name the main text body goes to
fields:['name']
fields:['name','field']
});
$("#node-input-name").focus();
});
@@ -87,5 +110,4 @@
delete this.editor;
}
});
</script>

View File

@@ -0,0 +1,61 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
function TemplateNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.field = n.field || "payload";
this.template = n.template;
var node = this;
var b = node.field.split(".");
var i = 0;
var m = null;
var rec = function(obj) {
i += 1;
if ((i < b.length) && (typeof obj[b[i-1]] === "object")) {
rec(obj[b[i-1]]); // not there yet - carry on digging
}
else {
if (i === b.length) { // we've finished so assign the value
obj[b[i-1]] = mustache.render(node.template,m);
node.send(m);
}
else {
obj[b[i-1]] = {}; // needs to be a new object so create it
rec(obj[b[i-1]]); // and carry on digging
}
}
}
node.on("input", function(msg) {
try {
m = msg;
i = 0;
rec(msg);
} catch(err) {
node.error(err.message);
}
});
}
RED.nodes.registerType("template",TemplateNode);
RED.library.register("templates");
}

View File

@@ -0,0 +1,193 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- First, the content of the edit dialog is defined. -->
<script type="text/x-red" data-template-name="delay">
<div class="form-row">
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> Action</label>
<select id="node-input-pauseType" style="width:270px !important">
<option value="delay">Delay message</option>
<option value="random">Random delay</option>
<option value="rate">Limit rate to</option>
<option value="queue">Topic based fair queue</option>
</select>
</div>
<div id="delay-details" class="form-row">
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> For</label>
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
<select id="node-input-timeoutUnits" style="width:200px !important">
<option value="milliseconds">Milliseconds</option>
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
</select>
</div>
<div id="rate-details" class="form-row">
<label for="node-input-rate"><i class="fa fa-clock-o"></i> Rate</label>
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
<label for="node-input-rateUnits">msg(s) per</label>
<select id="node-input-rateUnits" style="width:140px !important">
<option value="second">Second</option>
<option value="minute">Minute</option>
<option value="hour">Hour</option>
<option value="day">Day</option>
</select>
<br/>
<div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label></div>
</div>
<div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> Between</label>
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
<label for="node-input-randomLast" style="width:20px"> &amp; </label>
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
<select id="node-input-randomUnits" style="width:140px !important">
<option value="milliseconds">Milliseconds</option>
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<!-- Next, some simple help text is provided for the node. -->
<script type="text/x-red" data-help-name="delay">
<p>Introduces a delay into a flow or rate limits messages.</p>
<p>Default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured.</p>
<p>If you select a rate limit you may optionally discard any intermediate messages that arrive.</p>
<p>The "topic based fair queue" adds messages to a release queue tagged by their <b>msg.topic</b> property.
At each "tick", derived from the rate, the next "topic" is released.
Any messages arriving on the same topic before release replace those in that position in the queue.
So each "topic" gets a turn - but the most recent value is always the one sent.</p>
</script>
<!-- Finally, the node type is registered along with all of its properties -->
<script type="text/javascript">
RED.nodes.registerType('delay',{
category: 'function', // the palette category
color:"#E6E0F8",
defaults: { // defines the editable properties of the node
name: {value:""}, // along with default values.
pauseType: {value:"delay", required:true},
timeout: {value:"5", required:true, validate:RED.validators.number()},
timeoutUnits: {value:"seconds"},
rate: {value:"1", required:true, validate:RED.validators.number()},
rateUnits: {value: "second"},
randomFirst: {value:"1", required:true, validate:RED.validators.number()},
randomLast: {value:"5", required:true, validate:RED.validators.number()},
randomUnits: {value: "seconds"},
drop: {value:false}
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
icon: "timer.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
if (this.pauseType == "delay") {
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
return this.name||"delay "+this.timeout+" " + units;
} else if (this.pauseType == "rate") {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name||"limit "+this.rate+" msg/"+ units;
} else if (this.pauseType == "random") {
return this.name || "random";
}
else {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name || "queue" +this.rate+" msg/"+ units;
}
},
labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-timeout" ).spinner({min:1,max:60});
$( "#node-input-rate" ).spinner({min:1});
$( "#node-input-randomFirst" ).spinner({min:0});
$( "#node-input-randomLast" ).spinner({min:1});
if (this.pauseType == "delay") {
$("#delay-details").show();
$("#rate-details").hide();
$("#random-details").hide();
$("#node-input-dr").hide();
} else if (this.pauseType == "rate") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").show();
} else if (this.pauseType == "random") {
$("#delay-details").hide();
$("#rate-details").hide();
$("#random-details").show();
$("#node-input-dr").hide();
} else if (this.pauseType == "queue") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").hide();
}
if (!this.timeoutUnits) {
$("#node-input-timeoutUnits option").filter(function() {
return $(this).val() == 'seconds';
}).attr('selected', true);
}
if (!this.randomUnits) {
$("#node-input-randomUnits option").filter(function() {
return $(this).val() == 'seconds';
}).attr('selected', true);
}
$("#node-input-pauseType").on("change",function() {
if (this.value == "delay") {
$("#delay-details").show();
$("#rate-details").hide();
$("#random-details").hide();
$("#node-input-dr").hide();
} else if (this.value == "rate") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").show();
} else if (this.value == "random") {
$("#delay-details").hide();
$("#rate-details").hide();
$("#random-details").show();
$("#node-input-dr").hide();
} else if (this.value == "queue") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").hide();
}
});
}
});
</script>

191
nodes/core/core/89-delay.js Normal file
View File

@@ -0,0 +1,191 @@
/**
* Copyright 2013, 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
//Simple node to introduce a pause into a flow
module.exports = function(RED) {
"use strict";
var MILLIS_TO_NANOS = 1000000;
var SECONDS_TO_NANOS = 1000000000;
function DelayNode(n) {
RED.nodes.createNode(this,n);
this.pauseType = n.pauseType;
this.timeoutUnits = n.timeoutUnits;
this.randomUnits = n.randomUnits;
this.rateUnits = n.rateUnits;
if (n.timeoutUnits === "milliseconds") {
this.timeout = n.timeout;
} else if (n.timeoutUnits === "seconds") {
this.timeout = n.timeout * 1000;
} else if (n.timeoutUnits === "minutes") {
this.timeout = n.timeout * (60 * 1000);
} else if (n.timeoutUnits === "hours") {
this.timeout = n.timeout * (60 * 60 * 1000);
} else if (n.timeoutUnits === "days") {
this.timeout = n.timeout * (24 * 60 * 60 * 1000);
}
if (n.rateUnits === "second") {
this.rate = 1000/n.rate;
} else if (n.rateUnits === "minute") {
this.rate = (60 * 1000)/n.rate;
} else if (n.rateUnits === "hour") {
this.rate = (60 * 60 * 1000)/n.rate;
} else if (n.rateUnits === "day") {
this.rate = (24 * 60 * 60 * 1000)/n.rate;
}
if (n.randomUnits === "milliseconds") {
this.randomFirst = n.randomFirst * 1;
this.randomLast = n.randomLast * 1;
} else if (n.randomUnits === "seconds") {
this.randomFirst = n.randomFirst * 1000;
this.randomLast = n.randomLast * 1000;
} else if (n.randomUnits === "minutes") {
this.randomFirst = n.randomFirst * (60 * 1000);
this.randomLast = n.randomLast * (60 * 1000);
} else if (n.randomUnits === "hours") {
this.randomFirst = n.randomFirst * (60 * 60 * 1000);
this.randomLast = n.randomLast * (60 * 60 * 1000);
} else if (n.randomUnits === "days") {
this.randomFirst = n.randomFirst * (24 * 60 * 60 * 1000);
this.randomLast = n.randomLast * (24 * 60 * 60 * 1000);
}
this.diff = this.randomLast - this.randomFirst;
this.name = n.name;
this.idList = [];
this.buffer = [];
this.intervalID = -1;
this.randomID = -1;
this.lastSent = null;
this.drop = n.drop;
var node = this;
if (this.pauseType === "delay") {
this.on("input", function(msg) {
var id;
id = setTimeout(function(){
node.idList.splice(node.idList.indexOf(id),1);
node.send(msg);
}, node.timeout);
this.idList.push(id);
});
this.on("close", function() {
for (var i=0; i<this.idList.length; i++ ) {
clearTimeout(this.idList[i]);
}
this.idList = [];
});
} else if (this.pauseType === "rate") {
this.on("input", function(msg) {
if (!node.drop) {
if ( node.intervalID !== -1) {
node.buffer.push(msg);
if (node.buffer.length > 0) {
node.status({text:node.buffer.length});
}
if (node.buffer.length > 1000) {
node.warn(this.name + " buffer exceeded 1000 messages");
}
} else {
node.send(msg);
node.intervalID = setInterval(function() {
if (node.buffer.length === 0) {
clearInterval(node.intervalID);
node.intervalID = -1;
node.status({text:""});
}
if (node.buffer.length > 0) {
node.send(node.buffer.shift());
node.status({text:node.buffer.length});
}
},node.rate);
}
} else {
var timeSinceLast;
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
node.send(msg);
} else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
node.send(msg);
}
}
});
this.on("close", function() {
clearInterval(this.intervalID);
this.buffer = [];
});
} else if (this.pauseType === "queue") {
this.intervalID = setInterval(function() {
if (node.buffer.length > 0) {
node.send(node.buffer.shift()); // send the first on the queue
}
node.status({text:node.buffer.length});
},node.rate);
this.on("input", function(msg) {
if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
var hit = false;
for (var b in node.buffer) { // check if already in queue
if (msg.topic === node.buffer[b].topic) {
node.buffer[b] = msg; // if so - replace existing entry
hit = true;
}
}
if (!hit) { node.buffer.push(msg); } // if not add to end of queue
node.status({text:node.buffer.length});
});
this.on("close", function() {
clearInterval(this.intervalID);
this.buffer = [];
node.status({text:node.buffer.length});
});
} else if (this.pauseType === "random") {
this.on("input", function(msg) {
var wait = node.randomFirst + (node.diff * Math.random());
var id = setTimeout(function(){
node.idList.splice(node.idList.indexOf(id),1);
node.send(msg);
}, wait);
this.idList.push(id);
});
this.on("close", function() {
for (var i=0; i<this.idList.length; i++ ) {
clearTimeout(this.idList[i]);
}
this.idList = [];
});
}
}
RED.nodes.registerType("delay",DelayNode);
}

View File

@@ -0,0 +1,130 @@
<!--
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="trigger">
<div class="form-row">
<label for="node-input-op1type"><i class="fa fa-arrow-up"></i> Output</label>
<select id="node-input-op1type" style="width:73% !important">
<option value="val">the value below</option>
<option value="pay">the existing payload</option>
<option value="nul">nothing (no output)</option>
</select>
</div>
<div class="form-row" id="node-op1">
<label for="node-input-op1">&nbsp;</label>
<input type="text" id="node-input-op1">
</div>
<div class="form-row">
<label for="node-input-duration"><i class="fa fa-clock-o"></i> then wait</label>
<input type="text" id="node-input-duration" placeholder="250" style="direction:rtl; width:70px !important">
<select id="node-input-units" style="width:140px !important">
<option value="ms">Milliseconds</option>
<option value="s">Seconds</option>
<option value="min">Minutes</option>
<option value="hr">Hours</option>
</select>
</div>
<div class="form-row">
<label for="node-input-op2type"><i class="fa fa-arrow-down"></i> output</label>
<select id="node-input-op2type" style="width:73% !important">
<option value="val">the value below</option>
<option value="pay">the existing payload</option>
<option value="nul">nothing (no output)</option>
</select>
</div>
<div class="form-row" id="node-op2">
<label for="node-input-op2">&nbsp;</label>
<input type="text" id="node-input-op2">
</div>
<div class="form-row">
<label for="node-input-extend"><i class="fa fa-repeat"></i> and</label>
<select id="node-input-extend" style="width:73% !important">
<option value="false">don't extend the timer if retriggered</option>
<option value="true">extend the timer if retriggered</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<!-- <div class="form-tips">Tip: Outputs can be values, null, {{templated}} or msg.payload<br/> -->
<div class="form-tips">Setting the timeout to 0 sets an infinite timeout = single shot.</div>
<script>
{
$("#node-input-op1type").change(function() {
if ($("#node-input-op1type").val() == "val") { $("#node-op1").show(); }
else { $("#node-op1").hide(); }
});
$("#node-input-op2type").change(function() {
if ($("#node-input-op2type").val() == "val") { $("#node-op2").show(); }
else { $("#node-op2").hide(); }
});
}
</script>
</script>
<script type="text/x-red" data-help-name="trigger">
<p>Creates two messages on the output separated by a timeout whenever ANY <b>msg</b> arrives on the input.</p>
<p>For example, this can be used to toggle a Raspberry PI GPIO pin on and off.</p>
<p>The two output states can be specified as can the duration of the timer.
Either output can be set to a value, or templated from the inbound
<b>msg</b> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
<p>If the payload is an object then setting the output to <i>existing payload</i> will pass the complete payload object through.</p>
<p>Optionally the timer can be extended by being retriggered... or not.</p>
<p>By setting the first output to <i>nothing</i>, and selecting extend timer - a watchdog timer can be created.
No output will happen as long as repeated inputs occur within the timeout period.</p>
<p>Setting the timer to 0 creates an "infinite" timeout - the first output will happen but the second
never will, and neither can the first be retriggered - so a true one shot.</p>
<p>If a <b>msg.reset</b> property is present any timeout currently in progress
will be cleared and the second output will not happen.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('trigger',{
category: 'function',
color:"#E6E0F8",
defaults: {
op1: {value:"1"},
op2: {value:"0"},
op1type: {value:""},
op2type: {value:""},
duration: {value:"250",required:true,validate:RED.validators.number()},
extend: {value:"false"},
units: {value: "ms"},
name: {value:""}
},
inputs:1,
outputs:1,
icon: "trigger.png",
label: function() {
if (this.duration > 0) {
return this.name||"trigger "+this.duration+this.units;
}
else {
return this.name||"trigger once &infin;";
}
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-duration" ).spinner({
min:1,
increment:25
});
}
});
</script>

View File

@@ -0,0 +1,91 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
function TriggerNode(n) {
RED.nodes.createNode(this,n);
this.op1 = n.op1 || "1";
this.op2 = n.op2 || "0";
this.op1type = n.op1type || "val";
this.op2type = n.op2type || "val";
this.extend = n.extend || false;
this.units = n.units || "ms";
this.duration = n.duration || 250;
if (this.duration <= 0) { this.duration = 0; }
else {
if (this.units == "s") { this.duration = this.duration * 1000; }
if (this.units == "min") { this.duration = this.duration * 1000 * 60; }
if (this.units == "hr") { this.duration = this.duration * 1000 *60 * 60; }
}
this.op1Templated = this.op1.indexOf("{{") != -1;
this.op2Templated = this.op2.indexOf("{{") != -1;
if (!isNaN(this.op1)) { this.op1 = Number(this.op1); }
if (!isNaN(this.op2)) { this.op2 = Number(this.op2); }
if (this.op1 == "true") { this.op1 = true; }
if (this.op2 == "true") { this.op2 = true; }
if (this.op1 == "false") { this.op1 = false; }
if (this.op2 == "false") { this.op2 = false; }
if (this.op1 == "null") { this.op1 = null; }
if (this.op2 == "null") { this.op2 = null; }
try { this.op1 = JSON.parse(this.op1); }
catch(e) { this.op1 = this.op1; }
try { this.op2 = JSON.parse(this.op2); }
catch(e) { this.op2 = this.op2; }
var node = this;
var tout = null;
var m2;
this.on("input", function(msg) {
if (msg.hasOwnProperty("reset")) {
clearTimeout(tout);
tout = null;
}
else {
if (!tout) {
if (node.op2type === "pay") { m2 = msg.payload; }
else if (node.op2Templated) { m2 = mustache.render(node.op2,msg); }
else { m2 = node.op2; }
if (node.op1type === "pay") { }
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
else { msg.payload = node.op1; }
if (node.op1type !== "nul") { node.send(msg); }
if (node.duration === 0) { tout = "infinite"; }
else {
tout = setTimeout(function() {
msg.payload = m2;
if (node.op2type !== "nul") { node.send(msg); }
tout = null;
},node.duration);
}
}
else if ((node.extend == "true") && (node.duration > 0)) {
clearTimeout(tout);
tout = setTimeout(function() {
msg.payload = m2;
if (node.op2type !== "nul") { node.send(msg); }
tout = null;
},node.duration);
}
}
});
this.on("close", function() {
if (tout) { clearTimeout(tout); }
});
}
RED.nodes.registerType("trigger",TriggerNode);
}

View File

@@ -15,20 +15,22 @@
-->
<script type="text/x-red" data-template-name="comment">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Comment</label>
<input type="text" id="node-input-name" placeholder="Comment">
</div>
<div class="form-row">
<label for="node-input-info" style="width: 100% !important;"><i class="icon-file"></i> More</label>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-comment"></i> Title</label>
<input type="text" id="node-input-name" placeholder="Comment">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> Body - will be rendered in info tab.</label>
<input type="hidden" id="node-input-info" autofocus="autofocus">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor" ></div>
</div>
<div class="form-tips">Tip: this isn't meant for War and Peace - but useful notes can be kept here.</div>
<div class="form-tips">Tip: The text here can be styled as <i><a href="https://help.github.com/articles/markdown-basics/" target="_new">Github flavored Markdown</a></i></div>
</script>
<script type="text/x-red" data-help-name="comment">
<p>Simple comment block.</p>
<p>A node you can use to add comments to your flows.</p>
</script>
<script type="text/javascript">
@@ -41,25 +43,37 @@
},
inputs:0,
outputs:0,
icon: "file.png",
icon: "comment.png",
label: function() {
return this.name||"";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
info: function() {
return "### "+this.name+"\n"+this.info;
},
oneditprepare: function() {
$( "#node-input-outputs" ).spinner({
min:1
});
function functionDialogResize(ev,ui) {
$("#node-input-info-editor").css("height",(ui.size.height-235)+"px");
function functionDialogResize() {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
functionDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
functionDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

View File

@@ -14,10 +14,10 @@
* limitations under the License.
**/
var RED = require("../../red/red");
function CommentNode(n) {
RED.nodes.createNode(this,n);
module.exports = function(RED) {
"use strict";
function CommentNode(n) {
RED.nodes.createNode(this,n);
}
RED.nodes.registerType("comment",CommentNode);
}
RED.nodes.registerType("comment",CommentNode);

View File

@@ -0,0 +1,51 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="unknown">
<div class="form-tips"><p>This node is a type unknown to your installation of Node-RED.</p>
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
the flow will not start until the missing type is installed.</i></p>
<p>See the Info side bar for more help</p></div>
</script>
<script type="text/x-red" data-help-name="unknown">
<p>This node is a type unknown to your installation of Node-RED.</p>
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
the flow will not start until the missing type is installed.</i></p>
<p>It is possible this node type is already installed, but is missing a dependency. Check the Node-RED start-up log for
any error messages associated with the missing node type. Use <b>npm install &lt;module&gt;</b> to install any missing modules
and restart Node-RED and reimport the nodes.</p>
<p>Otherwise, you should contact the author of the flow to obtain a copy of the missing node type.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('unknown',{
category: 'unknown',
color:"#fff0f0",
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "",
label: function() {
return "("+this.name+")"||"unknown";
},
labelStyle: function() {
return "node_label_unknown";
}
});
</script>

View File

@@ -0,0 +1,23 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
function UnknownNode(n) {
RED.nodes.createNode(this,n);
}
RED.nodes.registerType("unknown",UnknownNode);
}

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,22 +15,22 @@
-->
<script type="text/x-red" data-template-name="arduino in">
<div class="form-row">
<label for="node-input-arduino"><i class="icon-tasks"></i> Arduino</label>
<label for="node-input-arduino"><i class="fa fa-tasks"></i> Arduino</label>
<input type="text" id="node-input-arduino">
</div>
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> Pin</label>
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
<input type="text" id="node-input-pin" placeholder="2">
</div>
<div class="form-row">
<label for="node-input-state"><i class="icon-wrench"></i> Type</label>
<label for="node-input-state"><i class="fa fa-wrench"></i> Type</label>
<select type="text" id="node-input-state" style="width: 150px;">
<option value="INPUT">Digital pin</option>
<option value="ANALOG">Analogue pin</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips"><b>Note:</b> You cannot use the same pin for both output and input.</div>
@@ -38,6 +38,7 @@
<script type="text/x-red" data-help-name="arduino in">
<p>Arduino input node. Connects to local Arduino and monitors the selected pin for changes. Uses <a href="http://firmata.org/" target="_new"><i>Firmata</i>.</a></p>
<p>The Arduino must be loaded with the Standard Firmata sketch available in the Arduino examples.</p>
<p>You can select either Digital or Analogue input. Outputs the value read as <b>msg.payload</b> and the pin number as <b>msg.topic</b>.</p>
<p>It only outputs on a change of value - fine for digital inputs, but you can get a lot of data from analogue pins which you must then handle.</p>
<p>You can set the sample rate in ms from 20 to 65535.</p>
@@ -45,13 +46,13 @@
<script type="text/javascript">
RED.nodes.registerType('arduino in',{
category: 'advanced-input',
category: 'Arduino',
color:"#3fadb5",
defaults: {
name: {value:""},
pin: {value:"",required:true},
state: {value:"INPUT",required:true},
arduino: {type:"arduino-board",required:true}
arduino: {type:"arduino-board"}
},
inputs:0,
outputs:1,
@@ -69,23 +70,23 @@
<script type="text/x-red" data-template-name="arduino out">
<div class="form-row">
<label for="node-input-arduino"><i class="icon-tasks"></i> Arduino</label>
<label for="node-input-arduino"><i class="fa fa-tasks"></i> Arduino</label>
<input type="text" id="node-input-arduino">
</div>
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> Pin</label>
<label for="node-input-pin"><i class="fa fa-circle"></i> Pin</label>
<input type="text" id="node-input-pin" placeholder="13">
</div>
<div class="form-row">
<label for="node-input-state"><i class="icon-wrench"></i> Type</label>
<select type="text" id="node-input-state" style="width: 150px;">
<label for="node-input-state"><i class="fa fa-wrench"></i> Type</label>
<select type="text" id="node-input-state" style="width: 200px;">
<option value="OUTPUT">Digital (0/1)</option>
<option value="PWM">Analogue (0-255)</option>
<option value="SERVO">Servo (0-180)</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips"><b>Note:</b> You cannot use the same pin for both output and input.</div>
@@ -93,18 +94,19 @@
<script type="text/x-red" data-help-name="arduino out">
<p>Arduino output node. Connects to local Arduino and writes to the selected digital pin. Uses <a href="http://firmata.org/" target="_new"><i>Firmata</i>.</a></p>
<p>The Arduino must be loaded with the Standard Firmata sketch available in the Arduino examples.</p>
<p>You can select Digital, Analogue (PWM) or Servo type outputs. Expects a numeric value in <b>msg.payload</b>. The pin number is set in the properties panel.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('arduino out',{
category: 'advanced-output',
category: 'Arduino',
color:"#3fadb5",
defaults: {
name: {value:""},
pin: {value:""},
pin: {value:"",required:true},
state: {value:"",required:true},
arduino: {type:"arduino-board",required:true}
arduino: {type:"arduino-board"}
},
inputs:1,
outputs:0,
@@ -122,29 +124,48 @@
<script type="text/x-red" data-template-name="arduino-board">
<div class="form-row">
<label for="node-config-input-device"><i class="icon-bullhorn"></i> Arduino Port</label>
<input type="text" id="node-config-input-device" placeholder="/dev/ttyUSB0" style="width:50%;">
<label for="node-config-input-device"><i class="fa fa-random"></i> Port</label>
<input type="text" id="node-config-input-device" style="width:60%;" placeholder="e.g. /dev/ttyUSB0 COM1"/>
<a id="node-config-lookup-serial" class="btn"><i id="node-config-lookup-serial-icon" class="fa fa-search"></i></a>
</div>
<div class="form-row">
<label for="node-config-input-repeat"><i class="icon-repeat"></i> Sample (ms)</label>
<input type="text" id="node-config-input-repeat" placeholder="25">
</div>
<!-- <div class="form-row">
<label for="node-config-input-baud"><i class="icon-bullhorn"></i> Baudrate</label>
<input type="text" id="node-config-input-baud" placeholder="115200" style="width:50%;">
</div> -->
<div class="form-tips"><b>Tip:</b> Use search to try to auto-detect serial port.</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('arduino-board',{
category: 'config',
defaults: {
//baud: {baud:"57600",required:true},
repeat: {value:"50",required:true,validate:RED.validators.number()},
device: {value:"",required:true}
},
label: function() {
return this.device||"arduino";
},
oneditprepare: function() {
try {
$("#node-config-input-device").autocomplete( "destroy" );
} catch(err) { }
$("#node-config-lookup-serial").click(function() {
$("#node-config-lookup-serial-icon").removeClass('fa-search');
$("#node-config-lookup-serial-icon").addClass('spinner');
$("#node-config-lookup-serial").addClass('disabled');
$.getJSON('arduinoports',function(data) {
$("#node-config-lookup-serial-icon").addClass('fa-search');
$("#node-config-lookup-serial-icon").removeClass('spinner');
$("#node-config-lookup-serial").removeClass('disabled');
var ports = [];
$.each(data, function(i, port){
ports.push(port);
});
$("#node-config-input-device").autocomplete({
source:ports,
minLength:0,
close: function( event, ui ) {
$("#node-config-input-device").autocomplete( "destroy" );
}
}).autocomplete("search","");
});
});
}
});
</script>

View File

@@ -0,0 +1,157 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var ArduinoFirmata = require('arduino-firmata');
var fs = require('fs');
var plat = require('os').platform();
var portlist = ArduinoFirmata.list(function (err, ports) {
portlist = ports;
});
// The Board Definition - this opens (and closes) the connection
function ArduinoNode(n) {
RED.nodes.createNode(this,n);
this.device = n.device || null;
this.repeat = n.repeat||25;
//node.log("opening connection "+this.device);
var node = this;
node.board = new ArduinoFirmata();
if (portlist.indexOf(node.device) === -1) {
node.warn("device "+node.device+" not found");
}
else {
node.board.connect(node.device);
}
node.board.on('boardReady', function(){
if (RED.settings.verbose) { node.log("version "+node.board.boardVersion); }
});
node.on('close', function(done) {
if (node.board) {
try {
node.board.close(function() {
done();
if (RED.settings.verbose) { node.log("port closed"); }
});
} catch(e) { done(); }
} else { done(); }
});
}
RED.nodes.registerType("arduino-board",ArduinoNode);
// The Input Node
function DuinoNodeIn(n) {
RED.nodes.createNode(this,n);
this.buttonState = -1;
this.pin = n.pin;
this.state = n.state;
this.arduino = n.arduino;
this.serverConfig = RED.nodes.getNode(this.arduino);
if (typeof this.serverConfig === "object") {
this.board = this.serverConfig.board;
//this.repeat = this.serverConfig.repeat;
var node = this;
node.status({fill:"red",shape:"ring",text:"connecting"});
node.board.on('connect', function() {
node.status({fill:"green",shape:"dot",text:"connected"});
//console.log("i",node.state,node.pin);
if (node.state == "ANALOG") {
node.board.on('analogChange', function(e) {
if (e.pin == node.pin) {
var msg = {payload:e.value, topic:"A"+e.pin};
node.send(msg);
}
});
}
else {
node.board.pinMode(node.pin, ArduinoFirmata.INPUT);
node.board.on('digitalChange', function(e) {
if (e.pin == node.pin) {
var msg = {payload:e.value, topic:e.pin};
node.send(msg);
}
});
}
});
}
else {
this.warn("port not configured");
}
}
RED.nodes.registerType("arduino in",DuinoNodeIn);
// The Output Node
function DuinoNodeOut(n) {
RED.nodes.createNode(this,n);
this.buttonState = -1;
this.pin = n.pin;
this.state = n.state;
this.arduino = n.arduino;
this.serverConfig = RED.nodes.getNode(this.arduino);
if (typeof this.serverConfig === "object") {
this.board = this.serverConfig.board;
var node = this;
node.status({fill:"red",shape:"ring",text:"connecting"});
node.board.on('connect', function() {
node.status({fill:"green",shape:"dot",text:"connected"});
//console.log("o",node.state,node.pin);
node.board.pinMode(node.pin, node.state);
node.on("input", function(msg) {
if (node.state == "OUTPUT") {
if ((msg.payload == true)||(msg.payload == 1)||(msg.payload.toString().toLowerCase() == "on")) {
node.board.digitalWrite(node.pin, true);
}
if ((msg.payload == false)||(msg.payload == 0)||(msg.payload.toString().toLowerCase() == "off")) {
node.board.digitalWrite(node.pin, false);
}
}
if (node.state == "PWM") {
msg.payload = msg.payload * 1;
if ((msg.payload >= 0) && (msg.payload <= 255)) {
//console.log(msg.payload, node.pin);
node.board.analogWrite(node.pin, msg.payload);
}
}
if (node.state == "SERVO") {
msg.payload = msg.payload * 1;
if ((msg.payload >= 0) && (msg.payload <= 180)) {
//console.log(msg.payload, node.pin);
node.board.servoWrite(node.pin, msg.payload);
}
}
});
});
}
else {
this.warn("port not configured");
}
}
RED.nodes.registerType("arduino out",DuinoNodeOut);
RED.httpAdmin.get("/arduinoports", RED.auth.needsPermission("arduino.read"), function(req,res) {
ArduinoFirmata.list(function (err, ports) {
res.json(ports);
});
});
}

View File

@@ -0,0 +1,354 @@
<!--
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option>
<option value="3">3 - SDA1 </option>
<option value="5">5 - SCL1 </option>
<option value="7">7 - GPIO7</option>
<option value="8">8 - TxD </option>
<option value="10">10 - RxD </option>
<option value="11">11 - GPIO0</option>
<option value="12">12 - GPIO1</option>
<option value="13">13 - GPIO2</option>
<option value="15">15 - GPIO3</option>
<option value="16">16 - GPIO4</option>
<option value="18">18 - GPIO5</option>
<option value="19">19 - MOSI </option>
<option value="21">21 - MISO </option>
<option value="22">22 - GPIO6</option>
<option value="23">23 - SCLK </option>
<option value="24">24 - CE0 </option>
<option value="26">26 - CE1 </option>
</select>
&nbsp;<span id="pitype"></span>
</div>
<div class="form-row">
<label for="node-input-intype"><i class="fa fa-level-up"></i> Resistor ?</label>
<select type="text" id="node-input-intype" style="width: 150px;">
<option value="tri">none</option>
<option value="up">pullup</option>
<option value="down">pulldown</option>
</select>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-read" style="width: 70%;">Read initial state of pin on deploy/restart ?</label>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
<div class="form-tips">Tip: Only Digital Input is supported - input must be 0 or 1.</div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Pi input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin. Requires the gpio command to work.</p>
<p>You may also enable the input pullup resistor or the pulldown resistor.</p>
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
</script>
<script type="text/javascript">
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio in',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.number() },
intype: { value: "in" },
read: { value:false }
},
inputs:0,
outputs:1,
icon: "rpi.png",
info: function() {
if ( Object.keys(pinsInUse).length !== 0 ) {
return "**Pins in use** : "+Object.keys(pinsInUse);
}
else { return ""; }
},
label: function() {
return this.name||"Pin: "+this.pin ;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var pinnow = this.pin;
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) {
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO5"));
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO6"));
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO12"));
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO13"));
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO19"));
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO16"));
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO26"));
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO20"));
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO21"));
$('#node-input-pin').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn");
}
pinnow = pinnew;
}
});
$("#node-input-intype").change(function() {
var newtype = $("#node-input-intype option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
}
});
}
});
</script>
<script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option>
<option value="3">3 - SDA1 </option>
<option value="5">5 - SCL1 </option>
<option value="7">7 - GPIO7</option>
<option value="8">8 - TxD </option>
<option value="10">10 - RxD </option>
<option value="11">11 - GPIO0</option>
<option value="12">12 - GPIO1</option>
<option value="13">13 - GPIO2</option>
<option value="15">15 - GPIO3</option>
<option value="16">16 - GPIO4</option>
<option value="18">18 - GPIO5</option>
<option value="19">19 - MOSI </option>
<option value="21">21 - MISO </option>
<option value="22">22 - GPIO6</option>
<option value="23">23 - SCLK </option>
<option value="24">24 - CE0 </option>
<option value="26">26 - CE1 </option>
</select>
&nbsp;<span id="pitype"></span>
</div>
<div class="form-row" id="node-set-pwm">
<label>&nbsp;&nbsp;&nbsp;&nbsp;Type</label>
<select id="node-input-out" style="width: 250px;">
<option value="out">Digital output</option>
<option value="pwm">PWM output</option>
</select>
</div>
<div class="form-row" id="node-set-tick">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-set" style="width: 70%;">Initialise pin state ?</label>
</div>
<div class="form-row" id="node-set-state">
<label for="node-input-level">&nbsp;</label>
<select id="node-input-level" style="width: 250px;">
<option value="0">initial level of pin - low (0)</option>
<option value="1">initial level of pin - high (1)</option>
</select>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
<div class="form-tips" id="dig-tip"><b>Tip</b>: For digital output - input must be 0 or 1.</div>
<div class="form-tips" id="pwm-tip"><b>Tip</b>: For PWM output - input must be between 0 to 1023.</div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Pi output node. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false). Requires the gpio command to work.</p>
<p>Will set the selected physical pin high or low depending on the value passed in.</p>
<p>The initial value of the pin at deploy time can also be set to 0 or 1.</p>
<p>When using PWM mode - expects an input value of a number 0 - 100.</p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
</script>
<script type="text/javascript">
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio out',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.number() },
set: { value:"" },
level: { value:"0" },
out: { value:"out" }
},
inputs:1,
outputs:0,
icon: "rpi.png",
info: function() {
if ( Object.keys(pinsInUse).length !== 0 ) {
return "**Pins in use** : "+Object.keys(pinsInUse);
}
else { return ""; }
},
align: "right",
label: function() {
if (this.out === "pwm") { return this.name || "PWM: "+this.pin; }
else { return this.name||"Pin: "+this.pin ; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var pinnow = this.pin;
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) {
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO5"));
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO6"));
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO12"));
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO13"));
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO19"));
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO16"));
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO26"));
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO20"));
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO21"));
$('#node-input-pin').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn");
}
pinnow = pinnew;
}
});
$("#node-input-out").change(function() {
var newtype = $("#node-input-out option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
}
});
var hidestate = function () {
if ($("#node-input-out").val() === "pwm") {
$('#node-set-tick').hide();
$('#node-set-state').hide();
$('#node-input-set').prop('checked', false);
$("#dig-tip").hide();
$("#pwm-tip").show();
}
else {
$('#node-set-tick').show();
$("#dig-tip").show();
$("#pwm-tip").hide();
}
};
$("#node-input-out").change(function () { hidestate(); });
hidestate();
var setstate = function () {
if ($('#node-input-set').is(":checked")) {
$("#node-set-state").show();
} else {
$("#node-set-state").hide();
}
};
$("#node-input-set").change(function () { setstate(); });
setstate();
}
});
</script>
<script type="text/x-red" data-template-name="rpi-mouse">
<div class="form-row">
<label for="node-input-butt"><i class="fa fa-circle"></i> Button</label>
<select type="text" id="node-input-butt" style="width: 250px;">
<option value="1">left</option>
<option value="2">right</option>
<option value="4">middle</option>
<option value="7">any</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi mouse button node. Generates a <b>msg.payload</b> with
either a 1 or 0 when the selected mouse button is pressed and released</p>
<p>Also sets <b>msg.button</b> to the code value, 1 = left, 2 = right, 4 = middle,
so you can work out which button or combination was pressed.</p>
<p>And sets <b>msg.topic</b> to <i>pi/mouse</i>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-mouse',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
butt: { value:"1",required:true }
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
var na = "Pi Mouse";
if (this.butt === "1") { na += " Left"; }
if (this.butt === "2") { na += " Right"; }
if (this.butt === "4") { na += " Middle"; }
return this.name||na;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,283 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var fs = require('fs');
var gpioCommand = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
//RED.log.info("Ignoring Raspberry Pi specific node.");
throw "Info : Ignoring Raspberry Pi specific node.";
}
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
RED.log.warn("Can't find Pi RPi.GPIO python library.");
throw "Warning : Can't find Pi RPi.GPIO python library.";
}
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
RED.log.error(gpioCommand+" needs to be executable.");
throw "Error : nrgpio must to be executable.";
}
// the magic to make python print stuff immediately
process.env.PYTHONUNBUFFERED = 1;
var pinsInUse = {};
var pinTypes = {"out":"digital output", "tri":"input", "up":"input with pull up", "down":"input with pull down", "pwm":"PWM output"};
function GPIOInNode(n) {
RED.nodes.createNode(this,n);
this.buttonState = -1;
this.pin = n.pin;
this.intype = n.intype;
this.read = n.read || false;
if (this.read) { this.buttonState = -2; }
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.intype;
}
else {
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
}
if (node.pin !== undefined) {
if (node.intype === "tri") {
node.child = spawn(gpioCommand, ["in",node.pin]);
} else {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.child.stdout.on('data', function (data) {
data = data.toString().trim();
if (data.length > 0) {
if (node.buttonState !== -1) {
node.send({ topic:"pi/"+node.pin, payload:Number(data) });
}
node.buttonState = data;
node.status({fill:"green",shape:"dot",text:data});
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
}
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
else { node.error('error: ' + err.errno); }
});
}
else {
node.warn("Invalid GPIO pin: "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
function GPIOOutNode(n) {
RED.nodes.createNode(this,n);
this.pin = n.pin;
this.set = n.set || false;
this.level = n.level || 0;
this.out = n.out || "out";
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.out;
}
else {
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
}
function inputlistener(msg) {
if (msg.payload === "true") { msg.payload = true; }
if (msg.payload === "false") { msg.payload = false; }
var out = Number(msg.payload);
var limit = 1;
if (node.out === "pwm") { limit = 100; }
if ((out >= 0) && (out <= limit)) {
if (RED.settings.verbose) { node.log("out: "+msg.payload); }
if (node.child !== null) {
node.child.stdin.write(msg.payload+"\n");
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
}
else {
node.error("nrpgio python command not running");
node.status({fill:"red",shape:"ring",text:"not running"});
}
}
else { node.warn("Invalid input: "+out); }
}
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
} else {
node.child = spawn(gpioCommand, [node.out,node.pin]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.on("input", inputlistener);
node.child.stdout.on('data', function (data) {
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
else { node.error('error: ' + err.errno); }
});
}
else {
node.warn("Invalid GPIO pin: "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
var pitype = { type:"" };
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
if (err) {
RED.log.info('Version command failed for some reason.');
}
else {
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
else { RED.log.info("Saw Pi Type",stdout.trim()); }
}
});
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
function PiMouseNode(n) {
RED.nodes.createNode(this,n);
this.butt = n.butt || 7;
var node = this;
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"OK"});
node.child.stdout.on('data', function (data) {
data = Number(data);
if (data === 0) { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
else { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
else if (err.errno === "EACCES") { node.error('nrgpio ommand not executable'); }
else { node.error('error: ' + err.errno); }
});
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
RED.nodes.registerType("rpi-mouse",PiMouseNode);
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype);
});
RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pinsInUse);
});
}

16
nodes/core/hardware/nrgpio Executable file
View File

@@ -0,0 +1,16 @@
#
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
BASEDIR=$(dirname $0)
sudo python -u $BASEDIR/nrgpio.py $@

197
nodes/core/hardware/nrgpio.py Executable file
View File

@@ -0,0 +1,197 @@
#!/usr/bin/python
#
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Import library functions we need
import RPi.GPIO as GPIO
import sys
bounce = 20 # bounce time in mS to apply
if sys.version_info >= (3,0):
print("Sorry - currently only configured to work with python 2.x")
sys.exit(1)
if len(sys.argv) > 1:
cmd = sys.argv[1].lower()
pin = int(sys.argv[2])
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
if cmd == "pwm":
#print "Initialised pin "+str(pin)+" to PWM"
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.start(0)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
p.ChangeDutyCycle(float(data))
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
elif cmd == "buzz":
#print "Initialised pin "+str(pin)+" to Buzz"
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.stop()
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
elif float(data) == 0:
p.stop()
else:
p.start(50)
p.ChangeFrequency(float(data))
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
elif cmd == "out":
#print "Initialised pin "+str(pin)+" to OUT"
GPIO.setup(pin,GPIO.OUT)
if len(sys.argv) == 4:
GPIO.output(pin,int(sys.argv[3]))
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
data = int(data)
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except:
data = 0
if data != 0:
data = 1
GPIO.output(pin,data)
elif cmd == "in":
#print "Initialised pin "+str(pin)+" to IN"
def handle_callback(chan):
print GPIO.input(chan)
if len(sys.argv) == 4:
if sys.argv[3].lower() == "up":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
elif sys.argv[3].lower() == "down":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_DOWN)
else:
GPIO.setup(pin,GPIO.IN)
else:
GPIO.setup(pin,GPIO.IN)
print GPIO.input(pin)
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
elif cmd == "byte":
#print "Initialised BYTE mode - "+str(pin)+
list = [7,11,13,12,15,16,18,22]
GPIO.setup(list,GPIO.OUT)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
data = int(data)
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup()
sys.exit(0)
except:
data = 0
for bit in range(8):
if pin == 1:
mask = 1 << (7 - bit)
else:
mask = 1 << bit
GPIO.output(list[bit], data & mask)
elif cmd == "borg":
#print "Initialised BORG mode - "+str(pin)+
GPIO.setup(11,GPIO.OUT)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(15,GPIO.OUT)
r = GPIO.PWM(11, 100)
g = GPIO.PWM(13, 100)
b = GPIO.PWM(15, 100)
r.start(0)
g.start(0)
b.start(0)
while True:
try:
data = raw_input()
if 'close' in data:
sys.exit(0)
c = data.split(",")
r.ChangeDutyCycle(float(c[0]))
g.ChangeDutyCycle(float(c[1]))
b.ChangeDutyCycle(float(c[2]))
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
GPIO.cleanup()
sys.exit(0)
except:
data = 0
elif cmd == "rev":
print GPIO.RPI_REVISION
elif cmd == "ver":
print GPIO.VERSION
elif cmd == "mouse": # catch mice button events
file = open( "/dev/input/mice", "rb" )
oldbutt = 0
def getMouseEvent():
global oldbutt
global pin
buf = file.read(3)
pin = pin & 0x07
button = ord( buf[0] ) & pin # mask out just the required button(s)
if button != oldbutt: # only send if changed
oldbutt = button
print button
while True:
try:
getMouseEvent()
except:
file.close()
sys.exit(0)
else:
print "Bad parameters - {in|out|pwm} {pin} {value|up|down}"

157
nodes/core/io/10-mqtt.html Normal file
View File

@@ -0,0 +1,157 @@
<!--
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="mqtt in">
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
<input type="text" id="node-input-broker">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="mqtt in">
<p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
<p><b>msg.payload</b> is a String.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('mqtt in',{
category: 'input',
defaults: {
name: {value:""},
topic: {value:"",required:true},
broker: {type:"mqtt-broker", required:true}
},
color:"#d8bfd8",
inputs:0,
outputs:1,
icon: "bridge.png",
label: function() {
return this.name||this.topic||"mqtt";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="mqtt out">
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label>
<input type="text" id="node-input-broker">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label for="node-input-qos"><i class="fa fa-empire"></i> QoS</label>
<select id="node-input-qos" style="width:125px !important">
<option value=""></option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;Retain &nbsp;<select id="node-input-retain" style="width:125px !important">
<option value=""></option>
<option value="false">false</option>
<option value="true">true</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: Leave topic, qos or retain blank if you want to set them via msg properties.</div>
</script>
<script type="text/x-red" data-help-name="mqtt out">
<p>Connects to a MQTT broker and publishes <b>msg.payload</b> either to the <b>msg.topic</b> or to the topic specified in the edit window. The value in the edit window has precedence.</p>
<p>Likewise QoS and/or retain values in the edit panel will overwrite any <b>msg.qos</b> and <b>msg.retain</b> properties. If nothing is set they default to <i>0</i> and <i>false</i> respectively.</p>
<p>If <b>msg.payload</b> contains an object it will be stringified before being sent.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('mqtt out',{
category: 'output',
defaults: {
name: {value:""},
topic: {value:""},
qos: {value:""},
retain: {value:""},
broker: {type:"mqtt-broker", required:true}
},
color:"#d8bfd8",
inputs:1,
outputs:0,
icon: "bridge.png",
align: "right",
label: function() {
return this.name||this.topic||"mqtt";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="mqtt-broker">
<div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> Broker</label>
<input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
</div>
<div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> Client ID</label>
<input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated">
</div>
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-config-input-user">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-config-input-password">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('mqtt-broker',{
category: 'config',
defaults: {
broker: {value:"",required:true},
port: {value:1883,required:true,validate:RED.validators.number()},
clientid: { value:"" }
},
credentials: {
user: {type:"text"},
password: {type: "password"}
},
label: function() {
if (this.broker == "") { this.broker = "localhost"; }
return (this.clientid?this.clientid+"@":"")+this.broker+":"+this.port;
}
});
</script>

121
nodes/core/io/10-mqtt.js Normal file
View File

@@ -0,0 +1,121 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var connectionPool = require("./lib/mqttConnectionPool");
var isUtf8 = require('is-utf8');
function MQTTBrokerNode(n) {
RED.nodes.createNode(this,n);
this.broker = n.broker;
this.port = n.port;
this.clientid = n.clientid;
if (this.credentials) {
this.username = this.credentials.user;
this.password = this.credentials.password;
}
}
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
credentials: {
user: {type:"text"},
password: {type: "password"}
}
});
function MQTTInNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.broker = n.broker;
this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this;
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
if (isUtf8(payload)) { payload = payload.toString(); }
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
if ((node.brokerConfig.broker === "localhost")||(node.brokerConfig.broker === "127.0.0.1")) {
msg._topic = topic;
}
node.send(msg);
});
this.client.on("connectionlost",function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
});
this.client.on("connect",function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
this.client.connect();
} else {
this.error("missing broker configuration");
}
this.on('close', function() {
if (this.client) {
this.client.disconnect();
}
});
}
RED.nodes.registerType("mqtt in",MQTTInNode);
function MQTTOutNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.qos = n.qos || null;
this.retain = n.retain;
this.broker = n.broker;
this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this;
this.on("input",function(msg) {
if (msg.qos) {
msg.qos = parseInt(msg.qos);
if ((msg.qos !== 0) && (msg.qos !== 1) && (msg.qos !== 2)) {
msg.qos = null;
}
}
msg.qos = Number(node.qos || msg.qos || 0);
msg.retain = node.retain || msg.retain || false;
msg.retain = ((msg.retain === true) || (msg.retain === "true")) || false;
if (node.topic) {
msg.topic = node.topic;
}
if ((msg.hasOwnProperty("topic")) && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
this.client.publish(msg); // send the message
}
else { node.warn("Invalid topic specified"); }
});
this.client.on("connectionlost",function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
});
this.client.on("connect",function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
this.client.connect();
} else {
this.error("missing broker configuration");
}
this.on('close', function() {
if (this.client) {
this.client.disconnect();
}
});
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
}

View File

@@ -0,0 +1,272 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="http in">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
<select type="text" id="node-input-method" style="width:72%;">
<option value="get">GET</option>
<option value="post">POST</option>
<option value="put">PUT</option>
<option value="delete">DELETE</option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> url</label>
<input type="text" id="node-input-url" placeholder="/url">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div id="node-input-tip" class="form-tips">The url will be relative to <code><span id="node-input-path"></span></code>.</div>
</script>
<script type="text/x-red" data-help-name="http in">
<p>Provides an input node for http requests, allowing the creation of simple web services.</p>
<p>The resulting message has the following properties:
<ul>
<li>msg.req : <a href="http://expressjs.com/api.html#req">http request</a></li>
<li>msg.res : <a href="http://expressjs.com/api.html#res">http response</a></li>
</ul>
</p>
<p>For POST/PUT requests, the body is available under <code>msg.req.body</code>. This
uses the <a href="http://expressjs.com/api.html#bodyParser">Express bodyParser middleware</a> to parse the content to a JSON object.
</p>
<p>
By default, this expects the body of the request to be url encoded:
<pre>foo=bar&amp;this=that</pre>
</p>
<p>
To send JSON encoded data to the node, the content-type header of the request must be set to
<code>application/json</code>.
</p>
<p>
<b>Note: </b>This node does not send any response to the http request. This should be done with
a subsequent HTTP Response node, or Function node.
In the case of a Function node, the <a href="http://expressjs.com/api.html#res">Express response documentation</a>
describes how this should be done. For example:
<pre>msg.res.send(200, 'Thanks for the request ');<br/>return msg;</pre>
</p>
</script>
<script type="text/x-red" data-template-name="http response">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">The messages sent to this node <b>must</b> originate from an <i>http input</i> node</div>
</script>
<script type="text/x-red" data-help-name="http response">
<p>Sends responses back to http requests received from an HTTP Input node.</p>
<p>The response can be customised using the following message properties:</p>
<ul>
<li><code>payload</code> is sent as the body of the response</li>
<li><code>statusCode</code>, if set, is used as the response status code (default: 200)</li>
<li><code>headers</code>, if set, should be an object containing field/value
pairs to be added as response headers.</li>
</ul>
</script>
<script type="text/x-red" data-template-name="http request">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label>
<select type="text" id="node-input-method" style="width:72%;">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="use">- set by msg.method -</option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> URL</label>
<input type="text" id="node-input-url" placeholder="http://">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useAuth" style="width: 70%;">Use basic authentication ?</label>
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-user">
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-input-password">
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> Return</label>
<select type="text" id="node-input-ret" style="width:72%;">
<option value="txt">a UTF-8 string</option>
<option value="bin">a binary buffer</option>
<option value="obj">a parsed JSON object</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="tip-json" hidden>Tip: If the JSON parse fails the fetched string is returned as-is.</div>
</script>
<script type="text/x-red" data-help-name="http request">
<p>Provides a node for making http requests.</p>
<p>The URL and HTTP method can be configured in the node, if they are left blank they should be set in an incoming message on <code>msg.url</code> and <code>msg.method</code>:</p>
<ul>
<li><code>url</code>, if set, is used as the url of the request. Must start with http: or https:</li>
<li><code>method</code>, if set, is used as the HTTP method of the request.
Must be one of <code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>PATCH</code> or <code>DELETE</code> (default: GET)</li>
<li><code>headers</code>, if set, should be an object containing field/value
pairs to be added as request headers</li>
<li><code>payload</code> is sent as the body of the request</li>
</ul>
<p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_new">mustache-style</a> tags. These allow the
url to be constructed using values of the incoming message. For example, if the url is set to
<code>example.com/{{topic}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.</p>
<p>
The output message contains the following properties:
<ul>
<li><code>payload</code> is the body of the response</li>
<li><code>statusCode</code> is the status code of the response, or the error code if the request could not be completed</li>
<li><code>headers</code> is an object containing the response headers</li>
</ul>
</script>
<script type="text/javascript">
RED.nodes.registerType('http in',{
category: 'input',
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""},
url: {value:"",required:true},
method: {value:"get",required:true}
},
inputs:0,
outputs:1,
icon: "white-globe.png",
label: function() {
if (this.name) {
return this.name;
} else if (this.url) {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) != "/") {
root = root+"/";
}
if (this.url.charAt(0) == "/") {
root += this.url.slice(1);
} else {
root += this.url;
}
return "["+this.method+"] "+root;
} else {
return "http";
}
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) == "/") {
root = root.slice(0,-1);
}
if (root == "") {
$("#node-input-tip").hide();
} else {
$("#node-input-path").html(root);
$("#node-input-tip").show();
}
//document.getElementById("node-config-wsdocpath").innerHTML=
}
});
RED.nodes.registerType('http response',{
category: 'output',
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""}
},
inputs:1,
outputs:0,
align: "right",
icon: "white-globe.png",
label: function() {
return this.name||"http";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
RED.nodes.registerType('http request',{
category: 'function',
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""},
method:{value:"GET"},
ret: {value:"txt"},
url:{value:""},
//user -> credentials
//pass -> credentials
},
credentials: {
user: {type:"text"},
password: {type: "password"}
},
inputs:1,
outputs:1,
align: "right",
icon: "white-globe.png",
label: function() {
return this.name||"http request";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.credentials.user || this.credentials.has_password) {
$('#node-input-useAuth').prop('checked', true);
$(".node-input-useAuth-row").show();
} else {
$('#node-input-useAuth').prop('checked', false);
$(".node-input-useAuth-row").hide();
}
$("#node-input-useAuth").change(function() {
if ($(this).is(":checked")) {
$(".node-input-useAuth-row").show();
} else {
$(".node-input-useAuth-row").hide();
$('#node-input-user').val('');
$('#node-input-password').val('');
}
});
$("#node-input-ret").change(function() {
if ($("#node-input-ret").val() === "obj") {
$("#tip-json").show();
} else {
$("#tip-json").hide();
}
});
}
});
</script>

299
nodes/core/io/21-httpin.js Normal file
View File

@@ -0,0 +1,299 @@
/**
* Copyright 2013,2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var http = require("follow-redirects").http;
var https = require("follow-redirects").https;
var urllib = require("url");
var express = require("express");
var getBody = require('raw-body');
var mustache = require("mustache");
var querystring = require("querystring");
var cors = require('cors');
var jsonParser = express.json();
var urlencParser = express.urlencoded();
var onHeaders = require('on-headers');
function rawBodyParser(req, res, next) {
if (req._body) { return next(); }
req.body = "";
req._body = true;
getBody(req, {
limit: '1mb',
length: req.headers['content-length'],
encoding: 'utf8'
}, function (err, buf) {
if (err) { return next(err); }
req.body = buf;
next();
});
}
function HTTPIn(n) {
RED.nodes.createNode(this,n);
if (RED.settings.httpNodeRoot !== false) {
this.url = n.url;
this.method = n.method;
var node = this;
this.errorHandler = function(err,req,res,next) {
node.warn(err);
res.send(500);
};
this.callback = function(req,res) {
if (node.method == "post") {
node.send({req:req,res:res,payload:req.body});
} else if (node.method == "get") {
node.send({req:req,res:res,payload:req.query});
} else {
node.send({req:req,res:res});
}
};
var corsHandler = function(req,res,next) { next(); }
if (RED.settings.httpNodeCors) {
corsHandler = cors(RED.settings.httpNodeCors);
RED.httpNode.options(this.url,corsHandler);
}
var metricsHandler = function(req,res,next) { next(); }
if (this.metric()) {
metricsHandler = function(req, res, next) {
var startAt = process.hrtime();
onHeaders(res, function() {
if(res._msgId) {
var diff = process.hrtime(startAt);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
var metricResponseTime = ms.toFixed(3);
var metricContentLength = res._headers["content-length"];
//assuming that _id has been set for res._metrics in HttpOut node!
node.metric("response.time.millis", {_id:res._msgId} , metricResponseTime);
node.metric("response.content-length.bytes", {_id:res._msgId} , metricContentLength);
}
});
next();
};
}
if (this.method == "get") {
RED.httpNode.get(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
} else if (this.method == "post") {
RED.httpNode.post(this.url,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
} else if (this.method == "put") {
RED.httpNode.put(this.url,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
} else if (this.method == "delete") {
RED.httpNode.delete(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
}
this.on("close",function() {
var routes = RED.httpNode.routes[this.method];
for (var i = 0; i<routes.length; i++) {
if (routes[i].path == this.url) {
routes.splice(i,1);
//break;
}
}
if (RED.settings.httpNodeCors) {
var routes = RED.httpNode.routes['options'];
if (routes) {
for (var j = 0; j<routes.length; j++) {
if (routes[j].path == this.url) {
routes.splice(j,1);
//break;
}
}
}
}
});
} else {
this.warn("Cannot create http-in node when httpNodeRoot set to false");
}
}
RED.nodes.registerType("http in",HTTPIn);
function HTTPOut(n) {
RED.nodes.createNode(this,n);
var node = this;
this.on("input",function(msg) {
if (msg.res) {
if (msg.headers) {
msg.res.set(msg.headers);
}
var statusCode = msg.statusCode || 200;
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
msg.res.jsonp(statusCode,msg.payload);
} else {
if (msg.res.get('content-length') == null) {
var len;
if (msg.payload == null) {
len = 0;
} else if (typeof msg.payload == "number") {
len = Buffer.byteLength(""+msg.payload);
} else {
len = Buffer.byteLength(msg.payload);
}
msg.res.set('content-length', len);
}
msg.res._msgId = msg._id;
msg.res.send(statusCode,msg.payload);
}
} else {
node.warn("No response object");
}
});
}
RED.nodes.registerType("http response",HTTPOut);
function HTTPRequest(n) {
RED.nodes.createNode(this,n);
var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET";
this.ret = n.ret || "txt";
var node = this;
this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"requesting"});
var url;
if (msg.url) {
if (n.url && (n.url !== msg.url)) {
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
}
url = msg.url;
} else if (isTemplatedUrl) {
url = mustache.render(nodeUrl,msg);
} else {
url = nodeUrl;
}
// url must start http:// or https:// so assume http:// if not set
if (!((url.indexOf("http://")===0) || (url.indexOf("https://")===0))) {
url = "http://"+url;
}
var method;
if (msg.method) { // if method set in msg
if (n.method && (n.method !== "use")) { // warn if override option not set
node.warn("Deprecated: msg properties should not override fixed node properties. Use explicit override option. See bit.ly/nr-override-msg-props");
}
method = msg.method.toUpperCase(); // but use it anyway
} else {
if (n.method !== "use") {
method = nodeMethod.toUpperCase(); // otherwise use the selected method
} else { // unless they selected override
method = "GET"; // - in which case default to GET
}
}
var opts = urllib.parse(url);
opts.method = method;
opts.headers = {};
if (msg.headers) {
for (var v in msg.headers) {
if (msg.headers.hasOwnProperty(v)) {
var name = v.toLowerCase();
if (name !== "content-type" && name !== "content-length") {
// only normalise the known headers used later in this
// function. Otherwise leave them alone.
name = v;
}
opts.headers[name] = msg.headers[v];
}
}
}
if (this.credentials && this.credentials.user) {
opts.auth = this.credentials.user+":"+(this.credentials.password||"");
}
var payload = null;
if (msg.payload && (method == "POST" || method == "PUT" || method == "PATCH" ) ) {
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
payload = msg.payload;
} else if (typeof msg.payload == "number") {
payload = msg.payload+"";
} else {
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
payload = querystring.stringify(msg.payload);
} else {
payload = JSON.stringify(msg.payload);
if (opts.headers['content-type'] == null) {
opts.headers['content-type'] = "application/json";
}
}
}
if (opts.headers['content-length'] == null) {
opts.headers['content-length'] = Buffer.byteLength(payload);
}
}
var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.payload = "";
msg.url = url;
res.on('data',function(chunk) {
msg.payload += chunk;
});
res.on('end',function() {
if (node.metric()) {
// Calculate request time
var diff = process.hrtime(preRequestTimestamp);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
var metricRequestDurationMillis = ms.toFixed(3);
node.metric("duration.millis", msg, metricRequestDurationMillis);
if(res.client && res.client.bytesRead) {
node.metric("size.bytes", msg, res.client.bytesRead);
}
}
if (node.ret === "bin") {
msg.payload = new Buffer(msg.payload,"binary");
}
else if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); }
catch(e) { node.warn("JSON parse error"); }
}
node.send(msg);
node.status({});
});
});
req.on('error',function(err) {
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.send(msg);
node.status({fill:"red",shape:"ring",text:err.code});
});
if (payload) {
req.write(payload);
}
req.end();
});
}
RED.nodes.registerType("http request",HTTPRequest,{
credentials: {
user: {type:"text"},
password: {type: "password"}
}
});
}

View File

@@ -0,0 +1,279 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/javascript">
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
</script>
<!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket input node.</p>
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
The socket can be configured to expect a properly formed JSON string, in which
case it will parse the JSON and send on the resulting object as the entire message.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket in',{
category: 'input',
defaults: {
name: {value:""},
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:0,
outputs:1,
icon: "white-globe.png",
labelStyle: function() {
return this.name?"node_label_italic":"";
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
</script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p>
<p>If the message arriving at this node started at a WebSocket In node, the message
will be sent back to the client that triggered the flow. Otherwise, the message
will be broadcast to all connected clients.</p>
<p>If you want to broadcast a message that started at a WebSocket In node, you
should delete the <b>msg._session</b> property within the flow</p>.
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket out',{
category: 'output',
defaults: {
name: {value:""},
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:1,
outputs:0,
icon: "white-globe.png",
align: "right",
labelStyle: function() {
return this.name?"node_label_italic":"";
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
</script>
<!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-config-input-path" placeholder="/ws/example">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option>
</select>
</div>
<div class="form-tips">
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
The listener can be configured to send or receive the entire message object as a JSON formatted string.
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
</div>
</script>
<script type="text/x-red" data-help-name="websocket-listener">
<p>This configuration node creates a WebSocket Server using the specified path</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-listener',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) != "/") {
root = root+"/";
}
if (this.path.charAt(0) == "/") {
root += this.path.slice(1);
} else {
root += this.path;
}
return root;
},
oneditprepare: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) == "/") {
root = root.slice(0,-1);
}
if (root == "") {
$("#node-config-ws-tip").hide();
} else {
$("#node-config-ws-path").html(root);
$("#node-config-ws-tip").show();
}
}
});
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option>
</select>
</div>
<div class="form-tips">
<p>URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.</p>
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
The client can be configured to send or receive the entire message object as a JSON formatted string.
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-client',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
return this.path;
}
});
</script>

View File

@@ -0,0 +1,204 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var ws = require("ws"),
inspect = require("sys").inspect;
// A node red node that sets up a local websocket server
function WebSocketListenerNode(n) {
// Create a RED node
RED.nodes.createNode(this,n);
var node = this;
// Store local copies of the node configuration (as defined in the .html)
node.path = n.path;
node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events
node._clients = {};
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
node._clients[id] = socket;
socket.on('close',function() {
delete node._clients[id];
});
socket.on('message',function(data,flags){
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
node.warn("An error occured on the ws connection: "+inspect(err));
});
}
// match absolute url
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
if(node.isServer)
{
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server
node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener){
if(event == "error" || event == "upgrade" || event == "listening"){
node._serverListeners[event] = listener;
}
}
RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path});
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events
RED.server.removeListener('newListener',storeListener);
node.server.on('connection', handleConnection);
}
else
{
// Connect to remote endpoint
var socket = new ws(node.path);
node.server = socket; // keep for closing
handleConnection(socket);
}
node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253
// Remove listeners from RED.server
var listener = null;
for(var event in node._serverListeners) {
if (node._serverListeners.hasOwnProperty(event)) {
listener = node._serverListeners[event];
if(typeof listener === "function"){
RED.server.removeListener(event,listener);
}
}
}
node._serverListeners = {};
node.server.close();
node._inputNodes = [];
});
}
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){
this._inputNodes.push(handler);
}
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
var msg;
if (this.wholemsg) {
try {
msg = JSON.parse(data);
}
catch(err) {
msg = { payload:data };
}
} else {
msg = {
payload:data
};
}
msg._session = {type:"websocket",id:id};
for (var i = 0; i < this._inputNodes.length; i++) {
this._inputNodes[i].send(msg);
}
}
WebSocketListenerNode.prototype.broadcast = function(data){
try {
if(this.isServer) {
for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data);
}
}
else {
this.server.send(data);
}
}
catch(e) { // swallow any errors
this.warn("ws:"+i+" : "+e);
}
}
WebSocketListenerNode.prototype.reply = function(id,data) {
var session = this._clients[id];
if (session) {
try {
session.send(data);
}
catch(e) { // swallow any errors
}
}
}
function WebSocketInNode(n) {
RED.nodes.createNode(this,n);
this.server = (n.client)?n.client:n.server;
var node = this;
this.serverConfig = RED.nodes.getNode(this.server);
if (this.serverConfig) {
this.serverConfig.registerInputNode(this);
} else {
this.error("Missing server configuration");
}
}
RED.nodes.registerType("websocket in",WebSocketInNode);
function WebSocketOutNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) {
this.error("Missing server configuration");
}
this.on("input", function(msg) {
var payload;
if (this.serverConfig.wholemsg) {
delete msg._session;
payload = JSON.stringify(msg);
} else {
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
payload = RED.util.ensureString(msg.payload);
}
else {
payload = msg.payload;
}
}
if (msg._session && msg._session.type == "websocket") {
node.serverConfig.reply(msg._session.id,payload);
} else {
node.serverConfig.broadcast(payload,function(error){
if (!!error) {
node.warn("An error occurred while sending:" + inspect(error));
}
});
}
});
}
RED.nodes.registerType("websocket out",WebSocketOutNode);
}

View File

@@ -16,20 +16,27 @@
<script type="text/x-red" data-template-name="watch">
<div class="form-row node-input-filename">
<label for="node-input-files"><i class="icon-file"></i> File(s)</label>
<label for="node-input-files"><i class="fa fa-file"></i> File(s)</label>
<input type="text" id="node-input-files" placeholder="File(s) or Directory">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div id="node-input-tip" class="form-tips">On Windows you must use double back-slashes \\ in any directory names.</div>
</script>
<script type="text/x-red" data-help-name="watch">
<p>Watches a file or directory for any changes.</p>
<p>You can enter a list of comma separated files, or directories if you like. You will need to put " around any that have spaces in.</p>
<p>The filename of the file that actually changed is put into <b>msg.payload</b>, while a stringified version of the watched criteria is returned in <b>msg.topic</b>.</p>
<p>Of course in Linux, <i>everything</i> could be a file and thus watched...</p>
<p>Watches a directory or file for changes.</p>
<p>You can enter a list of comma separated directories and/or files. You will
need to put quotes "..." around any that have spaces in.</p>
<p>On Windows you must use double back-slashes \\ in any directory names.</p>
<p>The full filename of the file that actually changed is put into <b>msg.payload</b>,
while a stringified version of the watch list is returned in <b>msg.topic</b>.</p>
<p><b>msg.file</b> contains just the short filename of the file that changed.</p>
<p>Of course in Linux, <i>everything</i> is a file and thus can be watched...</p>
<p><b>Note: </b>The directory or file must exist in order to be watched. If the file
or directory gets deleted it may no longer be monitored even if it gets re-created.</p>
</script>
<script type="text/javascript">

51
nodes/core/io/23-watch.js Normal file
View File

@@ -0,0 +1,51 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var Notify = require("fs.notify");
var fs = require("fs");
var sep = require("path").sep;
function WatchNode(n) {
RED.nodes.createNode(this,n);
this.files = n.files.split(",");
for (var f =0; f < this.files.length; f++) {
this.files[f] = this.files[f].trim();
}
this.p = (this.files.length == 1) ? this.files[0] : JSON.stringify(this.files);
var node = this;
var notifications = new Notify(node.files);
notifications.on('change', function (file, event, path) {
try {
if (fs.statSync(path).isDirectory()) { path = path + sep + file; }
} catch(e) { }
var msg = { payload: path, topic: node.p, file: file };
node.send(msg);
});
notifications.on('error', function (error, path) {
node.warn(error);
});
this.close = function() {
notifications.close();
}
}
RED.nodes.registerType("watch",WatchNode);
}

View File

@@ -0,0 +1,266 @@
<!--
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="serial in">
<div class="form-row node-input-serial">
<label for="node-input-serial"><i class="fa fa-random"></i> Serial Port</label>
<input type="text" id="node-input-serial">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="serial in">
<p>Reads data from a local serial port.</p>
<p>Can either <ul><li>wait for a "split" character (default \n). Also accepts hex notation (0x0a).</li>
<li>Wait for a timeout in milliseconds for the first character received</li>
<li>Wait to fill a fixed sized buffer</li></ul></p>
<p>It then outputs <b>msg.payload</b> as either a UTF8 ascii string or a binary Buffer object.</p>
<p>If no split character is specified, or a timeout or buffer size of 0, then a stream of single characters is sent - again either as ascii chars or size 1 binary buffers.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('serial in',{
category: 'input',
defaults: {
name: {name:""},
serial: {type:"serial-port",required:true}
},
color:"BurlyWood",
inputs:0,
outputs:1,
icon: "serial.png",
label: function() {
var serialNode = RED.nodes.node(this.serial);
return this.name||(serialNode?serialNode.label().split(":")[0]:"serial");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="serial out">
<div class="form-row node-input-serial">
<label for="node-input-serial"><i class="fa fa-random"></i> Serial Port</label>
<input type="text" id="node-input-serial">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="serial out">
<p>Provides a connection to an outbound serial port.</p>
<p>Only the <b>msg.payload</b> is sent.</p>
<p>Optionally the new line character used to split the input can be appended to every message sent out to the serial port.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('serial out',{
category: 'output',
defaults: {
name: {name:""},
serial: {type:"serial-port",required:true}
},
color:"BurlyWood",
inputs:1,
outputs:0,
icon: "serial.png",
align: "right",
label: function() {
var serialNode = RED.nodes.node(this.serial);
return this.name||(serialNode?serialNode.label().split(":")[0]:"serial");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="serial-port">
<div class="form-row">
<label for="node-config-input-serialport"><i class="fa fa-random"></i> Serial Port</label>
<input type="text" id="node-config-input-serialport" style="width:60%;" placeholder="/dev/ttyUSB0"/>
<a id="node-config-lookup-serial" class="btn"><i id="node-config-lookup-serial-icon" class="fa fa-search"></i></a>
</div>
<div class="form-row">
<table><tr>
<td width="102px"><i class="fa fa-wrench"></i> Settings</td>
<td width="100px">Baud Rate</td>
<td width="80px">Data Bits</td>
<td width="80px">Parity</td>
<td width="80px">Stop Bits</td>
</tr><tr><td>&nbsp;</td>
<td>
<select type="text" id="node-config-input-serialbaud" style="width: 100px;">
<option value="115200">115200</option>
<option value="57600">57600</option>
<option value="38400">38400</option>
<option value="19200">19200</option>
<option value="9600">9600</option>
<option value="4800">4800</option>
<option value="2400">2400</option>
<option value="1800">1800</option>
<option value="1200">1200</option>
<option value="600">600</option>
<option value="300">300</option>
<option value="200">200</option>
<option value="150">150</option>
<option value="134">134</option>
<option value="110">110</option>
<option value="75">75</option>
<option value="50">50</option>
</select>
</td><td>
<select type="text" id="node-config-input-databits" style="width: 80px;">
<option value="8">8</option>
<option value="7">7</option>
<option value="6">6</option>
<option value="5">5</option>
</select>
</td><td>
<select type="text" id="node-config-input-parity" style="width: 80px;">
<option value="none">None</option>
<option value="even">Even</option>
<option value="mark">Mark</option>
<option value="odd">Odd</option>
<option value="space">Space</option>
</select>
</td><td>
<select type="text" id="node-config-input-stopbits" style="width: 80px;">
<option value="2">2</option>
<option value="1">1</option>
</select>
</td>
</tr></table><br/>
<div class="form-row">
<label><i class="fa fa-sign-in"></i> Input</label>
</div>
<div class="form-row" style="padding-left: 10px;">
Split input
<select type="text" id="node-config-input-out" style="margin-left: 5px; width:200px;">
<option value="char">on the character</option>
<option value="time">after a timeout of</option>
<option value="count">into fixed lengths of</option>
</select>
<input type="text" id="node-config-input-newline" style="width:50px;">
<span id="node-units"></span>
</div>
<div class="form-row" style="padding-left: 10px;">
and deliver
<select type="text" id="node-config-input-bin" style="margin-left: 5px; width: 150px;">
<option value="false">ascii strings</option>
<option value="bin">binary buffers</option>
</select>
</div>
<br/>
<div id="node-config-addchar">
<div class="form-row">
<label><i class="fa fa-sign-out"></i> Output</label>
</div>
<div class="form-row">
<input style="width: 30px;margin-left: 10px; vertical-align: top;" type="checkbox" id="node-config-input-addchar"><label style="width: auto;" for="node-config-input-addchar">add split character to output messages</label>
</div>
</div>
<div class="form-tips" id="tip-split">Tip: the "Split on" character is used to split the input into separate messages. It can also be added to every message sent out to the serial port.</div>
<div class="form-tips" id="tip-bin" hidden>Tip: In timeout mode timeout starts from arrival of first character.</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('serial-port',{
category: 'config',
defaults: {
//name: {value:""},
serialport: {value:"",required:true},
serialbaud: {value:57600,required:true},
databits: {value:8,required:true},
parity: {value:"none",required:true},
stopbits: {value:1,required:true},
newline: {value:"\\n"},
bin: {value:"false"},
out: {value:"char"},
addchar: {value:false}
},
label: function() {
this.serialbaud = this.serialbaud || 57600;
this.databits = this.databits || 8;
this.parity = this.parity || 'none';
this.stopbits = this.stopbits || 1;
return this.serialport+":"+this.serialbaud+"-"+this.databits+this.parity.charAt(0).toUpperCase()+this.stopbits;
},
oneditprepare: function() {
var previous = null;
$("#node-config-input-out").on('focus', function () { previous = this.value; }).change(function() {
if (previous == null) { previous = $("#node-config-input-out").val(); }
if ($("#node-config-input-out").val() == "char") {
if (previous != "char") { $("#node-config-input-newline").val("\\n"); }
$("#node-units").text("");
$("#node-config-addchar").show();
$("#tip-split").show();
$("#tip-bin").hide();
}
else if ($("#node-config-input-out").val() == "time") {
if (previous != "time") { $("#node-config-input-newline").val("0"); }
$("#node-units").text("ms");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").show();
}
else {
if (previous != "count") { $("#node-config-input-newline").val(""); }
$("#node-units").text("chars");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").hide();
}
});
try {
$("#node-config-input-serialport").autocomplete( "destroy" );
} catch(err) {
}
$("#node-config-lookup-serial").click(function() {
//$("#node-config-lookup-serial-icon").removeClass('fa fa-search');
//$("#node-config-lookup-serial-icon").addClass('fa fa-spinner');
$("#node-config-lookup-serial").addClass('disabled');
$.getJSON('serialports',function(data) {
//$("#node-config-lookup-serial-icon").addClass('fa fa-search');
//$("#node-config-lookup-serial-icon").removeClass('fa fa-spinner');
$("#node-config-lookup-serial").removeClass('disabled');
var ports = [];
$.each(data, function(i, port){
ports.push(port.comName);
});
$("#node-config-input-serialport").autocomplete({
source:ports,
minLength:0,
close: function( event, ui ) {
$("#node-config-input-serialport").autocomplete( "destroy" );
}
}).autocomplete("search","");
});
});
}
});
</script>

306
nodes/core/io/25-serial.js Normal file
View File

@@ -0,0 +1,306 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var settings = RED.settings;
var events = require("events");
var serialp = require("serialport");
var bufMaxSize = 32768; // Max serial buffer size, for inputs...
// TODO: 'serialPool' should be encapsulated in SerialPortNode
function SerialPortNode(n) {
RED.nodes.createNode(this,n);
this.serialport = n.serialport;
this.newline = n.newline;
this.addchar = n.addchar || "false";
this.serialbaud = parseInt(n.serialbaud) || 57600;
this.databits = parseInt(n.databits) || 8;
this.parity = n.parity || "none";
this.stopbits = parseInt(n.stopbits) || 1;
this.bin = n.bin || "false";
this.out = n.out || "char";
}
RED.nodes.registerType("serial-port",SerialPortNode);
function SerialOutNode(n) {
RED.nodes.createNode(this,n);
this.serial = n.serial;
this.serialConfig = RED.nodes.getNode(this.serial);
if (this.serialConfig) {
var node = this;
node.port = serialPool.get(this.serialConfig.serialport,
this.serialConfig.serialbaud,
this.serialConfig.databits,
this.serialConfig.parity,
this.serialConfig.stopbits,
this.serialConfig.newline);
node.addCh = "";
if (node.serialConfig.addchar == "true") {
node.addCh = this.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0");
}
node.on("input",function(msg) {
var payload = msg.payload;
if (!Buffer.isBuffer(payload)) {
if (typeof payload === "object") {
payload = JSON.stringify(payload);
} else {
payload = payload.toString();
}
payload += node.addCh;
} else if (node.addCh !== "") {
payload = Buffer.concat([payload,new Buffer(node.addCh)]);
}
node.port.write(payload,function(err,res) {
if (err) {
node.error(err);
}
});
});
node.port.on('ready', function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
node.port.on('closed', function() {
node.status({fill:"red",shape:"ring",text:"not connected"});
});
} else {
this.error("missing serial config");
}
this.on("close", function(done) {
if (this.serialConfig) {
serialPool.close(this.serialConfig.serialport,done);
} else {
done();
}
});
}
RED.nodes.registerType("serial out",SerialOutNode);
function SerialInNode(n) {
RED.nodes.createNode(this,n);
this.serial = n.serial;
this.serialConfig = RED.nodes.getNode(this.serial);
if (this.serialConfig) {
var node = this;
node.tout = null;
var buf;
if (node.serialConfig.out != "count") { buf = new Buffer(bufMaxSize); }
else { buf = new Buffer(Number(node.serialConfig.newline)); }
var i = 0;
node.status({fill:"grey",shape:"dot",text:"unknown"});
node.port = serialPool.get(this.serialConfig.serialport,
this.serialConfig.serialbaud,
this.serialConfig.databits,
this.serialConfig.parity,
this.serialConfig.stopbits,
this.serialConfig.newline
);
var splitc;
if (node.serialConfig.newline.substr(0,2) == "0x") {
splitc = new Buffer([parseInt(node.serialConfig.newline)]);
} else {
splitc = new Buffer(node.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"));
}
this.port.on('data', function(msg) {
// single char buffer
if ((node.serialConfig.newline === 0)||(node.serialConfig.newline === "")) {
if (node.serialConfig.bin !== "bin") { node.send({"payload": String.fromCharCode(msg)}); }
else { node.send({"payload": new Buffer([msg])}); }
}
else {
// do the timer thing
if (node.serialConfig.out === "time") {
if (node.tout) {
i += 1;
buf[i] = msg;
}
else {
node.tout = setTimeout(function () {
node.tout = null;
var m = new Buffer(i+1);
buf.copy(m,0,0,i+1);
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
node.send({"payload": m});
m = null;
}, node.serialConfig.newline);
i = 0;
buf[0] = msg;
}
}
// count bytes into a buffer...
else if (node.serialConfig.out === "count") {
buf[i] = msg;
i += 1;
if ( i >= parseInt(node.serialConfig.newline)) {
var m = new Buffer(i);
buf.copy(m,0,0,i);
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
node.send({"payload":m});
m = null;
i = 0;
}
}
// look to match char...
else if (node.serialConfig.out === "char") {
buf[i] = msg;
i += 1;
if ((msg === splitc[0]) || (i === bufMaxSize)) {
var m = new Buffer(i);
buf.copy(m,0,0,i);
if (node.serialConfig.bin !== "bin") { m = m.toString(); }
node.send({"payload":m});
m = null;
i = 0;
}
}
else { node.log("should never get here"); }
}
});
this.port.on('ready', function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
this.port.on('closed', function() {
node.status({fill:"red",shape:"ring",text:"not connected"});
});
} else {
this.error("missing serial config");
}
this.on("close", function(done) {
if (this.serialConfig) {
serialPool.close(this.serialConfig.serialport,done);
} else {
done();
}
});
}
RED.nodes.registerType("serial in",SerialInNode);
var serialPool = function() {
var connections = {};
return {
get:function(port,baud,databits,parity,stopbits,newline,callback) {
var id = port;
if (!connections[id]) {
connections[id] = function() {
var obj = {
_emitter: new events.EventEmitter(),
serial: null,
_closing: false,
tout: null,
on: function(a,b) { this._emitter.on(a,b); },
close: function(cb) { this.serial.close(cb); },
write: function(m,cb) { this.serial.write(m,cb); },
}
//newline = newline.replace("\\n","\n").replace("\\r","\r");
var setupSerial = function() {
//if (newline == "") {
obj.serial = new serialp.SerialPort(port,{
baudrate: baud,
databits: databits,
parity: parity,
stopbits: stopbits,
parser: serialp.parsers.raw
},true, function(err, results) { if (err) { obj.serial.emit('error',err); } });
//}
//else {
// obj.serial = new serialp.SerialPort(port,{
// baudrate: baud,
// databits: databits,
// parity: parity,
// stopbits: stopbits,
// parser: serialp.parsers.readline(newline)
// },true, function(err, results) { if (err) obj.serial.emit('error',err); });
//}
obj.serial.on('error', function(err) {
RED.log.error("serial port "+port+" error "+err);
obj._emitter.emit('closed');
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
});
obj.serial.on('close', function() {
if (!obj._closing) {
RED.log.error("serial port "+port+" closed unexpectedly");
obj._emitter.emit('closed');
obj.tout = setTimeout(function() {
setupSerial();
}, settings.serialReconnectTime);
}
});
obj.serial.on('open',function() {
RED.log.info("serial port "+port+" opened at "+baud+" baud "+databits+""+parity.charAt(0).toUpperCase()+stopbits);
if (obj.tout) { clearTimeout(obj.tout); }
//obj.serial.flush();
obj._emitter.emit('ready');
});
obj.serial.on('data',function(d) {
//console.log(Buffer.isBuffer(d),d.length,d);
//if (typeof d !== "string") {
// //d = d.toString();
for (var z=0; z<d.length; z++) {
obj._emitter.emit('data',d[z]);
}
//}
//else {
// obj._emitter.emit('data',d);
//}
});
obj.serial.on("disconnect",function() {
RED.log.error("serial port "+port+" gone away");
});
}
setupSerial();
return obj;
}();
}
return connections[id];
},
close: function(port,done) {
if (connections[port]) {
if (connections[port].tout != null) {
clearTimeout(connections[port].tout);
}
connections[port]._closing = true;
try {
connections[port].close(function() {
RED.log.info("serial port closed");
done();
});
}
catch(err) { }
delete connections[port];
} else {
done();
}
}
}
}();
RED.httpAdmin.get("/serialports", RED.auth.needsPermission('serial.read'), function(req,res) {
serialp.list(function (err, ports) {
res.json(ports);
});
});
}

300
nodes/core/io/31-tcpin.html Normal file
View File

@@ -0,0 +1,300 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="tcp in">
<div class="form-row">
<label for="node-input-server"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-server" style="width:120px; margin-right:5px;">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
port <input type="text" id="node-input-port" style="width: 50px">
</div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
</div>
<div class="form-row">
<label><i class="fa fa-sign-out"></i> Output</label>
a
<select id="node-input-datamode" style="width:110px;">
<option value="stream">stream of</option>
<option value="single">single</option>
</select>
<select id="node-input-datatype" style="width:140px;">
<option value="buffer">Buffer</option>
<option value="utf8">String</option>
<option value="base64">Base64 String</option>
</select>
payload<span id="node-input-datamode-plural">s</span>
</div>
<div id="node-row-newline" class="form-row hidden" style="padding-left: 110px;">
delimited by <input type="text" id="node-input-newline" style="width: 110px;">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="tcp in">
<p>Provides a choice of tcp inputs. Can either connect to a remote tcp port,
or accept incoming connections.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('tcp in',{
category: 'input',
color:"Silver",
defaults: {
server: {value:"server",required:true},
host: {value:"",validate:function(v) { return (this.server == "server")||v.length > 0;} },
port: {value:"",required:true,validate:RED.validators.number()},
datamode:{value:"stream"},
datatype:{value:"buffer"},
newline:{value:""},
topic: {value:""},
name: {value:""},
base64: {/*deprecated*/ value:false,required:true}
},
inputs:0,
outputs:1,
icon: "bridge-dash.png",
label: function() {
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var updateOptions = function() {
var sockettype = $("#node-input-server option:selected").val();
if (sockettype == "client") {
$("#node-input-host-row").show();
} else {
$("#node-input-host-row").hide();
}
var datamode = $("#node-input-datamode option:selected").val();
var datatype = $("#node-input-datatype option:selected").val();
if (datamode == "stream") {
$("#node-input-datamode-plural").show();
if (datatype == "utf8") {
$("#node-row-newline").show();
} else {
$("#node-row-newline").hide();
}
} else {
$("#node-input-datamode-plural").hide();
$("#node-row-newline").hide();
}
};
updateOptions();
$("#node-input-server").change(updateOptions);
$("#node-input-datatype").change(updateOptions);
$("#node-input-datamode").change(updateOptions);
}
});
</script>
<script type="text/x-red" data-template-name="tcp out">
<div class="form-row">
<label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-beserver" style="width:150px; margin-right:5px;">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
<option value="reply">Reply to TCP</option>
</select>
<span id="node-input-port-row">port <input type="text" id="node-input-port" style="width: 50px"></span>
</div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
</div>
<div class="form-row hidden" id="node-input-end-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-end" style="width: 70%;">Close connection after each message is sent ?</label>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 message ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips hidden" id="fin-tip">
<b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example.
</div>
<div class="form-tips hidden" id="fin-tip2">
<b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example. The receiving client will need to reconnect.
</div>
</script>
<script type="text/x-red" data-help-name="tcp out">
<p>Provides a choice of tcp outputs. Can either connect to a remote tcp port,
accept incoming connections, or reply to messages received from a TCP In node.</p>
<p>Only <b>msg.payload</b> is sent.</p>
<p>If <b>msg.payload</b> is a string containing a Base64 encoding of binary
data, the Base64 decoding option will cause it to be converted back to binary
before being sent.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('tcp out',{
category: 'output',
color:"Silver",
defaults: {
host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v) } },
beserver: {value:"client",required:true},
base64: {value:false,required:true},
end: {value:false,required:true},
name: {value:""}
},
inputs:1,
outputs:0,
icon: "bridge-dash.png",
align: "right",
label: function() {
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
},
labelStyle: function() {
return (this.name)?"node_label_italic":"";
},
oneditprepare: function() {
var updateOptions = function() {
var sockettype = $("#node-input-beserver option:selected").val();
if (sockettype == "reply") {
$("#node-input-port-row").hide();
$("#node-input-host-row").hide();
$("#node-input-end-row").hide();
} else {
$("#node-input-port-row").show();
$("#node-input-end-row").show();
}
if (sockettype == "client") {
$("#node-input-host-row").show();
$("#fin-tip").show();
} else {
$("#node-input-host-row").hide();
$("#fin-tip").hide();
}
if (sockettype == "server") {
$("#fin-tip2").show();
}
else {
$("#fin-tip2").hide();
}
};
updateOptions();
$("#node-input-beserver").change(updateOptions);
}
});
</script>
<script type="text/x-red" data-template-name="tcp request">
<div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
<input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">
&nbsp;port <input type="text" id="node-input-port" placeholder="number" style="width:60px">
</div>
<div class="form-row">
<label for="node-input-out"><i class="fa fa-sign-out"></i> Return</label>
<select type="text" id="node-input-out" style="width:52%;">
<option value="time">after a fixed timeout of</option>
<option value="char">when character received is</option>
<option value="count">a fixed number of characters</option>
<option value="sit">never. Keep connection open</option>
</select>
<input type="text" id="node-input-splitc" style="width:50px;">
<span id="node-units"></span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips"><b>Tip:</b> Outputs a binary <b>Buffer</b>, so you may want to .toString() it.</br/>
<b>Tip:</b> Leave host and port blank if you want to overide with msg.host and msg.port properties.</div>
<script>
var previous = null;
$("#node-input-out").on('focus', function () { previous = this.value; }).change(function() {
if (previous == null) { previous = $("#node-input-out").val(); }
if ($("#node-input-out").val() == "char") {
if (previous != "char") $("#node-input-splitc").val("\\n");
$("#node-units").text("");
}
else if ($("#node-input-out").val() == "time") {
if (previous != "time") $("#node-input-splitc").val("0");
$("#node-units").text("ms");
}
else if ($("#node-input-out").val() == "count") {
if (previous != "count") $("#node-input-splitc").val("12");
$("#node-units").text("chars");
}
else {
if (previous != "sit") $("#node-input-splitc").val("0");
$("#node-units").text("");
}
});
</script>
<script type="text/x-red" data-help-name="tcp request">
<p>A simple TCP request node - sends the <b>msg.payload</b> to a server tcp port and expects a response.</p>
<p>Connects, sends the "request", reads the "response". It can either count a number of
returned characters into a fixed buffer, match a specified character before returning,
wait a fixed timeout from first reply and then return, or just sit and wait for data.</p>
<p>The response will be output in <b>msg.payload</b> as a buffer, so you may want to .toString() it.</p>
<p>If you leave tcp host or port blank they must be set by using the <b>msg.host</b> and <b>msg.port</b> properties.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('tcp request',{
category: 'function',
color:"Silver",
defaults: {
server: {value:""},
port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)},
out: {value:"time",required:true},
splitc: {value:"0",required:true},
name: {value:""}
},
inputs:1,
outputs:1,
icon: "bridge-dash.png",
label: function() {
return this.name || "tcp:"+(this.server?this.server+":":"")+this.port;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

506
nodes/core/io/31-tcpin.js Normal file
View File

@@ -0,0 +1,506 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var reconnectTime = RED.settings.socketReconnectTime||10000;
var socketTimeout = RED.settings.socketTimeout||null;
var net = require('net');
var connectionPool = {};
function TcpIn(n) {
RED.nodes.createNode(this,n);
this.host = n.host;
this.port = n.port * 1;
this.topic = n.topic;
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r");
this.base64 = n.base64;
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
this.closing = false;
this.connected = false;
var node = this;
var count = 0;
if (!node.server) {
var buffer = null;
var client;
var reconnectTimeout;
var end = false;
var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port);
node.status({fill:"grey",shape:"dot",text:"connecting"});
var id = (1+Math.random()*4294967295).toString(16);
client = net.connect(node.port, node.host, function() {
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
node.connected = true;
node.log("connected to "+node.host+":"+node.port);
node.status({fill:"green",shape:"dot",text:"connected"});
});
connectionPool[id] = client;
client.on('data', function (data) {
if (node.datatype != 'buffer') {
data = data.toString(node.datatype);
}
if (node.stream) {
if ((node.datatype) === "utf8" && node.newline != "") {
buffer = buffer+data;
var parts = buffer.split(node.newline);
for (var i = 0;i<parts.length-1;i+=1) {
var msg = {topic:node.topic, payload:parts[i]};
msg._session = {type:"tcp",id:id};
node.send(msg);
}
buffer = parts[parts.length-1];
} else {
var msg = {topic:node.topic, payload:data};
msg._session = {type:"tcp",id:id};
node.send(msg);
}
} else {
if ((typeof data) === "string") {
buffer = buffer+data;
} else {
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
}
}
});
client.on('end', function() {
if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
var msg = {topic:node.topic,payload:buffer};
msg._session = {type:"tcp",id:id};
if (buffer.length !== 0) {
end = true; // only ask for fast re-connect if we actually got something
node.send(msg);
}
buffer = null;
}
});
client.on('close', function() {
delete connectionPool[id];
node.connected = false;
node.status({fill:"red",shape:"ring",text:"disconnected"});
if (!node.closing) {
if (end) { // if we were asked to close then try to reconnect once very quick.
end = false;
reconnectTimeout = setTimeout(setupTcpClient, 20);
}
else {
node.log("connection lost to "+node.host+":"+node.port);
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
}
} else {
if (node.done) { node.done(); }
}
});
client.on('error', function(err) {
node.log(err);
});
}
setupTcpClient();
this.on('close', function(done) {
node.done = done;
this.closing = true;
client.end();
clearTimeout(reconnectTimeout);
if (!node.connected) { done(); }
});
} else {
var server = net.createServer(function (socket) {
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = (1+Math.random()*4294967295).toString(16);
connectionPool[id] = socket;
node.status({text:++count+" connections"});
var buffer = (node.datatype == 'buffer')? new Buffer(0):"";
socket.on('data', function (data) {
if (node.datatype != 'buffer') {
data = data.toString(node.datatype);
}
if (node.stream) {
if ((typeof data) === "string" && node.newline != "") {
buffer = buffer+data;
var parts = buffer.split(node.newline);
for (var i = 0;i<parts.length-1;i+=1) {
var msg = {topic:node.topic, payload:parts[i],ip:socket.remoteAddress,port:socket.remotePort};
msg._session = {type:"tcp",id:id};
node.send(msg);
}
buffer = parts[parts.length-1];
} else {
var msg = {topic:node.topic, payload:data};
msg._session = {type:"tcp",id:id};
node.send(msg);
}
} else {
if ((typeof data) === "string") {
buffer = buffer+data;
} else {
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
}
}
});
socket.on('end', function() {
if (!node.stream || (node.datatype === "utf8" && node.newline !== "")) {
if (buffer.length > 0) {
var msg = {topic:node.topic,payload:buffer};
msg._session = {type:"tcp",id:id};
node.send(msg);
}
buffer = null;
}
});
socket.on('timeout', function() {
node.log('timeout closed socket port '+node.port);
socket.end();
});
socket.on('close', function() {
delete connectionPool[id];
node.status({text:--count+" connections"});
});
socket.on('error',function(err) {
node.log(err);
});
});
server.on('error', function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
}
});
server.listen(node.port, function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
} else {
node.log('listening on port '+node.port);
node.on('close', function() {
for (var c in connectionPool) {
connectionPool[c].end();
connectionPool[c].unref();
}
node.closing = true;
server.close();
node.log('stopped listening on port '+node.port);
});
}
});
}
}
RED.nodes.registerType("tcp in",TcpIn);
function TcpOut(n) {
RED.nodes.createNode(this,n);
this.host = n.host;
this.port = n.port * 1;
this.base64 = n.base64;
this.doend = n.end || false;
this.beserver = n.beserver;
this.name = n.name;
this.closing = false;
this.connected = false;
var node = this;
if (!node.beserver||node.beserver=="client") {
var reconnectTimeout;
var client = null;
var end = false;
var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port);
node.status({fill:"grey",shape:"dot",text:"connecting"});
client = net.connect(node.port, node.host, function() {
node.connected = true;
node.log("connected to "+node.host+":"+node.port);
node.status({fill:"green",shape:"dot",text:"connected"});
});
client.on('error', function (err) {
node.log('error : '+err);
});
client.on('end', function (err) {
});
client.on('close', function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
node.connected = false;
client.destroy();
if (!node.closing) {
if (end) {
end = false;
reconnectTimeout = setTimeout(setupTcpClient,20);
}
else {
node.log("connection lost to "+node.host+":"+node.port);
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
}
} else {
if (node.done) { node.done(); }
}
});
}
setupTcpClient();
node.on("input", function(msg) {
if (node.connected && msg.payload != null) {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(new Buffer(msg.payload,'base64'));
} else {
client.write(new Buffer(""+msg.payload));
}
if (node.doend === true) {
end = true;
client.end();
}
}
});
node.on("close", function(done) {
node.done = done;
this.closing = true;
client.end();
clearTimeout(reconnectTimeout);
if (!node.connected) { done(); }
});
} else if (node.beserver == "reply") {
node.on("input",function(msg) {
if (msg._session && msg._session.type == "tcp") {
var client = connectionPool[msg._session.id];
if (client) {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(new Buffer(msg.payload,'base64'));
} else {
client.write(new Buffer(""+msg.payload));
}
}
}
});
} else {
var connectedSockets = [];
node.status({text:"0 connections"});
var server = net.createServer(function (socket) {
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
node.log("connection from "+remoteDetails);
connectedSockets.push(socket);
node.status({text:connectedSockets.length+" connections"});
socket.on('timeout', function() {
node.log('timeout closed socket port '+node.port);
socket.end();
});
socket.on('close',function() {
node.log("connection closed from "+remoteDetails);
connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:connectedSockets.length+" connections"});
});
socket.on('error',function() {
node.log("socket error from "+remoteDetails);
connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:connectedSockets.length+" connections"});
});
});
node.on("input", function(msg) {
if (msg.payload != null) {
var buffer;
if (Buffer.isBuffer(msg.payload)) {
buffer = msg.payload;
} else if (typeof msg.payload === "string" && node.base64) {
buffer = new Buffer(msg.payload,'base64');
} else {
buffer = new Buffer(""+msg.payload);
}
for (var i = 0; i<connectedSockets.length;i+=1) {
if (node.doend === true) { connectedSockets[i].end(buffer); }
else { connectedSockets[i].write(buffer); }
}
}
});
server.on('error', function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
}
});
server.listen(node.port, function(err) {
if (err) {
node.error('unable to listen on port '+node.port+' : '+err);
} else {
node.log('listening on port '+node.port);
node.on('close', function() {
for (var c in connectedSockets) {
connectedSockets[c].end();
connectedSockets[c].unref();
}
server.close();
node.log('stopped listening on port '+node.port);
});
}
});
}
}
RED.nodes.registerType("tcp out",TcpOut);
function TcpGet(n) {
RED.nodes.createNode(this,n);
this.server = n.server;
this.port = Number(n.port);
this.out = n.out;
this.splitc = n.splitc;
if (this.out != "char") { this.splitc = Number(this.splitc); }
else { this.splitc.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0"); }
var buf;
if (this.out == "count") { buf = new Buffer(this.splitc); }
else { buf = new Buffer(32768); } // set it to 32k... hopefully big enough for most.... but only hopefully
this.connected = false;
var node = this;
var client;
this.on("input", function(msg) {
var i = 0;
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
msg.payload = msg.payload.toString();
}
if (!node.connected) {
client = net.Socket();
client.setTimeout(socketTimeout);
node.status({});
var host = node.server || msg.host;
var port = node.port || msg.port;
if (host && port) {
client.connect(port, host, function() {
//node.log('client connected');
node.status({fill:"green",shape:"dot",text:"connected"});
node.connected = true;
client.write(msg.payload);
});
}
else {
node.warn("Host and/or port not set");
}
client.on('data', function(data) {
//node.log("data:"+ data.length+":"+ data);
if (node.splitc === 0) {
node.send({"payload": data});
}
else if (node.out === "sit") { // if we are staying connected just send the buffer
node.send({"payload": data});
}
else {
for (var j = 0; j < data.length; j++ ) {
if (node.out === "time") {
// do the timer thing
if (node.tout) {
i += 1;
buf[i] = data[j];
}
else {
node.tout = setTimeout(function () {
node.tout = null;
var m = new Buffer(i+1);
buf.copy(m,0,0,i+1);
node.send({"payload": m});
client.end();
m = null;
}, node.splitc);
i = 0;
buf[0] = data[j];
}
}
// count bytes into a buffer...
else if (node.out == "count") {
buf[i] = data[j];
i += 1;
if ( i >= node.serialConfig.count) {
node.send({"payload": buf});
client.end();
i = 0;
}
}
// look for a char
else {
buf[i] = data[j];
i += 1;
if (data[j] == node.splitc) {
var m = new Buffer(i);
buf.copy(m,0,0,i);
node.send({"payload": m});
client.end();
m = null;
i = 0;
}
}
}
}
});
client.on('end', function() {
//node.log('client disconnected');
node.connected = false;
node.status({});
client = null;
});
client.on('close', function() {
if (node.done) { node.done(); }
});
client.on('error', function() {
node.log('connect failed');
node.status({fill:"red",shape:"ring",text:"error"});
if (client) { client.end(); }
});
client.on('timeout',function() {
node.log('connect timeout');
if (client) {
client.end();
setTimeout(function() {
client.connect(port, host, function() {
//node.log('client connected');
node.connected = true;
client.write(msg.payload);
});
},reconnectTime);
}
});
}
else { client.write(msg.payload); }
});
this.on("close", function(done) {
node.done = done;
if (client) {
buf = null;
client.end();
}
if (!node.connected) { done(); }
});
}
RED.nodes.registerType("tcp request",TcpGet);
}

View File

@@ -17,7 +17,7 @@
<!-- The Input Node -->
<script type="text/x-red" data-template-name="udp in">
<div class="form-row">
<label for="node-input-port"><i class="icon-inbox"></i> Listen</label>
<label for="node-input-port"><i class="fa fa-sign-in"></i> Listen</label>
on port <input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
for <select id="node-input-multicast" style='width:40%'>
<option value="false">udp messages</option>
@@ -25,15 +25,15 @@
</select>
</div>
<div class="form-row node-input-group">
<label for="node-input-group"><i class="icon-list"></i> Group</label>
<label for="node-input-group"><i class="fa fa-list"></i> Group</label>
<input type="text" id="node-input-group" placeholder="225.0.18.83">
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="icon-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="eth0">
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
</div>
<div class="form-row">
<label for="node-input-datatype"><i class="icon-file"></i> Output</label>
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> Output</label>
<select id="node-input-datatype" style="width: 70%;">
<option value="buffer">a Buffer</option>
<option value="utf8">a String</option>
@@ -41,7 +41,7 @@
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: Make sure your firewall will allow the data in.</div>
@@ -62,7 +62,7 @@
<script type="text/x-red" data-help-name="udp in">
<p>A udp input node, that produces a <b>msg.payload</b> containing a <i>BUFFER</i>, string, or base64 encoded string. Supports multicast.</p>
<p>It also provides <b>msg.ip</b> and <b>msg.port</b> to the ip address and port from which the message was received.</b>
<p>It also provides <b>msg.ip</b> and <b>msg.port</b> to the ip address and port from which the message was received.</p>
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
</script>
@@ -72,7 +72,6 @@
color:"Silver",
defaults: {
name: {value:""},
host: {value:""},
iface: {value:""},
port: {value:"",required:true,validate:RED.validators.number()},
datatype: {value:"buffer",required:true},
@@ -98,44 +97,51 @@
<!-- The Output Node -->
<script type="text/x-red" data-template-name="udp out">
<div class="form-row">
<label for="node-input-port"><i class="icon-envelope"></i> Send a</label>
<label for="node-input-port"><i class="fa fa-envelope"></i> Send a</label>
<select id="node-input-multicast" style='width:40%'>
<option value="false">udp message</option>
<option value="broad">broadcast message</option>
<option value="multi">multicast message</option>
</select>
to port <input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
to port <input type="text" id="node-input-port" placeholder="port" style="width: 70px">
</div>
<div class="form-row node-input-addr">
<label for="node-input-addr" id="node-input-addr-label"><i class="icon-list"></i> Address</label>
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> Address</label>
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 70%;">
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="icon-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="eth0">
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
</div>
<div class="form-row">
<label for="node-input-outport-type">&nbsp;</label>
<select id="node-input-outport-type">
<option id="node-input-outport-type-random" value="random">use random local port</option>
<option value="fixed">bind to local port</option>
</select>
<input type="text" id="node-input-outport" style="width: 70px;" placeholder="port">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<input type="checkbox" id="node-input-base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 encoded payload ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>.</div>
<script>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
console.log(id,$("#node-input-addr")[0].placeholder);
if (id !== "multi") {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="icon-list"></i> Address');
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Address');
$("#node-input-addr")[0].placeholder = 'destination ip';
}
else {
$(".node-input-iface").show();
$("#node-input-addr-label").html('<i class="icon-list"></i> Group');
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Group');
$("#node-input-addr")[0].placeholder = '225.0.18.83';
}
if (id === "broad") {
@@ -161,6 +167,7 @@
addr: {value:""},
iface: {value:""},
port: {value:""},
outport: {value:""},
base64: {value:false,required:true},
multicast: {value:"false"}
},
@@ -173,6 +180,33 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var type = this.outport==""?"random":"fixed";
$("#node-input-outport-type option").filter(function() {
return $(this).val() == type;
}).attr('selected',true);
$("#node-input-outport-type").change(function() {
var type = $(this).children("option:selected").val();
if (type == "random") {
$("#node-input-outport").val("").hide();
} else {
$("#node-input-outport").show();
}
});
$("#node-input-outport-type").change();
$("#node-input-multicast").change(function() {
var type = $(this).children("option:selected").val();
if (type == "false") {
$("#node-input-outport-type-random").html("bind to random local port");
} else {
$("#node-input-outport-type-random").html("bind to target port");
}
});
$("#node-input-multicast").change();
}
});
</script>

173
nodes/core/io/32-udp.js Normal file
View File

@@ -0,0 +1,173 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var dgram = require('dgram');
// The Input Node
function UDPin(n) {
RED.nodes.createNode(this,n);
this.group = n.group;
this.port = n.port;
this.datatype = n.datatype;
this.iface = n.iface || null;
this.multicast = n.multicast;
var node = this;
var server = dgram.createSocket('udp4');
server.on("error", function (err) {
if ((err.code == "EACCES") && (node.port < 1024)) {
node.error("UDP access error, you may need root access for ports below 1024");
} else {
node.error("UDP error : "+err.code);
}
server.close();
});
server.on('message', function (message, remote) {
var msg;
if (node.datatype =="base64") {
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
} else if (node.datatype =="utf8") {
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
} else {
msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
}
node.send(msg);
});
server.on('listening', function () {
var address = server.address();
node.log('udp listener at ' + address.address + ":" + address.port);
if (node.multicast == "true") {
server.setBroadcast(true);
try {
server.setMulticastTTL(128);
server.addMembership(node.group,node.iface);
node.log("udp multicast group "+node.group);
} catch (e) {
if (e.errno == "EINVAL") {
node.error("Bad Multicast Address");
} else if (e.errno == "ENODEV") {
node.error("Must be ip address of the required interface");
} else {
node.error("Error :"+e.errno);
}
}
}
});
node.on("close", function() {
try {
server.close();
node.log('udp listener stopped');
} catch (err) {
node.error(err);
}
});
// Hack for when you have both in and out udp nodes sharing a port
// if udp in starts last it shares better - so give it a chance to be last
setTimeout( function() { server.bind(node.port,node.iface); }, 250);;
}
RED.nodes.registerType("udp in",UDPin);
// The Output Node
function UDPout(n) {
RED.nodes.createNode(this,n);
//this.group = n.group;
this.port = n.port;
this.outport = n.outport||"";
this.base64 = n.base64;
this.addr = n.addr;
this.iface = n.iface || null;
this.multicast = n.multicast;
var node = this;
var sock = dgram.createSocket('udp4'); // only use ipv4 for now
if (node.multicast != "false") {
if (node.outport == "") { node.outport = node.port; }
sock.bind(node.outport, function() { // have to bind before you can enable broadcast...
sock.setBroadcast(true); // turn on broadcast
if (node.multicast == "multi") {
try {
sock.setMulticastTTL(128);
sock.addMembership(node.addr,node.iface); // Add to the multicast group
node.log('udp multicast ready : '+node.outport+' -> '+node.addr+":"+node.port);
} catch (e) {
if (e.errno == "EINVAL") {
node.error("Bad Multicast Address");
} else if (e.errno == "ENODEV") {
node.error("Must be ip address of the required interface");
} else {
node.error("Error :"+e.errno);
}
}
} else {
node.log('udp broadcast ready : '+node.outport+' -> '+node.addr+":"+node.port);
}
});
} else if (node.outport != "") {
sock.bind(node.outport);
node.log('udp ready : '+node.outport+' -> '+node.addr+":"+node.port);
} else {
node.log('udp ready : '+node.addr+":"+node.port);
}
node.on("input", function(msg) {
if (msg.payload != null) {
var add = node.addr || msg.ip || "";
var por = node.port || msg.port || 0;
if (add == "") {
node.warn("udp: ip address not set");
} else if (por == 0) {
node.warn("udp: port not set");
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
node.warn("udp: port number not valid");
} else {
var message;
if (node.base64) {
message = new Buffer(msg.payload, 'base64');
} else if (msg.payload instanceof Buffer) {
message = msg.payload;
} else {
message = new Buffer(""+msg.payload);
}
sock.send(message, 0, message.length, por, add, function(err, bytes) {
if (err) {
node.error("udp : "+err);
}
message = null;
});
}
}
});
node.on("close", function() {
try {
sock.close();
node.log('udp output stopped');
} catch (err) {
node.error(err);
}
});
}
RED.nodes.registerType("udp out",UDPout);
}

254
nodes/core/io/lib/mqtt.js Normal file
View File

@@ -0,0 +1,254 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var mqtt = require("mqtt");
var events = require("events");
//var inspect = require("sys").inspect;
//var Client = module.exports.Client = function(
var port = 1883;
var host = "localhost";
function MQTTClient(port,host) {
this.port = port||1883;
this.host = host||"localhost";
this.messageId = 1;
this.pendingSubscriptions = {};
this.inboundMessages = {};
this.lastOutbound = (new Date()).getTime();
this.lastInbound = (new Date()).getTime();
this.connected = false;
this._nextMessageId = function() {
this.messageId += 1;
if (this.messageId > 0xFFFF) {
this.messageId = 1;
}
return this.messageId;
}
events.EventEmitter.call(this);
}
util.inherits(MQTTClient, events.EventEmitter);
MQTTClient.prototype.connect = function(options) {
if (!this.connected) {
var self = this;
options = options||{};
self.options = options;
self.options.keepalive = options.keepalive||15;
self.options.clean = self.options.clean||true;
self.options.protocolId = 'MQIsdp';
self.options.protocolVersion = 3;
self.client = mqtt.createConnection(this.port,this.host,function(err,client) {
if (err) {
self.connected = false;
clearInterval(self.watchdog);
self.connectionError = true;
//util.log('[mqtt] ['+self.uid+'] connection error 1 : '+inspect(err));
self.emit('connectionlost',err);
return;
}
client.on('close',function(e) {
//util.log('[mqtt] ['+self.uid+'] on close');
clearInterval(self.watchdog);
if (!self.connectionError) {
if (self.connected) {
self.connected = false;
self.emit('connectionlost',e);
} else {
self.emit('disconnect');
}
}
});
client.on('error',function(e) {
//util.log('[mqtt] ['+self.uid+'] on error : '+inspect(e));
clearInterval(self.watchdog);
if (self.connected) {
self.connected = false;
self.emit('connectionlost',e);
}
});
client.on('connack',function(packet) {
if (packet.returnCode == 0) {
self.watchdog = setInterval(function(self) {
var now = (new Date()).getTime();
//util.log('[mqtt] ['+self.uid+'] watchdog '+inspect({connected:self.connected,connectionError:self.connectionError,pingOutstanding:self.pingOutstanding,now:now,lastOutbound:self.lastOutbound,lastInbound:self.lastInbound}));
if (now - self.lastOutbound > self.options.keepalive*500 || now - self.lastInbound > self.options.keepalive*500) {
if (self.pingOutstanding) {
//util.log('[mqtt] ['+self.uid+'] watchdog pingOustanding - disconnect');
try {
self.client.disconnect();
} catch (err) {
}
} else {
//util.log('[mqtt] ['+self.uid+'] watchdog pinging');
self.lastOutbound = (new Date()).getTime();
self.lastInbound = (new Date()).getTime();
self.pingOutstanding = true;
self.client.pingreq();
}
}
},self.options.keepalive*500,self);
self.pingOutstanding = false;
self.lastInbound = (new Date()).getTime()
self.lastOutbound = (new Date()).getTime()
self.connected = true;
self.connectionError = false;
self.emit('connect');
} else {
self.connected = false;
self.emit('connectionlost');
}
});
client.on('suback',function(packet) {
self.lastInbound = (new Date()).getTime()
var topic = self.pendingSubscriptions[packet.messageId];
self.emit('subscribe',topic,packet.granted[0]);
delete self.pendingSubscriptions[packet.messageId];
});
client.on('unsuback',function(packet) {
self.lastInbound = (new Date()).getTime()
var topic = self.pendingSubscriptions[packet.messageId];
self.emit('unsubscribe',topic,packet.granted[0]);
delete self.pendingSubscriptions[packet.messageId];
});
client.on('publish',function(packet) {
self.lastInbound = (new Date()).getTime();
if (packet.qos < 2) {
var p = packet;
self.emit('message',p.topic,p.payload,p.qos,p.retain);
} else {
self.inboundMessages[packet.messageId] = packet;
this.lastOutbound = (new Date()).getTime()
self.client.pubrec(packet);
}
if (packet.qos == 1) {
this.lastOutbound = (new Date()).getTime()
self.client.puback(packet);
}
});
client.on('pubrel',function(packet) {
self.lastInbound = (new Date()).getTime()
var p = self.inboundMessages[packet.messageId];
if (p) {
self.emit('message',p.topic,p.payload,p.qos,p.retain);
delete self.inboundMessages[packet.messageId];
}
self.lastOutbound = (new Date()).getTime()
self.client.pubcomp(packet);
});
client.on('puback',function(packet) {
self.lastInbound = (new Date()).getTime()
// outbound qos-1 complete
});
client.on('pubrec',function(packet) {
self.lastInbound = (new Date()).getTime()
self.lastOutbound = (new Date()).getTime()
self.client.pubrel(packet);
});
client.on('pubcomp',function(packet) {
self.lastInbound = (new Date()).getTime()
// outbound qos-2 complete
});
client.on('pingresp',function(packet) {
//util.log('[mqtt] ['+self.uid+'] received pingresp');
self.lastInbound = (new Date()).getTime()
self.pingOutstanding = false;
});
this.lastOutbound = (new Date()).getTime()
this.connectionError = false;
client.connect(self.options);
});
}
}
MQTTClient.prototype.subscribe = function(topic,qos) {
var self = this;
if (self.connected) {
var options = {
subscriptions:[{topic:topic,qos:qos}],
messageId: self._nextMessageId()
};
this.pendingSubscriptions[options.messageId] = topic;
this.lastOutbound = (new Date()).getTime();
self.client.subscribe(options);
self.client.setPacketEncoding('binary');
}
}
MQTTClient.prototype.unsubscribe = function(topic) {
var self = this;
if (self.connected) {
var options = {
topic:topic,
messageId: self._nextMessageId()
};
this.pendingSubscriptions[options.messageId] = topic;
this.lastOutbound = (new Date()).getTime()
self.client.unsubscribe(options);
}
}
MQTTClient.prototype.publish = function(topic,payload,qos,retain) {
var self = this;
if (self.connected) {
if (!Buffer.isBuffer(payload)) {
if (typeof payload === "object") {
payload = JSON.stringify(payload);
} else if (typeof payload !== "string") {
payload = ""+payload;
}
}
var options = {
topic: topic,
payload: payload,
qos: qos||0,
retain:retain||false
};
if (options.qos != 0) {
options.messageId = self._nextMessageId();
}
this.lastOutbound = (new Date()).getTime()
self.client.publish(options);
}
}
MQTTClient.prototype.disconnect = function() {
var self = this;
if (this.connected) {
this.connected = false;
try {
this.client.disconnect();
} catch(err) {
}
}
}
MQTTClient.prototype.isConnected = function() {
return this.connected;
}
module.exports.createClient = function(port,host) {
var mqtt_client = new MQTTClient(port,host);
return mqtt_client;
}

View File

@@ -15,22 +15,32 @@
**/
var util = require("util");
var mqtt = require("./mqtt");
var settings = require("../../../red/red").settings;
var settings = require(process.env.NODE_RED_HOME+"/red/red").settings;
var connections = {};
function matchTopic(ts,t) {
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/#$/,".*"));
function matchTopic(ts,t) {
if (ts == "#") {
return true;
}
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
return re.test(t);
}
module.exports = {
get: function(broker,port) {
var id = broker+":"+port;
get: function(broker,port,clientid,username,password,will) {
var id = "["+(username||"")+":"+(password||"")+"]["+(clientid||"")+"]@"+broker+":"+port;
if (!connections[id]) {
connections[id] = function() {
var uid = (1+Math.random()*4294967295).toString(16);
var client = mqtt.createClient(port,broker);
var options = {keepalive:15,clientId:'mqtt_' + (1+Math.random()*4294967295).toString(16)};
client.uid = uid;
client.setMaxListeners(0);
var options = {keepalive:15};
options.clientId = clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
options.username = username;
options.password = password;
options.will = will;
var queue = [];
var subscriptions = [];
var connecting = false;
@@ -65,7 +75,7 @@ module.exports = {
client.once(a,b);
},
connect: function() {
if (!client.isConnected() && !connecting) {
if (client && !client.isConnected() && !connecting) {
connecting = true;
client.connect(options);
}
@@ -80,33 +90,33 @@ module.exports = {
}
};
client.on('connect',function() {
util.log('[mqtt] connected to broker tcp://'+broker+':'+port);
connecting = false;
for (var s in subscriptions) {
var topic = subscriptions[s].topic;
var qos = subscriptions[s].qos;
var callback = subscriptions[s].callback;
client.subscribe(topic,qos);
}
//console.log("connected - publishing",queue.length,"messages");
while(queue.length) {
var msg = queue.shift();
//console.log(msg);
client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
if (client) {
util.log('[mqtt] ['+uid+'] connected to broker tcp://'+broker+':'+port);
connecting = false;
for (var s in subscriptions) {
var topic = subscriptions[s].topic;
var qos = subscriptions[s].qos;
var callback = subscriptions[s].callback;
client.subscribe(topic,qos);
}
//console.log("connected - publishing",queue.length,"messages");
while(queue.length) {
var msg = queue.shift();
//console.log(msg);
client.publish(msg.topic,msg.payload,msg.qos,msg.retain);
}
}
});
client.on('connectionlost', function(err) {
util.log('[mqtt] connection lost to broker tcp://'+broker+':'+port);
util.log('[mqtt] ['+uid+'] connection lost to broker tcp://'+broker+':'+port);
connecting = false;
setTimeout(function() {
if (client) {
client.connect(options);
}
obj.connect();
}, settings.mqttReconnectTime||5000);
});
client.on('disconnect', function() {
util.log('[mqtt] disconnected from broker tcp://'+broker+':'+port);
connecting = false;
util.log('[mqtt] ['+uid+'] disconnected from broker tcp://'+broker+':'+port);
});
return obj
@@ -116,4 +126,3 @@ module.exports = {
return connections[id];
}
};

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013, 2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,47 +16,55 @@
<script type="text/x-red" data-template-name="switch">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row" style="padding-top:10px;">
<div class="form-row">
If msg.<input type="text" id="node-input-property" style="width: 200px;"/>
</div>
<div class="form-row">
<div id="node-input-rule-container-div" style="border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;">
</ol>
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;"></ol>
</div>
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="icon-plus"></i> Add</a>
</div>
<div class="form-row">
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a>
</div>
<div class="form-row">
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
<option value="true">checking all rules</option>
<option value="false">stopping after first match</option>
</select>
</div>
</script>
<script type="text/x-red" data-help-name="switch">
<p>A simple function node to route messages based on its properties.</p>
<p>When a message arrives, the selected property is evaluated against each
of the defined rules. The message is then sent to the output of <i>all</i>
rules that pass.</p>
<p>A simple function node to route messages based on its properties.</p>
<p>When a message arrives, the selected property is evaluated against each
of the defined rules. The message is then sent to the output of <i>all</i>
rules that pass.</p>
<p>Note: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('switch',{
color:"#E2D96E",
RED.nodes.registerType('switch', {
color: "#E2D96E",
category: 'function',
defaults: {
name: {value:""},
property: {value: "payload",required:true},
rules:{value:[{t:"eq",v:""}]},
outputs:{value:1}
property: {value:"payload", required:true},
rules: {value:[{t:"eq", v:""}]},
checkall: {value:"true", required:true},
outputs: {value:1}
},
inputs:1,
outputs:1,
inputs: 1,
outputs: 1,
icon: "switch.png",
label: function() {
return this.name;
return this.name||"switch";
},
oneditprepare: function() {
var operators = [
{v:"eq",t:"=="},
{v:"neq",t:"!="},
@@ -70,30 +78,33 @@
{v:"true",t:"is true"},
{v:"false",t:"is false"},
{v:"null",t:"is null"},
{v:"nnull",t:"is not null"}
{v:"nnull",t:"is not null"},
{v:"else",t:"otherwise"}
];
function generateRule(i,rule) {
var container = $('<li/>',{style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var row = $('<div/>').appendTo(container);
var row2 = $('<div/>',{style:"padding-top: 5px; text-align: right;"}).appendTo(container);
$('<i style="color: #eee; cursor: move;" class="node-input-rule-handle fa fa-bars"></i>').appendTo(row);
var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
for (var d in operators) {
selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
}
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row);
var btwnField = $('<span/>').appendTo(row);
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px; width: 50px;"}).appendTo(btwnField);
btwnField.append(" and ");
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row);
finalspan.append(' send to <span class="node-input-rule-index">'+i+'</span> ');
finalspan.append(' &#8594; <span class="node-input-rule-index">'+i+'</span> ');
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
selectField.change(function() {
var type = selectField.children("option:selected").val();
if (type.length < 4) {
@@ -108,17 +119,15 @@
btwnField.show();
} else {
btwnField.hide();
if (type === "true" || type === "false" || type === "null" || type === "nnull") {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
valueField.hide();
} else {
valueField.show();
}
}
});
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"icon-remove"}).appendTo(deleteButton);
deleteButton.click(function() {
container.css({"background":"#fee"});
container.fadeOut(300, function() {
@@ -126,43 +135,63 @@
$("#node-input-rule-container").children().each(function(i) {
$(this).find(".node-input-rule-index").html(i+1);
});
});
});
$("#node-input-rule-container").append(container);
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
if (rule.t == "btwn") {
btwnValueField.val(rule.v);
btwnValue2Field.val(rule.v2);
} else if (rule.v) {
} else if (typeof rule.v != "undefined") {
valueField.val(rule.v);
}
selectField.change();
}
$("#node-input-add-rule").click(function() {
generateRule($("#node-input-rule-container").children().length+1,{t:"",v:"",v2:""});
$("#node-input-rule-container-div").scrollTop($("#node-input-rule-container-div").get(0).scrollHeight);
});
for (var i=0;i<this.rules.length;i++) {
var rule = this.rules[i];
generateRule(i+1,rule);
}
function switchDialogResize(ev,ui) {
$("#node-input-rule-container-div").css("height",(ui.size.height-260)+"px");
function switchDialogResize() {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-rule-container-div").css("height",height+"px");
};
$( "#node-input-rule-container" ).sortable({
axis: "y",
update: function( event, ui ) {
var rules = $("#node-input-rule-container").children();
rules.each(function(i) {
$(this).find(".node-input-rule-index").html(i+1);
});
},
handle:".node-input-rule-handle",
cursor: "move"
});
$( "#node-input-rule-container .node-input-rule-handle" ).disableSelection();
$( "#dialog" ).on("dialogresize", switchDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-switch');
if (size) {
switchDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
switchDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
@@ -172,15 +201,13 @@
oneditsave: function() {
var rules = $("#node-input-rule-container").children();
var ruleset;
var node = this;
node.rules= [];
rules.each(function(i) {
var rule = $(this);
var type = rule.find("select option:selected").val();
var r = {t:type};
if (!(type === "true" || type === "false" || type === "null" || type === "nnull")) {
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
if (type === "btwn") {
r.v = rule.find(".node-input-rule-btwn-value").val();
r.v2 = rule.find(".node-input-rule-btwn-value2").val();
@@ -189,11 +216,8 @@
}
}
node.rules.push(r);
});
node.outputs = node.rules.length;
}
});
</script>

View File

@@ -0,0 +1,78 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var operators = {
'eq': function(a, b) { return a == b; },
'neq': function(a, b) { return a != b; },
'lt': function(a, b) { return a < b; },
'lte': function(a, b) { return a <= b; },
'gt': function(a, b) { return a > b; },
'gte': function(a, b) { return a >= b; },
'btwn': function(a, b, c) { return a >= b && a <= c; },
'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
'regex': function(a, b) { return (a + "").match(new RegExp(b)); },
'true': function(a) { return a === true; },
'false': function(a) { return a === false; },
'null': function(a) { return typeof a == "undefined"; },
'nnull': function(a) { return typeof a != "undefined"; },
'else': function(a) { return a === true; }
};
function SwitchNode(n) {
RED.nodes.createNode(this, n);
this.rules = n.rules;
this.property = n.property;
this.checkall = n.checkall || "true";
var propertyParts = n.property.split(".");
var node = this;
for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i];
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
rule.v2 = Number(rule.v2);
}
}
this.on('input', function (msg) {
var onward = [];
try {
var prop = propertyParts.reduce(function (obj, i) {
return obj[i]
}, msg);
var elseflag = true;
for (var i=0; i<node.rules.length; i+=1) {
var rule = node.rules[i];
var test = prop;
if (rule.t == "else") { test = elseflag; elseflag = true; }
if (operators[rule.t](test,rule.v, rule.v2)) {
onward.push(msg);
elseflag = false;
if (node.checkall == "false") { break; }
} else {
onward.push(null);
}
}
this.send(onward);
} catch(err) {
node.warn(err);
}
});
}
RED.nodes.registerType("switch", SwitchNode);
}

View File

@@ -0,0 +1,143 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="change">
<div>
<select id="node-input-action" style="width:95%; margin-right:5px;">
<option value="replace">Set the value of the message property</option>
<option value="change">Search/replace the value of the message property</option>
<option value="delete">Delete the message property</option>
</select>
</div>
<div class="form-row" style="padding-top:10px;" id="node-prop1-row">
<label for="node-input-property">called</label> msg.<input type="text" id="node-input-property" style="width: 63%;"/>
</div>
<div class="form-row" id="node-from-row">
<label for="node-input-from" id="node-input-f"></label>
<input type="text" id="node-input-from" placeholder="this"/>
</div>
<div class="form-row" id="node-to-row">
<label for="node-input-to" id="node-input-t"></label>
<input type="text" id="node-input-to" placeholder="that"/>
</div>
<div class="form-row" id="node-reg-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-reg" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-reg" style="width: 70%;">Use regular expressions</label>
</div>
<div class="form-tips" id="node-tip"></div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="change">
<p>A simple function node to set, replace or delete properties of a message.</p>
<p>When a message arrives, the selected property is modified by the defined rules.
The message is then sent to the output.</p>
<p><b>Note:</b> Set and replace only operate using <b>strings</b>. Anything else will be passed straight through.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('change', {
color: "#E2D96E",
category: 'function',
defaults: {
action: {value:"replace",required:true},
property: {value:"payload",required:true},
from: {value:"",validate: function(v) {
if (this.action === "change" && !this.from) {
return false;
} else if (this.action === "change" && this.reg) {
try {
var re = new RegExp(this.from, "g");
return true;
} catch(err) {
return false;
}
}
return true;
}},
to: {value:""},
reg: {value:false},
name: {value:""}
},
inputs: 1,
outputs: 1,
icon: "swap.png",
label: function() {
if (this.name) {
return this.name;
}
if (this.action === "replace") {
return "set msg."+this.property;
} else if (this.action === "change") {
return "replace msg."+this.property;
} else {
return this.action+" msg."+this.property
}
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
$("#node-input-action").change( function() {
var a = $("#node-input-action").val();
if (a === "replace") {
$("#node-input-todo").html("called");
//$("#node-input-f").html("name");
$("#node-input-t").html("to");
$("#node-from-row").hide();
$("#node-to-row").show();
$("#node-reg-row").hide();
$("#node-tip").show();
$("#node-tip").html("Tip: expects a new property name and either a fixed value OR the full name of another message property eg: msg.sentiment.score");
}
if (a === "delete") {
$("#node-input-todo").html("called");
//$("#node-input-f").html("called");
//$("#node-input-t").html("to");
$("#node-from-row").hide();
$("#node-to-row").hide();
$("#node-reg-row").hide();
$("#node-tip").hide();
}
if (a === "change") {
$("#node-input-todo").html("called");
$("#node-input-f").html("Search for");
$("#node-input-t").html("replace with");
$("#node-from-row").show();
$("#node-to-row").show();
$("#node-reg-row").show();
$("#node-tip").show();
$("#node-tip").html("Tip: only works on string properties. If regular expressions are used, the <i>replace with</i> field can contain capture results, eg $1.");
}
//if (a === "replace") {
// $("#node-input-todo").html("called");
// //$("#node-input-f").html("with");
// $("#node-input-t").html("with");
// $("#node-from-row").hide();
// $("#node-to-row").show();
// $("#node-tip").html("Tip: accepts either a fixed value OR the full name of another msg.property eg: msg.sentiment.score");
//}
});
$("#node-input-action").change();
}
});
</script>

View File

@@ -0,0 +1,90 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
function ChangeNode(n) {
RED.nodes.createNode(this, n);
this.action = n.action;
this.property = n.property || "";
this.from = n.from || "";
this.to = n.to || "";
this.reg = (n.reg === null || n.reg);
var node = this;
if (node.reg === false) {
this.from = this.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
this.on('input', function(msg) {
var propertyParts;
var depth = 0;
if (node.action === "change") {
try {
node.re = new RegExp(this.from, "g");
} catch (e) {
node.error(e.message);
}
}
propertyParts = node.property.split(".");
try {
propertyParts.reduce(function(obj, i) {
var to = node.to;
// Set msg from property to another msg property
if (node.action === "replace" && node.to.indexOf("msg.") === 0) {
var parts = to.substring(4);
var msgPropParts = parts.split(".");
try {
msgPropParts.reduce(function(ob, j) {
to = (typeof ob[j] !== "undefined" ? ob[j] : undefined);
return to;
}, msg);
} catch (err) {}
}
if (++depth === propertyParts.length) {
if (node.action === "change") {
if (typeof obj[i] === "string") {
obj[i] = obj[i].replace(node.re, node.to);
}
} else if (node.action === "replace") {
if (typeof to === "undefined") {
delete(obj[i]);
} else {
obj[i] = to;
}
} else if (node.action === "delete") {
delete(obj[i]);
}
} else {
// to property doesn't exist, don't create empty object
if (typeof to === "undefined") {
return;
// setting a non-existent multilevel object, create empty parent
} else if (!obj[i]) {
obj[i] = {};
}
return obj[i];
}
}, msg);
} catch (err) {}
node.send(msg);
});
}
RED.nodes.registerType("change", ChangeNode);
};

View File

@@ -0,0 +1,81 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="range">
<div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> Action</label>
<select id="node-input-action" style="width:70%; margin-right:5px;">
<option value="scale">Scale msg.payload</option>
<option value="clamp">Scale and limit to the target range</option>
<option value="roll">Scale and wrap within the target range</option>
</select>
</div>
<br/>
<div class="form-row"><i class="fa fa-sign-in"></i> Map the input range:</div>
<div class="form-row"><label></label>
from: <input type="text" id="node-input-minin" placeholder="e.g. 0" style="width:100px;"/>
&nbsp;&nbsp;to: <input type="text" id="node-input-maxin" placeholder="e.g. 99" style="width:100px;"/>
</div>
<div class="form-row"><i class="fa fa-sign-out"></i> to the result range:</div>
<div class="form-row"><label></label>
from: <input type="text" id="node-input-minout" placeholder="e.g. 0" style="width:100px;"/>
&nbsp;&nbsp;to: <input type="text" id="node-input-maxout" placeholder="e.g. 255" style="width:100px;"/>
</div>
<br/>
<div class="form-row"><label></label>
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-round">Round result to the nearest integer?</label></input>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="node-tip">Tip: This node ONLY works with numbers.</div>
</script>
<script type="text/x-red" data-help-name="range">
<p>A simple function node to remap numeric input values to another scale.</p>
<p>Currently only does a linear scaling.</p>
<p><b>Note:</b> This only operates on <b>numbers</b>. Anything else will try to be made into a number and rejected if that fails.</p>
<p><i>Scale and limit to target range</i> means that the result will never be outside the range specified within the result range.</p>
<p><i>Scale and wrap within the target range</i> means that the result will essentially be a "modulo-style" wrap-around within the result range.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('range', {
color: "#E2D96E",
category: 'function',
defaults: {
minin: {value:"",required:true,validate:RED.validators.number()},
maxin: {value:"",required:true,validate:RED.validators.number()},
minout: {value:"",required:true,validate:RED.validators.number()},
maxout: {value:"",required:true,validate:RED.validators.number()},
action: {value:"scale"},
round: {value:false},
name: {value:""}
},
inputs: 1,
outputs: 1,
icon: "range.png",
label: function() {
return this.name || "range";
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
}
});
</script>

View File

@@ -0,0 +1,48 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
function RangeNode(n) {
RED.nodes.createNode(this, n);
this.action = n.action;
this.round = n.round || false;
this.minin = Number(n.minin);
this.maxin = Number(n.maxin);
this.minout = Number(n.minout);
this.maxout = Number(n.maxout);
var node = this;
this.on('input', function (msg) {
var n = Number(msg.payload);
if (!isNaN(n)) {
if (node.action == "clamp") {
if (n < node.minin) { n = node.minin; }
if (n > node.maxin) { n = node.maxin; }
}
if (node.action == "roll") {
if (n >= node.maxin) { n = (n - node.minin) % (node.maxin - node.minin) + node.minin; }
if (n < node.minin) { n = (n - node.minin) % (node.maxin - node.minin) + node.maxin; }
}
msg.payload = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
if (node.round) { msg.payload = Math.round(msg.payload); }
node.send(msg);
}
else { node.log("Not a number: "+msg.payload); }
});
}
RED.nodes.registerType("range", RangeNode);
}

View File

@@ -0,0 +1,124 @@
<!--
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="csv">
<div class="form-row">
<label for="node-input-temp"><i class="fa fa-list"></i> Columns</label>
<input type="text" id="node-input-temp" placeholder="comma-separated column names">
</div>
<div class="form-row">
<label for="node-input-select-sep"><i class="fa fa-text-width"></i> Separator</label>
<select style="width: 150px" id="node-input-select-sep">
<option value=",">comma</option>
<option value="\t">tab</option>
<option value=" ">space</option>
<option value=";">semicolon</option>
<option value=":">colon</option>
<option value="#">hashtag</option>
<option value="">other...</option>
</select>
<input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<hr align="middle"/>
<div class="form-row">
<label style="width: 100%;"><i class="fa fa-gears"></i> CSV-to-Object options</label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Input</label>
<input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin">first row contains column names</span>
</div>
<div class="form-row">
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> Output</label>
<select type="text" id="node-input-multi" style="width: 250px;">
<option value="one">a message per row</option>
<option value="mult">a single message [array]</option>
</select>
</div>
<hr align="middle"/>
<div class="form-row">
<label style="width: 100%;"><i class="fa fa-gears"></i> Object-to-CSV options</label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Output</label>
<input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout">include column name row</span>
</div>
<div class="form-row">
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> Newline</label>
<select style="width: 150px" id="node-input-ret">
<option value='\n'>Linux (\n)</option>
<option value='\r'>Mac (\r)</option>
<option value='\r\n'>Windows (\r\n)</option>
</select>
</div>
</script>
<script type="text/x-red" data-help-name="csv">
<p>A function that parses the <b>msg.payload</b> to convert csv to/from a javascript object.
Places the result in the payload.</p>
<p>If the input is a string it tries to parse it as CSV and creates a javascript object.</p>
<p>If the input is a javascript object it tries to build a CSV string.</p>
<p>The columns template should contain an ordered list of column headers. For csv input these become the property names.
For csv output these specify the properties to extract from the object and the order for the csv.</p>
<p><b>Note:</b> the columns should always be specified comma separated - even if another separator is chosen for the data.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('csv',{
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""},
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
//quo: {value:'"',required:true},
hdrin: {value:""},
hdrout: {value:""},
multi: {value:"one",required:true},
ret: {value:'\\n'},
temp: {value:""}
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"csv";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " " || this.sep == "#") {
$("#node-input-select-sep").val(this.sep);
$("#node-input-sep").hide();
} else {
$("#node-input-select-sep").val("");
$("#node-input-sep").val(this.sep);
$("#node-input-sep").show();
}
$("#node-input-select-sep").change(function() {
var v = $("#node-input-select-sep option:selected").val();
$("#node-input-sep").val(v);
if (v == "") {
$("#node-input-sep").val("");
$("#node-input-sep").show().focus();
} else {
$("#node-input-sep").hide();
}
});
}
});
</script>

View File

@@ -0,0 +1,170 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
function CSVNode(n) {
RED.nodes.createNode(this,n);
this.template = n.temp.split(",");
this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
this.quo = '"';
this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
this.winflag = (this.ret === "\r\n");
this.lineend = "\n";
this.multi = n.multi || "one";
this.hdrin = n.hdrin || false;
this.hdrout = n.hdrout || false;
this.goodtmpl = true;
var node = this;
// pass in an array of column names to be trimed, de-quoted and retrimed
var clean = function(col) {
for (var t = 0; t < col.length; t++) {
col[t] = col[t].trim(); // remove leading and trailing whitespace
if (col[t].charAt(0) === '"' && col[t].charAt(col[t].length -1) === '"') {
// remove leading and trailing quotes (if they exist) - and remove whitepace again.
col[t] = col[t].substr(1,col[t].length -2).trim();
}
}
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
else { node.goodtmpl = true; }
return col;
}
node.template = clean(node.template);
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") { // convert object to CSV string
try {
var ou = "";
if (node.hdrout) {
ou += node.template.join(node.sep) + node.ret;
}
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
for (var s = 0; s < msg.payload.length; s++) {
for (var t=0; t < node.template.length; t++) {
// aaargh - resorting to eval here - but fairly contained front and back.
var p = RED.util.ensureString(eval("msg.payload[s]."+node.template[t]));
if (p === "undefined") { p = ""; }
if (p.indexOf(node.sep) != -1) { // add quotes if any "commas"
ou += node.quo + p + node.quo + node.sep;
}
else if (p.indexOf(node.quo) != -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep;
}
else { ou += p + node.sep; } // otherwise just add
}
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
}
msg.payload = ou;
node.send(msg);
}
catch(e) { node.error(e); }
}
else if (typeof msg.payload == "string") { // convert CSV string to object
try {
var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
var j = 0; // pointer into array of template items
var k = [""]; // array of data for each of the template items
var o = {}; // output object to build up
var a = []; // output array is needed for multiline option
var first = true; // is this the first line
var line = msg.payload;
var tmp = "";
// For now we are just going to assume that any \r or \n means an end of line...
// got to be a weird csv that has singleton \r \n in it for another reason...
// Now process the whole file/line
for (var i = 0; i < line.length; i++) {
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")) { // look for first line break
node.template = clean(tmp.split(node.sep));
first = false;
}
else { tmp += line[i]; }
}
else {
if (line[i] === node.quo) { // if it's a quote toggle inside or outside
f = !f;
if (line[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote
if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; }
}
else if ((line[i] === node.sep) && f) { // if we are outside of quote (ie valid separator
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) {
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
o[node.template[j]] = k[j];
}
j += 1;
k[j] = "";
}
else if (f && ((line[i] === "\n") || (line[i] === "\r"))) { // handle multiple lines
//console.log(j,k,o,k[j]);
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
else { k[j].replace(/\r$/,''); }
o[node.template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
if (node.multi === "one") {
var newMessage = RED.util.cloneMessage(msg);
newMessage.payload = o;
node.send(newMessage); // either send
}
else { a.push(o); } // or add to the array
}
j = 0;
k = [""];
o = {};
}
else { // just add to the part of the message
k[j] += line[i];
}
}
}
// Finished so finalize and send anything left
//console.log(j,k,o,k[j]);
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
else { k[j].replace(/\r$/,''); }
o[node.template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
if (node.multi === "one") {
var newMessage = RED.util.cloneMessage(msg);
newMessage.payload = o;
node.send(newMessage); // either send
}
else { a.push(o); } // or add to the aray
}
if (node.multi !== "one") {
msg.payload = a;
node.send(msg); // finally send the array
}
}
catch(e) { node.error(e); }
}
else { node.warn("This node only handles csv strings or js objects."); }
}
});
}
RED.nodes.registerType("csv",CSVNode);
}

View File

@@ -0,0 +1,74 @@
<!--
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="html">
<div class="form-row">
<label for="node-input-tag"><i class="fa fa-filter"></i> Select</label>
<input type="text" id="node-input-tag" placeholder="h1">
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-sign-out"></i> Output</label>
<select id="node-input-ret" style="width:73% !important">
<option value="html">the html content of the elements</option>
<option value="text">only the text content of the elements</option>
<!-- <option value="attr">an object of any attributes</option> -->
<!-- <option value="val">return the value from a form element</option> -->
</select>
</div>
<div class="form-row">
<label for="node-input-as">&nbsp;</label>
<select id="node-input-as" style="width:73% !important">
<option value="single">as a single message containing an array</option>
<option value="multi">as multiple messages, one for each element</option>
</select>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: The <b>Select</b> value is a <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new"><i><u>CSS Selector</u></i></a>, similar to a jQuery selector.</div>
</script>
<script type="text/x-red" data-help-name="html">
<p>Extracts elements from an html document held in <b>msg.payload</b> using a selector.</p>
<p>The selector uses <a href=="https://github.com/cheeriojs/cheerio/blob/master/Readme.md" target="_new">Cheerio</a>
which uses the <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new">CSS selector</a> syntax.</p>
<p>The result can be either a single message with a payload containing an array of the matched elements, or multiple
messages that each contain a matched element.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('html',{
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""},
tag: {value:""},
ret: {value:"html"},
as: {value:"single"}
},
inputs:1,
outputs:1,
icon: "jq.png",
label: function() {
return this.name||this.tag||"html";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,60 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var cheerio = require('cheerio');
function CheerioNode(n) {
RED.nodes.createNode(this,n);
this.tag = n.tag || "h1";
this.ret = n.ret || "html";
this.as = n.as || "single";
var node = this;
this.on("input", function(msg) {
try {
var $ = cheerio.load(msg.payload);
var pay = [];
$(node.tag).each(function() {
if (node.as === "multi") {
var pay2 = null;
if (node.ret === "html") { pay2 = $(this).html(); }
if (node.ret === "text") { pay2 = $(this).text(); }
//if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
//if (node.ret === "val") { pay2 = $(this).val(); }
if (pay2) {
msg.payload = pay2;
node.send(msg);
}
}
if (node.as === "single") {
if (node.ret === "html") { pay.push( $(this).html() ); }
if (node.ret === "text") { pay.push( $(this).text() ); }
//if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
//if (node.ret === "val") { pay.push( $(this).val() ); }
}
});
if ((node.as === "single") && (pay.length !== 0)) {
msg.payload = pay;
node.send(msg);
}
} catch (error) {
node.error('Error: '+error.message);
}
});
}
RED.nodes.registerType("html",CheerioNode);
}

View File

@@ -0,0 +1,47 @@
<!--
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="json">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="json">
<p>A function that parses the <b>msg.payload</b> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
<p>If the input is a JSON string it tries to parse it to a javascript object.</p>
<p>If the input is a javascript object it creates a JSON string.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('json',{
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"json";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,46 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var util = require("util");
function JSONNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload === "string") {
try {
msg.payload = JSON.parse(msg.payload);
node.send(msg);
}
catch(e) { node.error(e+ "\n"+msg.payload); }
}
else if (typeof msg.payload === "object") {
if (!Buffer.isBuffer(msg.payload) ) {
if (!util.isArray(msg.payload)) {
msg.payload = JSON.stringify(msg.payload);
node.send(msg);
}
}
}
else { node.warn("dropped: "+msg.payload); }
}
});
}
RED.nodes.registerType("json",JSONNode);
}

View File

@@ -0,0 +1,78 @@
<!--
Copyright 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="xml">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name" style="width:280px !important">
</div>
<div class="form-row" id="advanced">
</div>
<div id="advanced-options">
<div class="form-row">
<i class="fa fa-key"></i> Represent XML tag attributes as a property named <input type="text" id="node-input-attr" style="width:20px !important">
</div>
<div class="form-row">
<i class="fa fa-key"></i> Prefix to access character content <input type="text" id="node-input-chr" style="width:20px !important">
</div>
<div class="form-tips">There is no simple way to convert XML attributes to JSON
so the approach taken here is to add a property, named $ by default, to the JSON structure.</div>
</div>
<script> {
var showadvanced = showadvanced || true;
var showall = function() {
showadvanced = !showadvanced;
if (showadvanced) {
$("#advanced-options").show();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> Advanced options</label>');
}
else {
$("#advanced-options").hide();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> Advanced options ...</label>');
}
};
showall();
$("#advanced").click( function() { showall(); });
} </script>
</script>
<script type="text/x-red" data-help-name="xml">
<p>A function that parses the <b>msg.payload</b> to convert xml to/from a javascript object. Places the result in the payload.</p>
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
<p>If the input is a javascript object it tries to build an XML string.</p>
<p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('xml',{
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""},
attr: {value:'$',required:true},
chr: {value:'_',required:true}
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"xml";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,48 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var xml2js = require('xml2js');
var parseString = xml2js.parseString;
var builder = new xml2js.Builder({renderOpts:{pretty:false}});
function XMLNode(n) {
RED.nodes.createNode(this,n);
this.attrkey = n.attr || '$';
this.charkey = n.chr || '_';
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") {
msg.payload = builder.buildObject(msg.payload);
node.send(msg);
}
else if (typeof msg.payload == "string") {
parseString(msg.payload, {strict:true,async:true,attrkey:node.attrkey,charkey:node.charkey}, function (err, result) {
if (err) { node.error(err); }
else {
msg.payload = result;
node.send(msg);
}
});
}
else { node.warn("This node only handles xml strings or js objects."); }
}
});
}
RED.nodes.registerType("xml",XMLNode);
}

View File

@@ -0,0 +1,222 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="twitter-credentials">
<div class="form-row" id="node-config-twitter-row"></div>
<input type="hidden" id="node-config-input-screen_name">
</script>
<script type="text/javascript">
(function() {
var twitterConfigNodeId = null;
var twitterConfigNodeIntervalId = null;
function showTwitterAuthStart() {
var pathname = document.location.pathname;
if (pathname.slice(-1) != "/") {
pathname += "/";
}
var callback = encodeURIComponent(location.protocol+"//"+location.hostname+":"+location.port+pathname+"twitter-credentials/"+twitterConfigNodeId+"/auth/callback");
$("#node-config-dialog-ok").button("disable");
$("#node-config-twitter-row").html('<div style="text-align: center; margin-top: 20px; "><a class="btn" id="node-config-twitter-start" href="twitter-credentials/'+twitterConfigNodeId+'/auth?callback='+callback+'" target="_blank">Click here to authenticate with Twitter.</a></div>');
$("#node-config-twitter-start").click(function() {
twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
});
}
function updateTwitterScreenName(sn) {
$("#node-config-input-screen_name").val(sn);
$("#node-config-twitter-row").html('<label><i class="fa fa-user"></i> Twitter ID</label><span class="input-xlarge uneditable-input">'+sn+'</span>');
}
function pollTwitterCredentials(e) {
$.getJSON('credentials/twitter-credentials/'+twitterConfigNodeId,function(data) {
if (data.screen_name) {
updateTwitterScreenName(data.screen_name);
twitterConfigNodeIntervalId = null;
$("#node-config-dialog-ok").button("enable");
} else {
twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000);
}
})
}
RED.nodes.registerType('twitter-credentials',{
category: 'config',
defaults: {
screen_name: {value:""}
},
credentials: {
screen_name: {type:"text"},
access_token: {type: "password"},
access_token_secret: {type:"password"}
},
label: function() {
return this.screen_name;
},
exportable: false,
oneditprepare: function() {
twitterConfigNodeId = this.id;
if (!this.screen_name || this.screen_name == "") {
showTwitterAuthStart();
} else {
if (this.credentials.screen_name) {
updateTwitterScreenName(this.credentials.screen_name);
} else {
showTwitterAuthStart();
}
}
},
oneditsave: function() {
if (twitterConfigNodeIntervalId) {
window.clearTimeout(twitterConfigNodeIntervalId);
}
},
oneditcancel: function(adding) {
if (twitterConfigNodeIntervalId) {
window.clearTimeout(twitterConfigNodeIntervalId);
}
}
});
})();
</script>
<script type="text/x-red" data-template-name="twitter in">
<div class="form-row">
<label for="node-input-twitter"><i class="fa fa-user"></i> Log in as</label>
<input type="text" id="node-input-twitter">
</div>
<div class="form-row">
<label for="node-input-user"><i class="fa fa-search"></i> Search</label>
<select type="text" id="node-input-user" style="display: inline-block; vertical-align: middle; width:60%;">
<option value="false">all public tweets</option>
<option value="true">the tweets of who you follow</option>
<option value="user">the tweets of specific users</option>
<option value="dm">your direct messages</option>
</select>
</div>
<div class="form-row" id="node-input-tags-row">
<label for="node-input-tags"><i class="fa fa-tags"></i> <span id="node-input-tags-label">for</span></label>
<input type="text" id="node-input-tags" placeholder="comma-separated words, @ids, #tags">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: Use commas without spaces between multiple search terms. Comma = OR, Space = AND.
<br/>The Twitter API WILL NOT deliver 100% of all tweets.
<br/>Tweets of who you follow will include their retweets and favourites.</div>
</script>
<script type="text/x-red" data-help-name="twitter in">
<p>Twitter input node. Can be used to search either:
<ul><li>the public or a user's stream for tweets containing the configured search term</li>
<li>all tweets by specific users</li>
<li>direct messages received by the authenticated user</li>
</ul></p>
<p>Use space for <i>and</i> and comma , for <i>or</i> when searching for multiple terms.</p>
<p>Sets the <b>msg.topic</b> to <i>tweets/</i> and then appends the senders screen name.</p>
<p>Sets <b>msg.location</b> to the tweeters location if known.</p>
<p>Sets <b>msg.tweet</b> to the full tweet object as documented by <a href="https://dev.twitter.com/docs/platform-objects/tweets">Twitter</a>.
<p><b>Note:</b> when set to a specific user's tweets, or your direct messages, the node is subject to
Twitter's API rate limiting. If you deploy the flows multiple times within a 15 minute window, you may
exceed the limit and will see errors from the node. These errors will clear when the current 15 minute window
passes.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('twitter in',{
category: 'social-input',
color:"#C0DEED",
defaults: {
twitter: {type:"twitter-credentials",required:true},
tags: {value:"",validate:function(v) { return this.user == "dm" || v.length > 0;}},
user: {value:"false",required:true},
name: {value:""},
topic: {value:"tweets"}
},
inputs:0,
outputs:1,
icon: "twitter.png",
label: function() {
if (this.name) {
return this.name;
}
if (this.user == "dm") {
var user = RED.nodes.node(this.twitter);
return (user?user.label()+" ":"")+"DMs";
} else if (this.user == "user") {
return this.tags+" tweets";
}
return this.tags;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-user").change(function() {
var type = $("#node-input-user option:selected").val();
if (type == "user") {
$("#node-input-tags-row").show();
$("#node-input-tags-label").html("User");
$("#node-input-tags").attr("placeholder","comma-separated @twitter handles");
} else if (type == "dm") {
$("#node-input-tags-row").hide();
} else {
$("#node-input-tags-row").show();
$("#node-input-tags-label").html("for");
$("#node-input-tags").attr("placeholder","comma-separated words, @ids, #hashtags");
}
});
$("#node-input-user").change();
}
});
</script>
<script type="text/x-red" data-template-name="twitter out">
<div class="form-row">
<label for="node-input-twitter"><i class="fa fa-user"></i> Twitter</label>
<input type="text" id="node-input-twitter">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="twitter out">
<p>Twitter out node. Tweets the <b>msg.payload</b>.</p>
<p>To send a Direct Message (DM) - use a payload like "D {username} {message}"</p>
<p>If <b>msg.media</b> exists and is a Buffer object, this node will treat it
as an image and attach it to the tweet.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('twitter out',{
category: 'social-output',
color:"#C0DEED",
defaults: {
twitter: {type:"twitter-credentials",required:true},
name: {value:"Tweet"}
},
inputs:1,
outputs:0,
icon: "twitter.png",
align: "right",
label: function() {
return this.name;
}
});
</script>

View File

@@ -0,0 +1,383 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var ntwitter = require('twitter-ng');
var OAuth= require('oauth').OAuth;
var request = require('request');
function TwitterNode(n) {
RED.nodes.createNode(this,n);
this.screen_name = n.screen_name;
}
RED.nodes.registerType("twitter-credentials",TwitterNode,{
credentials: {
screen_name: {type:"text"},
access_token: {type: "password"},
access_token_secret: {type:"password"}
}
});
/**
* Populate msg.location based on data found in msg.tweet.
*/
function addLocationToTweet(msg) {
if(msg.tweet) {
if(msg.tweet.geo) { // if geo is set, always set location from geo
if(msg.tweet.geo.coordinates && msg.tweet.geo.coordinates.length === 2) {
if (!msg.location) { msg.location = {}; }
// coordinates[0] is lat, coordinates[1] is lon
msg.location.lat = msg.tweet.geo.coordinates[0];
msg.location.lon = msg.tweet.geo.coordinates[1];
msg.location.icon = "twitter";
}
} else if(msg.tweet.coordinates) { // otherwise attempt go get it from coordinates
if(msg.tweet.coordinates.coordinates && msg.tweet.coordinates.coordinates.length === 2) {
if (!msg.location) { msg.location = {}; }
// WARNING! coordinates[1] is lat, coordinates[0] is lon!!!
msg.location.lat = msg.tweet.coordinates.coordinates[1];
msg.location.lon = msg.tweet.coordinates.coordinates[0];
msg.location.icon = "twitter";
}
} // if none of these found then just do nothing
} // if no msg.tweet then just do nothing
}
function TwitterInNode(n) {
RED.nodes.createNode(this,n);
this.active = true;
this.user = n.user;
//this.tags = n.tags.replace(/ /g,'');
this.tags = n.tags;
this.twitter = n.twitter;
this.topic = n.topic||"tweets";
this.twitterConfig = RED.nodes.getNode(this.twitter);
var credentials = RED.nodes.getCredentials(this.twitter);
if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
var twit = new ntwitter({
consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
access_token_key: credentials.access_token,
access_token_secret: credentials.access_token_secret
});
//setInterval(function() {
// twit.get("/application/rate_limit_status.json",null,function(err,cb) {
// console.log("direct_messages:",cb["resources"]["direct_messages"]);
// });
//
//},10000);
var node = this;
if (this.user === "user") {
node.poll_ids = [];
node.since_ids = {};
var users = node.tags.split(",");
for (var i=0;i<users.length;i++) {
var user = users[i].replace(" ","");
twit.getUserTimeline({
screen_name:user,
trim_user:0,
count:1
},function() {
var u = user+"";
return function(err,cb) {
if (err) {
node.error(err);
return;
}
if (cb[0]) {
node.since_ids[u] = cb[0].id_str;
} else {
node.since_ids[u] = '0';
}
node.poll_ids.push(setInterval(function() {
twit.getUserTimeline({
screen_name:u,
trim_user:0,
since_id:node.since_ids[u]
},function(err,cb) {
if (cb) {
for (var t=cb.length-1;t>=0;t-=1) {
var tweet = cb[t];
var where = tweet.user.location;
var la = tweet.lang || tweet.user.lang;
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, lang:la, tweet:tweet };
if (where) {
msg.location = {place:where};
addLocationToTweet(msg);
}
node.send(msg);
if (t == 0) {
node.since_ids[u] = tweet.id_str;
}
}
}
if (err) {
node.error(err);
}
});
},60000));
}
}());
}
} else if (this.user === "dm") {
node.poll_ids = [];
twit.getDirectMessages({
screen_name:node.twitterConfig.screen_name,
trim_user:0,
count:1
},function(err,cb) {
if (err) {
node.error(err);
return;
}
if (cb[0]) {
node.since_id = cb[0].id_str;
} else {
node.since_id = '0';
}
node.poll_ids.push(setInterval(function() {
twit.getDirectMessages({
screen_name:node.twitterConfig.screen_name,
trim_user:0,
since_id:node.since_id
},function(err,cb) {
if (cb) {
for (var t=cb.length-1;t>=0;t-=1) {
var tweet = cb[t];
var where = tweet.sender.location;
var la = tweet.lang || tweet.sender.lang;
var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, lang:la, tweet:tweet };
if (where) {
msg.location = {place:where};
addLocationToTweet(msg);
}
node.send(msg);
if (t == 0) {
node.since_id = tweet.id_str;
}
}
}
if (err) {
node.error(err);
}
});
},120000));
});
} else if (this.tags !== "") {
try {
var thing = 'statuses/filter';
if (this.user === "true") { thing = 'user'; }
var st = { track: [node.tags] };
var bits = node.tags.split(",");
if (bits.length == 4) {
if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) {
st = { locations: node.tags };
}
else {
node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat");
}
}
var setupStream = function() {
if (node.active) {
twit.stream(thing, st, function(stream) {
//console.log(st);
//twit.stream('user', { track: [node.tags] }, function(stream) {
//twit.stream('site', { track: [node.tags] }, function(stream) {
//twit.stream('statuses/filter', { track: [node.tags] }, function(stream) {
node.stream = stream;
stream.on('data', function(tweet) {
if (tweet.user !== undefined) {
var where = tweet.user.location;
var la = tweet.lang || tweet.user.lang;
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, lang:la, tweet:tweet };
if (where) {
msg.location = {place:where};
addLocationToTweet(msg);
}
node.send(msg);
}
});
stream.on('limit', function(tweet) {
node.warn("tweet rate limit hit");
});
stream.on('error', function(tweet,rc) {
if (rc == 420) {
node.warn("Twitter rate limit hit");
} else {
node.warn("Stream error:"+tweet.toString()+" ("+rc+")");
}
setTimeout(setupStream,10000);
});
stream.on('destroy', function (response) {
if (this.active) {
node.warn("twitter ended unexpectedly");
setTimeout(setupStream,10000);
}
});
});
}
}
setupStream();
}
catch (err) {
node.error(err);
}
} else {
this.error("Invalid tag property");
}
} else {
this.error("missing twitter credentials");
}
this.on('close', function() {
if (this.stream) {
this.active = false;
this.stream.destroy();
}
if (this.poll_ids) {
for (var i=0;i<this.poll_ids.length;i++) {
clearInterval(this.poll_ids[i]);
}
}
});
}
RED.nodes.registerType("twitter in",TwitterInNode);
function TwitterOutNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.twitter = n.twitter;
this.twitterConfig = RED.nodes.getNode(this.twitter);
var credentials = RED.nodes.getCredentials(this.twitter);
var node = this;
if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
var twit = new ntwitter({
consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
access_token_key: credentials.access_token,
access_token_secret: credentials.access_token_secret
});
node.on("input", function(msg) {
node.status({fill:"blue",shape:"dot",text:"tweeting"});
if (msg.payload.length > 140) {
msg.payload = msg.payload.slice(0,139);
node.warn("Tweet greater than 140 : truncated");
}
if (msg.media && Buffer.isBuffer(msg.media)) {
var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
var signedUrl = oa.signUrl(apiUrl,
credentials.access_token,
credentials.access_token_secret,
"POST");
var r = request.post(signedUrl,function(err,httpResponse,body) {
if (err) {
node.error(err.toString());
node.status({fill:"red",shape:"ring",text:"failed"});
} else {
var response = JSON.parse(body);
if (response.errors) {
var errorList = response.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
node.error("tweet failed: "+errorList);
node.status({fill:"red",shape:"ring",text:"failed"});
} else {
node.status({});
}
}
});
var form = r.form();
form.append("status",msg.payload);
form.append("media[]",msg.media,{filename:"image"});
} else {
twit.updateStatus(msg.payload, function (err, data) {
if (err) {
node.status({fill:"red",shape:"ring",text:"failed"});
node.error(err);
}
node.status({});
});
}
});
}
}
RED.nodes.registerType("twitter out",TwitterOutNode);
var oa = new OAuth(
"https://api.twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/access_token",
"OKjYEd1ef2bfFolV25G5nQ",
"meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
"1.0",
null,
"HMAC-SHA1"
);
RED.httpAdmin.get('/twitter-credentials/:id/auth', function(req, res){
var credentials = {};
oa.getOAuthRequestToken({
oauth_callback: req.query.callback
},function(error, oauth_token, oauth_token_secret, results){
if (error) {
var resp = '<h2>Oh no!</h2>'+
'<p>Something went wrong with the authentication process. The following error was returned:<p>'+
'<p><b>'+error.statusCode+'</b>: '+error.data+'</p>'+
'<p>One known cause of this type of failure is if the clock is wrong on system running Node-RED.';
res.send(resp)
} else {
credentials.oauth_token = oauth_token;
credentials.oauth_token_secret = oauth_token_secret;
res.redirect('https://twitter.com/oauth/authorize?oauth_token='+oauth_token)
RED.nodes.addCredentials(req.params.id,credentials);
}
});
});
RED.httpAdmin.get('/twitter-credentials/:id/auth/callback', function(req, res, next){
var credentials = RED.nodes.getCredentials(req.params.id);
credentials.oauth_verifier = req.query.oauth_verifier;
oa.getOAuthAccessToken(
credentials.oauth_token,
credentials.token_secret,
credentials.oauth_verifier,
function(error, oauth_access_token, oauth_access_token_secret, results){
if (error){
RED.log.error(error);
res.send("something in twitter oauth broke.");
} else {
credentials = {};
credentials.access_token = oauth_access_token;
credentials.access_token_secret = oauth_access_token_secret;
credentials.screen_name = "@"+results.screen_name;
RED.nodes.addCredentials(req.params.id,credentials);
res.send("<html><head></head><body>Authorised - you can close this window and return to Node-RED</body></html>");
}
}
);
});
}

View File

@@ -16,22 +16,22 @@
<script type="text/x-red" data-template-name="feedparse">
<div class="form-row">
<label for="node-input-url"><i class="icon-globe"></i> Feed url</label>
<label for="node-input-url"><i class="fa fa-globe"></i> Feed url</label>
<input type="text" id="node-input-url">
</div>
<div class="form-row">
<label for="node-input-interval"><i class="icon-repeat"></i> Repeat <span style="font-size: 0.9em;">(M)</span></label>
<label for="node-input-interval"><i class="fa fa-repeat"></i> Repeat <span style="font-size: 0.9em;">(M)</span></label>
<input type="text" id="node-input-interval" placeholder="minutes">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<!-- <div class="form-tips"></div> -->
</script>
<script type="text/x-red" data-help-name="feedparse">
<p>Monitors an RSS/atom feed for new entries.</p>
<p>Monitors an RSS/atom feed for new entries.</p>
</script>
<script type="text/javascript">
@@ -53,5 +53,4 @@
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,79 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var FeedParser = require("feedparser");
var request = require("request");
function FeedParseNode(n) {
RED.nodes.createNode(this,n);
this.url = n.url;
this.interval = (parseInt(n.interval)||15) * 60000;
var node = this;
this.interval_id = null;
this.seen = {};
if (this.url !== "") {
var getFeed = function() {
var req = request(node.url, {timeout: 10000, pool: false});
//req.setMaxListeners(50);
//req.setHeader('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36');
req.setHeader('accept', 'text/html,application/xhtml+xml');
var feedparser = new FeedParser();
req.on('error', function(err) { node.error(err); });
req.on('response', function(res) {
if (res.statusCode != 200) { node.warn('error - Bad status code'); }
else { res.pipe(feedparser); }
});
feedparser.on('error', function(error) { node.error(error); });
feedparser.on('readable', function () {
var stream = this, article;
while (article = stream.read()) {
if (!(article.guid in node.seen) || ( node.seen[article.guid] !== 0 && node.seen[article.guid] != article.date.getTime())) {
node.seen[article.guid] = article.date?article.date.getTime():0;
var msg = {
topic: article.origlink || article.link,
payload: article.description,
article: article
};
node.send(msg);
}
}
});
feedparser.on('meta', function (meta) {});
feedparser.on('end', function () {});
};
this.interval_id = setInterval(function() { getFeed(); }, node.interval);
getFeed();
} else {
this.error("Invalid url");
}
this.on("close", function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
}
});
}
RED.nodes.registerType("feedparse",FeedParseNode);
}

View File

@@ -0,0 +1,193 @@
<!--
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="e-mail">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-envelope"></i> To</label>
<input type="text" id="node-input-name" placeholder="email@address.com">
</div>
<!-- <div class="form-row">
<label for="node-input-pin"><i class="fa fa-asterisk"></i> Service</label>
<select type="text" id="node-input-pin" style="width: 150px;">
<option value="-" disabled> </option>
<option value="DynectEmail">DynectEmail</option>
<option value="Gmail">Gmail</option>
<option value="hot.ee">hot.ee</option>
<option value="Hotmail">Hotmail</option>
<option value="iCloud">iCloud</option>
<option value="mail.ee">mail.ee</option>
<option value="Mail.Ru">Mail.Ru</option>
<option value="Mailgun">Mailgun</option>
<option value="Mailjet">Mailjet</option>
<option value="Mandrill">Mandrill</option>
<option value="Postmark">Postmark</option>
<option value="QQ">QQ</option>
<option value="QQex">QQex</option>
<option value="SendGrid">SendGrid</option>
<option value="SendCloud">SendCloud</option>
<option value="SES">SES</option>
<option value="Yahoo">Yahoo</option>
<option value="yandex">yandex</option>
<option value="Zoho">Zoho</option>
</select>
</div> -->
<div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
<input type="text" id="node-input-server" placeholder="smtp.gmail.com">
</div>
<div class="form-row">
<label for="node-input-port"><i class="fa fa-random"></i> Port</label>
<input type="text" id="node-input-port" placeholder="465">
</div>
<div class="form-row">
<label for="node-input-userid"><i class="fa fa-user"></i> Userid</label>
<input type="text" id="node-input-userid">
</div>
<div class="form-row">
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-input-password">
</div>
<br/>
<div class="form-row">
<label for="node-input-dname"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-dname" placeholder="Name">
</div>
<div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div>
</script>
<script type="text/x-red" data-help-name="e-mail">
<p>Sends the <b>msg.payload</b> as an email, with a subject of <b>msg.topic</b>.</p>
<p>The default message recipient can be configured in the node, if it is left
blank it should be set using the <b>msg.to</b> property of the incoming message.</p>
<p>The payload can be html format.</p>
<p>If the payload is a binary buffer then it will be converted to an attachment.
The filename should be set using <b>msg.filename</b>. Optionally <b>msg.description</b> can be added for the body text.</p>
<p>Alternatively you may provide <b>msg.attachments</b> which should contain an array of one or
more attachments in <a href="https://www.npmjs.com/package/nodemailer#attachments" target="_new">nodemailer</a> format.</p>
</script>
<script type="text/javascript">
(function() {
RED.nodes.registerType('e-mail',{
category: 'social-output',
color:"#c7e9c0",
defaults: {
server: {value:"smtp.gmail.com",required:true},
port: {value:"465",required:true},
name: {value:"",required:true},
dname: {value:""}
},
credentials: {
userid: {type:"text"},
password: {type: "password"},
global: { type:"boolean"}
},
inputs:1,
outputs:0,
icon: "envelope.png",
align: "right",
label: function() {
return this.dname||this.name||"email";
},
labelStyle: function() {
return (this.dname||!this.topic)?"node_label_italic":"";
},
oneditprepare: function() {
if (this.credentials.global) {
$('#node-tip').show();
} else {
$('#node-tip').hide();
};
}
});
})();
</script>
<script type="text/x-red" data-template-name="e-mail in">
<div class="form-row node-input-repeat">
<label for="node-input-repeat"><i class="fa fa-repeat"></i> Check Repeat (S)</label>
<input type="text" id="node-input-repeat" placeholder="300">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
<input type="text" id="node-input-server" placeholder="imap.gmail.com">
</div>
<div class="form-row">
<label for="node-input-port"><i class="fa fa-random"></i> Port</label>
<input type="text" id="node-input-port" placeholder="993">
</div>
<div class="form-row">
<label for="node-input-userid"><i class="fa fa-user"></i> Userid</label>
<input type="text" id="node-input-userid">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-input-password">
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div>
<div id="node-input-tip" class="form-tips">Tip: <b>ONLY</b> retrieves the single most recent email.</div>
</script>
<script type="text/x-red" data-help-name="e-mail in">
<p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p>
<p>The subject is loaded into <b>msg.topic</b> and <b>msg.payload</b> is the plain text body.
If there is text/html then that is returned in <b>msg.html</b>. <b>msg.from</b> and <b>msg.date</b> are also set if you need them.</p>
<p>Uses the imap module.</p>
<p><b>Note:</b> this node <i>only</i> gets the most recent single email from the inbox, so set the repeat (polling) time appropriately.</p>
</script>
<script type="text/javascript">
(function() {
RED.nodes.registerType('e-mail in',{
category: 'social-input',
color:"#c7e9c0",
defaults: {
repeat: {value:"300",required:true},
server: {value:"imap.gmail.com",required:true},
port: {value:"993",required:true},
name: {value:""}
},
credentials: {
userid: {type:"text"},
password: {type: "password"},
global: { type:"boolean"}
},
inputs:0,
outputs:1,
icon: "envelope.png",
label: function() {
return this.name||"email";
},
labelStyle: function() {
return (this.name||!this.topic)?"node_label_italic":"";
},
oneditprepare: function() {
if (this.credentials.global) {
$('#node-tip').show();
} else {
$('#node-tip').hide();
};
}
});
})();
</script>

View File

@@ -0,0 +1,260 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var nodemailer = require("nodemailer");
var Imap = require('imap');
//console.log(nodemailer.Transport.transports.SMTP.wellKnownHosts);
try {
var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
} catch(err) {
}
function EmailNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.name = n.name;
this.outserver = n.server;
this.outport = n.port;
var flag = false;
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
this.userid = this.credentials.userid;
} else {
if (globalkeys) {
this.userid = globalkeys.user;
flag = true;
} else {
this.error("No e-mail userid set");
}
}
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.password = this.credentials.password;
} else {
if (globalkeys) {
this.password = globalkeys.pass;
flag = true;
} else {
this.error("No e-mail password set");
}
}
if (flag) {
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
}
var node = this;
var smtpTransport = nodemailer.createTransport({
host: node.outserver,
port: node.outport,
secure: true,
auth: {
user: node.userid,
pass: node.password
}
});
this.on("input", function(msg) {
if (smtpTransport) {
node.status({fill:"blue",shape:"dot",text:"sending"});
if (msg.to && node.name && (msg.to !== node.name)) {
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
}
var sendopts = { from: node.userid }; // sender address
sendopts.to = msg.to || node.name; // comma separated list of addressees
sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line
if (Buffer.isBuffer(msg.payload)) { // if it's a buffer in the payload then auto create an attachment instead
sendopts.attachments = [ { content: msg.payload, filename:(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin") } ];
if (msg.hasOwnProperty("headers") && msg.headers.hasOwnProperty("content-type")) {
sendopts.attachments[0].contentType = msg.headers["content-type"];
}
// Create some body text..
sendopts.text = "Your file from Node-RED is attached : "+(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin")+ (msg.hasOwnProperty("description") ? "\n\n"+msg.description : "");
}
else {
var payload = RED.util.ensureString(msg.payload);
sendopts.text = payload; // plaintext body
if (/<[a-z][\s\S]*>/i.test(payload)) { sendopts.html = payload; } // html body
if (msg.attachments) { sendopts.attachments = msg.attachments; } // add attachments
}
smtpTransport.sendMail(sendopts, function(error, info) {
if (error) {
node.error(error);
node.status({fill:"red",shape:"ring",text:"send failed"});
} else {
node.log("Message sent: " + info.response);
node.status({});
}
});
}
else { node.warn("No Email credentials found. See info panel."); }
});
}
RED.nodes.registerType("e-mail",EmailNode,{
credentials: {
userid: {type:"text"},
password: {type: "password"},
global: { type:"boolean"}
}
});
function EmailInNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.repeat = n.repeat * 1000 || 300000;
this.inserver = n.server || globalkeys.server || "imap.gmail.com";
this.inport = n.port || globalkeys.port || "993";
var flag = false;
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
this.userid = this.credentials.userid;
} else {
if (globalkeys) {
this.userid = globalkeys.user;
flag = true;
} else {
this.error("No e-mail userid set");
}
}
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.password = this.credentials.password;
} else {
if (globalkeys) {
this.password = globalkeys.pass;
flag = true;
} else {
this.error("No e-mail password set");
}
}
if (flag) {
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
}
var node = this;
this.interval_id = null;
var oldmail = {};
var imap = new Imap({
user: node.userid,
password: node.password,
host: node.inserver,
port: node.inport,
tls: true,
tlsOptions: { rejectUnauthorized: false },
connTimeout: node.repeat,
authTimeout: node.repeat
});
if (!isNaN(this.repeat) && this.repeat > 0) {
node.log("repeat = "+this.repeat);
this.interval_id = setInterval( function() {
node.emit("input",{});
}, this.repeat );
}
this.on("input", function(msg) {
imap.once('ready', function() {
node.status({fill:"blue",shape:"dot",text:"fetching"});
var pay = {};
imap.openBox('INBOX', false, function(err, box) {
if (box.messages.total > 0) {
var f = imap.seq.fetch(box.messages.total + ':*', { markSeen:true, bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
f.on('message', function(msg, seqno) {
node.log('message: #'+ seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
var buffer = '';
stream.on('data', function(chunk) {
buffer += chunk.toString('utf8');
});
stream.on('end', function() {
if (info.which !== 'TEXT') {
pay.from = Imap.parseHeader(buffer).from[0];
pay.topic = Imap.parseHeader(buffer).subject[0];
pay.date = Imap.parseHeader(buffer).date[0];
} else {
var parts = buffer.split("Content-Type");
for (var p = 0; p < parts.length; p++) {
if (parts[p].indexOf("text/plain") >= 0) {
pay.payload = parts[p].split("\n").slice(1,-2).join("\n").trim();
}
if (parts[p].indexOf("text/html") >= 0) {
pay.html = parts[p].split("\n").slice(1,-2).join("\n").trim();
}
}
//pay.body = buffer;
}
});
});
msg.on('end', function() {
//node.log('Finished: '+prefix);
});
});
f.on('error', function(err) {
node.warn('fetch error: ' + err);
node.status({fill:"red",shape:"ring",text:"fetch error"});
});
f.on('end', function() {
if (JSON.stringify(pay) !== oldmail) {
node.send(pay);
oldmail = JSON.stringify(pay);
node.log('received new email: '+pay.topic);
}
else { node.log('duplicate not sent: '+pay.topic); }
//node.status({fill:"green",shape:"dot",text:"ok"});
node.status({});
imap.end();
});
}
else {
node.log("you have achieved inbox zero");
//node.status({fill:"green",shape:"dot",text:"ok"});
node.status({});
imap.end();
}
});
});
node.status({fill:"grey",shape:"dot",text:"connecting"});
imap.connect();
});
imap.on('error', function(err) {
node.log(err);
node.status({fill:"red",shape:"ring",text:"connect error"});
});
this.on("error", function(err) {
node.log("error: ",err);
});
this.on("close", function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
}
if (imap) { imap.destroy(); }
});
node.emit("input",{});
}
RED.nodes.registerType("e-mail in",EmailInNode,{
credentials: {
userid: {type:"text"},
password: {type: "password"},
global: { type:"boolean"}
}
});
};

View File

@@ -0,0 +1,206 @@
<!--
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="irc in">
<div class="form-row">
<label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label>
<input type="text" id="node-input-ircserver">
</div>
<div class="form-row">
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
<input type="text" id="node-input-channel" placeholder="#nodered">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/>
You may join multiple channels by comma separating a list - #chan1,#chan2,etc.</div>
</script>
<script type="text/x-red" data-help-name="irc in">
<p>Connects to a channel on an IRC server.</p>
<p>You may join multiple channels by comma separating a list - #chan1,#chan2,#etc.</p>
<p>Any messages on that channel will appear on the <code>msg.payload</code> at the output,
while <code>msg.topic</code> will contain who it is from.
<code>msg.to</code> contains either the name of the channel or PRIV in the case of a pm.</p>
<p>The second output provides a <code>msg.payload</code> that has any status messages such as joins, parts, kicks etc.</p>
<p>The type of the status message is set as <code>msg.payload.type</code>.</p>
<p>The possible status types are: <br />
<table border="1" cellpadding="1" cellspacing="1">
<thead>
<tr>
<th scope="col">Type</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>message</td>
<td>message is sent into the channel</td>
</tr>
<tr>
<td>pm</td>
<td>private message to the bot</td>
</tr>
<tr>
<td>join</td>
<td>a user joined the channel (also triggered when the bot joins a channel)</td>
</tr>
<tr>
<td>invite</td>
<td>the bot is being invited to a channel</td>
</tr>
<tr>
<td>part</td>
<td>a user leaves a channel</td>
</tr>
<tr>
<td>quit</td>
<td>a user quits a channel</td>
</tr>
<tr>
<td>kick</td>
<td>a user is kicked from a channel</td>
</tr>
<tr>
<td>names</td>
<td>retrieves the list of users when the bot joins a channel</td>
</tr>
</tbody>
</table>
</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('irc in',{
category: 'social-input',
defaults: {
name: {value:""},
ircserver: {type:"irc-server", required:true},
channel: {value:"",required:true,validate:RED.validators.regex(/^#/)}
},
color:"Silver",
inputs:0,
outputs:2,
icon: "hash.png",
label: function() {
var ircNode = RED.nodes.node(this.ircserver);
return this.name || (ircNode ? ircNode.label() : "irc");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if ((this.ircserver !== undefined) && (this.ircserver !== "")) {
this.channel = this.channel || RED.nodes.node(this.ircserver).channel;
$("#node-input-channel").val(this.channel);
}
else { this.channel = this.channel; }
$("#node-input-channel").val(this.channel);
}
});
</script>
<script type="text/x-red" data-template-name="irc out">
<div class="form-row">
<label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label>
<input type="text" id="node-input-ircserver">
</div>
<div class="form-row">
<label for="node-input-channel"><i class="fa fa-random"></i> Channel</label>
<input type="text" id="node-input-channel" placeholder="#nodered">
</div>
<div class="form-row">
<label for="node-input-sendObject"><i class="fa fa-arrows"></i> Action</label>
<select type="text" id="node-input-sendObject" style="display: inline-block; vertical-align: middle; width:70%;">
<option value="pay">Send payload to channel(s)</option>
<option value="true">Use msg.topic to set nickname or channel(s)</option>
<option value="false">Send complete msg object to channel(s)</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/>
Sending the complete object will stringify the whole msg object before sending.</div>
</script>
<script type="text/x-red" data-help-name="irc out">
<p>Sends messages to a channel on an IRC server</p>
<p>You can send just the <code>msg.payload</code>, or the complete <code>msg</code> object to the selected channel,
or you can select to use <code>msg.topic</code> to send the <code>msg.payload</code> to a specific user (private message) or channel.</p>
<p>If multiple output channels are listed (eg. #chan1,#chan2), then the message will be sent to all of them.</p>
<p><b>Note:</b> you can only send to channels you have previously joined so they MUST be specified in the node - even if you then decide to use a subset in msg.topic</p>
<p>You may send RAW commands using <code>msg.raw</code> - This must contain an array of parameters - eg. <pre>["privmsg","#nodered","Hello world"]</pre></p>
</script>
<script type="text/javascript">
RED.nodes.registerType('irc out',{
category: 'social-output',
defaults: {
name: {value:""},
sendObject: {value:"pay", required:true},
ircserver: {type:"irc-server", required:true},
channel: {value:"",required:true,validate:RED.validators.regex(/^#/)}
},
color:"Silver",
inputs:1,
outputs:0,
icon: "hash.png",
align: "right",
label: function() {
return this.name || (this.ircserver ? RED.nodes.node(this.ircserver).label() : "irc");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if ((this.ircserver !== undefined) && (this.ircserver !== "")) {
this.channel = this.channel || RED.nodes.node(this.ircserver).channel;
$("#node-input-channel").val(this.channel);
}
else { this.channel = this.channel; }
}
});
</script>
<script type="text/x-red" data-template-name="irc-server">
<div class="form-row">
<label for="node-config-input-server"><i class="fa fa-globe"></i> IRC Server</label>
<input type="text" id="node-config-input-server" placeholder="irc.freenode.net">
</div>
<div class="form-row">
<label for="node-config-input-nickname"><i class="fa fa-user"></i> Nickname</label>
<input type="text" id="node-config-input-nickname" placeholder="joe123">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('irc-server',{
category: 'config',
defaults: {
server: {value:"",required:true},
nickname: {value:"",required:true}
},
label: function() {
return this.server;
}
});
</script>

277
nodes/core/social/91-irc.js Normal file
View File

@@ -0,0 +1,277 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var irc = require("irc");
// The Server Definition - this opens (and closes) the connection
function IRCServerNode(n) {
RED.nodes.createNode(this,n);
this.server = n.server;
this.channel = n.channel;
this.nickname = n.nickname;
this.lastseen = 0;
this.ircclient = null;
this.on("close", function() {
if (this.ircclient != null) {
this.ircclient.removeAllListeners();
this.ircclient.disconnect();
}
});
}
RED.nodes.registerType("irc-server",IRCServerNode);
// The Input Node
function IrcInNode(n) {
RED.nodes.createNode(this,n);
this.ircserver = n.ircserver;
this.serverConfig = RED.nodes.getNode(this.ircserver);
this.channel = n.channel || this.serverConfig.channel;
var node = this;
if (node.serverConfig.ircclient === null) {
node.log("CONNECT: "+node.serverConfig.server);
node.status({fill:"grey",shape:"dot",text:"connecting"});
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:true,autoRejoin:false,floodProtection:true,retryDelay:20000});
node.serverConfig.ircclient.setMaxListeners(0);
node.serverConfig.ircclient.addListener('error', function(message) {
if (RED.settings.verbose) { node.log("ERR: "+JSON.stringify(message)); }
});
node.serverConfig.ircclient.addListener('netError', function(message) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("NET: "+JSON.stringify(message)); }
node.status({fill:"red",shape:"ring",text:"net error"});
});
node.serverConfig.ircclient.addListener('connect', function() {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("CONNECTED "); }
});
node.serverConfig.ircclient.addListener('registered', function(message) {
node.serverConfig.lastseen = Date.now();
node.log(node.serverConfig.ircclient.nick+" ONLINE: "+message.server);
node.status({fill:"yellow",shape:"dot",text:"connected"});
node.serverConfig.ircclient.join( node.channel, function(data) {
node.log(data+" JOINED: "+node.channel);
node.status({fill:"green",shape:"dot",text:"joined"});
});
});
node.serverConfig.ircclient.addListener('ping', function(server) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("PING from "+JSON.stringify(server)); }
node.status({fill:"green",shape:"dot",text:"ok"});
});
node.serverConfig.ircclient.addListener('quit', function(nick, reason, channels, message) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("QUIT: "+nick+" "+reason+" "+channels+" "+JSON.stringify(message)); }
node.status({fill:"grey",shape:"ring",text:"quit"});
//node.serverConfig.ircclient.disconnect( function() {
// node.serverConfig.ircclient.connect();
//});
//if (RED.settings.verbose) { node.log("restart"); } // then retry
});
node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
//console.log("RAW:"+JSON.stringify(message));
if (message.commandType === "reply") {
//console.log("RAW:"+JSON.stringify(message));
node.serverConfig.lastseen = Date.now();
}
});
node.recon = setInterval( function() {
//console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
if ((Date.now()-node.serverConfig.lastseen) > 240000) { // if more than 4 mins since last seen
node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // If more than 5 mins
//node.serverConfig.ircclient.disconnect();
//node.serverConfig.ircclient.connect();
node.status({fill:"grey",shape:"ring",text:"no connection"});
if (RED.settings.verbose) { node.log("CONNECTION LOST ?"); }
}
//node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}, 60000); // check every 1 min
//node.serverConfig.ircclient.connect();
}
else { node.status({text:""}); }
node.ircclient = node.serverConfig.ircclient;
node.ircclient.addListener('registered', function(message) {
//node.log(node.ircclient.nick+" ONLINE");
node.status({fill:"yellow",shape:"dot",text:"connected"});
node.ircclient.join( node.channel, function(data) {
// node.log(data+" JOINED "+node.channel);
node.status({fill:"green",shape:"dot",text:"joined"});
});
});
node.ircclient.addListener('message', function (from, to, message) {
//node.log(from + ' => ' + to + ' : ' + message);
if (~node.channel.toLowerCase().indexOf(to.toLowerCase())) {
var msg = { "topic":from, "from":from, "to":to, "payload":message };
node.send([msg,null]);
}
//else { console.log(node.channel,to); }
});
node.ircclient.addListener('pm', function(from, message) {
//node.log("PM => "+from + ': ' + message);
var msg = { "topic":from, "from":from, "to":"PRIV", "payload":message };
node.send([msg,null]);
});
node.ircclient.addListener('join', function(channel, who) {
var msg = { "payload": { "type":"join", "who":who, "channel":channel } };
node.send([null,msg]);
//node.log(who+' has joined '+channel);
});
node.ircclient.addListener('invite', function(channel, from, message) {
var msg = { "payload": { "type":"invite", "who":from, "channel":channel, "message":message } };
node.send([null,msg]);
//node.log(from+' sent invite to '+channel+': '+message);
});
node.ircclient.addListener('part', function(channel, who, reason) {
var msg = { "payload": { "type":"part", "who":who, "channel":channel, "reason":reason } };
node.send([null,msg]);
//node.log(who+' has left '+channel+': '+reason);
});
node.ircclient.addListener('quit', function(nick, reason, channels, message) {
var msg = { "payload": { "type":"quit", "who":nick, "channel":channels, "reason":reason } };
node.send([null,msg]);
//node.log(nick+' has quit '+channels+': '+reason);
});
node.ircclient.addListener('kick', function(channel, who, by, reason) {
var msg = { "payload": { "type":"kick", "who":who, "channel":channel, "by":by, "reason":reason } };
node.send([null,msg]);
//node.log(who+' was kicked from '+channel+' by '+by+': '+reason);
});
node.ircclient.addListener('names', function (channel, nicks) {
var msg = { "payload": { "type": "names", "channel": channel, "names": nicks} };
node.send([null, msg]);
});
node.ircclient.addListener('raw', function (message) { // any message means we are alive
node.serverConfig.lastseen = Date.now();
});
node.on("close", function() {
node.ircclient.removeAllListeners();
if (node.recon) { clearInterval(node.recon); }
});
}
RED.nodes.registerType("irc in",IrcInNode);
// The Output Node
function IrcOutNode(n) {
RED.nodes.createNode(this,n);
this.sendFlag = n.sendObject;
this.ircserver = n.ircserver;
this.serverConfig = RED.nodes.getNode(this.ircserver);
this.channel = n.channel || this.serverConfig.channel;
var node = this;
if (node.serverConfig.ircclient === null) {
node.log("CONNECT: "+node.serverConfig.server);
node.status({fill:"grey",shape:"dot",text:"connecting"});
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:true,autoRejoin:false,floodProtection:true,retryDelay:20000});
node.serverConfig.ircclient.setMaxListeners(0);
node.serverConfig.ircclient.addListener('error', function(message) {
if (RED.settings.verbose) { node.log("ERR: "+JSON.stringify(message)); }
});
node.serverConfig.ircclient.addListener('netError', function(message) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("NET: "+JSON.stringify(message)); }
node.status({fill:"red",shape:"ring",text:"net error"});
});
node.serverConfig.ircclient.addListener('connect', function() {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("CONNECTED "); }
});
node.serverConfig.ircclient.addListener('registered', function(message) {
node.serverConfig.lastseen = Date.now();
node.log(node.serverConfig.ircclient.nick+" ONLINE: "+message.server);
node.status({fill:"yellow",shape:"dot",text:"connected"});
node.serverConfig.ircclient.join( node.channel, function(data) {
node.log(data+" JOINED: "+node.channel);
node.status({fill:"green",shape:"dot",text:"joined"});
});
});
node.serverConfig.ircclient.addListener('ping', function(server) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("PING from "+JSON.stringify(server)); }
node.status({fill:"green",shape:"dot",text:"ok"});
});
node.serverConfig.ircclient.addListener('quit', function(nick, reason, channels, message) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("QUIT: "+nick+" "+reason+" "+channels+" "+JSON.stringify(message)); }
node.status({fill:"grey",shape:"ring",text:"quit"});
//node.serverConfig.ircclient.disconnect( function() {
// node.serverConfig.ircclient.connect();
//});
//if (RED.settings.verbose) { node.log("restart"); } // then retry
});
node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
//console.log("RAW:"+JSON.stringify(message));
if (message.commandType === "reply") {
//console.log("RAW:"+JSON.stringify(message));
node.serverConfig.lastseen = Date.now();
}
});
node.recon = setInterval( function() {
//console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
if ((Date.now()-node.serverConfig.lastseen) > 240000) { // if more than 4 mins since last seen
node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // If more than 5 mins
//node.serverConfig.ircclient.disconnect();
//node.serverConfig.ircclient.connect();
node.status({fill:"grey",shape:"ring",text:"no connection"});
if (RED.settings.verbose) { node.log("CONNECTION LOST ?"); }
}
//node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}, 60000); // check every 1 min
//node.serverConfig.ircclient.connect();
}
else { node.status({text:""}); }
node.ircclient = node.serverConfig.ircclient;
node.on("input", function(msg) {
if (Object.prototype.toString.call( msg.raw ) === '[object Array]') {
if (RED.settings.verbose) { node.log("RAW command:"+msg.raw); }
node.ircclient.send.apply(node.ircclient,msg.raw);
}
else {
if (msg._topic) { delete msg._topic; }
var ch = node.channel.split(","); // split on , so we can send to multiple
if (node.sendFlag == "true") { // override channels with msg.topic
if ((msg.hasOwnProperty('topic'))&&(typeof msg.topic === "string")) {
ch = msg.topic.split(","); // split on , so we can send to multiple
}
else { node.warn("msg.topic not set"); }
}
for (var c = 0; c < ch.length; c++) {
if (node.sendFlag == "false") { // send whole message object to each channel
node.ircclient.say(ch[c], JSON.stringify(msg));
}
else { // send just the payload to each channel
if (typeof msg.payload === "object") { msg.payload = JSON.stringify(msg.payload); }
node.ircclient.say(ch[c], msg.payload);
}
}
}
});
node.on("close", function() {
node.ircclient.removeAllListeners();
if (node.recon) { clearInterval(node.recon); }
});
}
RED.nodes.registerType("irc out",IrcOutNode);
}

View File

@@ -16,7 +16,7 @@
<script type="text/x-red" data-template-name="tail">
<div class="form-row node-input-filename">
<label for="node-input-filename"><i class="icon-file"></i> Filename</label>
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
<div class="form-row">
@@ -25,15 +25,15 @@
<label for="node-input-split" style="width: 70%;">Split lines if we see \n ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">WON'T work on Windows.</div>
<!-- <div class="form-tips">WON'T work on Windows.</div> -->
</script>
<script type="text/x-red" data-help-name="tail">
<p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
<p>This won't work on Windows filesystems (as it relies on the tail -f command) so we will probably have to hide it in future.</p>
<p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
<p>This won't work on Windows filesystems, as it relies on the tail -F command.</p>
</script>
<script type="text/javascript">

View File

@@ -0,0 +1,69 @@
/**
* Copyright 2013, 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var spawn = require('child_process').spawn;
var plat = require('os').platform();
if (plat.match(/^win/)) {
throw "Info : Currently not supported on Windows.";
}
function TailNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename;
this.split = n.split;
var node = this;
var err = "";
// TODO: rewrite to use node-tail
var tail = spawn("tail", ["-F", "-n", "0", this.filename]);
tail.stdout.on("data", function (data) {
if (node.split) {
// TODO: allow customisation of the line break - as we do elsewhere
var strings = data.toString().split("\n");
for (var s in strings) {
//TODO: should we really filter blanks? Is that expected?
if (strings[s] !== "") {
node.send({
topic: node.filename,
payload: strings[s]
});
}
}
}
else {
var msg = {
topic:node.filename,
payload: data.toString()
};
node.send(msg);
}
});
tail.stderr.on("data", function(data) {
node.error(data.toString());
});
this.on("close", function() {
if (tail) { tail.kill(); }
});
}
RED.nodes.registerType("tail",TailNode);
}

View File

@@ -0,0 +1,120 @@
<!--
Copyright 2013, 2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="file">
<div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
<div class="form-row">
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> Action</label>
<select type="text" id="node-input-overwriteFile" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">append to file</option>
<option value="true">overwrite file</option>
<option value="delete">delete file</option>
</select>
</div>
<div class="form-row" id="node-appline">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-appendNewline" style="width: 70%;">Add newline (\n) to each payload ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="file">
<p>Writes <b>msg.payload</b> to the file specified, e.g. to create a log.</p>
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
<p>A newline is added to every message. But this can be turned off if required, for example, to allow binary files to be written.</p>
<p>The default behaviour is to append to the file. This can be changed to overwrite the file each time, for example if you want to output a "static" web page or report.</p>
<p>This node can also be configured to delete a file if required. <i>Note:</i> Using msg.delete is now deprecated.</p>
</script>
<script type="text/x-red" data-template-name="file in">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
<div class="form-row">
<label for="node-input-format"><i class="fa fa-sign-out"></i> Output as</label>
<select id="node-input-format">
<option value="utf8">a utf8 string</option>
<option value="">a Buffer</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="file in">
<p>Reads the specified file and sends the content as <b>msg.payload</b>, and the filename as <b>msg.filename</b>.</p>
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('file',{
category: 'storage-output',
defaults: {
name: {value:""},
filename: {value:""},
appendNewline: {value:true},
overwriteFile: {value:"false"}
},
color:"BurlyWood",
inputs:1,
outputs:0,
icon: "file.png",
align: "right",
label: function() {
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; }
else { return this.name||this.filename; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-overwriteFile").on("change",function() {
if (this.value === "delete") { $("#node-appline").hide(); }
else { $("#node-appline").show(); }
});
}
});
RED.nodes.registerType('file in',{
category: 'storage-input',
defaults: {
name: {value:""},
filename: {value:""},
format: {value:"utf8"},
},
color:"BurlyWood",
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||this.filename;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -0,0 +1,119 @@
/**
* Copyright 2013, 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var fs = require("fs");
function FileNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename || "";
this.appendNewline = n.appendNewline;
this.overwriteFile = n.overwriteFile.toString();
var node = this;
this.on("input",function(msg) {
var filename;
if (msg.filename) {
if (n.filename && (n.filename !== msg.filename)) {
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
}
filename = msg.filename;
node.status({fill:"grey",shape:"dot",text:msg.filename});
} else {
filename = this.filename;
}
if (filename === "") {
node.warn('No filename specified');
} else if (msg.hasOwnProperty('delete')) {
node.warn("Deprecated: please use specific delete option in config dialog.");
fs.unlink(filename, function (err) {
if (err) { node.error('Failed to delete file : '+err); }
});
} else if (typeof msg.payload != "undefined") {
var data = msg.payload;
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
data = JSON.stringify(data);
}
if (typeof data === "boolean") { data = data.toString(); }
if ((this.appendNewline)&&(!Buffer.isBuffer(data))) { data += "\n"; }
if (this.overwriteFile === "true") {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
fs.writeFile(filename, data, "binary", function (err) {
if (err) { node.error('Failed to write to file : '+err); }
else if (RED.settings.verbose) { node.log('wrote to file: '+filename); }
});
}
else if (this.overwriteFile === "delete") {
fs.unlink(filename, function (err) {
if (err) { node.error('Failed to delete file : '+err); }
else if (RED.settings.verbose) { node.log("deleted file: "+filename); }
});
}
else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
fs.appendFile(filename, data, "binary", function (err) {
if (err) { node.error('Failed to append to file : '+err); }
else if (RED.settings.verbose) { node.log('appended to file: '+filename); }
});
}
}
});
}
RED.nodes.registerType("file",FileNode);
function FileInNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename || "";
this.format = n.format;
var node = this;
var options = {};
if (this.format) {
options['encoding'] = this.format;
}
this.on("input",function(msg) {
var filename;
if (msg.filename) {
if (n.filename && (n.filename !== msg.filename)) {
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
}
filename = msg.filename;
} else {
filename = this.filename;
}
if (filename === "") {
node.warn('No filename specified');
} else {
msg.filename = filename;
fs.readFile(filename,options,function(err,data) {
if (err) {
node.error(err);
msg.error = err;
delete msg.payload;
} else {
msg.payload = data;
delete msg.error;
}
node.send(msg);
});
}
});
}
RED.nodes.registerType("file in",FileInNode);
}

View File

@@ -16,17 +16,17 @@
<script type="text/x-red" data-template-name="redis out">
<div class="form-row node-input-hostname">
<label for="node-input-hostname"><i class="icon-bookmark"></i> Host</label>
<label for="node-input-hostname"><i class="fa fa-bookmark"></i> Host</label>
<input class="input-append-left" type="text" id="node-input-hostname" placeholder="127.0.0.1" style="width: 40%;" ><button id="node-input-hostname-lookup" class="btn input-append-right"><span class="caret"></span></button>
<label for="node-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
<input type="text" id="node-input-port" placeholder="6379" style="width:45px">
</div>
<div class="form-row">
<label for="node-input-key"><i class="icon-briefcase"></i> Key</label>
<label for="node-input-key"><i class="fa fa-key"></i> Key</label>
<input type="text" id="node-input-key" placeholder="Redis Key">
</div>
<div class="form-row">
<label for="node-input-type"><i class="icon-th"></i> Type</label>
<label for="node-input-type"><i class="fa fa-th"></i> Type</label>
<select type="text" id="node-input-structtype" style="width: 150px;">
<option value="string">String</option>
<option value="hash">Hash</option>
@@ -35,18 +35,18 @@
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">
If key is blank, the topic will be used as the key.<br>
If type is hash, payload should be field=value.
If type is hash, payload should be an object or field=value string.
</div>
</script>
<script type="text/x-red" data-help-name="redis out">
<p>A Redis output node. Options include Hash, Set, List and String.</p>
<p>To run this you need a local Redis server running. For details see <a href="http://redis.io/" target="_new">the Redis site</a>.</p>
<p>A Redis output node. Options include Hash, Set, List and String.</p>
<p>To run this you need a local Redis server running. For details see <a href="http://redis.io/" target="_new">the Redis site</a>.</p>
</script>
<script type="text/javascript">

View File

@@ -0,0 +1,110 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var redis = require("redis");
var hashFieldRE = /^([^=]+)=(.*)$/;
var redisConnectionPool = function() {
var connections = {};
var obj = {
get: function(host,port) {
var id = host+":"+port;
if (!connections[id]) {
connections[id] = redis.createClient(port,host);
connections[id].on("error",function(err) {
RED.log.error(err);
});
connections[id].on("connect",function() {
if (RED.settings.verbose) { RED.log.info("connected to "+host+":"+port); }
});
connections[id]._id = id;
connections[id]._nodeCount = 0;
}
connections[id]._nodeCount += 1;
return connections[id];
},
close: function(connection) {
connection._nodeCount -= 1;
if (connection._nodeCount === 0) {
if (connection) {
clearTimeout(connection.retry_timer);
connection.end();
}
delete connections[connection._id];
}
}
};
return obj;
}();
function RedisOutNode(n) {
RED.nodes.createNode(this,n);
this.port = n.port||"6379";
this.hostname = n.hostname||"127.0.0.1";
this.key = n.key;
this.structtype = n.structtype;
this.client = redisConnectionPool.get(this.hostname,this.port);
if (this.client.connected) {
this.status({fill:"green",shape:"dot",text:"connected"});
} else {
this.status({fill:"red",shape:"ring",text:"disconnected"},true);
}
var node = this;
this.client.on("end", function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
});
this.client.on("connect", function() {
node.status({fill:"green",shape:"dot",text:"connected"});
});
this.on("input", function(msg) {
var k = this.key || msg.topic;
if (k) {
if (this.structtype == "string") {
this.client.set(k,RED.util.ensureString(msg.payload));
} else if (this.structtype == "hash") {
if (typeof msg.payload == "object") {
this.client.hmset(k,msg.payload);
} else {
var r = hashFieldRE.exec(msg.payload);
if (r) {
this.client.hset(k,r[1],r[2]);
} else {
this.warn("Invalid payload for redis hash");
}
}
} else if (this.structtype == "set") {
this.client.sadd(k,msg.payload);
} else if (this.structtype == "list") {
this.client.rpush(k,msg.payload);
}
} else {
this.warn("No key or topic set");
}
});
this.on("close", function() {
redisConnectionPool.close(node.client);
});
}
RED.nodes.registerType("redis out",RedisOutNode);
}

View File

@@ -0,0 +1,231 @@
<!--
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="mongodb">
<div class="form-row">
<label for="node-config-input-hostname"><i class="fa fa-bookmark"></i> Host</label>
<input class="input-append-left" type="text" id="node-config-input-hostname" placeholder="localhost" style="width: 40%;" >
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
<input type="text" id="node-config-input-port" placeholder="27017" style="width:45px">
</div>
<div class="form-row">
<label for="node-config-input-db"><i class="fa fa-database"></i> Database</label>
<input type="text" id="node-config-input-db" placeholder="test">
</div>
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-config-input-user">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-config-input-password">
</div>
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('mongodb', {
category: 'config',
color: "rgb(218, 196, 180)",
defaults: {
hostname: {value: "127.0.0.1", required: true},
port: {value: 27017, required: true},
db: {value: "", required: true},
name: {value: ""}
},
credentials: {
user: {type: "text"},
password: {type: "password"}
},
label: function() {
return this.name || this.hostname + ":" + this.port + "/" + this.db;
}
});
</script>
<script type="text/x-red" data-template-name="mongodb out">
<div class="form-row">
<label for="node-input-mongodb"><i class="fa fa-bookmark"></i> Server</label>
<input type="text" id="node-input-mongodb">
</div>
<div class="form-row">
<label for="node-input-collection"><i class="fa fa-briefcase"></i> Collection</label>
<input type="text" id="node-input-collection" placeholder="collection">
</div>
<div class="form-row">
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
<select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
<option value="store">save</option>
<option value="insert">insert</option>
<option value="update">update</option>
<option value="delete">remove</option>
</select>
</div>
<div class="form-row node-input-payonly">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-payonly" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-payonly" style="width: 70%;">Only store msg.payload object</label>
</div>
<div class="form-row node-input-upsert">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-upsert" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-upsert" style="width: 70%;">Create a new document if no match found</label>
</div>
<div class="form-row node-input-multi">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-multi" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;;">
<label for="node-input-multi" style="width: 70%;">Update all matching documents</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="node-warning" style="display: none"><b> Tip:</b> If no collection is set, ensure <b>msg.collection</b> will contain the collection name
</div>
</script>
<script type="text/x-red" data-help-name="mongodb out">
<p>A simple MongoDB output node. Can save, insert, update and remove objects from a chosen collection.</p>
<p>Save will update an existing object or insert a new object if one does not already exist.</p>
<p>Insert will insert a new object.</p>
<p>Save and insert either store <b>msg</b> or <b>msg.payload</b>.</p>
<p>Update will modify an existing object or objects. The query to select objects to update uses <b>msg.query</b> and the update to the element uses <b>msg.payload</b>.</p>
<p>Update can add a object if it does not exist or update multiple objects.</p>
<p>Remove will remove objects that match the query passed in on <b>msg.payload</b>. A blank query will delete <i>all of the objects</i> in the collection.</p>
<p>You can either set the collection method in the node config or on <b>msg.collection</b>. Setting it in the node will override <b>msg.collection</b>.</p>
<p>By default MongoDB creates an <i>_id</i> property as the primary key - so repeated injections of the same <b>msg</b> will result in many database entries.</p>
<p>If this is NOT the desired behaviour - ie. you want repeated entries to overwrite, then you must set the <b>msg._id</b> property to be a constant by the use of a previous function node.</p>
<p>This could be a unique constant or you could create one based on some other msg property.</p>
<p>Currently we do not limit or cap the collection size at all... this may well change.</p>
</script>
<script type="text/javascript">
function oneditprepare() {
$("#node-input-operation").change(function () {
var id = $("#node-input-operation option:selected").val();
if (id === "update") {
$(".node-input-payonly").hide();
$(".node-input-upsert, .node-input-multi").show();
} else if (id === "delete") {
$(".node-input-payonly, .node-input-upsert, .node-input-multi").hide();
} else {
$(".node-input-payonly").show();
$(".node-input-upsert, .node-input-multi").hide();
}
});
$("#node-input-collection").change(function () {
if($("#node-input-collection").val() === "") {
$("#node-warning").show();
} else {
$("#node-warning").hide();
}
});
}
RED.nodes.registerType('mongodb out', {
category: 'storage-output',
color: "rgb(218, 196, 180)",
defaults: {
mongodb: {type: "mongodb", required: true},
name: {value: ""},
collection: {value: ""},
payonly: {value: false},
upsert: {value: false},
multi: {value: false},
operation: {value: "store"}
},
inputs: 1,
outputs: 0,
icon: "mongodb.png",
align: "right",
label: function() {
var mongoNode = RED.nodes.node(this.mongodb);
return this.name || (mongoNode ? mongoNode.label() + " " + this.collection: "mongodb");
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: oneditprepare
});
</script>
<script type="text/x-red" data-template-name="mongodb in">
<div class="form-row">
<label for="node-input-mongodb"><i class="fa fa-bookmark"></i> Server</label>
<input type="text" id="node-input-mongodb">
</div>
<div class="form-row">
<label for="node-input-collection"><i class="fa fa-briefcase"></i> Collection</label>
<input type="text" id="node-input-collection" placeholder="collection">
</div>
<div class="form-row">
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
<select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
<option value="find">find</option>
<option value="count">count</option>
<option value="aggregate">aggregate</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips" id="node-warning" style="display: none"><b> Tip:</b> If no collection is set, ensure <b>msg.collection</b> will contain the collection name
</div>
</script>
<script type="text/x-red" data-help-name="mongodb in">
<p>Calls a MongoDB collection method based on the selected operator.</p>
<p>Find queries a collection using the <b>msg.payload</b> as the query statement as per the .find() function. Optionally, you may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object, a <b>msg.limit</b> number and a <b>msg.skip</b> number.</p>
<p>Count returns a count of the number of documents in a collection or matching a query using the <b>msg.payload</b> as the query statement.</p>
<p>Aggregate provides access to the aggregation pipeline using the <b>msg.payload</b> as the pipeline array.</p>
<p>You can either set the collection method in the node config or on <b>msg.collection</b>. Setting it in the node will override <b>msg.collection</b>.</p>
<p>See the <a href="http://docs.mongodb.org/manual/reference/method/db.collection.find/" target="new"><i>MongoDB collection methods docs</i></a> for examples.</p>
<p>The result is returned in <b>msg.payload</b>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('mongodb in', {
category: 'storage-input',
color: "rgb(218, 196, 180)",
defaults: {
mongodb: {type: "mongodb", required: true},
name: {value: ""},
collection: {value: ""},
operation: {value: "find"}
},
inputs: 1,
outputs: 1,
icon: "mongodb.png",
label: function() {
var mongoNode = RED.nodes.node(this.mongodb);
return this.name || (mongoNode ? mongoNode.label() + " " + this.collection: "mongodb");
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: oneditprepare
});
</script>

View File

@@ -0,0 +1,243 @@
/**
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var mongo = require('mongodb');
var MongoClient = mongo.MongoClient;
function MongoNode(n) {
RED.nodes.createNode(this,n);
this.hostname = n.hostname;
this.port = n.port;
this.db = n.db;
this.name = n.name;
var url = "mongodb://";
if (this.credentials && this.credentials.user && this.credentials.password) {
url += this.credentials.user+":"+this.credentials.password+"@";
}
url += this.hostname+":"+this.port+"/"+this.db;
this.url = url;
}
RED.nodes.registerType("mongodb",MongoNode,{
credentials: {
user: {type:"text"},
password: {type: "password"}
}
});
function ensureValidSelectorObject(selector) {
if (selector != null && (typeof selector != 'object' || Buffer.isBuffer(selector))) {
return {};
}
return selector;
}
function MongoOutNode(n) {
RED.nodes.createNode(this,n);
this.collection = n.collection;
this.mongodb = n.mongodb;
this.payonly = n.payonly || false;
this.upsert = n.upsert || false;
this.multi = n.multi || false;
this.operation = n.operation;
this.mongoConfig = RED.nodes.getNode(this.mongodb);
if (this.mongoConfig) {
var node = this;
MongoClient.connect(this.mongoConfig.url, function(err, db) {
if (err) {
node.error(err);
} else {
node.clientDb = db;
var coll;
if (node.collection) {
coll = db.collection(node.collection);
}
node.on("input",function(msg) {
if (!node.collection) {
if (msg.collection) {
coll = db.collection(msg.collection);
} else {
node.error("No collection defined");
return;
}
}
delete msg._topic;
delete msg.collection;
if (node.operation === "store") {
if (node.payonly) {
if (typeof msg.payload !== "object") {
msg.payload = {"payload": msg.payload};
}
coll.save(msg.payload,function(err, item) {
if (err) {
node.error(err);
}
});
} else {
coll.save(msg,function(err, item) {
if (err) {
node.error(err);
}
});
}
} else if (node.operation === "insert") {
if (node.payonly) {
if (typeof msg.payload !== "object") {
msg.payload = {"payload": msg.payload};
}
coll.insert(msg.payload, function(err, item) {
if (err) {
node.error(err);
}
});
} else {
coll.insert(msg, function(err,item) {
if (err) {
node.error(err);
}
});
}
} else if (node.operation === "update") {
if (typeof msg.payload !== "object") {
msg.payload = {"payload": msg.payload};
}
var query = msg.query || {};
var payload = msg.payload || {};
var options = {
upsert: node.upsert,
multi: node.multi
};
coll.update(query, payload, options, function(err, item) {
if (err) {
node.error(err + " " + payload);
}
});
} else if (node.operation === "delete") {
coll.remove(msg.payload, function(err, items) {
if (err) {
node.error(err);
}
});
}
});
}
});
} else {
this.error("missing mongodb configuration");
}
this.on("close", function() {
if (this.clientDb) {
this.clientDb.close();
}
});
}
RED.nodes.registerType("mongodb out",MongoOutNode);
function MongoInNode(n) {
RED.nodes.createNode(this,n);
this.collection = n.collection;
this.mongodb = n.mongodb;
this.operation = n.operation || "find";
this.mongoConfig = RED.nodes.getNode(this.mongodb);
if (this.mongoConfig) {
var node = this;
MongoClient.connect(this.mongoConfig.url, function(err,db) {
if (err) {
node.error(err);
} else {
node.clientDb = db;
var coll;
if (node.collection) {
coll = db.collection(node.collection);
}
node.on("input", function(msg) {
if (!node.collection) {
if (msg.collection) {
coll = db.collection(msg.collection);
} else {
node.error("No collection defined");
return;
}
}
if (node.operation === "find") {
msg.projection = msg.projection || {};
var selector = ensureValidSelectorObject(msg.payload);
var limit = msg.limit;
if (typeof limit === "string" && !isNaN(limit)) {
limit = Number(limit);
}
var skip = msg.skip;
if (typeof skip === "string" && !isNaN(skip)) {
skip = Number(skip);
}
coll.find(selector,msg.projection).sort(msg.sort).limit(limit).skip(skip).toArray(function(err, items) {
if (err) {
node.error(err);
} else {
msg.payload = items;
delete msg.projection;
delete msg.sort;
delete msg.limit;
delete msg.skip;
node.send(msg);
}
});
} else if (node.operation === "count") {
var selector = ensureValidSelectorObject(msg.payload);
coll.count(selector, function(err, count) {
if (err) {
node.error(err);
} else {
msg.payload = count;
node.send(msg);
}
});
} else if (node.operation === "aggregate") {
msg.payload = (Array.isArray(msg.payload)) ? msg.payload : [];
coll.aggregate(msg.payload, function(err, result) {
if (err) {
node.error(err);
} else {
msg.payload = result;
node.send(msg);
}
});
}
});
}
});
} else {
this.error("missing mongodb configuration");
}
this.on("close", function() {
if (this.clientDb) {
this.clientDb.close();
}
});
}
RED.nodes.registerType("mongodb in",MongoInNode);
}

View File

@@ -1,69 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="socket in">
<div class="form-row">
<label for="node-input-transport"><i class="icon-tasks"></i> Type</label>
<select type="text" id="node-input-transport" style="width: 150px;">
<option value="http">http listen</option>
<option value="tcp">tcp server</option>
<!-- <option value="tcpc">tcp client</option> -->
<option value="udp">udp socket</option>
</select>
</div>
<div class="form-row">
<label for="node-input-port"><i class="icon-random"></i> Port</label>
<input type="text" id="node-input-port" placeholder="Port">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: sends the received data as a Buffer object.</div>
</script>
<script type="text/x-red" data-help-name="socket in">
<p>Provides a input node for http, tcp or udp sockets. Topic is optional. These are server like sockets.</p>
<p>The TCP and UDP sockets produce a <i>BUFFER</i> object msg.payload and NOT a String. If you need a String then use .toString() on msg.payload in your next function block.</p>
<p>TCP and UDP sockets also provide <b>msg.fromip</b> of the form ipaddress:port</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('socket in',{
category: 'deprecated',
color:"Silver",
defaults: {
name: {value:""},
topic: {value:""},
port: {value:"",required:true},
transport: {value:"tcp",required:true}
},
inputs:0,
outputs:1,
icon: "bridge-dash.png",
label: function() {
return this.name||this.topic||("socket "+this.transport+":"+this.port);
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,152 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
function SocketIn(n) {
RED.nodes.createNode(this,n);
this.warn("node type deprecated - will be removed in a future release");
this.port = n.port;
this.topic = n.topic;
this.trans = (n.transport||n.trans||"").toLowerCase();
var node = this;
if (this.trans == "http") {
var http = require('http');
var server = http.createServer(function (req, res) {
//node.log("http "+req.url);
var msg = {topic:node.topic,payload:req.url.slice(1)};
node.send(msg);
res.writeHead(304, {'Content-Type': 'text/plain'});
res.end('\n');
}).listen(node.port);
server.on('error', function (e) {
if (e.code == 'EADDRINUSE') {
setTimeout(node.error('TCP port is already in use - please reconfigure socket.'),250);
}
else { console.log(e); }
server = null;
});
node.log('http listener at http://127.0.0.1:'+node.port+'/');
this._close = function() {
if (server) server.close();
node.log('http listener stopped');
}
}
if (this.trans == "tcp") {
var net = require('net');
var server = net.createServer(function (socket) {
var buffer = null;
socket.on('data', function (chunk) {
if (buffer == null) {
buffer = chunk;
} else {
buffer = Buffer.concat([buffer,chunk]);
}
});
socket.on('end', function() {
var msg = {topic:node.topic, payload:buffer, fromip:socket.remoteAddress+':'+socket.remotePort};
node.send(msg);
});
});
server.on('error', function (e) {
if (e.code == 'EADDRINUSE') {
setTimeout(node.error('TCP port is already in use - please reconfigure socket.'),250);
}
else { console.log(e); }
server = null;
});
server.listen(node.port);
node.log('tcp listener on port :'+node.port);
this._close = function() {
if (server) server.close();
node.log('tcp listener stopped');
}
}
if (this.trans == "tcpc") {
var net = require('net');
var client;
var to;
function setupTcpClient() {
node.log('tcpc connecting to port :'+node.port);
client = net.connect({port: node.port}, function() {
node.log("tcpc connected");
});
client.on('data', function (data) {
var msg = {topic:node.topic, payload:data};
node.send(msg);
});
client.on('end', function() {
node.log("tcpc socket ended");
});
client.on('close', function() {
node.log('tcpc socket closed');
to = setTimeout(setupTcpClient, 10000); //Try to reconnect
});
client.on('error', function() {
node.log('tcpc socket error');
client = null;
to = setTimeout(setupTcpClient, 10000); //Try to reconnect
});
}
setupTcpClient();
this._close = function() {
if (client) client.end();
//client.destroy();
clearTimeout(to);
node.log('tcpc stopped client');
}
setupTcpClient();
}
if (this.trans == "udp") {
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on('listening', function () {
var address = server.address();
node.log('udp listener at ' + address.address + ":" + address.port);
});
server.on('message', function (message, remote) {
var msg = {topic:node.topic,payload:message,fromip:remote.address+':'+remote.port};
node.send(msg);
});
server.on('error', function (e) {
console.log(e);
server = null;
});
server.bind(node.port);
this._close = function() {
if (server) server.close();
node.log('udp listener stopped');
}
}
}
RED.nodes.registerType("socket in",SocketIn);
SocketIn.prototype.close = function() {
this._close();
}

View File

@@ -1,66 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="socket out">
<div class="form-row">
<label for="node-input-host"><i class="icon-bookmark"></i> Host</label>
<input type="text" id="node-input-host" placeholder="localhost" style="width: 40%;" >
<label for="node-input-port" style="margin-left: 10px; width: 35px;"> Port</label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
</div>
<div class="form-row">
<label for="node-input-transport"><i class="icon-tasks"></i> Type</label>
<select type="text" id="node-input-transport" style="width: 150px;">
<option value="http">http</option>
<option value="tcp">tcp</option>
<option value="udp">udp</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="socket out">
<p>Provides a choice of http, tcp or udp output connections. All connect, send their <b>msg.payload</b> and disconnect.</p>
<p>To use broadcast select udp and set the host to be either the subnet broadcast address required or 255.255.255.255</p>
<p>If you need a response from an http request use the httpget node instead.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('socket out',{
category: 'deprecated',
color:"Silver",
defaults: {
host: {value:"127.0.0.1",required:true},
port: {value:"",required:true},
name: {value:""},
transport: {value:"tcp",required:true}
},
inputs:1,
outputs:0,
icon: "bridge-dash.png",
align: "right",
label: function() {
return this.name||this.topic||("socket "+this.transport+":"+this.port);
},
labelStyle: function() {
return (this.name||!this.topic)?"node_label_italic":"";
}
});
</script>

View File

@@ -1,65 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
function SocketOut(n) {
RED.nodes.createNode(this,n);
this.warn("node type deprecated - will be removed in a future release");
this.host = n.host;
this.port = n.port * 1;
this.name = n.name;
this.trans = n.transport||n.trans||"";
var node = this;
this.on("input", function(msg) {
if (msg != null) {
if (this.trans == "http") {
var http = require("http");
http.get(msg.payload, function(res) {
node.log("http : response : " + res.statusCode);
}).on('error', function(e) {
node.error("http : error : " + e.message);
});
}
if (this.trans == "tcp") {
var net = require('net');
var client = new net.Socket();
client.on('error', function (err) {
node.error('tcp : '+err);
});
client.connect(this.port, this.host, function() {
try { client.end(msg.payload); }
catch (e) { node.error(e); }
});
}
if (this.trans == "udp") {
var dgram = require('dgram');
var sock = dgram.createSocket('udp4'); // only use ipv4 for now
sock.bind(this.port); // have to bind before you can enable broadcast...
sock.setBroadcast(true); // turn on broadcast
var buf = new Buffer(msg.payload);
sock.send(buf, 0, buf.length, this.port, this.host, function(err, bytes) {
if (err) node.error("udp : "+err);
//util.log('[socket out] udp :' +bytes);
sock.close();
});
}
}
});
var node = this;
}
RED.nodes.registerType("socket out",SocketOut);

View File

@@ -1,131 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The Input Node -->
<script type="text/x-red" data-template-name="multicast in">
<div class="form-row">
<label for="node-input-group"><i class="icon-tasks"></i> Group</label>
<input type="text" id="node-input-group" placeholder="225.0.18.83" style="width: 40%;">
<label for="node-input-port" style="margin-left: 10px; width: 35px;"> Port</label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
</div>
<div class="form-row">
<label for="node-input-iface"><i class="icon-globe"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="eth0">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Base64 encode payload ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-tips">Tip: sends the received data as a Buffer object (not a String).<br/>Make sure your firewall will allow the data in.</div>
</script>
<script type="text/x-red" data-help-name="multicast in">
<p>A multicast udp input node, that produces a <b>msg.payload</b> containing a <i>BUFFER</i> object and NOT a String.</p>
<p>If you need a String then use <i>.toString()</i> on <b>msg.payload</b> in your next function block.</p>
<p>It also provides <b>msg.fromip</b> of the form ipaddress:port .</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('multicast in',{
category: 'deprecated',
color:"Silver",
defaults: {
name: {value:""},
group: {value:"",required:true},
host: {value:""},
iface: {value:""},
port: {value:"",required:true,validate:RED.validators.number()},
base64: {value:false,required:true},
multicast: {value:"true"}
},
inputs:0,
outputs:1,
icon: "bridge-dash.png",
label: function() {
if ((this.group!="") & (this.port!="")) {
return this.name||(this.group+":"+this.port);
}
else { return "multicast in"; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<!-- The Output Node -->
<script type="text/x-red" data-template-name="multicast out">
<div class="form-row">
<label for="node-input-group"><i class="icon-tasks"></i> Group</label>
<input type="text" id="node-input-group" placeholder="225.0.18.83" style="width: 40%;">
<label for="node-input-port" style="margin-left: 10px; width: 35px;"> Port</label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
</div>
<div class="form-row">
<label for="node-input-iface"><i class="icon-globe"></i> Interface</label>
<input type="text" id="node-input-iface" placeholder="eth0">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 encoded payload ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="multicast out">
<p>This node sends <b>msg.payload</b> to the designated multicast group and port.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('multicast out',{
category: 'deprecated',
color:"Silver",
defaults: {
name: {value:""},
group: {value:"",required:true},
host: {value:""},
iface: {value:""},
port: {value:"",required:true,validate:RED.validators.number()},
base64: {value:false,required:true},
multicast: {value:"true"}
},
inputs:1,
outputs:0,
icon: "bridge-dash.png",
align: "right",
label: function() {
if ((this.group!="") & (this.port!="")) {
return this.name||(this.group+":"+this.port);
}
else { return "multicast out"; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,119 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
var dgram = require('dgram');
// The Input Node
function MCastIn(n) {
RED.nodes.createNode(this,n);
this.warn("node type deprecated - will be removed in a future release");
this.group = n.group;
this.port = n.port;
this.host = n.host || null;
this.base64 = n.base64;
this.iface = n.iface || null;
this.multicast = n.multicast;
var node = this;
var server = dgram.createSocket('udp4');
server.on("error", function (err) {
console.log("udp listener error:\n" + err.stack);
server.close();
});
server.on('message', function (message, remote) {
var msg;
if (node.base64) { msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port }; }
else { msg = { payload:message, fromip:remote.address+':'+remote.port }; }
node.send(msg);
});
server.on('listening', function () {
var address = server.address();
node.log('udp listener at ' + address.address + ":" + address.port);
if (node.multicast) {
server.setBroadcast(true)
server.setMulticastTTL(128);
server.addMembership(node.group,node.iface);
node.log("udp multicast group "+node.group);
}
});
//server.bind(node.port,node.host);
server.bind(node.port,node.host);
this._close = function() {
server.close();
node.log('udp listener stopped');
}
}
MCastIn.prototype.close = function() {
this._close();
}
RED.nodes.registerType("multicast in",MCastIn);
// The Output Node
function MCastOut(n) {
RED.nodes.createNode(this,n);
this.warn("node type deprecated");
this.group = n.group;
this.port = n.port;
this.host = n.host || null;
this.base64 = n.base64;
this.iface = n.iface || null;
this.multicast = n.multicast;
var node = this;
var sock = dgram.createSocket('udp4'); // only use ipv4 for now
sock.bind(node.port); // have to bind before you can enable broadcast...
sock.setBroadcast(true); // turn on broadcast
sock.setMulticastTTL(128);
sock.addMembership(node.group,node.iface); // Add to the multicast group
node.log('udp multicaster ready on '+node.group+":"+node.port);
node.on("input", function(msg) {
if (msg.payload != null) {
console.log("MCast:",msg.payload);
var message;
if (node.base64) {
message = new Buffer(msg.payload,'base64');
}
else {
message = new Buffer(msg.payload);
}
sock.send(message, 0, message.length, node.port, node.group, function(err, bytes) {
if (err) node.error("udp : "+err);
//util.log('[socket out] udp :' +bytes);
});
}
});
this._close = function() {
sock.close();
node.log('udp multicaster stopped');
}
}
RED.nodes.registerType("multicast out",MCastOut);
MCastOut.prototype.close = function() {
this._close();
}

View File

@@ -1,69 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row">
<label for="node-input-pin"><i class="icon-asterisk"></i> Pin</label>
<select type="text" id="node-input-pin" style="width: 150px;">
<option value="7">7</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="18">18</option>
<option value="22">22</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-resistor"><i class=" icon-resize-full"></i> Resistor?</label>
<select type="text" id="node-input-resistor" style="width: 150px;">
<option value="no">no</option>
<option value="pullup">pullup</option>
<option value="pulldown">pulldown</option>
</select>
</div>
<div class="form-tips">Tip: if pull up/down resistor is selected, the <code>gpio-admin</code> command <em>must</em> be used
to do the actual enabling. If 'no' resistor is selected, nothing further needs to be done
to use this node. See <code>man gpio-admin</code> for more details.</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-gpio in',{
category: 'deprecated',
color:"#c6dbef",
defaults: {
name: { value:""},
resistor: { value: "no"},
pin: {value:"",required:true},
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
return this.name||"Pin: "+this.pin;
//+(this.resistor == "no"?"":" ("+(this.resistor=="pullup"?"":"&darr;")+")");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,70 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
var gpio = require("pi-gpio");
function GPIOInNode(n) {
RED.nodes.createNode(this,n);
this.warn("node type deprecated - will be removed in a future release");
this.buttonState = -1;
this.pin = n.pin;
this.resistor = n.resistor;
var node = this;
if (this.pin) {
var setupPin = function(err) {
if (err) {
node.error(err);
} else {
node._interval = setInterval(function(){
gpio.read(node.pin, function(err, value) {
if(err){
node.error(err);
} else{
if(node.buttonState !== value){
var previousState = node.buttonState;
node.buttonState = value;
if (previousState !== -1) {
var msg = {payload:node.buttonState};
node.send(msg);
}
}
}
});
}, 50);
}
};
if (this.resistor == "no") {
gpio.open(this.pin,"input",setupPin());
} else {
// Assume enabled externally via gpio-admin
setupPin();
}
} else {
this.error("Invalid GPIO pin: "+this.pin);
}
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
GPIOInNode.prototype.close = function() {
clearInterval(this._interval);
if (this.resistor == "no") {
gpio.close(this.pin);
}
}

Some files were not shown because too many files have changed in this diff Show More