├── .gitignore ├── .resin-sync.yml ├── Dockerfile ├── LICENSE ├── README.md ├── index.html ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.resin-sync.yml: -------------------------------------------------------------------------------- 1 | uuid: 82148865dea35aca52804f4b15ec97f6 2 | destination: /usr/src/app 3 | ignore: 4 | - .git 5 | - node_modules/ 6 | local_resinos: {} 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM resin/raspberrypi3-node:6 2 | 3 | RUN apt-get update && apt-get install -yq libraspberrypi-bin 4 | 5 | WORKDIR usr/src/app 6 | 7 | COPY package.json ./ 8 | RUN JOBS=MAX npm i --production 9 | 10 | COPY . ./ 11 | 12 | CMD npm start 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tim Perry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pi-cam 2 | 3 | This is a simple pure-JS raspberry pi web-accessible webcam, using [Resin.io](https://resin.io) for deployment. 4 | 5 | Tested with: 6 | 7 | * Raspberry Pi 3B 8 | * [Pi cam v2.1](https://shop.pimoroni.com/products/raspberry-pi-camera-module-v2-1-with-mount) 9 | 10 | ## Getting started 11 | 12 | - Set up your device and camera 13 | - Sign up for free on [resin.io](https://dashboard.resin.io/signup), create an application for your device, and provision it. You should see the new device appear on the dashboard after a couple of minutes. 14 | - Set the following environmental variables for your Resin application: 15 | * RESIN_HOST_CONFIG_gpu_mem: 128 // Sets the GPU memory for the device 16 | * RESIN_HOST_CONFIG_start_x: 1 // Enable the camera 17 | - Push the contents of this repo to your Resin.io application 18 | - Enable the device URL on your dashboard (it's the chain icon in the top right on the device's page) 19 | - Open the devices URL (it's in the dropdown under the previous button). -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | process.on('uncaughtException', function(err) { 2 | console.log('Caught exception: ' + err); 3 | console.log(err.stack); 4 | }); 5 | 6 | const express = require('express'); 7 | const raspividStream = require('raspivid-stream'); 8 | 9 | const app = express(); 10 | const wss = require('express-ws')(app); 11 | 12 | const Blinkt = require('node-blinkt'); 13 | const leds = new Blinkt(); 14 | leds.setup(); 15 | 16 | leds.setAllPixels(255, 255, 255, 1); 17 | leds.sendUpdate(); 18 | setTimeout(updateLeds, 1000); 19 | 20 | app.get('/', (req, res) => res.sendFile(__dirname + '/index.html')); 21 | 22 | app.ws('/video-stream', (ws, req) => { 23 | console.log('Client connected'); 24 | updateLeds(); 25 | 26 | ws.send(JSON.stringify({ 27 | action: 'init', 28 | width: '960', 29 | height: '540' 30 | })); 31 | 32 | var videoStream = raspividStream({ rotation: 180 }); 33 | 34 | videoStream.on('data', (data) => { 35 | ws.send(data, { binary: true }, (error) => { if (error) console.error(error); }); 36 | }); 37 | 38 | ws.on('close', () => { 39 | console.log('Client left'); 40 | updateLeds(); 41 | videoStream.removeAllListeners('data'); 42 | }); 43 | }); 44 | 45 | function updateLeds() { 46 | const clientCount = wss.getWss().clients.size; 47 | console.log('Updating leds to show', clientCount, 'clients'); 48 | 49 | leds.clearAll(); 50 | 51 | for (let i = 0; i < clientCount; i++) { 52 | let colors = [0, 0, 0]; 53 | colors[Math.floor(i / 8)] = 255; 54 | 55 | leds.setPixel(i % 8, ...colors, 1); 56 | } 57 | 58 | leds.sendUpdate(); 59 | } 60 | 61 | app.use(function (err, req, res, next) { 62 | console.error(err); 63 | next(err); 64 | }) 65 | 66 | app.listen(80, () => console.log('Server started on 80')); 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pi-cam", 3 | "version": "1.0.0", 4 | "description": "A pure-JS Raspberry Pi webcam", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/pimterry/pi-cam.git" 13 | }, 14 | "keywords": [ 15 | "resin-io", 16 | "raspberrypi", 17 | "webcam", 18 | "hardware", 19 | "iot" 20 | ], 21 | "author": "Tim Perry ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/pimterry/pi-cam/issues" 25 | }, 26 | "homepage": "https://github.com/pimterry/pi-cam#readme", 27 | "dependencies": { 28 | "express": "^4.15.2", 29 | "express-ws": "^3.0.0", 30 | "h264-live-player": "^1.3.1", 31 | "node-blinkt": "Irrelon/node-blinkt#2324852", 32 | "raspivid-stream": "^0.2.1-beta" 33 | } 34 | } 35 | --------------------------------------------------------------------------------