Testing the programs separately. The Camera works with pygame on Ubuntu, but on CentOS 8, I was not able to install the dependencies for pygame. Therefore, run the testcam.py in podman/docker container.
python temperature.py
python testcam.py
python sparkles.py
Build and push the image
docker build -t karve/sensehat .
docker run --privileged --name sensehat -ti karve/sensehat bash
docker push karve/sensehat
This deployment in microshift will start sending images and temperature readings from SenseHat to Node-Red
kubectl create -f sensehat.yaml
Delete the deployment to stop sending to Node Red
kubectl delete -f sensehat.yaml
- Create a IBM Cloud free tier account at https://www.ibm.com/cloud/free and login to Console (top right).
- Create an API Key and save it, Manage->Access->IAM->API Key->Create an IBM Cloud API Key
- Click on Catalog and Search for "Node-Red App", select it and click on "Get Started"
- Give a unique App name For example xxxxx-node-red and select the region nearest to you
- Select the Pricing Plan Lite, if you already have an existing instance of Cloudant, you may select it in Pricing Plan
- Click Create
- Under Deployment Automation -> Configure Continuous Delivery, click on "Deploy your app"
- Select the deployment target "Cloud Foundry"
- Enter the IBM Cloud API Key from Step 2, or click on "New" to create one
- The rest of the fields Region, Organization, Space will automatically get filled up. Use the default 256MB Memory and click "Next"
- In "Configure the DevOps toolchain", click Create
- Wait for 10 minutes for the Node Red instance to start
- Click on the "Visit App URL"
- On the Node Red page, create a new userid and password
- In Manage Palette, install the node-red-contrib-image-tools, node-red-contrib-image-output, node-red-node-base64, node-red-node-random and node-red-contrib-aedes
- Import required Chat and Image Viewer flows as below.
Connect to your own instance of NodeRed on IBM Cloud http://mynodered.mybluemix.net/simple
[
{
"id": "7c30fb48.99cd2c",
"type": "http in",
"z": "4024e2467929a360",
"name": "",
"url": "/simple",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 170,
"y": 140,
"wires": [
[
"3153fbd3.203a64"
]
]
},
{
"id": "3153fbd3.203a64",
"type": "template",
"z": "4024e2467929a360",
"name": "html",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "<h1>Upload a file here:</h1>\n\n<form action=\"/upload\" method=\"POST\" enctype=\"multipart/form-data\">\n <input type=\"file\" name=\"myFile\" />\n <input type=\"submit\" value=\"Submit\">\n</form>",
"output": "str",
"x": 510,
"y": 140,
"wires": [
[
"737e44d2.373a64"
]
]
},
{
"id": "737e44d2.373a64",
"type": "http response",
"z": "4024e2467929a360",
"name": "",
"x": 650,
"y": 140,
"wires": []
},
{
"id": "46e01eea.4a46a",
"type": "comment",
"z": "4024e2467929a360",
"name": "Simple",
"info": "",
"x": 150,
"y": 100,
"wires": []
},
{
"id": "7d8e179a.283e4",
"type": "http response",
"z": "4024e2467929a360",
"name": "",
"x": 650,
"y": 200,
"wires": []
},
{
"id": "25859a94.7d9976",
"type": "http in",
"z": "4024e2467929a360",
"name": "",
"url": "/upload",
"method": "post",
"upload": true,
"swaggerDoc": "",
"x": 170,
"y": 200,
"wires": [
[
"d7e5df8b.97a4e8",
"d63bc821ef4d1e2e"
]
]
},
{
"id": "e1643981.20d7c8",
"type": "template",
"z": "4024e2467929a360",
"name": "text",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "File {{name}} uploaded\n",
"output": "str",
"x": 510,
"y": 200,
"wires": [
[
"7d8e179a.283e4"
]
]
},
{
"id": "d7e5df8b.97a4e8",
"type": "function",
"z": "4024e2467929a360",
"name": "toBase64",
"func": "msg.name = msg.req.files[0].originalname;\n\nif (msg.req.files[0].mimetype.includes('image')) {\n msg.payload = `<img src=\"data:image/gif;base64,${msg.req.files[0].buffer.toString('base64')}\">`;\n} else {\n msg.payload = msg.req.files[0].buffer.toString();\n}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 360,
"y": 200,
"wires": [
[
"e1643981.20d7c8"
]
]
},
{
"id": "d63bc821ef4d1e2e",
"type": "function",
"z": "4024e2467929a360",
"name": "toBase64",
"func": "msg.name = msg.req.files[0].originalname;\n\nif (msg.req.files[0].mimetype.includes('image')) {\n msg.payload = `${msg.req.files[0].buffer.toString('base64')}`;\n} else {\n msg.payload = msg.req.files[0].buffer.toString();\n}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 350,
"y": 240,
"wires": [
[
"7518e9142f5680d8",
"43fafba17925df91"
]
]
},
{
"id": "7518e9142f5680d8",
"type": "image viewer",
"z": "4024e2467929a360",
"name": "",
"width": "640",
"data": "payload",
"dataType": "msg",
"active": false,
"x": 510,
"y": 240,
"wires": [
[]
]
},
{
"id": "1880286475e54335",
"type": "template",
"z": "4024e2467929a360",
"name": "html",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "<p></p>File {{name}} uploaded !</p>\n\n<h2>Contents:</h2>\n\n<pre>\n{{{payload}}}\n</pre>",
"output": "str",
"x": 800,
"y": 200,
"wires": [
[]
]
},
{
"id": "43fafba17925df91",
"type": "image",
"z": "4024e2467929a360",
"name": "",
"width": "640",
"data": "payload",
"dataType": "msg",
"thumbnail": false,
"active": true,
"pass": false,
"outputs": 0,
"x": 180,
"y": 280,
"wires": []
},
{
"id": "0ea52358ab8e2391",
"type": "comment",
"z": "4024e2467929a360",
"name": "Replace-text-by-html",
"info": "",
"x": 960,
"y": 200,
"wires": []
}
]
Connect to your own instance of NodeRed on IBM Cloud https://mynodered.mybluemix.net/chat
[
{
"id": "1f044347b4962306",
"type": "websocket in",
"z": "6e675b81333ad533",
"name": "",
"server": "bc740d23.438bf",
"x": 130,
"y": 60,
"wires": [
[
"09dc90bbddd6b23d"
]
]
},
{
"id": "09dc90bbddd6b23d",
"type": "function",
"z": "6e675b81333ad533",
"name": "",
"func": "delete msg._session;\nreturn msg;\n\n",
"outputs": 1,
"x": 304,
"y": 60,
"wires": [
[
"f16a1212f8341911"
]
]
},
{
"id": "f16a1212f8341911",
"type": "websocket out",
"z": "6e675b81333ad533",
"name": "",
"server": "bc740d23.438bf",
"x": 485,
"y": 60,
"wires": []
},
{
"id": "428922193679ca2d",
"type": "http in",
"z": "6e675b81333ad533",
"name": "",
"url": "/chat",
"method": "get",
"x": 138,
"y": 128,
"wires": [
[
"316414fb7c1e99f9"
]
]
},
{
"id": "316414fb7c1e99f9",
"type": "template",
"z": "6e675b81333ad533",
"name": "",
"field": "payload",
"fieldType": "msg",
"syntax": "mustache",
"template": "<head>\n <meta name=\"viewport\" content=\"width=320, initial-scale=1\">\n <title>Chat</title>\n<script type=\"text/javascript\">\n\n function createSystemMessage(message) {\n var message = document.createTextNode(message);\n\n var messageBox = document.createElement('p');\n messageBox.className = 'system';\n\n messageBox.appendChild(message);\n\n var chat = document.getElementById('chat_box');\n chat.appendChild(messageBox);\n }\n\n function createUserMessage(user, message) {\n var user = document.createTextNode(user + ': ');\n\n var userBox = document.createElement('span');\n userBox.className = 'username';\n userBox.appendChild(user);\n\n var message = document.createTextNode(message);\n\n var messageBox = document.createElement('p');\n messageBox.appendChild(userBox);\n messageBox.appendChild(message);\n\n var chat = document.getElementById('chat_box');\n chat.appendChild(messageBox);\n }\n\n var wsUri = \"ws://{{req.headers.host}}/ws/chat\";\n var wsglobal;\n \n function connect() {\n var ws = new WebSocket(wsUri);\n\n ws.onopen = function(ev) {\n createSystemMessage('[Connected]');\n };\n\n ws.onclose = function(ev) {\n createSystemMessage('[Disconnected]');\n connect();\n }\n\n ws.onmessage = function(ev) {\n var payload = JSON.parse(ev.data);\n createUserMessage(payload.user, payload.message);\n\n var chat = document.getElementById('chat_box');\n chat.scrollTop = chat.scrollHeight;\n }\n \n wsglobal=ws;\n }\n \n connect();\n function clearMessages() {\n document.getElementById('chat_box').innerHTML=\"\";\n }\n function sendMessage() {\n var user = document.getElementById('user');\n var message = document.getElementById('message');\n\n var payload = {\n message: message.value,\n user: user.value,\n ts: (new Date()).getTime()\n };\n\n wsglobal.send(JSON.stringify(payload));\n message.value = \"\";\n };\n</script>\n\n<style type=\"text/css\">\n * {\n font-family: \"Palatino Linotype\", \"Book Antiqua\", Palatino, serif;\n font-style: italic;\n font-size: 24px;\n }\n\n html, body, #wrapper {\n margin: 0;\n padding: 0;\n height: 100%;\n }\n\n #wrapper {\n background-color: #ecf0f1;\n }\n\n #chat_box {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n padding-bottom: 50px;\n }\n\n #footer {\n box-sizing: border-box;\n position: fixed;\n bottom: 0;\n height: 50px;\n width: 100%;\n background-color: #2980b9;\n }\n\n #footer .content {\n padding-top: 4px;\n position: relative;\n }\n\n #user { width: 15%; }\n #message { width: 58%; }\n #clear_btn {\n width: 10%;\n position: absolute;\n right: 15%;\n bottom: 0;\n margin: 0;\n }\n #send_btn {\n width: 10%;\n position: absolute;\n right: 0;\n bottom: 0;\n margin: 0;\n }\n\n .content {\n width: 70%;\n margin: 0 auto;\n }\n\n input[type=\"text\"],\n input[type=\"button\"] {\n border: 0;\n color: #fff;\n }\n\n input[type=\"text\"] {\n background-color: #146EA8;\n padding: 3px 10px;\n }\n\n input[type=\"button\"] {\n background-color: #f39c12;\n border-right: 2px solid #e67e22;\n border-bottom: 2px solid #e67e22;\n min-width: 70px;\n display: inline-block;\n }\n\n input[type=\"button\"]:hover {\n background-color: #e67e22;\n border-right: 2px solid #f39c12;\n border-bottom: 2px solid #f39c12;\n cursor: pointer;\n }\n\n .system,\n .username {\n color: #aaa;\n font-style: italic;\n font-family: monospace;\n font-size: 16px;\n }\n\n @media(max-width: 1000px) {\n .content { width: 90%; }\n }\n\n @media(max-width: 780px) {\n #footer { height: 91px; }\n #chat_box { padding-bottom: 91px; }\n\n #user { width: 100%; }\n #message { width: 80%; }\n }\n\n @media(max-width: 400px) {\n #footer { height: 135px; }\n #chat_box { padding-bottom: 135px; }\n\n #message { width: 100%; }\n #send_btn {\n position: relative;\n margin-top: 3px;\n width: 100%;\n }\n }\n</style>\n</head>\n\n<body>\n <div id=\"wrapper\">\n <div id=\"chat_box\" class=\"content\"></div>\n\n <div id=\"footer\">\n <div class=\"content\">\n <input type=\"text\" id=\"user\" placeholder=\"Who are you?\" />\n <input type=\"text\" id=\"message\" placeholder=\"What do you want to say?\" />\n <input type=\"button\" id=\"send_btn\" value=\"Send\" onclick=\"sendMessage()\">\n <input type=\"button\" id=\"clear_btn\" value=\"Clear\" onclick=\"clearMessages()\">\n </div>\n </div>\n </div>\n</body>\n\n",
"x": 304,
"y": 128,
"wires": [
[
"7f3f72c7d757f412"
]
]
},
{
"id": "7f3f72c7d757f412",
"type": "http response",
"z": "6e675b81333ad533",
"name": "",
"x": 447,
"y": 128,
"wires": []
},
{
"id": "bc740d23.438bf",
"type": "websocket-listener",
"path": "/ws/chat",
"wholemsg": "false"
}
]
[{"id":"0f60af10c76ec207","type":"tab","label":"ForecastFlow","disabled":false,"info":"","env":[]},{"id":"b2e8ef186edec15f","type":"aedes broker","z":"0f60af10c76ec207","name":"mqtt-broker","mqtt_port":1883,"mqtt_ws_bind":"port","mqtt_ws_port":"","mqtt_ws_path":"","cert":"","key":"","certname":"","keyname":"","dburl":"","usetls":false,"x":150,"y":300,"wires":[[],[]]},{"id":"30fab12f59a0d04d","type":"mqtt in","z":"0f60af10c76ec207","name":"","topic":"test/hello","qos":"2","datatype":"auto","broker":"f4579188cba3d931","nl":false,"rap":true,"rh":0,"inputs":0,"x":140,"y":720,"wires":[["d3ac56d1816a593a"]]},{"id":"a8ec2adf6caeb391","type":"mqtt out","z":"0f60af10c76ec207","name":"","topic":"test/hello","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"f4579188cba3d931","x":1740,"y":560,"wires":[]},{"id":"83770bd020198d6e","type":"inject","z":"0f60af10c76ec207","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"latitude\":41.3122,\"longitude\":-73.8566}","payloadType":"json","x":150,"y":460,"wires":[["0ddcdbebeb61f1a1"]]},{"id":"a40884897e8308b9","type":"function","z":"0f60af10c76ec207","name":"","func":"msg.payload=\"Forecast is: \"+msg.payload\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1580,"y":560,"wires":[["a8ec2adf6caeb391"]]},{"id":"d3ac56d1816a593a","type":"debug","z":"0f60af10c76ec207","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":330,"y":720,"wires":[]},{"id":"76fc93ef88068239","type":"http request","z":"0f60af10c76ec207","name":"weather-request","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"x":720,"y":560,"wires":[["de9b55534f1ab328"]]},{"id":"18ed439b14346dfd","type":"debug","z":"0f60af10c76ec207","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1590,"y":500,"wires":[]},{"id":"a316b596d6736118","type":"change","z":"0f60af10c76ec207","name":"shortForecast","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.properties.periods[0].shortForecast","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1400,"y":560,"wires":[["18ed439b14346dfd","a40884897e8308b9","5dc60d49b84db6e3"]]},{"id":"de9b55534f1ab328","type":"change","z":"0f60af10c76ec207","name":"set-shortForecastURL","rules":[{"t":"set","p":"url","pt":"msg","to":"payload.properties.forecastHourly","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":560,"wires":[["f32730c09d40debb","af8e87493683b96a"]]},{"id":"af8e87493683b96a","type":"http request","z":"0f60af10c76ec207","name":"shortForecast-request","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"x":1180,"y":560,"wires":[["a316b596d6736118"]]},{"id":"f32730c09d40debb","type":"debug","z":"0f60af10c76ec207","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"url","targetType":"msg","statusVal":"","statusType":"auto","x":1140,"y":620,"wires":[]},{"id":"9f8e7493b1c548c1","type":"http in","z":"0f60af10c76ec207","name":"forecast","url":"/forecast","method":"get","upload":false,"swaggerDoc":"","x":140,"y":560,"wires":[["81e2d2f6cb4b5803","b3c62f3b35107f2a"]]},{"id":"5dc60d49b84db6e3","type":"template","z":"0f60af10c76ec207","name":"page","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<html>\n<head>\n <h1>Forecast</h1>\n</head>\n<body>\n The forecast is: {{payload}} !\n</body>\n</html>","output":"str","x":1570,"y":620,"wires":[["6db639baea170317"]]},{"id":"6db639baea170317","type":"http response","z":"0f60af10c76ec207","name":"","statusCode":"","headers":{},"x":1730,"y":620,"wires":[]},{"id":"81e2d2f6cb4b5803","type":"debug","z":"0f60af10c76ec207","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"req.query","targetType":"msg","statusVal":"","statusType":"auto","x":340,"y":620,"wires":[]},{"id":"0ddcdbebeb61f1a1","type":"change","z":"0f60af10c76ec207","name":"set-weather-url","rules":[{"t":"set","p":"url","pt":"msg","to":"'https://api.weather.gov/points/' & msg.payload.latitude & ',' & msg.payload.longitude","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":560,"wires":[["76fc93ef88068239","4b3860bf37ff7173"]]},{"id":"4b3860bf37ff7173","type":"debug","z":"0f60af10c76ec207","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"url","targetType":"msg","statusVal":"","statusType":"auto","x":700,"y":620,"wires":[]},{"id":"b3c62f3b35107f2a","type":"change","z":"0f60af10c76ec207","name":"set-payload","rules":[{"t":"set","p":"payload","pt":"msg","to":"req.query","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":560,"wires":[["0ddcdbebeb61f1a1"]]},{"id":"6bcc358e8a5710c0","type":"comment","z":"0f60af10c76ec207","name":"For testing (no http response)","info":"Only for testing","x":200,"y":400,"wires":[]},{"id":"d769f8a1d5d9985c","type":"comment","z":"0f60af10c76ec207","name":"Two requests required for short forecast","info":"","x":230,"y":520,"wires":[]},{"id":"c4093a2f7c8375ee","type":"comment","z":"0f60af10c76ec207","name":"Receive a message in mqtt","info":"","x":190,"y":680,"wires":[]},{"id":"1dcaeb6447ef55ba","type":"comment","z":"0f60af10c76ec207","name":"Send message to mqtt","info":"","x":1920,"y":560,"wires":[]},{"id":"95d260ebeb05f159","type":"comment","z":"0f60af10c76ec207","name":"Return http response","info":"","x":1900,"y":620,"wires":[]},{"id":"bbbb2dc7c6a64437","type":"comment","z":"0f60af10c76ec207","name":"debug forecast","info":"","x":1780,"y":500,"wires":[]},{"id":"f4579188cba3d931","type":"mqtt-broker","name":"mqtt-broker","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]