Compare commits

...

No commits in common. "webSockets" and "master" have entirely different histories.

34 changed files with 700 additions and 80 deletions

View File

@ -1 +1,19 @@
# 🐱Cat 🔫Cannon
# 🐱Cat 🔫Cannon
The Cat Cannon is an automatically aiming water cannon designed to keep cats out of any location in which there presence is not desired. This solution is built on top of the Raspberry Pi driven by TypeScript and very little Python.
## Software Setup
Clone this repo and navigate to its saved directory
npm install
ts-node ./src/Robitics/main.ts
open a browser at http://localhost:5005
wait for cat to enter view of camera
what the machine protect your assets

4
dist/Vision/main.js vendored

File diff suppressed because one or more lines are too long

212
package-lock.json generated
View File

@ -96,6 +96,11 @@
"@types/node": "*"
}
},
"@types/component-emitter": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
},
"@types/connect": {
"version": "3.4.34",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz",
@ -105,6 +110,25 @@
"@types/node": "*"
}
},
"@types/cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg=="
},
"@types/cors": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz",
"integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg=="
},
"@types/engine.io": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@types/engine.io/-/engine.io-3.1.5.tgz",
"integrity": "sha512-DLVpLEGTEZGBXOYoYoagHSxXkDHONc0fZouF2ayw7Q18aRu1Afwci+1CFKvPpouCUOVWP+dmCaAWpQjswe7kpg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/eslint": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
@ -228,6 +252,32 @@
"@types/node": "*"
}
},
"@types/socket.io": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.12.tgz",
"integrity": "sha512-oStc5VFkpb0AsjOxQUj9ztX5Iziatyla/rjZTYbFGoVrrKwd+JU2mtxk7iSl5RGYx9WunLo6UXW1fBzQok/ZyA==",
"dev": true,
"requires": {
"@types/engine.io": "*",
"@types/node": "*",
"@types/socket.io-parser": "*"
}
},
"@types/socket.io-client": {
"version": "1.4.35",
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.35.tgz",
"integrity": "sha512-MI8YmxFS+jMkIziycT5ickBWK1sZwDwy16mgH/j99Mcom6zRG/NimNGQ3vJV0uX5G6g/hEw0FG3w3b3sT5OUGw==",
"dev": true
},
"@types/socket.io-parser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/socket.io-parser/-/socket.io-parser-2.2.1.tgz",
"integrity": "sha512-+JNb+7N7tSINyXPxAJb62+NcpC1x/fPn7z818W4xeNCdPTp6VsO/X8fCsg6+ug4a56m1v9sEiTIIUKVupcHOFQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/webgl-ext": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
@ -719,6 +769,11 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -780,6 +835,16 @@
}
}
},
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@ -1129,8 +1194,7 @@
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"compressible": {
"version": "2.0.18",
@ -1219,6 +1283,15 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@ -1244,7 +1317,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
},
@ -1252,8 +1324,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
@ -1440,6 +1511,64 @@
"once": "^1.4.0"
}
},
"engine.io": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.0.tgz",
"integrity": "sha512-vW7EAtn0HDQ4MtT5QbmCHF17TaYLONv2/JwdYsq9USPRZVM4zG7WB3k0Nc321z8EuSOlhGokrYlYx4176QhD0A==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~4.0.0",
"ws": "~7.4.2"
},
"dependencies": {
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"ws": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
}
}
},
"engine.io-client": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.0.tgz",
"integrity": "sha512-OUmn4m71/lW3ixICv4h3DuBRuh3ri0w3cDuepjsrINSbbqbni4Xw1shTFiKhl0v58lEtNpwJTpSKJJ3fondu5Q==",
"requires": {
"base64-arraybuffer": "0.1.4",
"component-emitter": "~1.3.0",
"debug": "~4.3.1",
"engine.io-parser": "~4.0.1",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~7.4.2",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
},
"dependencies": {
"ws": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
}
}
},
"engine.io-parser": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
"requires": {
"base64-arraybuffer": "0.1.4"
}
},
"enhanced-resolve": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
@ -2040,6 +2169,11 @@
"function-bind": "^1.1.1"
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -2954,8 +3088,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@ -3141,6 +3274,16 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
},
"parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -3946,6 +4089,51 @@
}
}
},
"socket.io": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.0.tgz",
"integrity": "sha512-Aqg2dlRh6xSJvRYK31ksG65q4kmBOqU4g+1ukhPcoT6wNGYoIwSYPlCPuRwOO9pgLUajojGFztl6+V2opmKcww==",
"requires": {
"@types/cookie": "^0.4.0",
"@types/cors": "^2.8.8",
"@types/node": "^14.14.10",
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.1",
"engine.io": "~4.1.0",
"socket.io-adapter": "~2.1.0",
"socket.io-parser": "~4.0.3"
}
},
"socket.io-adapter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz",
"integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg=="
},
"socket.io-client": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.0.tgz",
"integrity": "sha512-T4qPOL80KnoBwkdR70zMpiR6aH6zv3ZqLNriofHqsO9wvQllNTOez0mpV4GdVqo1Y55Z+h8YOlBo7c8pOxDlHw==",
"requires": {
"@types/component-emitter": "^1.2.10",
"backo2": "~1.0.2",
"component-emitter": "~1.3.0",
"debug": "~4.3.1",
"engine.io-client": "~4.1.0",
"parseuri": "0.0.6",
"socket.io-parser": "~4.0.4"
}
},
"socket.io-parser": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
"requires": {
"@types/component-emitter": "^1.2.10",
"component-emitter": "~1.3.0",
"debug": "~4.3.1"
}
},
"sockjs": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz",
@ -5139,6 +5327,11 @@
"async-limiter": "~1.0.0"
}
},
"xmlhttprequest-ssl": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
},
"y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
@ -5163,6 +5356,11 @@
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA=="
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -4,7 +4,9 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve",
"dev": "ts-node ./src/Robotics/main.ts",
"vision": "webpack serve",
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
@ -12,10 +14,14 @@
"dependencies": {
"@tensorflow-models/coco-ssd": "^2.2.1",
"@tensorflow/tfjs": "^2.8.2",
"express": "^4.17.1"
"express": "^4.17.1",
"socket.io": "^3.1.0",
"socket.io-client": "^3.1.0"
},
"devDependencies": {
"@types/express": "^4.17.11",
"@types/socket.io": "^2.1.12",
"@types/socket.io-client": "^1.4.35",
"ts-loader": "^8.0.13",
"typescript": "^4.1.3",
"webpack": "^5.11.1",

View File

@ -0,0 +1,26 @@
import { EventEmitter } from 'events'
import IEventManager from '../Interfaces/IEventManager'
let instance: EventManager | null = null
class EventManager implements IEventManager {
eventEmitter: EventEmitter
constructor () {
if (!instance) instance = this
this.eventEmitter = new EventEmitter()
return instance
}
listen = (eventName: string, callback: any) => {
this.eventEmitter.addListener(eventName, callback)
}
emit = (eventName: string, detail: any) => {
this.eventEmitter.emit(eventName, detail)
}
}
export default EventManager

View File

@ -0,0 +1,17 @@
import IMotor from "../Interfaces/IMotor"
class Motor implements IMotor {
public pinOne: number
public pinTwo: number
public pinThree: number
public pinFour: number
constructor (props: IMotor) {
this.pinOne = props.pinOne
this.pinTwo = props.pinTwo
this.pinThree = props.pinThree
this.pinFour = props.pinFour
}
}
export default Motor

View File

@ -0,0 +1,70 @@
import IMotor from "../Interfaces/IMotor"
import makeMotor from "../UseCases/Factories/makeMotor"
import * as childProcesses from 'child_process'
import IMotorMoverConstructor from "../Interfaces/IMotorMoverConstructor"
import IMotorMover from "../Interfaces/IMotorMover"
class MotorMover implements IMotorMover {
motor: IMotor
moveProcess: childProcesses.ChildProcessWithoutNullStreams | null = null
pauseIntervalTime: number
movementState: 'CLOCKWISE' | 'COUNTERCLOCKWISE' | "IDLE" = 'IDLE'
constructor (props: IMotorMoverConstructor) {
this.motor = makeMotor(props.motor)
this.pauseIntervalTime = props.pauseIntervalTime
}
public moveClockwise = () => {
if (this.movementState === 'CLOCKWISE') return
else {
if (!this.moveProcess?.killed) this.moveProcess?.kill()
}
const motorProcessArguments = [
'src/Robotics/moveStepper.py',
this.motor.pinOne.toString(),
this.motor.pinTwo.toString(),
this.motor.pinThree.toString(),
this.motor.pinFour.toString(),
'clockwise',
this.pauseIntervalTime.toString()
]
console.log('start clockwise')
this.moveProcess = childProcesses.spawn('python', motorProcessArguments)
this.movementState = 'CLOCKWISE'
}
public moveCounterClockwise = () => {
if (this.movementState === 'COUNTERCLOCKWISE') return
else {
if (!this.moveProcess?.killed) this.moveProcess?.kill()
}
const motorProcessArguments = [
'src/Robotics/moveStepper.py',
this.motor.pinOne.toString(),
this.motor.pinTwo.toString(),
this.motor.pinThree.toString(),
this.motor.pinFour.toString(),
'counterClockwise',
this.pauseIntervalTime.toString()
]
console.log('start counterclockwise')
this.moveProcess = childProcesses.spawn('python', motorProcessArguments)
this.movementState = 'COUNTERCLOCKWISE'
}
public stopMovement = () => {
if (this.movementState === 'IDLE') return
else {
if (!this.moveProcess?.killed) this.moveProcess?.kill()
console.log('start idle')
this.movementState = 'IDLE'
}
}
}
export default MotorMover

View File

@ -0,0 +1,69 @@
import express from 'express'
import path from 'path'
import bodyParser from 'body-parser'
import http from 'http'
import { Socket } from 'socket.io'
import IEventManager from '../Interfaces/IEventManager'
import EventManager from './EventManager'
class Server {
public app = express()
private eventManager: IEventManager
constructor (port: number) {
this.createApp()
this.setupAppOptions()
this.setupAppRoutes()
this.startServer(port)
this.eventManager = new EventManager()
}
createApp = () => {
this.app.use(express.json())
this.app.use(express.urlencoded({ extended: false }))
this.app.use(express.static(path.join(process.cwd(), '/dist/Vision/')))
this.app.use(bodyParser.json())
}
setupAppRoutes = () => {
this.app.use('/', (request, response, next) => {
response.sendFile(path.join(process.cwd(), './dist/Vision/index.html'))
})
}
setupAppOptions = () => {
this.app.use((request, response, next) => {
response.header('Access-Control-Allow-Origin', request.headers.origin || '*')
response.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,HEAD,DELETE,OPTIONS')
response.header('Access-Control-Allow-Headers', 'Content-Type,x-requested-with')
next()
})
}
startServer = (port: number) => {
const webService = new http.Server(this.app)
const socketService = require("socket.io")(webService)
this.openSockets(socketService)
webService.listen(port, () => {
console.log(`Server is listening on ${port}`)
})
}
openSockets = (socketService: any) => {
socketService.on('connection', (socket: Socket) => {
console.log('client connected')
socket.on('offsets', (offsets: any[]) => {
this.onReceiveOffsets(offsets)
})
})
}
public onReceiveOffsets = (offsets: any[]) => {
this.eventManager.emit('onReceiveOffsets', offsets)
}
}
export default Server

View File

@ -0,0 +1,13 @@
import IWaterPump from '../Interfaces/IWaterPump'
class WaterPump implements IWaterPump {
public pinOne: number
public pinTwo: number
constructor (props: IWaterPump) {
this.pinOne = props.pinOne
this.pinTwo = props.pinTwo
}
}
export default WaterPump

View File

@ -0,0 +1,46 @@
import IWaterPump from '../Interfaces/IWaterPump'
import * as childProcesses from 'child_process'
import IWaterPumperConstructor from '../Interfaces/IWaterPumperConstructor'
import IWaterPumper from '../Interfaces/IWaterPumper'
import makeWaterPump from '../UseCases/Factories/makeWaterPump'
class WaterPumper implements IWaterPumper {
public waterPump: IWaterPump
public isActive: boolean = false
private pumpProcess: childProcesses.ChildProcessWithoutNullStreams | null = null
private pumpActiveTimeInSeconds: number
private pumpCoolDownTimeInSeconds: number
constructor (props: IWaterPumperConstructor) {
this.waterPump = makeWaterPump({
pinOne: props.pinOne,
pinTwo: props.pinTwo
})
this.pumpActiveTimeInSeconds =props.pumpActiveTimeInSeconds
this.pumpCoolDownTimeInSeconds = props.pumpCoolDownTimeInSeconds
}
private async coolDown () {
return new Promise(resolve => setTimeout(resolve, this.pumpCoolDownTimeInSeconds * 1000))
}
public async pump () {
if (this.isActive) return
const pumpProcessArguments = [
'src/Robotics/moveDcMotor.py',
this.waterPump.pinOne.toString(),
this.waterPump.pinTwo.toString(),
this.pumpActiveTimeInSeconds.toString()
]
this.pumpProcess = childProcesses.spawn('python', pumpProcessArguments)
this.isActive = true
await this.coolDown()
this.isActive = false
}
}
export default WaterPumper

View File

@ -0,0 +1,6 @@
interface IEventManager {
listen(eventName: string, callback: any): void,
emit(eventName: string, detail: any): void
}
export default IEventManager

View File

@ -0,0 +1,8 @@
interface IMotor {
pinOne: number,
pinTwo: number,
pinThree: number,
pinFour: number
}
export default IMotor

View File

@ -0,0 +1,8 @@
interface IMotorMover {
moveClockwise(): void,
moveCounterClockwise(): void,
stopMovement(): void,
movementState: 'CLOCKWISE' | 'COUNTERCLOCKWISE' | "IDLE"
}
export default IMotorMover

View File

@ -0,0 +1,8 @@
import IMotor from "./IMotor";
interface IMotorMoverConstructor {
motor: IMotor,
pauseIntervalTime: number
}
export default IMotorMoverConstructor

View File

@ -0,0 +1,5 @@
interface IServer {
port: number
}
export default IServer

View File

@ -0,0 +1,6 @@
interface IWaterPump {
pinOne: number,
pinTwo: number
}
export default IWaterPump

View File

@ -0,0 +1,9 @@
import IWaterPump from "./IWaterPump";
interface IWaterPumper {
pump(): void
isActive: boolean
waterPump: IWaterPump
}
export default IWaterPumper

View File

@ -0,0 +1,8 @@
interface IWaterPumperConstructor {
pinOne: number,
pinTwo: number,
pumpActiveTimeInSeconds: number,
pumpCoolDownTimeInSeconds: number
}
export default IWaterPumperConstructor

View File

@ -0,0 +1,7 @@
import EventManager from "../../Entities/EventManager"
function makeEventManager () {
return new EventManager()
}
export default makeEventManager

View File

@ -0,0 +1,8 @@
import Motor from "../../Entities/Motor"
import IMotor from "../../Interfaces/IMotor"
function makeMotor (props: IMotor) {
return new Motor(props)
}
export default makeMotor

View File

@ -0,0 +1,8 @@
import MotorMover from "../../Entities/MotorMover"
import IMotorMoverConstructor from "../../Interfaces/IMotorMoverConstructor"
function makeMotorMover (props: IMotorMoverConstructor) {
return new MotorMover(props)
}
export default makeMotorMover

View File

@ -0,0 +1,9 @@
import Server from "../../Entities/Server"
function makeServer (port: number) {
const defaultPort = 5005
return new Server(port || defaultPort)
}
export default makeServer

View File

@ -0,0 +1,8 @@
import WaterPump from "../../Entities/WaterPump"
import IWaterPump from "../../Interfaces/IWaterPump"
function makeWaterPump (props: IWaterPump) {
return new WaterPump(props)
}
export default makeWaterPump

View File

@ -0,0 +1,8 @@
import WaterPumper from "../../Entities/WaterPumper"
import IWaterPumperConstructor from "../../Interfaces/IWaterPumperConstructor"
function makeWaterPumper (props: IWaterPumperConstructor) {
return new WaterPumper(props)
}
export default makeWaterPumper

60
src/Robotics/main.ts Normal file
View File

@ -0,0 +1,60 @@
import IEventManager from './Interfaces/IEventManager'
import IMotorMover from './Interfaces/IMotorMover'
import IWaterPumper from './Interfaces/IWaterPumper'
import makeServer from './UseCases/Factories/makeServer'
import makeEventManager from './UseCases/Factories/makeEventManager'
import makeMotorMover from './UseCases/Factories/makeMotorMover'
import makeWaterPumper from './UseCases/Factories/makeWaterPumper'
const main = () => {
console.log('Starting Robotics')
const port = 5005
makeServer(port)
const eventManager: IEventManager = makeEventManager()
const xAxisMotorMover: IMotorMover = makeMotorMover({
motor: { pinOne: 3, pinTwo: 5, pinThree: 7, pinFour: 11 },
pauseIntervalTime: 0.05
})
const yAxisMotorMover: IMotorMover = makeMotorMover({
motor: { pinOne: 13, pinTwo: 15, pinThree: 19, pinFour: 21 },
pauseIntervalTime: 0.05
})
const waterPumper: IWaterPumper = makeWaterPumper({
pinOne: 37,
pinTwo: 35,
pumpActiveTimeInSeconds: 1,
pumpCoolDownTimeInSeconds: 5
})
eventManager.listen('onReceiveOffsets', (offsets: any[]) => {
if (offsets[0]?.x > 50) {
xAxisMotorMover.moveCounterClockwise()
} else if (offsets[0]?.x < - 50) {
xAxisMotorMover.moveClockwise()
} else {
xAxisMotorMover.stopMovement()
}
if (offsets[0]?.y > 50) {
yAxisMotorMover.moveClockwise()
} else if (offsets[0]?.y < - 50) {
yAxisMotorMover.moveCounterClockwise()
} else {
yAxisMotorMover.stopMovement()
}
if (offsets[0]?.hypotenuse <= 80) {
waterPumper.pump()
}
})
}
main()
export { main }

View File

@ -0,0 +1,22 @@
# command arguments to run process
# 1: int pin_one
# 2: int pin_two
# 3: int motor_active_time
import RPi.GPIO as GPIO
from time import sleep
import sys
motor_channel = (int(sys.argv[1]), int(sys.argv[2])) # (37, 35)
motor_active_time = int(sys.argv[3])
GPIO.setwarnings(True)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(motor_channel, GPIO.OUT)
GPIO.output(motor_channel, (GPIO.HIGH, GPIO.LOW))
print('Should be on')
sleep(motor_active_time)
GPIO.output(motor_channel, (GPIO.LOW, GPIO.LOW))

View File

@ -1,38 +0,0 @@
import express from 'express'
import path from 'path'
import bodyParser from 'body-parser'
class Server {
public app = express()
constructor () {
this.createApp()
this.setupAppOptions()
this.setupAppRoutes()
}
createApp = () => {
this.app.use(express.json())
this.app.use(express.urlencoded({ extended: false }))
this.app.use(express.static(path.join(process.cwd(), '/build')))
this.app.use(bodyParser.json())
}
setupAppRoutes = () => {
// this.app.use('/api', apiRouter)
this.app.use('/', (request, response, next) => {
response.sendFile(path.join(process.cwd(), './dist/Vision/index.html'))
})
}
setupAppOptions = () => {
this.app.use((request, response, next) => {
response.header('Access-Control-Allow-Origin', request.headers.origin || '*')
response.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,HEAD,DELETE,OPTIONS')
response.header('Access-Control-Allow-Headers', 'Content-Type,x-requested-with')
next()
})
}
}
export default Server

View File

@ -1,29 +0,0 @@
import http from 'http'
import Server from './Server'
const main = () => {
const port = normalizePort(process.env.PORT || '5005')
const webService = createServer()
webService.listen(port, () => {
console.log(`Server is listening on ${port}`)
})
}
const createServer = () => {
const server = new Server()
return http.createServer(server.app)
}
const normalizePort = (portString: string) => {
const port = parseInt(portString, 10)
if (isNaN(port)) return portString
else if (port >= 0 ) return port
else return 0
}
main()
export { main, createServer, normalizePort }

View File

@ -0,0 +1,7 @@
import IOffset from "./IOffset";
interface IRoboticsCommunicator {
sendOffsets(offsets: IOffset[]): void
}
export default IRoboticsCommunicator

View File

@ -3,7 +3,7 @@ import ObjectDetector from "../ObjectDetector"
const defaultPredictions = [
(prediction: DetectedObject) => prediction.score > 0.6,
(prediction: DetectedObject) => prediction.class === 'person',
(prediction: DetectedObject) => prediction.class === 'cat',
]
function makeObjectDetector (filterPredicates?: Function[]): ObjectDetector {

View File

@ -0,0 +1,8 @@
import RoboticsCommunicator from "../RoboticsCommunicator"
function makeRoboticsCommunicator () {
return new RoboticsCommunicator()
}
export default makeRoboticsCommunicator

View File

@ -0,0 +1,16 @@
import * as io from 'socket.io-client'
import IOffset from '../Interfaces/IOffset'
class RoboticsCommunicator {
socket: SocketIOClient.Socket
constructor () {
this.socket = io.connect()
}
sendOffsets = (offsets: IOffset[]) => {
this.socket.emit('offsets', offsets)
}
}
export default RoboticsCommunicator

View File

@ -1,6 +1,7 @@
import IObjectDetector from './Interfaces/IObjectDetector'
import IObjectLocator from './Interfaces/IObjectLocator'
import IOffset from './Interfaces/IOffset'
import IRoboticsCommunicator from './Interfaces/IRoboticsCommunicator'
import IUiRenderer from './Interfaces/IUiRenderer'
import IVideoCapturer from "./Interfaces/IVideoCapturer"
@ -8,17 +9,20 @@ import makeObjectDetector from './UseCases/Factories/makeObjectDetector'
import makeObjectLocator from './UseCases/Factories/makeObjectLocator'
import makeUiRenderer from './UseCases/Factories/makeUiRenderer'
import makeVideoCapturer from './UseCases/Factories/makeVideoCatpurer'
import makeRoboticsCommunicator from './UseCases/Factories/mkaeRoboticsCommunicator'
class App {
private objectDetector: IObjectDetector
private objectLocator: IObjectLocator
private videoCapturer: IVideoCapturer
private roboticsCommunicator: IRoboticsCommunicator
private uiRenderer: IUiRenderer
constructor () {
this.videoCapturer = makeVideoCapturer()
this.objectDetector = makeObjectDetector()
this.objectLocator = makeObjectLocator()
this.roboticsCommunicator = makeRoboticsCommunicator()
this.uiRenderer = makeUiRenderer()
const eventTarget = new EventTarget()
@ -36,6 +40,7 @@ class App {
const predictedObjects = await this.objectDetector.getPredictionsFromImageData(imageData)
const offsets: IOffset[] = predictedObjects.map(obj => this.objectLocator.getOffsetsFromPredictions(obj))
this.roboticsCommunicator.sendOffsets(offsets)
this.uiRenderer.render({ imageData, predictedObjects, offsets })
window.requestAnimationFrame(this.predictImage)