I finally added the latest touch to my PiCam setup... keyboard control. Specifically the arrow keys on the keyboard (thanks to some posted code from @Steve-Mcl ) that I was able to add to the adjustment of both pan and tilt.
It is a bit of a spiderweb but it works.
The camera can be adjusted via the sliders, the buttons or the arrow keys on the keyboard (of the PC running the dashboard)
The trigger buttons disable after initial press of CLICK ME until camera has had time to snap the shot, then emailing is an option.
And additional white or IR lighting can be used as needed.
The hardware:
- RPi 3B
- Raspberry Pi Camera NoIR (AKA IR filter removed)
- Standard servos and P&T framework
- PCA9685 i2c board for the servo control
- BrightPi LED light to supply the white or IR lighting
The flow:
[
{
"id": "426d244e6a840d51",
"type": "inject",
"z": "ce319a63567d1926",
"name": "",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "",
"payload": "true",
"payloadType": "bool",
"x": 110,
"y": 120,
"wires": [
[
"3eeee6993133b587"
]
]
},
{
"id": "d7ea50835c54aece",
"type": "exec",
"z": "ce319a63567d1926",
"command": "raspistill -w 300 -h 195 -n -o",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "",
"x": 360,
"y": 160,
"wires": [
[
"7e1ed92f26b34a3c"
],
[],
[]
]
},
{
"id": "c6261caebc9cf9b2",
"type": "base64",
"z": "ce319a63567d1926",
"name": "",
"action": "str",
"property": "payload",
"x": 340,
"y": 100,
"wires": [
[
"f0724a7e706aa3d9",
"d18cf09a30d0de76"
]
]
},
{
"id": "f0724a7e706aa3d9",
"type": "template",
"z": "ce319a63567d1926",
"d": true,
"name": "",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "<img width=\"300px\" height=\"195px\" src=\"data:image/jpg;base64,{{{payload}}}\">",
"output": "str",
"x": 580,
"y": 60,
"wires": [
[
"9460a8944929a327"
]
]
},
{
"id": "9460a8944929a327",
"type": "ui_template",
"z": "ce319a63567d1926",
"d": true,
"group": "eb23a053.4aa63",
"name": "Snapshot Template",
"order": 1,
"width": 6,
"height": 4,
"format": "<div ng-bind-html=\"msg.payload\">\ndocument.\"msg.payload\".style.backgroundImage='none';\n</div>",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": true,
"templateScope": "local",
"x": 750,
"y": 60,
"wires": [
[]
]
},
{
"id": "a596243270bc62dc",
"type": "PCA9685 out",
"z": "ce319a63567d1926",
"name": "PWM Output 0 (Pan Servo)",
"pca9685": "6ce45bce.cdff94",
"channel": "15",
"payload": "",
"unit": "microseconds",
"onStep": "0",
"x": 1020,
"y": 540,
"wires": []
},
{
"id": "d041f2c5c1af0f2e",
"type": "PCA9685 out",
"z": "ce319a63567d1926",
"name": "PWM Output 1 (Tilt Servo)",
"pca9685": "6ce45bce.cdff94",
"channel": "14",
"payload": "",
"unit": "microseconds",
"onStep": "0",
"x": 1020,
"y": 620,
"wires": []
},
{
"id": "f5285e2fcaf21a50",
"type": "ui_button",
"z": "ce319a63567d1926",
"name": "",
"group": "d3d5aef6.5754d",
"order": 1,
"width": 0,
"height": 0,
"passthru": false,
"label": "CLICK ME",
"tooltip": "",
"color": "White",
"bgcolor": "Blue",
"icon": "",
"payload": "1",
"payloadType": "num",
"topic": "topic",
"topicType": "msg",
"x": 130,
"y": 200,
"wires": [
[
"71c04ceed3a62738",
"3eeee6993133b587"
]
]
},
{
"id": "71c04ceed3a62738",
"type": "trigger",
"z": "ce319a63567d1926",
"name": "Trigger delay",
"op1": "",
"op2": "",
"op1type": "pay",
"op2type": "str",
"duration": "8",
"extend": false,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 2,
"x": 150,
"y": 360,
"wires": [
[
"931bb7d59c550881"
],
[
"c6bc35aa878d479f"
]
]
},
{
"id": "931bb7d59c550881",
"type": "change",
"z": "ce319a63567d1926",
"name": "msg.enabled FALSE",
"rules": [
{
"t": "set",
"p": "enabled",
"pt": "msg",
"to": "false",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 160,
"y": 260,
"wires": [
[
"f5285e2fcaf21a50",
"bf2ab825c966b516"
]
]
},
{
"id": "c6bc35aa878d479f",
"type": "change",
"z": "ce319a63567d1926",
"name": "msg.enabled TRUE",
"rules": [
{
"t": "set",
"p": "enabled",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 150,
"y": 300,
"wires": [
[
"f5285e2fcaf21a50",
"bf2ab825c966b516"
]
]
},
{
"id": "3dfd73321652f851",
"type": "comment",
"z": "ce319a63567d1926",
"name": "Snap a RPiCam picture for display- with P&T",
"info": "",
"x": 210,
"y": 20,
"wires": []
},
{
"id": "3452efe35ccfe90b",
"type": "exec",
"z": "ce319a63567d1926",
"command": "i2cset -y 1 0x70 0x00 0x5a && i2cset -y 1 0x70 0x09 0x0f && i2cset -y 1 0x70 0x02 0x32 && i2cset -y 1 0x70 0x04 0x32 && i2cset -y 1 0x70 0x05 0x32 && i2cset -y 1 0x70 0x07 0x32",
"addpay": "",
"append": "",
"useSpawn": "false",
"timer": "",
"oldrc": false,
"name": "All White Full Bright",
"x": 950,
"y": 840,
"wires": [
[],
[],
[]
]
},
{
"id": "da8d841a11491b1f",
"type": "exec",
"z": "ce319a63567d1926",
"command": "i2cset -y 1 0x70 0x00 0x00",
"addpay": "",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "All OFF",
"x": 920,
"y": 780,
"wires": [
[],
[],
[]
]
},
{
"id": "1be9734b3e515b18",
"type": "exec",
"z": "ce319a63567d1926",
"command": "i2cset -y 1 0x70 0x00 0xa5 && i2cset -y 1 0x70 0x09 0x0f && i2cset -y 1 0x70 0x01 0x32 && i2cset -y 1 0x70 0x03 0x32 && i2cset -y 1 0x70 0x06 0x32 && i2cset -y 1 0x70 0x08 0x32",
"addpay": "",
"append": "",
"useSpawn": "false",
"timer": "",
"oldrc": false,
"name": "All IR Full Bright",
"x": 940,
"y": 900,
"wires": [
[],
[],
[]
]
},
{
"id": "90d1c8286d071012",
"type": "ui_multistate_switch",
"z": "ce319a63567d1926",
"name": "BrightPi Light",
"group": "d3d5aef6.5754d",
"order": 3,
"width": 0,
"height": 0,
"label": "BrightPi Light",
"stateField": "payload",
"enableField": "enable",
"rounded": true,
"useThemeColors": true,
"hideSelectedLabel": false,
"options": [
{
"label": "OFF",
"value": "0",
"valueType": "num",
"color": "#009933"
},
{
"label": "White",
"value": "1",
"valueType": "num",
"color": "#999999"
},
{
"label": "IR",
"value": "2",
"valueType": "num",
"color": "#ff6666"
}
],
"x": 650,
"y": 820,
"wires": [
[
"b27c4e32e1a11ec4"
]
]
},
{
"id": "b27c4e32e1a11ec4",
"type": "switch",
"z": "ce319a63567d1926",
"name": "",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "0",
"vt": "num"
},
{
"t": "eq",
"v": "1",
"vt": "num"
},
{
"t": "eq",
"v": "2",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 630,
"y": 880,
"wires": [
[
"da8d841a11491b1f",
"d041f2c5c1af0f2e",
"a596243270bc62dc"
],
[
"3452efe35ccfe90b"
],
[
"1be9734b3e515b18"
]
]
},
{
"id": "db673212cc5f686a",
"type": "link out",
"z": "ce319a63567d1926",
"name": "Tenserflow",
"links": [
"e9406dea66813d37"
],
"x": 775,
"y": 140,
"wires": []
},
{
"id": "ad2362e5d8c2f39d",
"type": "comment",
"z": "ce319a63567d1926",
"name": "Send 0 to P&T to reset it for use after BrightPi",
"info": "",
"x": 930,
"y": 720,
"wires": []
},
{
"id": "9adcfd1d82a78e24",
"type": "comment",
"z": "ce319a63567d1926",
"name": "BrightPi i2cset control",
"info": "",
"x": 680,
"y": 780,
"wires": []
},
{
"id": "115a7bc7335fea13",
"type": "inject",
"z": "ce319a63567d1926",
"name": "",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "0",
"payloadType": "num",
"x": 630,
"y": 720,
"wires": [
[
"a596243270bc62dc",
"d041f2c5c1af0f2e",
"da8d841a11491b1f"
]
]
},
{
"id": "510a5ff0fe205358",
"type": "comment",
"z": "ce319a63567d1926",
"name": "Pan & Tilt Servos",
"info": "",
"x": 120,
"y": 460,
"wires": []
},
{
"id": "bf2ab825c966b516",
"type": "ui_button",
"z": "ce319a63567d1926",
"name": "",
"group": "d3d5aef6.5754d",
"order": 2,
"width": 0,
"height": 0,
"passthru": false,
"label": "EMAIL",
"tooltip": "",
"color": "White",
"bgcolor": "Blue",
"icon": "",
"payload": "Raspi Camera Image",
"payloadType": "str",
"topic": "payload",
"topicType": "msg",
"x": 430,
"y": 280,
"wires": [
[
"92c3e64eb05e8cf4"
]
]
},
{
"id": "8be9af3b0ec0440c",
"type": "e-mail",
"z": "<PASS/KEY>",
"server": "<SMTP>",
"port": "<PORT>",
"secure": true,
"tls": true,
"name": "<EMAIL>",
"dname": "",
"credentials": {},
"x": 500,
"y": 360,
"wires": []
},
{
"id": "3eeee6993133b587",
"type": "change",
"z": "ce319a63567d1926",
"name": "Set filename",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "/home/pi/node-red-static/picam.jpg",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 130,
"y": 160,
"wires": [
[
"d7ea50835c54aece"
]
]
},
{
"id": "92c3e64eb05e8cf4",
"type": "file in",
"z": "ce319a63567d1926",
"name": "Grab image",
"filename": "/home/pi/node-red-static/picam.jpg",
"format": "",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 450,
"y": 320,
"wires": [
[
"8be9af3b0ec0440c"
]
]
},
{
"id": "e538f5fd3a7815ff",
"type": "inject",
"z": "ce319a63567d1926",
"name": "",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "true",
"payloadType": "bool",
"x": 430,
"y": 240,
"wires": [
[
"bf2ab825c966b516"
]
]
},
{
"id": "d632114fcddab87b",
"type": "image",
"z": "ce319a63567d1926",
"name": "",
"width": "400",
"data": "payload",
"dataType": "msg",
"thumbnail": false,
"active": true,
"pass": false,
"outputs": 0,
"x": 800,
"y": 180,
"wires": []
},
{
"id": "41ee22d90ad16a8a",
"type": "file in",
"z": "ce319a63567d1926",
"name": "Grab image",
"filename": "/home/pi/node-red-static/picam.jpg",
"format": "",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 610,
"y": 140,
"wires": [
[
"d632114fcddab87b",
"c6261caebc9cf9b2",
"db673212cc5f686a"
]
]
},
{
"id": "7e1ed92f26b34a3c",
"type": "delay",
"z": "ce319a63567d1926",
"name": "",
"pauseType": "delay",
"timeout": ".5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"x": 600,
"y": 180,
"wires": [
[
"41ee22d90ad16a8a"
]
]
},
{
"id": "d18cf09a30d0de76",
"type": "ui_template",
"z": "ce319a63567d1926",
"group": "eb23a053.4aa63",
"name": "Display image",
"order": 2,
"width": 6,
"height": 4,
"format": "<!DOCTYPE html>\n<html>\n<style>\n .img {\n border: 8px solid #000;\n border-radius: 8px;\n padding: 2px;\n width: 260px;\n }\n</style>\n\n<center>\n <table>\n <tr>\n <!-- Row 1 -->\n <td style=\"text-align: center\"><img src=\"data:image/jpg;base64,{{msg.payload}}\" class=img /></td>\n </tr>\n </table>\n</center>\n</html>",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": false,
"templateScope": "local",
"x": 740,
"y": 100,
"wires": [
[]
]
},
{
"id": "ac7c952dfd2bdf87",
"type": "ui_template",
"z": "ce319a63567d1926",
"group": "2b5eec28.269ea4",
"name": "Keyboard",
"order": 1,
"width": 0,
"height": 0,
"format": "<div id=\"key_press\">waiting key</div>\n<script type=\"text/javascript\">\n(function(scope) {\n const _scope = scope;\n $('body').on(\"keyup\", function(e) { \n var keyCode = (e || event || {}).keyCode;\n if(keyCode >= 37 && keyCode <= 40) {\n $(\"#key_press\").text(\"Detected Key: \" + keyCode);\n _scope.send({payload: keyCode});\n } \n });\n})(scope);\n</script>",
"storeOutMessages": true,
"fwdInMessages": true,
"resendOnRefresh": true,
"templateScope": "local",
"x": 160,
"y": 520,
"wires": [
[
"53d0c5ae1ffe495a"
]
]
},
{
"id": "b4a8db6e9e842570",
"type": "ui_slider",
"z": "ce319a63567d1926",
"name": "",
"label": "Pan",
"tooltip": "",
"group": "2b5eec28.269ea4",
"order": 2,
"width": 5,
"height": 1,
"passthru": false,
"outs": "end",
"topic": "topic",
"topicType": "msg",
"min": "500",
"max": "2200",
"step": "10",
"x": 590,
"y": 520,
"wires": [
[
"6a1aaa2a24c08602"
]
]
},
{
"id": "6a1aaa2a24c08602",
"type": "change",
"z": "ce319a63567d1926",
"name": "",
"rules": [
{
"t": "set",
"p": "Pan",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 750,
"y": 520,
"wires": [
[
"a596243270bc62dc"
]
]
},
{
"id": "53d0c5ae1ffe495a",
"type": "change",
"z": "ce319a63567d1926",
"name": "get slider values",
"rules": [
{
"t": "set",
"p": "Pan",
"pt": "msg",
"to": "Pan",
"tot": "flow"
},
{
"t": "set",
"p": "Tilt",
"pt": "msg",
"to": "Tilt",
"tot": "flow"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 460,
"y": 580,
"wires": [
[
"7cfb3d35bac01fd0"
]
]
},
{
"id": "7cfb3d35bac01fd0",
"type": "function",
"z": "ce319a63567d1926",
"name": "inc/dec/dheck",
"func": "const PanMIN = 500;\nconst PanMAX = 2200;\nconst TiltMIN = 2200;\nconst TiltMAX = 1300;\nconst INC = 100;\n\nlet Pan = msg.Pan || PanMIN;\nlet Tilt = msg.Tilt || PanMIN;\n\nswitch(msg.payload) {\n case 37:\n //left arrow\n Pan -= INC;\n break;\n \n case 38:\n //up arrow\n Tilt -= INC;\n break;\n \n case 39:\n //right arrow\n Pan += INC;\n break;\n\n case 40:\n //down arrow\n Tilt += INC;\n break;\n}\n\nif (Pan < PanMIN) {\n Pan = PanMIN;\n} else if (Pan > PanMAX) {\n Pan = PanMAX;\n}\n\nif (Tilt > TiltMIN) {\n Tilt = TiltMIN;\n} else if (Tilt < TiltMAX) {\n Tilt = TiltMAX;\n}\n\nlet msg1 = {payload: Pan};\nlet msg2 = {payload: Tilt};\n\nreturn [msg1,msg2];",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 640,
"y": 580,
"wires": [
[
"73015db0703cd6ad",
"a596243270bc62dc"
],
[
"ac0ced8f6b3cac48",
"d041f2c5c1af0f2e"
]
]
},
{
"id": "73015db0703cd6ad",
"type": "change",
"z": "ce319a63567d1926",
"name": "",
"rules": [
{
"t": "set",
"p": "Pan",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 410,
"y": 520,
"wires": [
[
"b4a8db6e9e842570"
]
]
},
{
"id": "5b2131a9de812e21",
"type": "ui_slider",
"z": "ce319a63567d1926",
"name": "",
"label": "Tilt",
"tooltip": "",
"group": "2b5eec28.269ea4",
"order": 5,
"width": 1,
"height": 5,
"passthru": false,
"outs": "end",
"topic": "topic",
"topicType": "msg",
"min": "2200",
"max": "1300",
"step": "10",
"x": 590,
"y": 640,
"wires": [
[
"729e7730024f693b"
]
]
},
{
"id": "729e7730024f693b",
"type": "change",
"z": "ce319a63567d1926",
"name": "",
"rules": [
{
"t": "set",
"p": "Tilt",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 750,
"y": 640,
"wires": [
[
"d041f2c5c1af0f2e"
]
]
},
{
"id": "ac0ced8f6b3cac48",
"type": "change",
"z": "ce319a63567d1926",
"name": "",
"rules": [
{
"t": "set",
"p": "Tilt",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 410,
"y": 640,
"wires": [
[
"5b2131a9de812e21"
]
]
},
{
"id": "b783aebc9fe9895f",
"type": "ui_button",
"z": "ce319a63567d1926",
"name": "",
"group": "2b5eec28.269ea4",
"order": 9,
"width": 2,
"height": 1,
"passthru": false,
"label": "LT",
"tooltip": "",
"color": "",
"bgcolor": "",
"icon": "",
"payload": "37",
"payloadType": "num",
"topic": "topic",
"topicType": "msg",
"x": 90,
"y": 620,
"wires": [
[
"53d0c5ae1ffe495a"
]
]
},
{
"id": "2a68ff2c48c463b0",
"type": "ui_button",
"z": "ce319a63567d1926",
"name": "",
"group": "2b5eec28.269ea4",
"order": 7,
"width": 2,
"height": 1,
"passthru": false,
"label": "UP",
"tooltip": "",
"color": "",
"bgcolor": "",
"icon": "",
"payload": "38",
"payloadType": "num",
"topic": "topic",
"topicType": "msg",
"x": 150,
"y": 580,
"wires": [
[
"53d0c5ae1ffe495a"
]
]
},
{
"id": "f792211e7c2a8fe1",
"type": "ui_button",
"z": "ce319a63567d1926",
"name": "",
"group": "2b5eec28.269ea4",
"order": 10,
"width": 2,
"height": 1,
"passthru": false,
"label": "RT",
"tooltip": "",
"color": "",
"bgcolor": "",
"icon": "",
"payload": "39",
"payloadType": "num",
"topic": "topic",
"topicType": "msg",
"x": 210,
"y": 620,
"wires": [
[
"53d0c5ae1ffe495a"
]
]
},
{
"id": "0275633bef01376a",
"type": "ui_button",
"z": "ce319a63567d1926",
"name": "",
"group": "2b5eec28.269ea4",
"order": 13,
"width": 2,
"height": 1,
"passthru": false,
"label": "DN",
"tooltip": "",
"color": "",
"bgcolor": "",
"icon": "",
"payload": "40",
"payloadType": "num",
"topic": "topic",
"topicType": "msg",
"x": 150,
"y": 660,
"wires": [
[
"53d0c5ae1ffe495a"
]
]
},
{
"id": "eb23a053.4aa63",
"type": "ui_group",
"name": "Snapshot",
"tab": "75252f1b.e6fdd",
"order": 2,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "6ce45bce.cdff94",
"type": "PCA9685",
"deviceNumber": "1",
"address": "64",
"frequency": "50"
},
{
"id": "d3d5aef6.5754d",
"type": "ui_group",
"name": "Trigger",
"tab": "75252f1b.e6fdd",
"order": 3,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "2b5eec28.269ea4",
"type": "ui_group",
"name": "P&T",
"tab": "75252f1b.e6fdd",
"order": 1,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "75252f1b.e6fdd",
"type": "ui_tab",
"name": "Raspi Cam",
"icon": "camera",
"order": 2,
"disabled": false,
"hidden": false
}
]