mirror of
https://github.com/ad1217/PrinterStatus
synced 2024-11-24 08:23:48 -05:00
Move octoprint communication to the server side with websockets
This commit is contained in:
parent
f163fde565
commit
ba54ba3db4
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
/.cache/
|
/.cache/
|
||||||
/dist/
|
/dist/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
|
/src/*.js
|
||||||
|
/config.yaml
|
||||||
|
71
package-lock.json
generated
71
package-lock.json
generated
@ -177,6 +177,32 @@
|
|||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/js-yaml": {
|
||||||
|
"version": "3.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz",
|
||||||
|
"integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA=="
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "12.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.4.tgz",
|
||||||
|
"integrity": "sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ=="
|
||||||
|
},
|
||||||
|
"@types/node-fetch": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/ws": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-yBTM0P05Tx9iXGq00BbJPo37ox68R5vaGTXivs6RGh/BQ6QP5zqZDGWdAO6JbRE/iR1l80xeGAwCQS2nMV9S/w==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@vue/component-compiler-utils": {
|
"@vue/component-compiler-utils": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.0.0.tgz",
|
||||||
@ -248,6 +274,14 @@
|
|||||||
"readable-stream": "^2.0.6"
|
"readable-stream": "^2.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"argparse": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
|
"requires": {
|
||||||
|
"sprintf-js": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
@ -261,6 +295,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||||
},
|
},
|
||||||
|
"async-limiter": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
|
||||||
|
},
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -478,6 +517,11 @@
|
|||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||||
|
},
|
||||||
"esutils": {
|
"esutils": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
@ -735,6 +779,15 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"js-yaml": {
|
||||||
|
"version": "3.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||||
|
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||||
|
"requires": {
|
||||||
|
"argparse": "^1.0.7",
|
||||||
|
"esprima": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"jsbn": {
|
"jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
@ -861,6 +914,11 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||||
|
},
|
||||||
"node-gyp": {
|
"node-gyp": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.0.3.tgz",
|
||||||
@ -1095,6 +1153,11 @@
|
|||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"sprintf-js": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
|
},
|
||||||
"sshpk": {
|
"sshpk": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||||
@ -1317,6 +1380,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
},
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==",
|
||||||
|
"requires": {
|
||||||
|
"async-limiter": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||||
|
14
package.json
14
package.json
@ -7,9 +7,21 @@
|
|||||||
"vue-template-compiler": "^2.6.10"
|
"vue-template-compiler": "^2.6.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/js-yaml": "^3.12.1",
|
||||||
|
"@types/node": "^12.7.4",
|
||||||
|
"@types/node-fetch": "^2.5.0",
|
||||||
|
"@types/ws": "^6.0.3",
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
"node-gyp": "^5.0.3",
|
"node-gyp": "^5.0.3",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-hot-reload-api": "^2.3.3",
|
"vue-hot-reload-api": "^2.3.3",
|
||||||
"vue-property-decorator": "^8.2.2"
|
"vue-property-decorator": "^8.2.2",
|
||||||
|
"ws": "^7.1.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc src/server.ts",
|
||||||
|
"start": "parcel src/index.html",
|
||||||
|
"serve": "npm run build && node src/server.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
src/App.vue
51
src/App.vue
@ -1,30 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<PrinterTile
|
<PrinterCard
|
||||||
v-for="printer in printers"
|
v-for="(status, name) in printers"
|
||||||
:key="printer.address"
|
:key="name"
|
||||||
v-bind="printer"
|
:name="name"
|
||||||
|
:status="status"
|
||||||
>
|
>
|
||||||
</PrinterTile>
|
</PrinterCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop } from 'vue-property-decorator';
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
||||||
|
|
||||||
import PrinterTile from './PrinterTile.vue';
|
import * as messages from './messages';
|
||||||
|
import * as octoprint from './octoprint';
|
||||||
|
import PrinterCard from './PrinterCard.vue';
|
||||||
|
|
||||||
@Component({ components: { PrinterTile } })
|
@Component({ components: { PrinterCard } })
|
||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
printers = [
|
websocket!: WebSocket;
|
||||||
{
|
printers: {
|
||||||
address: 'http://octopi.local:5000/',
|
[key: string]: octoprint.CurrentOrHistoryPayload | null;
|
||||||
apikey: 'BEF073DD42A64431BDD72D83FA563DF5',
|
} = {};
|
||||||
},
|
|
||||||
{
|
mounted() {
|
||||||
address: 'http://octopi.local:5000/',
|
let loc = window.location;
|
||||||
apikey: 'BEF073DD42A64431BDD72D83FA563DF5',
|
// TODO: make dynamic
|
||||||
},
|
// const ws_uri: string = loc.protocol === 'https' ? 'wss://' : 'ws://' + loc.host + '/ws';
|
||||||
];
|
const ws_uri = 'ws://localhost:4321';
|
||||||
|
this.websocket = new WebSocket(ws_uri);
|
||||||
|
this.websocket.onmessage = (ev: MessageEvent) => {
|
||||||
|
const event: messages.ExtendedMessage = JSON.parse(ev.data as string);
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
if ('init' in event) {
|
||||||
|
this.$set(this.printers, event.printer, null);
|
||||||
|
} else if ('current' in event) {
|
||||||
|
this.$set(this.printers, event.printer, event.current);
|
||||||
|
} else if ('history' in event) {
|
||||||
|
this.$set(this.printers, event.printer, event.history);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
60
src/PrinterCard.vue
Normal file
60
src/PrinterCard.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">{{ name || 'Unknown' }}</div>
|
||||||
|
<div v-if="webcamURL">
|
||||||
|
<img class="webcam" :src="webcamURL" />
|
||||||
|
</div>
|
||||||
|
<div v-if="status">
|
||||||
|
<div>{{ status.state.text }}</div>
|
||||||
|
<div>Job File Name: {{ status.job.file.name || 'None' }}</div>
|
||||||
|
<div>
|
||||||
|
Job Completion:
|
||||||
|
<progress
|
||||||
|
v-if="status.progress.completion"
|
||||||
|
:value="status.progress.completion"
|
||||||
|
>
|
||||||
|
{{ status.progress.completion }}
|
||||||
|
</progress>
|
||||||
|
<span v-else> - </span>
|
||||||
|
</div>
|
||||||
|
<div>User: {{ status.job.user || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
||||||
|
|
||||||
|
import * as octoprint from './octoprint';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class PrinterCard extends Vue {
|
||||||
|
@Prop(String) readonly name!: string;
|
||||||
|
@Prop(String) readonly webcamURL!: string;
|
||||||
|
@Prop(Object) readonly status?: octoprint.CurrentOrHistoryPayload;
|
||||||
|
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.card {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px,
|
||||||
|
rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webcam {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,107 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="currentJob" class="card">
|
|
||||||
<div class="card-header">{{ name || 'Unknown' }}</div>
|
|
||||||
<div v-if="webcamURL">
|
|
||||||
<img class="webcam" :src="webcamURL" />
|
|
||||||
</div>
|
|
||||||
<div>Job File Name: {{ currentJob.job.file.name || 'None' }}</div>
|
|
||||||
<div>
|
|
||||||
Job Completion:
|
|
||||||
<progress
|
|
||||||
v-if="currentJob.progress.completion"
|
|
||||||
:value="currentJob.progress.completion"
|
|
||||||
>
|
|
||||||
{{ currentJob.progress.completion }}
|
|
||||||
</progress>
|
|
||||||
<span v-else> - </span>
|
|
||||||
</div>
|
|
||||||
<div>User: {{ currentJob.job.user || '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
||||||
|
|
||||||
interface IJobInfo {
|
|
||||||
job: {
|
|
||||||
file: {
|
|
||||||
name: string;
|
|
||||||
display: string;
|
|
||||||
path: string;
|
|
||||||
type: string;
|
|
||||||
typePath: Array<string>;
|
|
||||||
};
|
|
||||||
user?: string;
|
|
||||||
estimatedPrintTime?: number;
|
|
||||||
lastPrintTime?: number;
|
|
||||||
filament?: {
|
|
||||||
length?: number;
|
|
||||||
volume?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
progress: {
|
|
||||||
completion: number;
|
|
||||||
filepos: number;
|
|
||||||
printTime: number;
|
|
||||||
printTimeLeft: number;
|
|
||||||
};
|
|
||||||
state: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
|
||||||
export default class PrinterTile extends Vue {
|
|
||||||
@Prop(String) readonly address!: string;
|
|
||||||
@Prop(String) readonly apikey!: string;
|
|
||||||
|
|
||||||
name: string = '';
|
|
||||||
webcamURL: string | null = null;
|
|
||||||
currentJob: IJobInfo | null = null;
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
fetch(this.address + '/api/settings', {
|
|
||||||
headers: {
|
|
||||||
'X-Api-Key': this.apikey,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(r => {
|
|
||||||
this.webcamURL = r.webcam.streamUrl;
|
|
||||||
this.name = r.appearance.name;
|
|
||||||
})
|
|
||||||
.then(() =>
|
|
||||||
fetch(this.address + '/api/job', {
|
|
||||||
headers: {
|
|
||||||
'X-Api-Key': this.apikey,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(r => {
|
|
||||||
this.currentJob = r;
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.card {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 1em;
|
|
||||||
padding: 1em;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px,
|
|
||||||
rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.5em;
|
|
||||||
text-align: center;
|
|
||||||
padding-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.webcam {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
5
src/messages.d.ts
vendored
Normal file
5
src/messages.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Message } from './octoprint';
|
||||||
|
|
||||||
|
export type ExtendedMessage = (Message | { init?: null }) & {
|
||||||
|
printer: string;
|
||||||
|
};
|
113
src/octoprint.d.ts
vendored
Normal file
113
src/octoprint.d.ts
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
export interface JobInformation {
|
||||||
|
file: {
|
||||||
|
name: string;
|
||||||
|
display: string;
|
||||||
|
path: string;
|
||||||
|
type: string;
|
||||||
|
typePath: Array<string>;
|
||||||
|
};
|
||||||
|
user?: string;
|
||||||
|
estimatedPrintTime?: number;
|
||||||
|
lastPrintTime?: number;
|
||||||
|
filament?: {
|
||||||
|
length?: number;
|
||||||
|
volume?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobResponse {
|
||||||
|
job: JobInformation;
|
||||||
|
progress: ProgressInformation;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressInformation {
|
||||||
|
completion: number;
|
||||||
|
filepos: number;
|
||||||
|
printTime: number;
|
||||||
|
printTimeLeft: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrinterState {
|
||||||
|
text: string;
|
||||||
|
flags: {
|
||||||
|
operational: boolean;
|
||||||
|
paused: boolean;
|
||||||
|
printing: boolean;
|
||||||
|
pausing: boolean;
|
||||||
|
cancelling: boolean;
|
||||||
|
sdReady: boolean;
|
||||||
|
error: boolean;
|
||||||
|
ready: boolean;
|
||||||
|
closedOrError: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemperatureOffsets {
|
||||||
|
// 'tool{n}' or 'bed'
|
||||||
|
[key: string]: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemperatureData {
|
||||||
|
actual: number;
|
||||||
|
target: number;
|
||||||
|
offset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HistoricTemperatureDataPoint {
|
||||||
|
time: number; // unix timestamp
|
||||||
|
bed: TemperatureData;
|
||||||
|
// 'tool{n}'
|
||||||
|
[key: string]: TemperatureData | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
admin: boolean;
|
||||||
|
user: boolean;
|
||||||
|
apikey: string;
|
||||||
|
settings: { [key: string]: string };
|
||||||
|
session: string;
|
||||||
|
_is_external_client: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CurrentOrHistoryPayload {
|
||||||
|
state: PrinterState;
|
||||||
|
job: JobInformation;
|
||||||
|
progress: ProgressInformation;
|
||||||
|
currentZ: number;
|
||||||
|
offsets?: TemperatureOffsets;
|
||||||
|
temps: HistoricTemperatureDataPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlicingProgressMessage {
|
||||||
|
slicingProgress: {
|
||||||
|
slicer: string;
|
||||||
|
source_location: string;
|
||||||
|
source_path: string;
|
||||||
|
dest_location: string;
|
||||||
|
dest_path: string;
|
||||||
|
progress: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConnectedMessage {
|
||||||
|
connected: {
|
||||||
|
apikey: string;
|
||||||
|
version: string;
|
||||||
|
branch: string;
|
||||||
|
display_version: string;
|
||||||
|
plugin_hash: string;
|
||||||
|
config_hash: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CurrentOrHistoryMessage {
|
||||||
|
current: CurrentOrHistoryPayload;
|
||||||
|
history: CurrentOrHistoryPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Message = Partial<
|
||||||
|
SlicingProgressMessage | ConnectedMessage | CurrentOrHistoryMessage
|
||||||
|
>;
|
131
src/server.ts
Normal file
131
src/server.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as url from 'url';
|
||||||
|
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import * as messages from './messages';
|
||||||
|
import * as octoprint from './octoprint';
|
||||||
|
import * as WebSocket from 'ws';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
const config: {
|
||||||
|
printers: { address: string; apikey: string }[];
|
||||||
|
} = yaml.safeLoad(fs.readFileSync('config.yaml', 'utf8'));
|
||||||
|
|
||||||
|
const proxyServer = new WebSocket.Server({ host: '127.0.0.1', port: 4321 });
|
||||||
|
let printerStatuses: PrinterStatus[] = [];
|
||||||
|
|
||||||
|
function broadcast(data: WebSocket.Data) {
|
||||||
|
proxyServer.clients.forEach((client: WebSocket) => {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function broadcastPayload(payload: messages.ExtendedMessage) {
|
||||||
|
broadcast(JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrinterStatus {
|
||||||
|
wss: WebSocket.Server;
|
||||||
|
address: string;
|
||||||
|
apikey: string;
|
||||||
|
|
||||||
|
webcamURL?: string;
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
websocket?: WebSocket;
|
||||||
|
lastStatus?: messages.ExtendedMessage;
|
||||||
|
|
||||||
|
constructor(wss: WebSocket.Server, address: string, apikey: string) {
|
||||||
|
this.wss = wss;
|
||||||
|
this.address = address;
|
||||||
|
this.apikey = apikey;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.init(); // async init
|
||||||
|
} catch (e) {
|
||||||
|
throw 'Failed to Init' + e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
// TODO: error handling (try/catch)
|
||||||
|
const settings = await this.api_get('settings');
|
||||||
|
this.webcamURL = settings.webcam.streamUrl;
|
||||||
|
this.name = settings.appearance.name;
|
||||||
|
|
||||||
|
// do passive login to get a session key from the API key
|
||||||
|
const login: octoprint.LoginResponse = await this.api_post('login', {
|
||||||
|
passive: 'true',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.websocket = new WebSocket(
|
||||||
|
url.resolve(this.address, '/sockjs/websocket')
|
||||||
|
);
|
||||||
|
this.websocket
|
||||||
|
.on('open', () => {
|
||||||
|
this.websocket!.send(
|
||||||
|
JSON.stringify({ auth: login.name + ':' + login.session })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.on('message', (data: WebSocket.Data) => {
|
||||||
|
const event: octoprint.Message = JSON.parse(data as string);
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
let ext_event: messages.ExtendedMessage = {
|
||||||
|
...event,
|
||||||
|
printer: this.name!,
|
||||||
|
};
|
||||||
|
broadcastPayload(ext_event);
|
||||||
|
|
||||||
|
if ('current' in event || 'history' in event) {
|
||||||
|
this.lastStatus = ext_event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async api_get(endpoint: string): Promise<any> {
|
||||||
|
const r = await fetch(url.resolve(this.address, '/api/' + endpoint), {
|
||||||
|
headers: { 'X-Api-Key': this.apikey },
|
||||||
|
});
|
||||||
|
return await r.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async api_post(endpoint: string, data: any): Promise<any> {
|
||||||
|
const r = await fetch(url.resolve(this.address, '/api/' + endpoint), {
|
||||||
|
headers: {
|
||||||
|
'X-Api-Key': this.apikey,
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
return await r.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
send_init(ws: WebSocket) {
|
||||||
|
let payload: messages.ExtendedMessage;
|
||||||
|
console.log(this.lastStatus);
|
||||||
|
if (this.lastStatus) {
|
||||||
|
payload = this.lastStatus;
|
||||||
|
} else {
|
||||||
|
payload = { init: null, printer: this.name! };
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_printers() {
|
||||||
|
printerStatuses = config.printers.map(
|
||||||
|
printer => new PrinterStatus(proxyServer, printer.address, printer.apikey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
init_printers();
|
||||||
|
|
||||||
|
proxyServer.on('connection', (ws: WebSocket) => {
|
||||||
|
printerStatuses.forEach((ps: PrinterStatus) => ps.send_init(ws));
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user