can't load files ...
can't load files ...
diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts
index 12b226ff9..176e43072 100644
--- a/src/app/files/files.component.ts
+++ b/src/app/files/files.component.ts
@@ -1,11 +1,13 @@
+import { HttpErrorResponse } from '@angular/common/http';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
-import { NgxSpinnerService } from 'ngx-spinner';
+import _ from 'lodash-es';
+import { AnimationOptions } from 'ngx-lottie';
-import { AppService } from '../app.service';
import { ConfigService } from '../config/config.service';
-import { File, FilesService, Folder } from '../files.service';
-import { JobService } from '../job.service';
+import { Directory, File } from '../model';
+import { NotificationService } from '../notification/notification.service';
+import { FilesService } from '../services/files/files.service';
@Component({
selector: 'app-files',
@@ -14,111 +16,81 @@ import { JobService } from '../job.service';
})
export class FilesComponent {
public currentFolder: string;
- public folderContent: (File | Folder)[];
+ public homeFolder = '/';
+ public directory: Directory;
public fileDetail: File;
+
public sortingAttribute: 'name' | 'date' | 'size';
public sortingOrder: 'asc' | 'dsc';
public showSorting = false;
- public homeFolder = '/';
+
+ public loadingOptions: AnimationOptions = {
+ path: '/assets/loading.json',
+ };
+ public loading = Date.now();
public constructor(
private filesService: FilesService,
- private spinner: NgxSpinnerService,
- private service: AppService,
+ private notificationService: NotificationService,
private router: Router,
- private jobService: JobService,
private configService: ConfigService,
) {
this.showLoader();
- this.folderContent = [];
+ this.directory = { files: [], folders: [] };
this.currentFolder = '/';
+
this.sortingAttribute = this.configService.getDefaultSortingAttribute();
this.sortingOrder = this.configService.getDefaultSortingOrder();
- this.openFolder(this.currentFolder);
- }
- public openDetails(filePath: string): void {
- this.filesService
- .getFile(filePath)
- .then((data): void => {
- this.fileDetail = data;
- })
- .catch((): void => {
- this.fileDetail = ({ name: 'error' } as unknown) as File;
- });
- const fileDOMElement = document.getElementById('fileDetailView');
- fileDOMElement.style.display = 'block';
- setTimeout((): void => {
- fileDOMElement.style.opacity = '1';
- }, 50);
+ this.openFolder(this.currentFolder);
}
public openFolder(folderPath: string): void {
setTimeout((): void => {
this.showLoader();
- this.folderContent = [];
- this.filesService
- .getFolder(folderPath)
- .then((data): void => {
- this.folderContent = data;
- if (folderPath === '/' && !(data[0].name === 'local' && data[1].name == 'sdcard')) {
- this.currentFolder = data[0].path.startsWith('/local') ? '/local' : '/sdcard';
+ this.directory = { files: [], folders: [] };
+
+ this.filesService.getFolderContent(folderPath).subscribe(
+ (directory: Directory) => {
+ this.directory = directory;
+ const mergedDirectory = _.concat(directory.files, directory.folders);
+ if (folderPath === '/' && !(mergedDirectory[0].name === 'local' && mergedDirectory[1].name == 'sdcard')) {
+ this.currentFolder = mergedDirectory[0].path.startsWith('/local') ? '/local' : '/sdcard';
this.homeFolder = this.currentFolder;
} else {
this.currentFolder = folderPath;
}
this.sortFolder(this.sortingAttribute, this.sortingOrder);
- this.spinner.hide();
- })
- .catch((): void => {
- this.folderContent = null;
+ },
+ (error: HttpErrorResponse) => {
+ this.notificationService.setError("Can't load file/folder!", error.message);
this.currentFolder = folderPath;
- this.spinner.hide();
- });
- }, 300);
+ },
+ () => {
+ this.hideLoader();
+ },
+ );
+ }, 240);
}
public sortFolder(by: 'name' | 'date' | 'size' = 'name', order: 'asc' | 'dsc' = 'asc'): void {
- switch (by) {
- case 'name': {
- this.folderContent.sort((a, b): number =>
- a.type === b.type
- ? (order === 'asc' ? a.name > b.name : a.name < b.name)
- ? 1
- : -1
- : a.type === 'folder'
- ? -1
- : 1,
- );
- break;
- }
- case 'date': {
- this.sortFolder('name', order);
- this.folderContent.sort((a, b): number => {
- if (a.type === b.type && a.type === 'file') {
- const aFile = (a as unknown) as File;
- const bFile = (b as unknown) as File;
- return (order === 'asc' ? aFile.date > bFile.date : aFile.date < bFile.date) ? 1 : -1;
- } else {
- return a.type === 'folder' ? -1 : 1;
- }
- });
- break;
- }
- case 'size': {
- this.sortFolder('name', order);
- this.folderContent.sort((a, b): number => {
- if (a.type === b.type && (a as File).type) {
- const aFile = (a as unknown) as File;
- const bFile = (b as unknown) as File;
- return (order === 'asc' ? aFile.size > bFile.size : aFile.size < bFile.size) ? 1 : -1;
- } else {
- return 1;
- }
- });
- break;
- }
- }
+ this.directory.folders.sort((a, b): number => ((order === 'asc' ? a.name > b.name : a.name < b.name) ? 1 : -1));
+ this.directory.files.sort((a, b): number => ((order === 'asc' ? a[by] > b[by] : a[by] < b[by]) ? 1 : -1));
+ }
+
+ public openDetails(filePath: string): void {
+ this.filesService.getFile(filePath).subscribe(
+ (fileData: File) => (this.fileDetail = fileData),
+ (error: HttpErrorResponse) => {
+ this.fileDetail = ({ name: 'error' } as unknown) as File;
+ this.notificationService.setError("Can't load file!", error.message);
+ },
+ );
+ const fileDOMElement = document.getElementById('fileDetailView');
+ fileDOMElement.style.display = 'block';
+ setTimeout((): void => {
+ fileDOMElement.style.opacity = '1';
+ }, 50);
}
public closeDetails(): void {
@@ -149,36 +121,37 @@ export class FilesComponent {
}
public loadFile(filePath: string): void {
+ this.filesService.loadFile(filePath);
+ this.filesService.setLoadedFile(true);
setTimeout((): void => {
- this.filesService.loadFile(filePath);
- this.service.setLoadedFile(true);
- this.jobService.deleteJobInformation();
this.router.navigate(['/main-screen']);
}, 300);
}
public printFile(filePath: string): void {
+ this.filesService.printFile(filePath);
setTimeout((): void => {
- this.filesService.printFile(filePath);
this.router.navigate(['/main-screen']);
- }, 300);
+ }, 550);
}
public deleteFile(filePath: string): void {
+ this.filesService.deleteFile(filePath);
setTimeout((): void => {
- this.filesService.deleteFile(filePath);
this.closeDetails();
this.openFolder(this.currentFolder);
}, 300);
}
+ private hideLoader(): void {
+ if (Date.now() - this.loading > 750) {
+ this.loading = 0;
+ } else {
+ setTimeout(this.hideLoader.bind(this), 750 - (Date.now() - this.loading));
+ }
+ }
+
private showLoader(): void {
- this.spinner.show(undefined, {
- bdColor: '#353b48',
- color: '#f5f6fa',
- size: 'medium',
- type: 'pacman',
- fullScreen: false,
- });
+ this.loading = Date.now();
}
}
diff --git a/src/app/height-progress/height-progress.component.html b/src/app/height-progress/height-progress.component.html
new file mode 100644
index 000000000..e488261f2
--- /dev/null
+++ b/src/app/height-progress/height-progress.component.html
@@ -0,0 +1,14 @@
+
+
+ Z {{ height }}mm
+
+
+
+
+
+ Layer
+ {{ height.current }}
+ of {{ height.total >= 0 ? height.total : '---' }}
+
+
diff --git a/src/app/height-progress/height-progress.component.scss b/src/app/height-progress/height-progress.component.scss
new file mode 100644
index 000000000..e807590e6
--- /dev/null
+++ b/src/app/height-progress/height-progress.component.scss
@@ -0,0 +1,23 @@
+.height-indication {
+ width: 100%;
+ display: block;
+ margin-top: 1vh;
+ text-align: center;
+ font-size: 4vw;
+
+ &__current-height {
+ font-size: 6vw;
+ font-weight: 500;
+ }
+
+ &__z {
+ border: 3px solid white;
+ padding: 0 0.2rem;
+ font-size: 0.5rem;
+ font-weight: bold;
+ border-radius: 0.1rem;
+ vertical-align: 0.05rem;
+ display: inline-block;
+ margin-right: 1.5vw;
+ }
+}
diff --git a/src/app/height-progress/height-progress.component.ts b/src/app/height-progress/height-progress.component.ts
new file mode 100644
index 000000000..741928858
--- /dev/null
+++ b/src/app/height-progress/height-progress.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Subscription } from 'rxjs';
+
+import { ZHeightLayer } from '../model';
+import { SocketService } from '../services/socket/socket.service';
+
+@Component({
+ selector: 'app-height-progress',
+ templateUrl: './height-progress.component.html',
+ styleUrls: ['./height-progress.component.scss'],
+})
+export class HeightProgressComponent implements OnInit, OnDestroy {
+ private subscriptions: Subscription = new Subscription();
+ public height: number | ZHeightLayer;
+
+ public constructor(private socketService: SocketService) {}
+
+ public ngOnInit(): void {
+ this.subscriptions.add(
+ this.socketService.getJobStatusSubscribable().subscribe(jobStatus => {
+ this.height = jobStatus.zHeight;
+ }),
+ );
+ }
+
+ public ngOnDestroy(): void {
+ this.subscriptions.unsubscribe();
+ }
+
+ public isNumber(variable: number | ZHeightLayer): boolean {
+ return typeof variable === 'number';
+ }
+}
diff --git a/src/app/job-status/job-status.component.html b/src/app/job-status/job-status.component.html
index 5faef367d..fc53f5724 100644
--- a/src/app/job-status/job-status.component.html
+++ b/src/app/job-status/job-status.component.html
@@ -1,58 +1,67 @@
-
-
+
+
-
{{ job.progress }}%
+
{{ jobStatus.progress }}%
-
+
+
-
+
+
+
-
+
- {{ job.progress }}%
+ {{ jobStatus.progress }}%
-
{{ job.filename }}
+
+
{{ jobStatus.file }}
-
{{ job.filamentAmount }}g Filament
+
{{ jobStatus.filamentAmount }}g Filament
+
-
- {{ job.timeLeft.value }}{{ job.timeLeft.unit }} left,
+
+ {{ jobStatus.timeLeft.value }}{{ jobStatus.timeLeft.unit }} left,
- elapsed: {{ job.timePrinted.value }}{{ job.timePrinted.unit }}
+ elapsed: {{ jobStatus.timePrinted.value }}{{ jobStatus.timePrinted.unit }}
-
+
+
-
+
discard
-
{{ job.filename }}
+
{{ jobStatus.file }}
+
-
-
-
+
+
-
+
- {{ job.estimatedPrintTime.value }}{{ job.estimatedPrintTime.unit }}
+ {{ jobStatus.estimatedPrintTime.value }}{{ jobStatus.estimatedPrintTime.unit }}
- - will finish ~{{ job.estimatedEndTime }}
+ - will finish ~{{ jobStatus.estimatedEndTime }}
-
+
+
- {{ job.filamentAmount }}g
+ {{ jobStatus.filamentAmount }}g
filament will be used
-
loading info
-
no job running ...
+
+
+ loading info
+
+
+
no job running ...
+
+
diff --git a/src/app/job-status/job-status.component.ts b/src/app/job-status/job-status.component.ts
index bdc902f02..bb16b36f1 100644
--- a/src/app/job-status/job-status.component.ts
+++ b/src/app/job-status/job-status.component.ts
@@ -1,10 +1,12 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
-import { AppService } from '../app.service';
import { ConfigService } from '../config/config.service';
-import { Job, JobService } from '../job.service';
-import { NotificationService } from '../notification/notification.service';
+import { EventService } from '../event.service';
+import { JobStatus } from '../model';
+import { FilesService } from '../services/files/files.service';
+import { JobService } from '../services/job/job.service';
+import { SocketService } from '../services/socket/socket.service';
@Component({
selector: 'app-job-status',
@@ -13,17 +15,33 @@ import { NotificationService } from '../notification/notification.service';
})
export class JobStatusComponent implements OnInit, OnDestroy {
private subscriptions: Subscription = new Subscription();
- public job: Job;
+
+ public jobStatus: JobStatus;
+ public thumbnail: string;
+ public showPreviewWhilePrinting: boolean;
public constructor(
private jobService: JobService,
- private service: AppService,
- private notificationService: NotificationService,
+ private fileService: FilesService,
+ private socketService: SocketService,
+ private eventService: EventService,
private configService: ConfigService,
- ) {}
+ ) {
+ this.showPreviewWhilePrinting = this.configService.showThumbnailByDefault();
+ }
public ngOnInit(): void {
- this.subscriptions.add(this.jobService.getObservable().subscribe((job: Job): Job => (this.job = job)));
+ this.subscriptions.add(
+ this.socketService.getJobStatusSubscribable().subscribe((jobStatus: JobStatus): void => {
+ if (jobStatus.file !== this.jobStatus?.file) {
+ this.fileService.getThumbnail(jobStatus.fullPath).subscribe(thumbnail => {
+ this.thumbnail = thumbnail;
+ console.log(this.thumbnail);
+ });
+ }
+ this.jobStatus = jobStatus;
+ }),
+ );
}
public ngOnDestroy(): void {
@@ -31,7 +49,7 @@ export class JobStatusComponent implements OnInit, OnDestroy {
}
public isFileLoaded(): boolean {
- return this.service.getLoadedFile();
+ return this.fileService.getLoadedFile();
}
public isPreheatEnabled(): boolean {
@@ -42,30 +60,23 @@ export class JobStatusComponent implements OnInit, OnDestroy {
this.jobService.preheat();
}
- public preheatDisabled(): void {
- this.notificationService.setWarning(
- 'Preheat Plugin is not enabled!',
- 'Please make sure to install and enable the Preheat Plugin to use this functionality.',
- );
- }
-
public discardLoadedFile(): void {
- this.service.setLoadedFile(false);
+ this.fileService.setLoadedFile(false);
}
public startJob(): void {
this.jobService.startJob();
setTimeout((): void => {
- this.service.setLoadedFile(false);
+ this.fileService.setLoadedFile(false);
}, 5000);
}
public isPrinting(): boolean {
- return this.jobService.isPrinting();
+ return this.eventService.isPrinting();
}
- public showPreview(): boolean {
- return this.jobService.showPreviewWhilePrinting();
+ public togglePreview(): void {
+ this.showPreviewWhilePrinting = !this.showPreviewWhilePrinting;
}
public hasProperty(object: Record
, name: string): boolean {
diff --git a/src/app/job.service.ts b/src/app/job.service.ts
deleted file mode 100644
index 1db006730..000000000
--- a/src/app/job.service.ts
+++ /dev/null
@@ -1,305 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Observable, Observer, Subscription, timer } from 'rxjs';
-import { shareReplay } from 'rxjs/operators';
-
-import { AppService } from './app.service';
-import { ConfigService } from './config/config.service';
-import { FilesService } from './files.service';
-import { NotificationService } from './notification/notification.service';
-import { JobCommand, OctoprintFilament, OctoprintJobStatus } from './octoprint/model/job';
-
-@Injectable({
- providedIn: 'root',
-})
-export class JobService {
- private httpGETRequest: Subscription;
- private httpPOSTRequest: Subscription;
- private observable: Observable;
- private observer: Observer;
- private printing = false;
- private previewWhilePrinting = false;
-
- public constructor(
- private configService: ConfigService,
- private http: HttpClient,
- private notificationService: NotificationService,
- private service: AppService,
- private fileService: FilesService,
- ) {
- this.previewWhilePrinting = this.configService.showThumbnailByDefault();
- this.observable = new Observable((observer: Observer): void => {
- this.observer = observer;
- timer(750, this.configService.getAPIPollingInterval()).subscribe((): void => {
- if (this.httpGETRequest) {
- this.httpGETRequest.unsubscribe();
- }
- this.httpGETRequest = this.http
- .get(this.configService.getURL('job'), this.configService.getHTTPHeaders())
- .subscribe(
- async (data: OctoprintJobStatus): Promise => {
- let job: Job = null;
- if (data.job && data.job.file.name) {
- this.printing = ['Printing', 'Pausing', 'Paused', 'Cancelling', 'Printing from SD'].includes(
- data.state,
- );
- try {
- job = {
- status: JobStatus[data.state],
- filename: data.job.file.display.replace('.gcode', '').replace('.ufp', ''),
- thumbnail: await this.fileService.getThumbnail(
- '/' + data.job.file.origin + '/' + data.job.file.path,
- ),
- progress: Math.round(data.progress.completion),
- ...(data.job.filament !== null
- ? {
- filamentAmount: this.service.convertFilamentLengthToWeight(
- this.getTotalAmountOfFilament(data.job.filament),
- ),
- }
- : {}),
- ...(data.progress.printTimeLeft !== null
- ? {
- timeLeft: {
- value: this.service.convertSecondsToHours(data.progress.printTimeLeft),
- unit: 'h',
- },
- }
- : {}),
- timePrinted: {
- value: this.service.convertSecondsToHours(data.progress.printTime),
- unit: 'h',
- },
- ...(data.job.estimatedPrintTime !== null
- ? {
- estimatedPrintTime: {
- value: this.service.convertSecondsToHours(data.job.estimatedPrintTime),
- unit: 'h',
- },
- estimatedEndTime: this.calculateEndTime(data.job.estimatedPrintTime),
- }
- : {}),
- };
- } catch (error) {
- this.notificationService.setError("Can't retrieve Job Status", error);
- }
- } else {
- this.printing = false;
- }
- observer.next(job);
- },
- (error: HttpErrorResponse): void => {
- this.printing = false;
- this.notificationService.setError("Can't retrieve jobs!", error.message);
- },
- );
- });
- }).pipe(shareReplay(1));
- this.observable.subscribe();
- }
-
- private getTotalAmountOfFilament(filamentAmount: OctoprintFilament): number {
- let filamentLength = 0;
- for (const property in filamentAmount) {
- if (
- Object.prototype.hasOwnProperty.call(filamentAmount, property) &&
- Object.prototype.hasOwnProperty.call(filamentAmount[property], 'length')
- ) {
- filamentLength += filamentAmount[property].length;
- }
- }
- return filamentLength;
- }
-
- public deleteJobInformation(): void {
- this.observer.next(null);
- }
-
- public getObservable(): Observable {
- return this.observable;
- }
-
- public isPrinting(): boolean {
- return this.printing;
- }
-
- public togglePreviewWhilePrinting(): void {
- this.previewWhilePrinting = !this.previewWhilePrinting;
- }
-
- public showPreviewWhilePrinting(): boolean {
- return this.previewWhilePrinting;
- }
-
- public cancelJob(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const cancelPayload: JobCommand = {
- command: 'cancel',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('job'), cancelPayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.notificationService.setError(
- "Can't cancel Job!",
- 'There is no running job, that could be cancelled (409)',
- );
- } else {
- this.notificationService.setError("Can't cancel Job!", error.message);
- }
- },
- );
- }
-
- public pauseJob(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const pausePayload: JobCommand = {
- command: 'pause',
- action: 'pause',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('job'), pausePayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.notificationService.setError(
- "Can't pause Job!",
- 'There is no running job, that could be paused (409)',
- );
- } else {
- this.notificationService.setError("Can't pause Job!", error.message);
- }
- },
- );
- }
-
- public restartJob(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const pausePayload: JobCommand = {
- command: 'restart',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('job'), pausePayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.notificationService.setError(
- "Can't restart Job!",
- 'There is no running job, that could be restarted (409)',
- );
- } else {
- this.notificationService.setError("Can't restart Job!", error.message);
- }
- },
- );
- }
-
- public resumeJob(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const pausePayload: JobCommand = {
- command: 'pause',
- action: 'resume',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('job'), pausePayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.notificationService.setError(
- "Can't resume Job!",
- 'There is no paused job, that could be resumed (409)',
- );
- } else {
- this.notificationService.setError("Can't resume Job!", error.message);
- }
- },
- );
- }
-
- public startJob(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const pausePayload: JobCommand = {
- command: 'start',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('job'), pausePayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.notificationService.setError("Can't start Job!", 'There is already a job running (409)');
- } else {
- this.notificationService.setError("Can't start Job!", error.message);
- }
- },
- );
- }
-
- public preheat(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const preheatPayload: JobCommand = {
- command: 'preheat',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('plugin/preheat'), preheatPayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't preheat printer!", error.message);
- },
- );
- }
-
- private calculateEndTime(duration: number): string {
- const date = new Date();
- date.setSeconds(date.getSeconds() + duration);
- return `${('0' + date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}`;
- }
-}
-
-interface Duration {
- value: string;
- unit: string;
-}
-
-export interface Job {
- status: JobStatus;
- filename: string;
- thumbnail: string | undefined;
- progress: number;
- filamentAmount?: number;
- timeLeft?: Duration;
- timePrinted: Duration;
- estimatedPrintTime?: Duration;
- estimatedEndTime?: string;
-}
-
-export enum JobStatus {
- Operational,
- Pausing,
- Paused,
- Printing,
- Cancelling,
- Error,
- Closed,
- Ready,
- SdReady,
- Loading,
-}
diff --git a/src/app/layer-progress/layer-progress.component.html b/src/app/layer-progress/layer-progress.component.html
deleted file mode 100644
index d544016a6..000000000
--- a/src/app/layer-progress/layer-progress.component.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
- Layer
- {{ layerProgress.current }}
- of
- {{ layerProgress.total }}
-
diff --git a/src/app/layer-progress/layer-progress.component.scss b/src/app/layer-progress/layer-progress.component.scss
deleted file mode 100644
index d28543325..000000000
--- a/src/app/layer-progress/layer-progress.component.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.layer-indication {
- width: 100%;
- display: block;
- margin-top: 1vh;
- text-align: center;
- font-size: 4vw;
-
- &__current-layer {
- font-size: 6vw;
- font-weight: 500;
- }
-}
diff --git a/src/app/layer-progress/layer-progress.component.ts b/src/app/layer-progress/layer-progress.component.ts
deleted file mode 100644
index 439fc5900..000000000
--- a/src/app/layer-progress/layer-progress.component.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
-import { Subscription } from 'rxjs';
-
-import { DisplayLayerProgressAPI, LayerProgressService } from '../plugins/layer-progress.service';
-
-@Component({
- selector: 'app-layer-progress',
- templateUrl: './layer-progress.component.html',
- styleUrls: ['./layer-progress.component.scss'],
-})
-export class LayerProgressComponent implements OnInit, OnDestroy {
- private subscriptions: Subscription = new Subscription();
- public layerProgress: LayerProgress;
-
- public constructor(private displayLayerProgressService: LayerProgressService) {
- this.layerProgress = {
- current: 0,
- total: 0,
- };
- }
-
- public ngOnInit(): void {
- this.subscriptions.add(
- this.displayLayerProgressService.getObservable().subscribe((layerProgress: DisplayLayerProgressAPI): void => {
- this.layerProgress.current = layerProgress.current;
- this.layerProgress.total = layerProgress.total;
- }),
- );
- }
-
- public ngOnDestroy(): void {
- this.subscriptions.unsubscribe();
- }
-}
-
-export interface LayerProgress {
- current: number;
- total: number;
-}
diff --git a/src/app/long-press.directive.ts b/src/app/long-press.directive.ts
index 5908320f8..e58340067 100644
--- a/src/app/long-press.directive.ts
+++ b/src/app/long-press.directive.ts
@@ -21,7 +21,7 @@ export class LongPress {
@Output()
onLongPressing = new EventEmitter();
- @HostListener('touchstart', ['$event'])
+ @HostListener('pointerdown', ['$event'])
onMouseDown(event: EventSource): void {
this.pressing = true;
this.longPressing = false;
@@ -34,8 +34,7 @@ export class LongPress {
}, this.duration);
}
- @HostListener('touchend', ['$event'])
- @HostListener('touchcancel', ['$event'])
+ @HostListener('pointerup', ['$event'])
endPress(event: EventSource): void {
clearTimeout(this.timeout);
clearInterval(this.interval);
@@ -46,7 +45,7 @@ export class LongPress {
this.pressing = false;
}
- @HostListener('touchmove', ['$event'])
+ @HostListener('pointerleave', ['$event'])
endPressMove(_: EventSource): void {
clearTimeout(this.timeout);
clearInterval(this.interval);
diff --git a/src/app/main-screen/main-screen.component.html b/src/app/main-screen/main-screen.component.html
index 01fa8007d..0ab009365 100644
--- a/src/app/main-screen/main-screen.component.html
+++ b/src/app/main-screen/main-screen.component.html
@@ -2,7 +2,7 @@
-
+
diff --git a/src/app/main-screen/main-screen.component.ts b/src/app/main-screen/main-screen.component.ts
index b744bdce9..cf4424b6d 100644
--- a/src/app/main-screen/main-screen.component.ts
+++ b/src/app/main-screen/main-screen.component.ts
@@ -1,7 +1,9 @@
import { Component } from '@angular/core';
+import { Router } from '@angular/router';
-import { AppService } from '../app.service';
-import { JobService } from '../job.service';
+import { ConfigService } from '../config/config.service';
+import { EventService } from '../event.service';
+import { FilesService } from '../services/files/files.service';
@Component({
selector: 'app-main-screen',
@@ -10,13 +12,22 @@ import { JobService } from '../job.service';
export class MainScreenComponent {
public printing = false;
- public constructor(private jobService: JobService, private service: AppService) {}
+ public constructor(
+ private eventService: EventService,
+ private fileService: FilesService,
+ private configService: ConfigService,
+ private router: Router,
+ ) {
+ if (!this.configService.isInitialized()) {
+ this.router.navigate(['/']);
+ }
+ }
public isPrinting(): boolean {
- return this.jobService.isPrinting();
+ return this.eventService.isPrinting();
}
public isFileLoaded(): boolean {
- return this.service.getLoadedFile();
+ return this.fileService.getLoadedFile();
}
}
diff --git a/src/app/main-screen/no-touch/main-screen-no-touch.component.html b/src/app/main-screen/no-touch/main-screen-no-touch.component.html
index 3762f4457..cb2bf88c6 100644
--- a/src/app/main-screen/no-touch/main-screen-no-touch.component.html
+++ b/src/app/main-screen/no-touch/main-screen-no-touch.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/app/model/auth.model.ts b/src/app/model/auth.model.ts
new file mode 100644
index 000000000..1d77c432a
--- /dev/null
+++ b/src/app/model/auth.model.ts
@@ -0,0 +1,4 @@
+export interface SocketAuth {
+ user: string;
+ session: string;
+}
diff --git a/src/app/model/enclosure.model.ts b/src/app/model/enclosure.model.ts
new file mode 100644
index 000000000..35ec4eea3
--- /dev/null
+++ b/src/app/model/enclosure.model.ts
@@ -0,0 +1,10 @@
+export interface TemperatureReading {
+ temperature: number;
+ humidity: number;
+ unit: string;
+}
+
+export enum PSUState {
+ ON,
+ OFF,
+}
diff --git a/src/app/model/event.model.ts b/src/app/model/event.model.ts
new file mode 100644
index 000000000..21f30cffa
--- /dev/null
+++ b/src/app/model/event.model.ts
@@ -0,0 +1,8 @@
+export enum PrinterEvent {
+ PRINTING,
+ PAUSED,
+ CLOSED,
+ CONNECTED,
+ IDLE,
+ UNKNOWN,
+}
diff --git a/src/app/plugins/filament/filament.interface.ts b/src/app/model/filament.model.ts
similarity index 57%
rename from src/app/plugins/filament/filament.interface.ts
rename to src/app/model/filament.model.ts
index f5a4f8ae8..c8536554b 100644
--- a/src/app/plugins/filament/filament.interface.ts
+++ b/src/app/model/filament.model.ts
@@ -1,11 +1,3 @@
-import { Observable } from 'rxjs';
-
-export interface FilamentManagementPlugin {
- getSpools(): Observable
>;
- getCurrentSpool(): Observable;
- setSpool(spool: FilamentSpool): Observable;
-}
-
export interface FilamentSpoolList {
spools: Array;
}
diff --git a/src/app/model/files.model.ts b/src/app/model/files.model.ts
new file mode 100644
index 000000000..12c74cf01
--- /dev/null
+++ b/src/app/model/files.model.ts
@@ -0,0 +1,22 @@
+export interface Directory {
+ folders: Array;
+ files: Array;
+}
+
+export interface Folder {
+ origin: string;
+ path: string;
+ name: string;
+ size: string;
+}
+
+export interface File {
+ origin: string;
+ path: string;
+ name: string;
+ size: string;
+ thumbnail: string;
+ printTime?: string;
+ filamentWeight?: number;
+ date?: string;
+}
diff --git a/src/app/model/index.ts b/src/app/model/index.ts
new file mode 100644
index 000000000..dfaa0c554
--- /dev/null
+++ b/src/app/model/index.ts
@@ -0,0 +1,9 @@
+export * from './auth.model';
+export * from './enclosure.model';
+export * from './event.model';
+export * from './filament.model';
+export * from './files.model';
+export * from './job.model';
+export * from './printer-profile.model';
+export * from './printer.model';
+export * from './system.model';
diff --git a/src/app/model/job.model.ts b/src/app/model/job.model.ts
new file mode 100644
index 000000000..60f794a2c
--- /dev/null
+++ b/src/app/model/job.model.ts
@@ -0,0 +1,21 @@
+export interface JobStatus {
+ file: string;
+ fullPath: string;
+ progress: number;
+ zHeight: number | ZHeightLayer;
+ filamentAmount?: number;
+ timePrinted: Duration;
+ timeLeft?: Duration;
+ estimatedPrintTime?: Duration;
+ estimatedEndTime?: string;
+}
+
+interface Duration {
+ value: string;
+ unit: string;
+}
+
+export interface ZHeightLayer {
+ current: number;
+ total: number;
+}
diff --git a/src/app/model/octoprint/auth.model.ts b/src/app/model/octoprint/auth.model.ts
new file mode 100644
index 000000000..97dcb163d
--- /dev/null
+++ b/src/app/model/octoprint/auth.model.ts
@@ -0,0 +1,27 @@
+/* eslint-disable camelcase */
+
+export interface OctoprintLogin {
+ _is_external_client: boolean;
+ _login_mechanism: string;
+ active: boolean;
+ admin: boolean;
+ apikey: string;
+ groups: Array;
+ name: string;
+ needs: {
+ groups: Array;
+ role: Array;
+ };
+ permissions: Array;
+ roles: Array;
+ session: string;
+ user: boolean;
+}
+
+export interface AppToken {
+ app_token: string;
+}
+
+export interface TokenSuccess {
+ api_key: string;
+}
diff --git a/src/app/model/octoprint/connection.model.ts b/src/app/model/octoprint/connection.model.ts
new file mode 100644
index 000000000..8b83a7e72
--- /dev/null
+++ b/src/app/model/octoprint/connection.model.ts
@@ -0,0 +1,8 @@
+export interface ConnectCommand {
+ command: string;
+ port?: string;
+ baudrate?: number;
+ printerProfile?: string;
+ save?: boolean;
+ autoconnect?: boolean;
+}
diff --git a/src/app/octoprint/model/file.ts b/src/app/model/octoprint/file.model.ts
similarity index 85%
rename from src/app/octoprint/model/file.ts
rename to src/app/model/octoprint/file.model.ts
index 40325eb10..586953d84 100644
--- a/src/app/octoprint/model/file.ts
+++ b/src/app/model/octoprint/file.model.ts
@@ -1,4 +1,4 @@
-import { OctoprintFilament } from './job';
+import { OctoprintFilament } from './socket.model';
export interface OctoprintFile {
date: number;
@@ -18,13 +18,7 @@ export interface OctoprintFile {
}
export interface OctoprintFolder {
- files: [OctoprintFile & OctoprintFolderContent];
- free: number;
- total: number;
-}
-
-export interface OctoprintFolderContent {
- children: [OctoprintFile & OctoprintFolderContent];
+ children: [OctoprintFile & OctoprintFolder];
display: string;
name: string;
origin: string;
@@ -74,3 +68,8 @@ interface OctoprintRefs {
download?: string;
resource: string;
}
+
+export interface FileCommand {
+ command: 'select';
+ print: boolean;
+}
diff --git a/src/app/model/octoprint/index.ts b/src/app/model/octoprint/index.ts
new file mode 100644
index 000000000..0c6a9efe6
--- /dev/null
+++ b/src/app/model/octoprint/index.ts
@@ -0,0 +1,13 @@
+export * from './auth.model';
+export * from './connection.model';
+export * from './file.model';
+export * from './job.model';
+export * from './printer-commands.model';
+export * from './printer-profile.model';
+export * from './socket.model';
+
+export * from './plugins/display-layer-progress.model';
+export * from './plugins/enclosure.model';
+export * from './plugins/filament-manager.model';
+export * from './plugins/psucontrol.model';
+export * from './plugins/tp-link.model';
diff --git a/src/app/model/octoprint/job.model.ts b/src/app/model/octoprint/job.model.ts
new file mode 100644
index 000000000..1056e27af
--- /dev/null
+++ b/src/app/model/octoprint/job.model.ts
@@ -0,0 +1,4 @@
+export interface JobCommand {
+ command: string;
+ action?: string;
+}
diff --git a/src/app/model/octoprint/plugins/display-layer-progress.model.ts b/src/app/model/octoprint/plugins/display-layer-progress.model.ts
new file mode 100644
index 000000000..6ceb60c1c
--- /dev/null
+++ b/src/app/model/octoprint/plugins/display-layer-progress.model.ts
@@ -0,0 +1,26 @@
+export interface DisplayLayerProgressData {
+ averageLayerDuration: string;
+ averageLayerDurationInSeconds: string;
+ changeFilamentCount: number;
+ changeFilamentTimeLeft: string;
+ changeFilamentTimeLeftInSeconds: string;
+ currentHeight: string;
+ currentHeightFormatted: string;
+ currentLayer: string;
+ estimatedChangeFilamentTime: string;
+ estimatedEndTime: string;
+ fanspeed: string;
+ feedrate: string;
+ feedrateG0: string;
+ feedrateG1: string;
+ lastLayerDuration: string;
+ m73progress: string;
+ printTimeLeft: string;
+ printTimeLeftInSeconds: string;
+ printerState: string;
+ progress: string;
+ totalHeight: string;
+ totalHeightFormatted: string;
+ totalLayer: string;
+ updateReason: string;
+}
diff --git a/src/app/model/octoprint/plugins/enclosure.model.ts b/src/app/model/octoprint/plugins/enclosure.model.ts
new file mode 100644
index 000000000..b6ad7db1e
--- /dev/null
+++ b/src/app/model/octoprint/plugins/enclosure.model.ts
@@ -0,0 +1,32 @@
+export interface EnclosurePluginAPI {
+ /* eslint-disable camelcase */
+ controlled_io: string;
+ temp_sensor_address: string;
+ temp_sensor_navbar: boolean;
+ temp_sensor_temp: number;
+ printer_action: string;
+ filament_sensor_enabled: boolean;
+ controlled_io_set_value: number;
+ temp_sensor_type: string;
+ temp_sensor_humidity: number;
+ filament_sensor_timeout: number;
+ edge: string;
+ ds18b20_serial: string;
+ action_type: string;
+ input_pull_resistor: string;
+ input_type: string;
+ label: string;
+ index_id: number;
+ use_fahrenheit: boolean;
+ gpio_pin: string;
+}
+
+export interface EnclosureColorBody {
+ red: number;
+ green: number;
+ blue: number;
+}
+
+export interface EnclosureOutputBody {
+ status: boolean;
+}
diff --git a/src/app/model/octoprint/plugins/filament-manager.model.ts b/src/app/model/octoprint/plugins/filament-manager.model.ts
new file mode 100644
index 000000000..e65cdf8bf
--- /dev/null
+++ b/src/app/model/octoprint/plugins/filament-manager.model.ts
@@ -0,0 +1,44 @@
+export interface FilamentManagerSpoolList {
+ spools: FilamentManagerSpool[];
+}
+
+export interface FilamentManagerSelections {
+ selections: FilamentManagerSelection[];
+}
+
+export interface FilamentManagerSelectionPatch {
+ selection: {
+ tool: number;
+ spool: {
+ id: number;
+ };
+ };
+}
+
+interface FilamentManagerSelection {
+ // eslint-disable-next-line camelcase
+ client_id: string;
+ spool: FilamentManagerSpool;
+ tool: number;
+}
+
+export interface FilamentManagerSpool {
+ /* eslint-disable camelcase */
+ cost: number;
+ id: number;
+ name: string;
+ displayName?: string;
+ color?: string;
+ profile: FilamentManagerProfile;
+ temp_offset: number;
+ used: number;
+ weight: number;
+}
+
+interface FilamentManagerProfile {
+ density: number;
+ diameter: number;
+ id: number;
+ material: string;
+ vendor: string;
+}
diff --git a/src/app/model/octoprint/plugins/psucontrol.model.ts b/src/app/model/octoprint/plugins/psucontrol.model.ts
new file mode 100644
index 000000000..83985f4f4
--- /dev/null
+++ b/src/app/model/octoprint/plugins/psucontrol.model.ts
@@ -0,0 +1,3 @@
+export interface PSUControlCommand {
+ command: 'turnPSUOn' | 'turnPSUOff';
+}
diff --git a/src/app/model/octoprint/plugins/tp-link.model.ts b/src/app/model/octoprint/plugins/tp-link.model.ts
new file mode 100644
index 000000000..1589fb1e1
--- /dev/null
+++ b/src/app/model/octoprint/plugins/tp-link.model.ts
@@ -0,0 +1,4 @@
+export interface TPLinkCommand {
+ command: 'turnOn' | 'turnOff';
+ ip: string;
+}
diff --git a/src/app/model/octoprint/printer-commands.model.ts b/src/app/model/octoprint/printer-commands.model.ts
new file mode 100644
index 000000000..102375b58
--- /dev/null
+++ b/src/app/model/octoprint/printer-commands.model.ts
@@ -0,0 +1,39 @@
+export interface JogCommand {
+ command: 'jog';
+ x: number;
+ y: number;
+ z: number;
+ speed: number;
+}
+
+export interface ExtrudeCommand {
+ command: 'extrude';
+ amount: number;
+ speed: number;
+}
+
+export interface GCodeCommand {
+ commands: string[];
+}
+
+export interface FeedrateCommand {
+ command: string;
+ factor: number;
+}
+
+export interface TemperatureHotendCommand {
+ command: string;
+ targets: {
+ tool0: number;
+ tool1?: number;
+ };
+}
+
+export interface TemperatureHeatbedCommand {
+ command: string;
+ target: number;
+}
+
+export interface DisconnectCommand {
+ command: string;
+}
diff --git a/src/app/octoprint/model/printerProfile.ts b/src/app/model/octoprint/printer-profile.model.ts
similarity index 81%
rename from src/app/octoprint/model/printerProfile.ts
rename to src/app/model/octoprint/printer-profile.model.ts
index fde49ab92..de837cb85 100644
--- a/src/app/octoprint/model/printerProfile.ts
+++ b/src/app/model/octoprint/printer-profile.model.ts
@@ -11,12 +11,12 @@ export interface OctoprintPrinterProfile {
axes: OctoprintPrinterAxis;
}
-export interface OctoprintPrinterAxis {
+interface OctoprintPrinterAxis {
x: OctoprintAxisDetails;
y: OctoprintAxisDetails;
z: OctoprintAxisDetails;
}
-export interface OctoprintAxisDetails {
+interface OctoprintAxisDetails {
inverted: boolean;
}
diff --git a/src/app/model/octoprint/socket.model.ts b/src/app/model/octoprint/socket.model.ts
new file mode 100644
index 000000000..d8875aad6
--- /dev/null
+++ b/src/app/model/octoprint/socket.model.ts
@@ -0,0 +1,99 @@
+/* eslint-disable camelcase */
+import { OctoprintFile } from './file.model';
+
+export interface OctoprintSocketCurrent {
+ current: {
+ busyFiles: Array;
+ currentZ: number;
+ job: OctoprintJob;
+ logs: Array;
+ messages: Array;
+ offsets: OctoprintOffsets;
+ progress: OctoprintProgress;
+ resends: OctoprintSocketResends;
+ serverTime: number;
+ state: OctoprintSocketState;
+ temps: OctoprintSocketTemperatures;
+ };
+}
+
+export interface OctoprintSocketEvent {
+ event: {
+ type: string;
+ payload: unknown;
+ };
+}
+export interface OctoprintPluginMessage {
+ plugin: {
+ plugin: string;
+ data: unknown;
+ };
+}
+
+interface OctoprintJob {
+ averagePrintTime: number;
+ estimatedPrintTime: number;
+ filament: OctoprintFilament;
+ file: OctoprintFile;
+ lastPrintTime: string;
+ user: string;
+}
+export interface OctoprintFilament {
+ [key: string]: OctoprintFilamentValues;
+}
+
+interface OctoprintFilamentValues {
+ length: number;
+ volume: number;
+}
+
+interface OctoprintOffsets {
+ tool0: number;
+}
+
+interface OctoprintProgress {
+ completion: number;
+ filepos: number;
+ printTime: number;
+ printTimeLeft: number;
+ printTimeLeftOrigin: string;
+}
+
+interface OctoprintSocketResends {
+ count: number;
+ transmitted: number;
+ ratio: number;
+}
+
+interface OctoprintSocketState {
+ text: string;
+ flags: OctoprintSocketStateFlags;
+}
+
+interface OctoprintSocketStateFlags {
+ cancelling: boolean;
+ closedOrError: boolean;
+ error: boolean;
+ finishing: boolean;
+ operational: boolean;
+ paused: boolean;
+ pausing: boolean;
+ printing: boolean;
+ ready: boolean;
+ resuming: boolean;
+ sdReady: boolean;
+}
+
+interface OctoprintSocketTemperatures {
+ [key: number]: {
+ time: number;
+ bed: OctoprintSocketTemperature;
+ chamber: OctoprintSocketTemperature;
+ tool0: OctoprintSocketTemperature;
+ };
+}
+
+interface OctoprintSocketTemperature {
+ actual: number;
+ target: number;
+}
diff --git a/src/app/model/printer-profile.model.ts b/src/app/model/printer-profile.model.ts
new file mode 100644
index 000000000..4ccb92677
--- /dev/null
+++ b/src/app/model/printer-profile.model.ts
@@ -0,0 +1,16 @@
+export interface PrinterProfile {
+ current: boolean;
+ name: string;
+ model: string;
+ axes: PrinterAxis;
+}
+
+export interface PrinterAxis {
+ x: AxisDetails;
+ y: AxisDetails;
+ z: AxisDetails;
+}
+
+export interface AxisDetails {
+ inverted: boolean;
+}
diff --git a/src/app/model/printer.model.ts b/src/app/model/printer.model.ts
new file mode 100644
index 000000000..dee9a39f1
--- /dev/null
+++ b/src/app/model/printer.model.ts
@@ -0,0 +1,22 @@
+export interface PrinterStatus {
+ status: PrinterState;
+ bed: Temperature;
+ tool0: Temperature;
+ fanSpeed: number;
+}
+
+interface Temperature {
+ current: number;
+ set: number;
+ unit: string;
+}
+
+export enum PrinterState {
+ operational,
+ pausing,
+ paused,
+ printing,
+ cancelling,
+ closed,
+ connecting,
+}
diff --git a/src/app/model/system.model.ts b/src/app/model/system.model.ts
new file mode 100644
index 000000000..afc37d66b
--- /dev/null
+++ b/src/app/model/system.model.ts
@@ -0,0 +1,24 @@
+export interface Notification {
+ heading: string;
+ text: string;
+ type: string;
+ closed: () => void;
+}
+
+export interface UpdateError {
+ error: {
+ message: string;
+ stack?: string;
+ };
+}
+
+export interface UpdateDownloadProgress {
+ percentage: number;
+ transferred: number;
+ total: number | string;
+ remaining: number;
+ eta: string;
+ runtime: string;
+ delta: number;
+ speed: number | string;
+}
diff --git a/src/app/notification/notification.component.ts b/src/app/notification/notification.component.ts
index d06385ec0..a0b306129 100644
--- a/src/app/notification/notification.component.ts
+++ b/src/app/notification/notification.component.ts
@@ -1,7 +1,8 @@
import { Component, NgZone, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
-import { Notification, NotificationService } from './notification.service';
+import { Notification } from '../model';
+import { NotificationService } from './notification.service';
@Component({
selector: 'app-notification',
diff --git a/src/app/notification/notification.service.ts b/src/app/notification/notification.service.ts
index fc94a7f88..22b69ab58 100644
--- a/src/app/notification/notification.service.ts
+++ b/src/app/notification/notification.service.ts
@@ -2,14 +2,15 @@ import { Injectable } from '@angular/core';
import { Observable, Observer } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
+import { Notification } from '../model';
+
@Injectable({
providedIn: 'root',
})
export class NotificationService {
private observable: Observable;
private observer: Observer;
- private hideNotifications = false;
- private bootGrace = true;
+ private bootGrace = false;
public constructor() {
this.observable = new Observable((observer: Observer): void => {
@@ -20,46 +21,30 @@ export class NotificationService {
}).pipe(shareReplay(1));
}
- public enableNotifications(): void {
- console.clear();
- this.hideNotifications = false;
- this.observer.next('close');
- }
-
- public disableNotifications(): void {
- console.clear();
- this.hideNotifications = true;
- this.observer.next('close');
- }
-
public closeNotification(): void {
this.observer.next('close');
}
public setError(heading: string, text: string): Promise {
return new Promise(resolve => {
- if ((!this.hideNotifications && !this.bootGrace) || (this.bootGrace && !text.endsWith('0 Unknown Error'))) {
- if (this.observer) {
- this.observer.next({ heading, text, type: 'error', closed: resolve });
- } else {
- setTimeout(() => {
- this.setError(heading, text);
- }, 1000);
- }
+ if (this.observer) {
+ this.observer.next({ heading, text, type: 'error', closed: resolve });
+ } else {
+ setTimeout(() => {
+ this.setError(heading, text);
+ }, 1000);
}
});
}
public setWarning(heading: string, text: string): Promise {
return new Promise(resolve => {
- if (!this.hideNotifications) {
- if (this.observer) {
- this.observer.next({ heading, text, type: 'warn', closed: resolve });
- } else {
- setTimeout(() => {
- this.setWarning(heading, text);
- }, 1000);
- }
+ if (this.observer) {
+ this.observer.next({ heading, text, type: 'warn', closed: resolve });
+ } else {
+ setTimeout(() => {
+ this.setWarning(heading, text);
+ }, 1000);
}
});
}
@@ -84,10 +69,3 @@ export class NotificationService {
return this.bootGrace;
}
}
-
-export interface Notification {
- heading: string;
- text: string;
- type: string;
- closed: () => void;
-}
diff --git a/src/app/octoprint.service.ts b/src/app/octoprint.service.ts
deleted file mode 100644
index 1c7e668b5..000000000
--- a/src/app/octoprint.service.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Subscription } from 'rxjs';
-
-import { ConfigService } from './config/config.service';
-import { NotificationService } from './notification/notification.service';
-
-@Injectable({
- providedIn: 'root',
-})
-export class OctoprintService {
- private httpPOSTRequest: Subscription;
-
- public constructor(
- private http: HttpClient,
- private configService: ConfigService,
- private notificationService: NotificationService,
- ) {}
-
- public disconnectPrinter(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const disconnectPayload: DisconnectCommand = {
- command: 'disconnect',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('connection'), disconnectPayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't disconnect from Printer!", error.message);
- },
- );
- }
-
- public sendSystemCommand(command: string): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL(`system/commands/core/${command}`), null, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError(`Can't execute ${command} command!`, error.message);
- },
- );
- }
-}
-
-interface DisconnectCommand {
- command: string;
-}
diff --git a/src/app/octoprint/auth.service.ts b/src/app/octoprint/auth.service.ts
deleted file mode 100644
index 41ffcc1fa..000000000
--- a/src/app/octoprint/auth.service.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint-disable camelcase */
-import { HttpClient, HttpResponseBase } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs';
-
-@Injectable({
- providedIn: 'root',
-})
-export class AuthService {
- constructor(private http: HttpClient) {}
-
- public probeSupport(octoprintURL: string): Observable {
- return this.http.get(`${octoprintURL}plugin/appkeys/probe`, { observe: 'response' });
- }
-
- public startProcess(octoprintURL: string): Observable {
- return this.http.post(`${octoprintURL}plugin/appkeys/request`, { app: 'OctoDash' });
- }
-
- public pollStatus(octoprintURL: string, token: string): Observable {
- return this.http.get(`${octoprintURL}plugin/appkeys/request/${token}`, { observe: 'response' });
- }
-}
-
-export interface AppToken {
- app_token: string;
-}
-
-export interface TokenSuccess {
- api_key: string;
-}
diff --git a/src/app/octoprint/model/connection.ts b/src/app/octoprint/model/connection.ts
deleted file mode 100644
index e8474547d..000000000
--- a/src/app/octoprint/model/connection.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-export interface OctoprintConnection {
- current: OctoprintCurrentConnection;
- options: OctoprintConnectionOptions;
-}
-
-interface OctoprintCurrentConnection {
- state: string;
- port: string;
- baudrate: number;
- printerProfile: string;
-}
-
-interface OctoprintConnectionOptions {
- ports: string[];
- baudrates: number[];
- printerProfiles: Record;
- portPreference: string;
- baudratePreference: string;
- printerProfilePreference: string;
- autoconnect: boolean;
-}
diff --git a/src/app/octoprint/model/job.ts b/src/app/octoprint/model/job.ts
deleted file mode 100644
index c95212f89..000000000
--- a/src/app/octoprint/model/job.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-export interface OctoprintJobStatus {
- job: OctoprintJob;
- progress: OctoprintProgress;
- state: string;
-}
-
-interface OctoprintJob {
- file: OctoprintFile;
- estimatedPrintTime: number;
- filament: OctoprintFilament;
-}
-
-interface OctoprintFile {
- name: string;
- origin: string;
- display: string;
- path?: string;
- type?: string;
- typePath?: string;
- size: number;
- date: number;
-}
-
-export interface OctoprintFilament {
- tool0?: OctoprintFilamentValues;
- tool1?: OctoprintFilamentValues;
- tool2?: OctoprintFilamentValues;
- tool3?: OctoprintFilamentValues;
- tool4?: OctoprintFilamentValues;
- tool5?: OctoprintFilamentValues;
- [key: string]: OctoprintFilamentValues;
-}
-
-interface OctoprintFilamentValues {
- length: number;
- volume: number;
-}
-
-interface OctoprintProgress {
- completion: number;
- filepos: number;
- printTime: number;
- printTimeLeft: number;
-}
-
-export interface JobCommand {
- command: string;
- action?: string;
-}
diff --git a/src/app/octoprint/model/layerProgress.ts b/src/app/octoprint/model/layerProgress.ts
deleted file mode 100644
index 6cb1eec72..000000000
--- a/src/app/octoprint/model/layerProgress.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export interface OctoprintLayerProgress {
- layer: OctoprintLayer;
- height: OctoprintHeight;
- fanSpeed: string;
- feedrate: string;
-}
-
-interface OctoprintLayer {
- total: string;
- current: string;
-}
-
-interface OctoprintHeight {
- total: string;
- totalWithExtrusion: string;
- current: string;
-}
diff --git a/src/app/octoprint/model/printerStatus.ts b/src/app/octoprint/model/printerStatus.ts
deleted file mode 100644
index 11f8971a3..000000000
--- a/src/app/octoprint/model/printerStatus.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-export interface OctoprintPrinterStatus {
- temperature: OctoprintTemperature;
- sd: OctoprintSD;
- state: OctoprintPrinterState;
-}
-
-interface OctoprintTemperature {
- tool0?: OctoprintTemperatureData;
- tool1?: OctoprintTemperatureData;
- tool2?: OctoprintTemperatureData;
- tool3?: OctoprintTemperatureData;
- tool4?: OctoprintTemperatureData;
- tool5?: OctoprintTemperatureData;
- bed?: OctoprintTemperatureData;
- [key: string]: OctoprintTemperatureData;
-}
-
-interface OctoprintTemperatureData {
- actual: number;
- target: number;
- offset: number;
-}
-
-interface OctoprintSD {
- ready: boolean;
-}
-
-interface OctoprintPrinterState {
- text: string;
- flags: OctoprintPrinterStateFlags;
-}
-
-interface OctoprintPrinterStateFlags {
- operational: boolean;
- paused: boolean;
- printing: boolean;
- pausing: boolean;
- cancelling: boolean;
- sdReady: boolean;
- error: boolean;
- ready: boolean;
- closedOrError: boolean;
-}
diff --git a/src/app/plugins/enclosure.service.ts b/src/app/plugins/enclosure.service.ts
deleted file mode 100644
index 8c359c24f..000000000
--- a/src/app/plugins/enclosure.service.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Observable, Observer, Subscription, timer } from 'rxjs';
-import { shareReplay } from 'rxjs/operators';
-
-import { TemperatureReading } from '../bottom-bar/bottom-bar.component';
-import { ConfigService } from '../config/config.service';
-import { NotificationService } from '../notification/notification.service';
-
-@Injectable({
- providedIn: 'root',
-})
-export class EnclosureService {
- private httpGETRequest: Subscription;
- private httpPOSTRequest: Subscription;
- private observable: Observable;
- private initialRequest = true;
-
- public constructor(
- private http: HttpClient,
- private configService: ConfigService,
- private notificationService: NotificationService,
- ) {
- this.observable = new Observable((observer: Observer): void => {
- timer(2500, 15000).subscribe((): void => {
- if (this.httpGETRequest) {
- this.httpGETRequest.unsubscribe();
- }
- this.httpGETRequest = this.http
- .get(
- this.configService
- .getURL('plugin/enclosure/inputs/' + this.configService.getAmbientTemperatureSensorName())
- .replace('/api', ''),
- this.configService.getHTTPHeaders(),
- )
- .subscribe(
- (data: EnclosurePluginAPI): void => {
- this.initialRequest = false;
- observer.next(({
- temperature: data.temp_sensor_temp,
- humidity: data.temp_sensor_humidity,
- unit: data.use_fahrenheit ? '°F' : '°C',
- } as unknown) as TemperatureReading);
- },
- (error: HttpErrorResponse): void => {
- if (this.initialRequest && error.status === 500) {
- this.initialRequest = false;
- } else {
- this.notificationService.setError("Can't retrieve enclosure temperature!", error.message);
- }
- },
- );
- });
- }).pipe(shareReplay(1));
- }
-
- public getObservable(): Observable {
- return this.observable;
- }
-
- public setLEDColor(identifier: number, red: number, green: number, blue: number): Promise {
- return new Promise((resolve, reject): void => {
- const colorBody: EnclosureColorBody = {
- red,
- green,
- blue,
- };
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- this.httpPOSTRequest = this.http
- .patch(
- this.configService.getURL('plugin/enclosure/neopixel/' + identifier).replace('/api', ''),
- colorBody,
- this.configService.getHTTPHeaders(),
- )
- .subscribe(
- (): void => resolve(),
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't set LED color!", error.message);
- reject();
- },
- );
- });
- }
-}
-
-interface EnclosurePluginAPI {
- /* eslint-disable camelcase */
- controlled_io: string;
- temp_sensor_address: string;
- temp_sensor_navbar: boolean;
- temp_sensor_temp: number;
- printer_action: string;
- filament_sensor_enabled: boolean;
- controlled_io_set_value: number;
- temp_sensor_type: string;
- temp_sensor_humidity: number;
- filament_sensor_timeout: number;
- edge: string;
- ds18b20_serial: string;
- action_type: string;
- input_pull_resistor: string;
- input_type: string;
- label: string;
- index_id: number;
- use_fahrenheit: boolean;
- gpio_pin: string;
-}
-
-interface EnclosureColorBody {
- red: number;
- green: number;
- blue: number;
-}
diff --git a/src/app/plugins/index.ts b/src/app/plugins/index.ts
deleted file mode 100644
index 1c5398613..000000000
--- a/src/app/plugins/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export * from './filament/filament-management.component';
-export * from './filament/filament-manager.service';
-export * from './filament/filament.interface';
-export * from './enclosure.service';
-export * from './layer-progress.service';
-export * from './psu-control.service';
-export * from './tplink-smartplug.service';
diff --git a/src/app/plugins/layer-progress.service.ts b/src/app/plugins/layer-progress.service.ts
deleted file mode 100644
index 484d784fa..000000000
--- a/src/app/plugins/layer-progress.service.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Observable, Observer, Subscription, timer } from 'rxjs';
-import { shareReplay } from 'rxjs/operators';
-
-import { ConfigService } from '../config/config.service';
-import { NotificationService } from '../notification/notification.service';
-import { OctoprintLayerProgress } from '../octoprint/model/layerProgress';
-
-@Injectable({
- providedIn: 'root',
-})
-export class LayerProgressService {
- private httpGETRequest: Subscription;
- private observable: Observable;
-
- public constructor(
- private configService: ConfigService,
- private notificationService: NotificationService,
- private http: HttpClient,
- ) {
- this.observable = new Observable((observer: Observer): void => {
- timer(1000, this.configService.getAPIPollingInterval()).subscribe((): void => {
- if (this.httpGETRequest) {
- this.httpGETRequest.unsubscribe();
- }
- this.httpGETRequest = this.http
- .get(
- this.configService.getURL('plugin/DisplayLayerProgress/values').replace('/api', ''),
- this.configService.getHTTPHeaders(),
- )
- .subscribe(
- (data: OctoprintLayerProgress): void => {
- observer.next({
- current: data.layer.current === '-' ? 0 : Number(data.layer.current),
- total: data.layer.total === '-' ? 0 : Number(data.layer.total),
- fanSpeed: data.fanSpeed === '-' ? 0 : data.fanSpeed === 'Off' ? 0 : data.fanSpeed.replace('%', ''),
- });
- },
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't retrieve layer progress!", error.message);
- },
- );
- });
- }).pipe(shareReplay(1));
- }
-
- public getObservable(): Observable {
- return this.observable;
- }
-}
-
-export interface DisplayLayerProgressAPI {
- current: number;
- total: number;
- fanSpeed: number | string;
-}
diff --git a/src/app/plugins/psu-control.service.ts b/src/app/plugins/psu-control.service.ts
deleted file mode 100644
index a6d1b3be0..000000000
--- a/src/app/plugins/psu-control.service.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Subscription } from 'rxjs';
-
-import { ConfigService } from '../config/config.service';
-import { NotificationService } from '../notification/notification.service';
-import { JobCommand } from '../octoprint/model/job';
-
-@Injectable({
- providedIn: 'root',
-})
-export class PsuControlService {
- private httpPOSTRequest: Subscription;
-
- public constructor(
- private configService: ConfigService,
- private notificationService: NotificationService,
- private http: HttpClient,
- ) {}
-
- public changePSUState(on: boolean): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const psuPayload: JobCommand = {
- command: on ? 'turnPSUOn' : 'turnPSUOff',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('plugin/psucontrol'), psuPayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't control PSU!", error.message);
- },
- );
- }
-
- public togglePSU(): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const psuPayload: JobCommand = {
- command: 'togglePSU',
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('plugin/psucontrol'), psuPayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't control PSU!", error.message);
- },
- );
- }
-}
diff --git a/src/app/plugins/tplink-smartplug.service.ts b/src/app/plugins/tplink-smartplug.service.ts
deleted file mode 100644
index febc5d60a..000000000
--- a/src/app/plugins/tplink-smartplug.service.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Subscription } from 'rxjs';
-
-import { ConfigService } from '../config/config.service';
-import { NotificationService } from '../notification/notification.service';
-
-@Injectable({
- providedIn: 'root',
-})
-export class TPLinkSmartPlugService {
- private httpPOSTRequest: Subscription;
-
- public constructor(
- private configService: ConfigService,
- private notificationService: NotificationService,
- private http: HttpClient,
- ) {}
-
- public changePowerState(on: boolean): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
-
- const payload = {
- command: on ? 'turnOn' : 'turnOff',
- ip: this.configService.getSmartPlugIP(),
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('plugin/tplinksmartplug'), payload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't control TPLink SmartPlug!", error.message);
- },
- );
- }
-}
diff --git a/src/app/print-control/print-control.component.ts b/src/app/print-control/print-control.component.ts
index 1eb416aae..34b36627d 100644
--- a/src/app/print-control/print-control.component.ts
+++ b/src/app/print-control/print-control.component.ts
@@ -4,9 +4,10 @@ import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { ConfigService } from '../config/config.service';
-import { Job, JobService, JobStatus } from '../job.service';
-import { DisplayLayerProgressAPI, LayerProgressService } from '../plugins/layer-progress.service';
-import { PrinterService, PrinterStatusAPI } from '../printer.service';
+import { PrinterState, PrinterStatus } from '../model';
+import { JobService } from '../services/job/job.service';
+import { PrinterService } from '../services/printer/printer.service';
+import { SocketService } from '../services/socket/socket.service';
@Component({
selector: 'app-print-control',
@@ -30,8 +31,8 @@ export class PrintControlComponent implements OnInit, OnDestroy {
public constructor(
private jobService: JobService,
private printerService: PrinterService,
- private displayLayerProgressService: LayerProgressService,
private configService: ConfigService,
+ private socketService: SocketService,
private router: Router,
) {
this.temperatureHotend = 0;
@@ -43,8 +44,8 @@ export class PrintControlComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
this.subscriptions.add(
- this.jobService.getObservable().subscribe((job: Job): void => {
- if (job.status === JobStatus.Paused) {
+ this.socketService.getPrinterStatusSubscribable().subscribe((printerStatus: PrinterStatus) => {
+ if (printerStatus.status === PrinterState.paused) {
if (!this.showedPauseScreen) {
this.view = ControlView.PAUSE;
this.showControls = true;
@@ -118,7 +119,7 @@ export class PrintControlComponent implements OnInit, OnDestroy {
this.view = ControlView.MAIN;
this.showControls = true;
} else {
- this.jobService.togglePreviewWhilePrinting();
+ document.getElementById('jobTogglePreview').click();
}
}
@@ -161,19 +162,13 @@ export class PrintControlComponent implements OnInit, OnDestroy {
}
private loadData(): void {
- this.printerService
- .getObservable()
+ this.socketService
+ .getPrinterStatusSubscribable()
.pipe(take(1))
- .subscribe((printerStatus: PrinterStatusAPI): void => {
- this.temperatureHotend = printerStatus.nozzle.set;
- this.temperatureHeatbed = printerStatus.heatbed.set;
- });
-
- this.displayLayerProgressService
- .getObservable()
- .pipe(take(1))
- .subscribe((layerProgress: DisplayLayerProgressAPI): void => {
- this.fanSpeed = Number(layerProgress.fanSpeed);
+ .subscribe((status: PrinterStatus): void => {
+ this.temperatureHotend = status.tool0.set;
+ this.temperatureHeatbed = status.bed.set;
+ this.fanSpeed = status.fanSpeed;
});
}
@@ -228,7 +223,7 @@ export class PrintControlComponent implements OnInit, OnDestroy {
public setAdjustParameters(event: MouseEvent): void {
if (this.showControls) {
this.printerService.setTemperatureHotend(this.temperatureHotend);
- this.printerService.setTemperatureHeatbed(this.temperatureHeatbed);
+ this.printerService.setTemperatureBed(this.temperatureHeatbed);
this.printerService.setFeedrate(this.feedrate);
this.printerService.setFanSpeed(this.fanSpeed);
this.hideControlOverlay(event);
diff --git a/src/app/printer-status/printer-status.component.html b/src/app/printer-status/printer-status.component.html
index b279a45ff..2c89e157e 100644
--- a/src/app/printer-status/printer-status.component.html
+++ b/src/app/printer-status/printer-status.component.html
@@ -2,32 +2,32 @@
- {{ printerStatus.nozzle.current }}°C
+ {{ printerStatus.tool0.current }}°C
-
- /{{ printerStatus.nozzle.set }}°C
+
+ /{{ printerStatus.tool0.set }}°C
|
- {{ printerStatus.heatbed.current }}°C
+ {{ printerStatus.bed.current }}°C
-
- /{{ printerStatus.heatbed.set }}°C
+
+ /{{ printerStatus.bed.set }}°C
|
- {{ printerStatus.fan }}%
+ {{ printerStatus.fanSpeed >= 0 ? printerStatus.fanSpeed : '-- ' }}%
|
diff --git a/src/app/printer-status/printer-status.component.ts b/src/app/printer-status/printer-status.component.ts
index eeac78a50..0414b2c68 100644
--- a/src/app/printer-status/printer-status.component.ts
+++ b/src/app/printer-status/printer-status.component.ts
@@ -2,8 +2,9 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { ConfigService } from '../config/config.service';
-import { DisplayLayerProgressAPI, LayerProgressService } from '../plugins/layer-progress.service';
-import { PrinterService, PrinterStatusAPI, PrinterValue } from '../printer.service';
+import { PrinterStatus } from '../model';
+import { PrinterService } from '../services/printer/printer.service';
+import { SocketService } from '../services/socket/socket.service';
@Component({
selector: 'app-printer-status',
@@ -13,30 +14,21 @@ import { PrinterService, PrinterStatusAPI, PrinterValue } from '../printer.servi
export class PrinterStatusComponent implements OnInit, OnDestroy {
private subscriptions: Subscription = new Subscription();
public printerStatus: PrinterStatus;
+ public fanSpeed: number;
public status: string;
- public QuickControlView = QuickControlView;
- public view = QuickControlView.NONE;
+
public hotendTarget: number;
public heatbedTarget: number;
public fanTarget: number;
+ public QuickControlView = QuickControlView;
+ public view = QuickControlView.NONE;
+
public constructor(
private printerService: PrinterService,
- private displayLayerProgressService: LayerProgressService,
private configService: ConfigService,
+ private socketService: SocketService,
) {
- this.printerStatus = {
- nozzle: {
- current: 0,
- set: 0,
- },
- heatbed: {
- current: 0,
- set: 0,
- },
- fan: 0,
- };
- this.status = 'connecting';
this.hotendTarget = this.configService.getDefaultHotendTemperature();
this.heatbedTarget = this.configService.getDefaultHeatbedTemperature();
this.fanTarget = this.configService.getDefaultFanSpeed();
@@ -44,16 +36,8 @@ export class PrinterStatusComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
this.subscriptions.add(
- this.printerService.getObservable().subscribe((printerStatus: PrinterStatusAPI): void => {
- this.printerStatus.nozzle = printerStatus.nozzle;
- this.printerStatus.heatbed = printerStatus.heatbed;
- this.status = printerStatus.status;
- }),
- );
-
- this.subscriptions.add(
- this.displayLayerProgressService.getObservable().subscribe((layerProgress: DisplayLayerProgressAPI): void => {
- this.printerStatus.fan = layerProgress.fanSpeed;
+ this.socketService.getPrinterStatusSubscribable().subscribe((status: PrinterStatus): void => {
+ this.printerStatus = status;
}),
);
}
@@ -159,7 +143,7 @@ export class PrinterStatusComponent implements OnInit, OnDestroy {
}
private setTemperatureHeatbed(): void {
- this.printerService.setTemperatureHeatbed(this.heatbedTarget);
+ this.printerService.setTemperatureBed(this.heatbedTarget);
this.hideQuickControl();
}
@@ -169,12 +153,6 @@ export class PrinterStatusComponent implements OnInit, OnDestroy {
}
}
-export interface PrinterStatus {
- nozzle: PrinterValue;
- heatbed: PrinterValue;
- fan: number | string;
-}
-
enum QuickControlView {
NONE,
HOTEND,
diff --git a/src/app/printer.service.ts b/src/app/printer.service.ts
deleted file mode 100644
index 8076fce07..000000000
--- a/src/app/printer.service.ts
+++ /dev/null
@@ -1,309 +0,0 @@
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Router } from '@angular/router';
-import { Observable, Observer, Subscription, timer } from 'rxjs';
-import { shareReplay } from 'rxjs/operators';
-
-import { ConfigService } from './config/config.service';
-import { NotificationService } from './notification/notification.service';
-import { OctoprintConnection } from './octoprint/model/connection';
-import { OctoprintPrinterStatus } from './octoprint/model/printerStatus';
-
-@Injectable({
- providedIn: 'root',
-})
-export class PrinterService {
- private httpGETRequest: Subscription;
- private httpPOSTRequest: Subscription;
- private observable: Observable;
-
- public constructor(
- private http: HttpClient,
- private configService: ConfigService,
- private notificationService: NotificationService,
- private router: Router,
- ) {
- this.observable = new Observable((observer: Observer): void => {
- timer(500, this.configService.getAPIPollingInterval()).subscribe((): void => {
- if (this.httpGETRequest) {
- this.httpGETRequest.unsubscribe();
- }
- this.httpGETRequest = this.http
- .get(this.configService.getURL('printer'), this.configService.getHTTPHeaders())
- .subscribe(
- (data: OctoprintPrinterStatus): void => {
- const printerStatus: PrinterStatusAPI = {
- status: data.state.text.toLowerCase(),
- nozzle: {
- current: Math.round(data.temperature.tool0.actual),
- set: Math.round(data.temperature.tool0.target),
- },
- heatbed: {
- current: data.temperature.bed ? Math.round(data.temperature.bed.actual) : 0,
- set: data.temperature.bed ? Math.round(data.temperature.bed.target) : 0,
- },
- };
- observer.next(printerStatus);
- },
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.isPrinterOffline().then((printerOffline): void => {
- if (printerOffline) {
- this.router.navigate(['/standby']);
- } else {
- this.notificationService.setError("Can't retrieve printer status!", error.message);
- }
- });
- } else if (error.status === 0 && this.notificationService.getBootGrace()) {
- const printerStatus: PrinterStatusAPI = {
- status: `connecting ...`,
- nozzle: {
- current: 0,
- set: 0,
- },
- heatbed: {
- current: 0,
- set: 0,
- },
- };
- observer.next(printerStatus);
- } else {
- const printerStatus: PrinterStatusAPI = {
- status: `error (${error.status})`,
- nozzle: {
- current: 0,
- set: 0,
- },
- heatbed: {
- current: 0,
- set: 0,
- },
- };
- observer.next(printerStatus);
- this.notificationService.setError("Can't retrieve printer status!", error.message);
- }
- },
- );
- });
- }).pipe(shareReplay(1));
- }
-
- public getObservable(): Observable {
- return this.observable;
- }
-
- public stopMotors(): void {
- this.executeGCode('M410');
- }
-
- public jog(x: number, y: number, z: number): void {
- const jogPayload: JogCommand = {
- command: 'jog',
- x,
- y,
- z,
- speed: z !== 0 ? this.configService.getZSpeed() * 60 : this.configService.getXYSpeed() * 60,
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/printhead'), jogPayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't move Printhead!", error.message);
- },
- );
- }
-
- public extrude(amount: number, speed: number): void {
- let multiplier = 1;
- let toBeExtruded: number;
- if (amount < 0) {
- multiplier = -1;
- toBeExtruded = amount * -1;
- } else {
- toBeExtruded = amount;
- }
-
- while (toBeExtruded > 0) {
- if (toBeExtruded >= 100) {
- toBeExtruded -= 100;
- this.moveExtruder(100 * multiplier, speed);
- } else {
- this.moveExtruder(toBeExtruded * multiplier, speed);
- toBeExtruded = 0;
- }
- }
- }
-
- private moveExtruder(amount: number, speed: number): void {
- const extrudePayload: ExtrudeCommand = {
- command: 'extrude',
- amount,
- speed: speed * 60,
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/tool'), extrudePayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't extrude Filament!", error.message);
- },
- );
- }
-
- public executeGCode(gCode: string): void {
- if (this.httpPOSTRequest) {
- this.httpPOSTRequest.unsubscribe();
- }
- const gCodePayload: GCodeCommand = {
- commands: gCode.split('; '),
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/command'), gCodePayload, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't send GCode!", error.message);
- },
- );
- }
-
- public setTemperatureHotend(temperature: number): void {
- const temperatureHotendCommand: TemperatureHotendCommand = {
- command: 'target',
- targets: {
- tool0: temperature,
- },
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/tool'), temperatureHotendCommand, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't set Hotend Temperature!", error.message);
- },
- );
- }
-
- public setTemperatureHeatbed(temperature: number): void {
- const temperatureHeatbedCommand: TemperatureHeatbedCommand = {
- command: 'target',
- target: temperature,
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/bed'), temperatureHeatbedCommand, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't set Heatbed Temperature!", error.message);
- },
- );
- }
-
- public setFeedrate(feedrate: number): void {
- if (feedrate === 100) {
- return;
- }
- const feedrateCommand: FeedrateCommand = {
- command: 'feedrate',
- factor: feedrate,
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/printhead'), feedrateCommand, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't set Feedrate!", error.message);
- },
- );
- }
-
- public setFlowrate(flowrate: number): void {
- if (flowrate === 100) {
- return;
- }
- const flowrateCommand: FeedrateCommand = {
- command: 'flowrate',
- factor: flowrate,
- };
- this.httpPOSTRequest = this.http
- .post(this.configService.getURL('printer/tool'), flowrateCommand, this.configService.getHTTPHeaders())
- .subscribe(
- (): void => null,
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't set Flowrate!", error.message);
- },
- );
- }
-
- public setFanSpeed(percentage: number): void {
- this.executeGCode('M106 S' + Math.round((percentage / 100) * 255));
- }
-
- public isPrinterOffline(): Promise {
- return new Promise((resolve): void => {
- this.http.get(this.configService.getURL('connection'), this.configService.getHTTPHeaders()).subscribe(
- (data: OctoprintConnection): void => {
- resolve(data.current.state === 'Closed' || data.current.state.includes('Error:'));
- },
- (error: HttpErrorResponse): void => {
- this.notificationService.setError("Can't retrieve connection state!", error.message);
- resolve(false);
- },
- );
- });
- }
-}
-
-export interface PrinterStatusAPI {
- status: string;
- nozzle: PrinterValue;
- heatbed: PrinterValue;
-}
-
-export interface PrinterValue {
- current: number;
- set: number;
-}
-
-interface JogCommand {
- command: 'jog';
- x: number;
- y: number;
- z: number;
- speed: number;
-}
-
-interface ExtrudeCommand {
- command: 'extrude';
- amount: number;
- speed: number;
-}
-
-interface ExtrudeCommand {
- command: 'extrude';
- amount: number;
- speed: number;
-}
-
-interface GCodeCommand {
- commands: string[];
-}
-
-interface FeedrateCommand {
- command: string;
- factor: number;
-}
-
-interface TemperatureHotendCommand {
- command: string;
- targets: {
- tool0: number;
- tool1?: number;
- };
-}
-
-interface TemperatureHeatbedCommand {
- command: string;
- target: number;
-}
diff --git a/src/app/printerprofile.service.ts b/src/app/printerprofile.service.ts
deleted file mode 100644
index 7e816a807..000000000
--- a/src/app/printerprofile.service.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-import { Router } from '@angular/router';
-import { Observable, Subscription } from 'rxjs';
-import { map } from 'rxjs/operators';
-
-import { ConfigService } from './config/config.service';
-import { NotificationService } from './notification/notification.service';
-import { OctoprintPrinterProfile, OctoprintPrinterProfiles } from './octoprint/model/printerProfile';
-import { PrinterService } from './printer.service';
-
-@Injectable({
- providedIn: 'root',
-})
-export class PrinterProfileService {
- private httpGETRequest: Subscription;
-
- public constructor(
- private http: HttpClient,
- private configService: ConfigService,
- private notificationService: NotificationService,
- private printerStatusService: PrinterService,
- private router: Router,
- ) {}
-
- public getDefaultPrinterProfile(): Promise {
- return new Promise((resolve, reject): void => {
- if (this.httpGETRequest) {
- this.httpGETRequest.unsubscribe();
- }
- this.httpGETRequest = this.http
- .get(this.configService.getURL('printerprofiles/_default'), this.configService.getHTTPHeaders())
- .subscribe(
- (printerProfile: OctoprintPrinterProfile): void => {
- resolve(printerProfile);
- },
- (error: HttpErrorResponse): void => {
- if (error.status === 409) {
- this.printerStatusService.isPrinterOffline().then((printerOffline): void => {
- if (printerOffline) {
- this.router.navigate(['/standby']);
- } else {
- this.notificationService.setError("Can't retrieve printer profile!", error.message);
- }
- });
- reject();
- } else {
- reject();
- if (error.status === 0 && this.notificationService.getBootGrace()) {
- this.notificationService.setError("Can't retrieve printer status!", error.message);
- }
- }
- },
- );
- });
- }
-
- // Needed for initial setup. Config not initialized yet, thus values need to be passed manually.
- public getActivePrinterProfileName(octoprintURL: string, apiKey: string): Observable {
- return this.http
- .get(`${octoprintURL}printerprofiles`, {
- headers: new HttpHeaders({
- 'x-api-key': apiKey,
- }),
- })
- .pipe(
- map(profiles => {
- for (const [_, profile] of Object.entries(profiles.profiles)) {
- return profile.name;
- }
- }),
- );
- }
-}
diff --git a/src/app/services/enclosure/enclosure.octoprint.service.ts b/src/app/services/enclosure/enclosure.octoprint.service.ts
new file mode 100644
index 000000000..a836db79f
--- /dev/null
+++ b/src/app/services/enclosure/enclosure.octoprint.service.ts
@@ -0,0 +1,113 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+import { ConfigService } from '../../config/config.service';
+import { PSUState, TemperatureReading } from '../../model';
+import {
+ EnclosureColorBody,
+ EnclosureOutputBody,
+ EnclosurePluginAPI,
+ PSUControlCommand,
+ TPLinkCommand,
+} from '../../model/octoprint';
+import { NotificationService } from '../../notification/notification.service';
+import { EnclosureService } from './enclosure.service';
+
+@Injectable()
+export class EnclosureOctoprintService implements EnclosureService {
+ public constructor(
+ private configService: ConfigService,
+ private notificationService: NotificationService,
+ private http: HttpClient,
+ ) {}
+ private currentPSUState = PSUState.ON;
+
+ getEnclosureTemperature(): Observable {
+ return this.http
+ .get(
+ this.configService.getApiURL(
+ 'plugin/enclosure/inputs/' + this.configService.getAmbientTemperatureSensorName(),
+ false,
+ ),
+ this.configService.getHTTPHeaders(),
+ )
+ .pipe(
+ map((data: EnclosurePluginAPI) => {
+ return {
+ temperature: data.temp_sensor_temp,
+ humidity: data.temp_sensor_humidity,
+ unit: data.use_fahrenheit ? '°F' : '°C',
+ } as TemperatureReading;
+ }),
+ );
+ }
+
+ setLEDColor(identifier: number, red: number, green: number, blue: number): void {
+ const colorBody: EnclosureColorBody = {
+ red,
+ green,
+ blue,
+ };
+ this.http
+ .patch(
+ this.configService.getApiURL('plugin/enclosure/neopixel/' + identifier, false),
+ colorBody,
+ this.configService.getHTTPHeaders(),
+ )
+ .pipe(catchError(error => this.notificationService.setError("Can't set LED color!", error.message)))
+ .subscribe();
+ }
+
+ setOutput(identifier: number, status: boolean): void {
+ const outputBody: EnclosureOutputBody = {
+ status,
+ };
+ this.http
+ .patch(
+ this.configService.getApiURL('plugin/enclosure/outputs/' + identifier, false),
+ outputBody,
+ this.configService.getHTTPHeaders(),
+ )
+ .pipe(catchError(error => this.notificationService.setError("Can't set output!", error.message)))
+ .subscribe();
+ }
+
+ setPSUState(state: PSUState): void {
+ if (this.configService.usePSUControl()) {
+ this.setPSUStatePSUControl(state);
+ } else if (this.configService.useTpLinkSmartPlug()) {
+ this.setPSUStateTPLink(state);
+ } else {
+ this.notificationService.setWarning("Can't change PSU State!", 'No provider for PSU Control is configured.');
+ }
+ }
+
+ private setPSUStatePSUControl(state: PSUState) {
+ const psuControlPayload: PSUControlCommand = {
+ command: state === PSUState.ON ? 'turnPSUOn' : 'turnPSUOff',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('plugin/psucontrol'), psuControlPayload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't send GCode!", error.message)))
+ .subscribe();
+ }
+
+ private setPSUStateTPLink(state: PSUState) {
+ const tpLinkPayload: TPLinkCommand = {
+ command: state === PSUState.ON ? 'turnOn' : 'turnOff',
+ ip: this.configService.getSmartPlugIP(),
+ };
+
+ this.http
+ .post(this.configService.getApiURL('plugin/tplinksmartplug'), tpLinkPayload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't send GCode!", error.message)))
+ .subscribe();
+ }
+
+ togglePSU(): void {
+ this.currentPSUState === PSUState.ON ? this.setPSUState(PSUState.OFF) : this.setPSUState(PSUState.ON);
+ }
+}
diff --git a/src/app/services/enclosure/enclosure.service.ts b/src/app/services/enclosure/enclosure.service.ts
new file mode 100644
index 000000000..0341407f5
--- /dev/null
+++ b/src/app/services/enclosure/enclosure.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { PSUState, TemperatureReading } from '../../model';
+
+@Injectable()
+export abstract class EnclosureService {
+ abstract getEnclosureTemperature(): Observable;
+
+ abstract setLEDColor(identifier: number, red: number, green: number, blue: number): void;
+
+ abstract setOutput(identifier: number, status: boolean): void;
+
+ abstract setPSUState(state: PSUState): void;
+
+ abstract togglePSU(): void;
+}
diff --git a/src/app/plugins/filament/filament-manager.service.ts b/src/app/services/filament/filament-manager.octoprint.service.ts
similarity index 62%
rename from src/app/plugins/filament/filament-manager.service.ts
rename to src/app/services/filament/filament-manager.octoprint.service.ts
index 30fe05f86..379531a69 100644
--- a/src/app/plugins/filament/filament-manager.service.ts
+++ b/src/app/services/filament/filament-manager.octoprint.service.ts
@@ -4,22 +4,24 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ConfigService } from '../../config/config.service';
-import { FilamentManagementPlugin, FilamentSpool } from './filament.interface';
+import { FilamentSpool } from '../../model';
+import {
+ FilamentManagerSelectionPatch,
+ FilamentManagerSelections,
+ FilamentManagerSpool,
+ FilamentManagerSpoolList,
+} from '../../model/octoprint';
+import { FilamentPluginService } from './filament-plugin.service';
const colorRegexp = /\((.*)\)$/g;
-@Injectable({
- providedIn: 'root',
-})
-export class FilamentManagerService implements FilamentManagementPlugin {
+@Injectable()
+export class FilamentManagerOctoprintService implements FilamentPluginService {
public constructor(private configService: ConfigService, private http: HttpClient) {}
public getSpools(): Observable> {
return this.http
- .get(
- this.configService.getURL('plugin/filamentmanager/spools').replace('/api', ''),
- this.configService.getHTTPHeaders(),
- )
+ .get(this.configService.getApiURL('plugin/filamentmanager/spools', false), this.configService.getHTTPHeaders())
.pipe(
map(
(spools: FilamentManagerSpoolList): Array => {
@@ -36,7 +38,7 @@ export class FilamentManagerService implements FilamentManagementPlugin {
public getCurrentSpool(): Observable {
return this.http
.get(
- this.configService.getURL('plugin/filamentmanager/selections').replace('/api', ''),
+ this.configService.getApiURL('plugin/filamentmanager/selections', false),
this.configService.getHTTPHeaders(),
)
.pipe(
@@ -83,54 +85,9 @@ export class FilamentManagerService implements FilamentManagementPlugin {
};
return this.http.patch(
- this.configService.getURL('plugin/filamentmanager/selections/0').replace('/api', ''),
+ this.configService.getApiURL('plugin/filamentmanager/selections/0', false),
setSpoolBody,
this.configService.getHTTPHeaders(),
);
}
}
-
-export interface FilamentManagerSpoolList {
- spools: FilamentManagerSpool[];
-}
-
-interface FilamentManagerSelections {
- selections: FilamentManagerSelection[];
-}
-
-interface FilamentManagerSelectionPatch {
- selection: {
- tool: number;
- spool: {
- id: number;
- };
- };
-}
-
-interface FilamentManagerSelection {
- // eslint-disable-next-line camelcase
- client_id: string;
- spool: FilamentManagerSpool;
- tool: number;
-}
-
-interface FilamentManagerSpool {
- /* eslint-disable camelcase */
- cost: number;
- id: number;
- name: string;
- displayName?: string;
- color?: string;
- profile: FilamentManagerProfile;
- temp_offset: number;
- used: number;
- weight: number;
-}
-
-interface FilamentManagerProfile {
- density: number;
- diameter: number;
- id: number;
- material: string;
- vendor: string;
-}
diff --git a/src/app/services/filament/filament-plugin.service.ts b/src/app/services/filament/filament-plugin.service.ts
new file mode 100644
index 000000000..c1990ce74
--- /dev/null
+++ b/src/app/services/filament/filament-plugin.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { FilamentSpool } from '../../model';
+
+@Injectable()
+export abstract class FilamentPluginService {
+ abstract getSpools(): Observable>;
+
+ abstract getCurrentSpool(): Observable;
+
+ abstract setSpool(spool: FilamentSpool): Observable;
+}
diff --git a/src/app/plugins/filament/filament-management.component.ts b/src/app/services/filament/filament.service.ts
similarity index 58%
rename from src/app/plugins/filament/filament-management.component.ts
rename to src/app/services/filament/filament.service.ts
index 70dac04eb..0bb318c24 100644
--- a/src/app/plugins/filament/filament-management.component.ts
+++ b/src/app/services/filament/filament.service.ts
@@ -1,50 +1,42 @@
import { HttpErrorResponse } from '@angular/common/http';
-import { Component, Injector } from '@angular/core';
-import { ConfigService } from 'src/app/config/config.service';
+import { Injectable } from '@angular/core';
+import { ConfigService } from '../../config/config.service';
+import { FilamentSpool } from '../../model';
import { NotificationService } from '../../notification/notification.service';
-import { FilamentManagementPlugin, FilamentSpool } from './filament.interface';
-import { FilamentManagerService } from './filament-manager.service';
+import { FilamentPluginService } from './filament-plugin.service';
-@Component({
- selector: 'backend-filament-manager',
- template: '',
- styles: [],
-})
-export class FilamentManagementComponent {
- private filamentPlugin: FilamentManagementPlugin;
-
- private _filamentSpools: Array;
- private _currentSpool: FilamentSpool;
-
- private _loading = true;
+@Injectable()
+export class FilamentService {
+ private filamentSpools: Array;
+ private currentSpool: FilamentSpool;
+ private loading = true;
constructor(
- private injector: Injector,
private notificationService: NotificationService,
private configService: ConfigService,
+ private filamentPluginService: FilamentPluginService,
) {
if (this.configService.isFilamentManagerEnabled()) {
- this.filamentPlugin = this.injector.get(FilamentManagerService);
this.loadSpools();
}
}
private loadSpools(): void {
- this.filamentPlugin.getSpools().subscribe(
+ this.filamentPluginService.getSpools().subscribe(
(spools: Array): void => {
- this._filamentSpools = spools;
+ this.filamentSpools = spools;
},
(error: HttpErrorResponse): void => {
this.notificationService.setError("Can't load filament spools!", error.message);
},
(): void => {
- this._loading = false;
+ this.loading = false;
},
);
- this.filamentPlugin.getCurrentSpool().subscribe(
+ this.filamentPluginService.getCurrentSpool().subscribe(
(spool: FilamentSpool): void => {
- this._currentSpool = spool;
+ this.currentSpool = spool;
},
(error: HttpErrorResponse): void => {
this.notificationService.setError("Can't load active spool!", error.message);
@@ -52,23 +44,23 @@ export class FilamentManagementComponent {
);
}
- public get filamentSpools(): Array {
- return this._filamentSpools;
+ public getFilamentSpools(): Array {
+ return this.filamentSpools;
}
- public get currentSpool(): FilamentSpool {
- return this._currentSpool;
+ public getCurrentSpool(): FilamentSpool {
+ return this.currentSpool;
}
- public get loading(): boolean {
- return this._loading;
+ public getLoading(): boolean {
+ return this.loading;
}
public setSpool(spool: FilamentSpool): Promise {
return new Promise((resolve, reject) => {
- this.filamentPlugin.setSpool(spool).subscribe(
+ this.filamentPluginService.setSpool(spool).subscribe(
(): void => {
- this.filamentPlugin.getCurrentSpool().subscribe(
+ this.filamentPluginService.getCurrentSpool().subscribe(
(spoolRemote: FilamentSpool): void => {
if (spool.id === spoolRemote.id) resolve();
else {
diff --git a/src/app/services/files/files.octoprint.service.ts b/src/app/services/files/files.octoprint.service.ts
new file mode 100644
index 000000000..27b1ba5e7
--- /dev/null
+++ b/src/app/services/files/files.octoprint.service.ts
@@ -0,0 +1,178 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import _ from 'lodash-es';
+import { Observable } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+import { ConfigService } from '../../config/config.service';
+import { ConversionService } from '../../conversion.service';
+import { Directory, File, Folder } from '../../model';
+import { FileCommand, OctoprintFile, OctoprintFolder } from '../../model/octoprint';
+import { NotificationService } from '../../notification/notification.service';
+import { FilesService } from './files.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class FilesOctoprintService implements FilesService {
+ private loadedFile = false;
+
+ public constructor(
+ private configService: ConfigService,
+ private notificationService: NotificationService,
+ private http: HttpClient,
+ private conversionService: ConversionService,
+ ) {}
+
+ public getFolderContent(folderPath?: string): Observable {
+ return this.http
+ .get(
+ this.configService.getApiURL('files' + (folderPath === '/' ? '' : folderPath)),
+ this.configService.getHTTPHeaders(),
+ )
+ .pipe(
+ map(response => {
+ if (Object.prototype.hasOwnProperty.call(response, 'children')) {
+ return response['children'];
+ } else {
+ return response['files'];
+ }
+ }),
+ map((folderContent: Array) => {
+ const directory: Directory = { files: [], folders: [] };
+
+ folderContent.forEach(fileOrFolder => {
+ if (fileOrFolder.type === 'folder') {
+ directory.folders.push({
+ origin: fileOrFolder.origin,
+ path: '/' + fileOrFolder.origin + '/' + fileOrFolder.path,
+ name: fileOrFolder.name,
+ size: this.conversionService.convertByteToMegabyte(fileOrFolder.size),
+ } as Folder);
+ }
+
+ if (fileOrFolder.typePath.includes('gcode')) {
+ directory.files.push({
+ origin: fileOrFolder.origin,
+ path: '/' + fileOrFolder.origin + '/' + fileOrFolder.path,
+ name: fileOrFolder.name,
+ date: this.conversionService.convertDateToString(new Date(fileOrFolder.date * 1000)),
+ size: this.conversionService.convertByteToMegabyte(fileOrFolder.size),
+ ...(fileOrFolder.gcodeAnalysis
+ ? {
+ printTime: this.conversionService.convertSecondsToHours(
+ fileOrFolder.gcodeAnalysis.estimatedPrintTime,
+ ),
+ filamentWeight: this.conversionService.convertFilamentLengthToWeight(
+ _.sumBy(_.values(fileOrFolder.gcodeAnalysis.filament), tool => tool.length),
+ ),
+ }
+ : {}),
+ } as File);
+ }
+ });
+
+ return directory;
+ }),
+ map((directory: Directory) => {
+ if (folderPath === '/') {
+ const localCount = _.sumBy(_.concat(directory.files, directory.folders), (fileOrFolder: File & Folder) =>
+ fileOrFolder.origin === 'local' ? 1 : 0,
+ );
+ const sdCardCount = _.sumBy(_.concat(directory.files, directory.folders), (fileOrFolder: File & Folder) =>
+ fileOrFolder.origin === 'sdcard' ? 1 : 0,
+ );
+
+ if (localCount > 0 && sdCardCount > 0) {
+ directory.folders.push({
+ origin: 'local',
+ path: '/local',
+ name: 'local',
+ size: `${localCount} files`,
+ } as Folder);
+ directory.folders.push({
+ origin: 'sdcard',
+ path: '/sdcard',
+ name: 'sdcard',
+ size: `${localCount} files`,
+ } as Folder);
+ }
+ }
+
+ return directory;
+ }),
+ );
+ }
+
+ public getFile(filePath: string): Observable {
+ return this.http.get(this.configService.getApiURL('files' + filePath), this.configService.getHTTPHeaders()).pipe(
+ map(
+ (file: OctoprintFile): File => {
+ return {
+ origin: file.origin,
+ path: '/' + file.origin + '/' + file.path,
+ name: file.name,
+ date: this.conversionService.convertDateToString(new Date(file.date * 1000)),
+ size: this.conversionService.convertByteToMegabyte(file.size),
+ thumbnail: file.thumbnail ? this.configService.getApiURL(file.thumbnail, false) : 'assets/object.svg',
+ ...(file.gcodeAnalysis
+ ? {
+ printTime: this.conversionService.convertSecondsToHours(file.gcodeAnalysis.estimatedPrintTime),
+ filamentWeight: this.conversionService.convertFilamentLengthToWeight(
+ _.sumBy(_.values(file.gcodeAnalysis.filament), tool => tool.length),
+ ),
+ }
+ : {}),
+ } as File;
+ },
+ ),
+ );
+ }
+
+ public getThumbnail(filePath: string): Observable {
+ return this.http.get(this.configService.getApiURL('files' + filePath), this.configService.getHTTPHeaders()).pipe(
+ map((file: OctoprintFile): string => {
+ return file.thumbnail ? this.configService.getApiURL(file.thumbnail, false) : 'assets/object.svg';
+ }),
+ );
+ }
+
+ public loadFile(filePath: string): void {
+ const payload: FileCommand = {
+ command: 'select',
+ print: false,
+ };
+
+ this.http
+ .post(this.configService.getApiURL('files' + filePath), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't load file!", error.message)))
+ .subscribe();
+ }
+
+ public printFile(filePath: string): void {
+ const payload: FileCommand = {
+ command: 'select',
+ print: true,
+ };
+
+ this.http
+ .post(this.configService.getApiURL('files' + filePath), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't start print!", error.message)))
+ .subscribe();
+ }
+
+ public deleteFile(filePath: string): void {
+ this.http
+ .delete(this.configService.getApiURL('files' + filePath), this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't delete file!", error.message)))
+ .subscribe();
+ }
+
+ public setLoadedFile(value: boolean): void {
+ this.loadedFile = value;
+ }
+
+ public getLoadedFile(): boolean {
+ return this.loadedFile;
+ }
+}
diff --git a/src/app/services/files/files.service.ts b/src/app/services/files/files.service.ts
new file mode 100644
index 000000000..676d9f3ba
--- /dev/null
+++ b/src/app/services/files/files.service.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { Directory, File } from '../../model';
+
+@Injectable()
+export abstract class FilesService {
+ abstract getFolderContent(folderPath: string): Observable;
+
+ abstract getFile(filePath: string): Observable;
+
+ abstract getThumbnail(filePath: string): Observable;
+
+ abstract loadFile(filePath: string): void;
+
+ abstract printFile(filePath: string): void;
+
+ abstract deleteFile(filePath: string): void;
+
+ abstract setLoadedFile(value: boolean): void;
+
+ abstract getLoadedFile(): boolean;
+}
diff --git a/src/app/services/job/job.octoprint.service.ts b/src/app/services/job/job.octoprint.service.ts
new file mode 100644
index 000000000..db591879f
--- /dev/null
+++ b/src/app/services/job/job.octoprint.service.ts
@@ -0,0 +1,85 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { catchError } from 'rxjs/operators';
+
+import { ConfigService } from '../../config/config.service';
+import { JobCommand } from '../../model/octoprint';
+import { NotificationService } from '../../notification/notification.service';
+import { JobService } from './job.service';
+
+@Injectable()
+export class JobOctoprintService implements JobService {
+ public constructor(
+ private configService: ConfigService,
+ private notificationService: NotificationService,
+ private http: HttpClient,
+ ) {}
+
+ startJob(): void {
+ const payload: JobCommand = {
+ command: 'start',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('job'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't start job!", error.message)))
+ .subscribe();
+ }
+
+ pauseJob(): void {
+ const payload: JobCommand = {
+ command: 'pause',
+ action: 'pause',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('job'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't pause job!", error.message)))
+ .subscribe();
+ }
+
+ resumeJob(): void {
+ const payload: JobCommand = {
+ command: 'pause',
+ action: 'resume',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('job'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't resume job!", error.message)))
+ .subscribe();
+ }
+
+ cancelJob(): void {
+ const payload: JobCommand = {
+ command: 'cancel',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('job'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't cancel job!", error.message)))
+ .subscribe();
+ }
+
+ restartJob(): void {
+ const payload: JobCommand = {
+ command: 'restart',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('job'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't restart job!", error.message)))
+ .subscribe();
+ }
+
+ preheat(): void {
+ const payload: JobCommand = {
+ command: 'preheat',
+ };
+
+ this.http
+ .post(this.configService.getApiURL('plugin/preheat'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't preheat printer!", error.message)))
+ .subscribe();
+ }
+}
diff --git a/src/app/services/job/job.service.ts b/src/app/services/job/job.service.ts
new file mode 100644
index 000000000..3eba8008d
--- /dev/null
+++ b/src/app/services/job/job.service.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export abstract class JobService {
+ abstract startJob(): void;
+
+ abstract pauseJob(): void;
+
+ abstract resumeJob(): void;
+
+ abstract cancelJob(): void;
+
+ abstract restartJob(): void;
+
+ abstract preheat(): void;
+}
diff --git a/src/app/services/printer/printer.octoprint.service.ts b/src/app/services/printer/printer.octoprint.service.ts
new file mode 100644
index 000000000..edd051391
--- /dev/null
+++ b/src/app/services/printer/printer.octoprint.service.ts
@@ -0,0 +1,170 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+import { ConfigService } from '../../config/config.service';
+import { PrinterProfile } from '../../model';
+import {
+ DisconnectCommand,
+ ExtrudeCommand,
+ FeedrateCommand,
+ GCodeCommand,
+ JogCommand,
+ OctoprintPrinterProfiles,
+ TemperatureHeatbedCommand,
+ TemperatureHotendCommand,
+} from '../../model/octoprint';
+import { NotificationService } from '../../notification/notification.service';
+import { PrinterService } from './printer.service';
+
+@Injectable()
+export class PrinterOctoprintService implements PrinterService {
+ public constructor(
+ private configService: ConfigService,
+ private notificationService: NotificationService,
+ private http: HttpClient,
+ ) {}
+
+ public getActiveProfile(): Observable {
+ return this.http
+ .get(
+ this.configService.getApiURL('printerprofiles'),
+ this.configService.getHTTPHeaders(),
+ )
+ .pipe(
+ map(profiles => {
+ for (const [_, profile] of Object.entries(profiles.profiles)) {
+ if (profile.current) return profile;
+ }
+ }),
+ );
+ }
+
+ public executeGCode(gCode: string): void {
+ const gCodePayload: GCodeCommand = {
+ commands: gCode.split('; '),
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/command'), gCodePayload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't send GCode!", error.message)))
+ .subscribe();
+ }
+
+ public jog(x: number, y: number, z: number): void {
+ const jogPayload: JogCommand = {
+ command: 'jog',
+ x,
+ y,
+ z,
+ speed: z !== 0 ? this.configService.getZSpeed() * 60 : this.configService.getXYSpeed() * 60,
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/printhead'), jogPayload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't move Printhead!", error.message)))
+ .subscribe();
+ }
+
+ private moveExtruder(amount: number, speed: number): void {
+ const extrudePayload: ExtrudeCommand = {
+ command: 'extrude',
+ amount,
+ speed: speed * 60,
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/tool'), extrudePayload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't extrude Filament!", error.message)))
+ .subscribe();
+ }
+
+ public setTemperatureHotend(temperature: number): void {
+ const temperatureHotendCommand: TemperatureHotendCommand = {
+ command: 'target',
+ targets: {
+ tool0: temperature,
+ },
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/tool'), temperatureHotendCommand, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't set Hotend Temperature!", error.message)))
+ .subscribe();
+ }
+
+ public setTemperatureBed(temperature: number): void {
+ const temperatureHeatbedCommand: TemperatureHeatbedCommand = {
+ command: 'target',
+ target: temperature,
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/bed'), temperatureHeatbedCommand, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't set Bed Temperature!", error.message)))
+ .subscribe();
+ }
+
+ public setFeedrate(feedrate: number): void {
+ if (feedrate === 100) {
+ return;
+ }
+ const feedrateCommand: FeedrateCommand = {
+ command: 'feedrate',
+ factor: feedrate,
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/printhead'), feedrateCommand, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't set Feedrate!", error.message)))
+ .subscribe();
+ }
+
+ public setFlowrate(flowrate: number): void {
+ if (flowrate === 100) {
+ return;
+ }
+ const flowrateCommand: FeedrateCommand = {
+ command: 'flowrate',
+ factor: flowrate,
+ };
+ this.http
+ .post(this.configService.getApiURL('printer/tool'), flowrateCommand, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't set Flowrate!", error.message)))
+ .subscribe();
+ }
+
+ public disconnectPrinter(): void {
+ const disconnectPayload: DisconnectCommand = {
+ command: 'disconnect',
+ };
+ this.http
+ .post(this.configService.getApiURL('connection'), disconnectPayload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't disconnect Printer!", error.message)))
+ .subscribe();
+ }
+
+ public extrude(amount: number, speed: number): void {
+ let multiplier = 1;
+ let toBeExtruded: number;
+ if (amount < 0) {
+ multiplier = -1;
+ toBeExtruded = amount * -1;
+ } else {
+ toBeExtruded = amount;
+ }
+
+ while (toBeExtruded > 0) {
+ if (toBeExtruded >= 100) {
+ toBeExtruded -= 100;
+ this.moveExtruder(100 * multiplier, speed);
+ } else {
+ this.moveExtruder(toBeExtruded * multiplier, speed);
+ toBeExtruded = 0;
+ }
+ }
+ }
+
+ public emergencyStop(): void {
+ this.executeGCode('M410');
+ }
+
+ public setFanSpeed(percentage: number): void {
+ this.executeGCode('M106 S' + Math.round((percentage / 100) * 255));
+ }
+}
diff --git a/src/app/services/printer/printer.service.ts b/src/app/services/printer/printer.service.ts
new file mode 100644
index 000000000..386a6aa0b
--- /dev/null
+++ b/src/app/services/printer/printer.service.ts
@@ -0,0 +1,29 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { PrinterProfile } from '../../model';
+
+@Injectable()
+export abstract class PrinterService {
+ abstract getActiveProfile(): Observable;
+
+ abstract executeGCode(gCode: string): void;
+
+ abstract jog(x: number, y: number, z: number): void;
+
+ abstract extrude(amount: number, speed: number): void;
+
+ abstract setTemperatureHotend(temperature: number): void;
+
+ abstract setTemperatureBed(temperature: number): void;
+
+ abstract setFanSpeed(percentage: number): void;
+
+ abstract setFeedrate(feedrate: number): void;
+
+ abstract setFlowrate(flowrate: number): void;
+
+ abstract disconnectPrinter(): void;
+
+ abstract emergencyStop(): void;
+}
diff --git a/src/app/services/socket/socket.octoprint.service.ts b/src/app/services/socket/socket.octoprint.service.ts
new file mode 100644
index 000000000..40342a7ab
--- /dev/null
+++ b/src/app/services/socket/socket.octoprint.service.ts
@@ -0,0 +1,309 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import _ from 'lodash-es';
+import { Observable, ReplaySubject, Subject } from 'rxjs';
+import { pluck, startWith } from 'rxjs/operators';
+import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
+
+import { ConfigService } from '../../config/config.service';
+import { ConversionService } from '../../conversion.service';
+import { JobStatus, PrinterEvent, PrinterState, PrinterStatus, SocketAuth } from '../../model';
+import {
+ DisplayLayerProgressData,
+ OctoprintFilament,
+ OctoprintPluginMessage,
+ OctoprintSocketCurrent,
+ OctoprintSocketEvent,
+} from '../../model/octoprint';
+import { SystemService } from '../system/system.service';
+import { SocketService } from './socket.service';
+
+@Injectable()
+export class OctoPrintSocketService implements SocketService {
+ private fastInterval = 0;
+ private socket: WebSocketSubject;
+
+ private printerStatusSubject: Subject;
+ private jobStatusSubject: Subject;
+ private eventSubject: Subject;
+
+ private printerStatus: PrinterStatus;
+ private jobStatus: JobStatus;
+ private lastState: PrinterEvent;
+
+ public constructor(
+ private configService: ConfigService,
+ private systemService: SystemService,
+ private conversionService: ConversionService,
+ private http: HttpClient,
+ ) {
+ this.printerStatusSubject = new ReplaySubject();
+ this.jobStatusSubject = new ReplaySubject();
+ this.eventSubject = new ReplaySubject();
+ }
+
+ //==== SETUP & AUTH ====//
+
+ public connect(): Promise {
+ this.initPrinterStatus();
+ this.initJobStatus();
+ this.lastState = PrinterEvent.UNKNOWN;
+
+ return new Promise(resolve => {
+ this.tryConnect(resolve);
+ });
+ }
+
+ private initPrinterStatus(): void {
+ this.printerStatus = {
+ status: PrinterState.connecting,
+ bed: {
+ current: 0,
+ set: 0,
+ unit: '°C',
+ },
+ tool0: {
+ current: 0,
+ set: 0,
+ unit: '°C',
+ },
+ fanSpeed: this.configService.isDisplayLayerProgressEnabled() ? 0 : -1,
+ } as PrinterStatus;
+ }
+
+ private initJobStatus(): void {
+ this.jobStatus = {
+ file: null,
+ fullPath: null,
+ progress: 0,
+ zHeight: this.configService.isDisplayLayerProgressEnabled() ? { current: 0, total: -1 } : 0,
+ filamentAmount: 0,
+ timePrinted: null,
+ timeLeft: {
+ value: '---',
+ unit: null,
+ },
+ estimatedPrintTime: null,
+ estimatedEndTime: null,
+ } as JobStatus;
+ }
+
+ private tryConnect(resolve: () => void): void {
+ this.systemService.getSessionKey().subscribe(
+ socketAuth => {
+ this.connectSocket();
+ this.setupSocket(resolve);
+ this.authenticateSocket(socketAuth);
+ },
+ () => {
+ setTimeout(this.tryConnect.bind(this), this.fastInterval < 6 ? 5000 : 15000, resolve);
+ this.fastInterval += 1;
+ },
+ );
+ }
+
+ private connectSocket() {
+ const url = `${this.configService.getApiURL('sockjs/websocket', false).replace(/^http/, 'ws')}`;
+ if (!this.socket) {
+ this.socket = webSocket(url);
+ }
+ }
+
+ private authenticateSocket(socketAuth: SocketAuth) {
+ const payload = {
+ auth: `${socketAuth.user}:${socketAuth.session}`,
+ };
+ this.socket.next(payload);
+ }
+
+ private setupSocket(resolve: () => void) {
+ this.socket.subscribe(message => {
+ if (Object.hasOwnProperty.bind(message)('current')) {
+ this.extractPrinterStatus(message as OctoprintSocketCurrent);
+ this.extractJobStatus(message as OctoprintSocketCurrent);
+ } else if (Object.hasOwnProperty.bind(message)('event')) {
+ this.extractPrinterEvent(message as OctoprintSocketEvent);
+ } else if (Object.hasOwnProperty.bind(message)('plugin')) {
+ const pluginMessage = message as OctoprintPluginMessage;
+ if (
+ pluginMessage.plugin.plugin === 'DisplayLayerProgress-websocket-payload' &&
+ this.configService.isDisplayLayerProgressEnabled()
+ ) {
+ this.extractFanSpeed(pluginMessage.plugin.data as DisplayLayerProgressData);
+ this.extractLayerHeight(pluginMessage.plugin.data as DisplayLayerProgressData);
+ }
+ } else if (Object.hasOwnProperty.bind(message)('reauth')) {
+ this.systemService.getSessionKey().subscribe(socketAuth => this.authenticateSocket(socketAuth));
+ } else if (Object.hasOwnProperty.bind(message)('connected')) {
+ resolve();
+ this.checkPrinterConnection();
+ }
+ });
+ }
+
+ private checkPrinterConnection() {
+ this.http
+ .get(this.configService.getApiURL('connection'), this.configService.getHTTPHeaders())
+ .pipe(pluck('current'), pluck('state'))
+ .subscribe((state: string) => {
+ if (state === 'Closed' || state === 'Error') {
+ this.eventSubject.next(PrinterEvent.CLOSED);
+ }
+ });
+ }
+
+ //==== Printer Status ====//
+
+ public extractPrinterStatus(message: OctoprintSocketCurrent): void {
+ if (message.current.temps[0]) {
+ this.printerStatus.bed = {
+ current: Math.round(message?.current?.temps[0]?.bed?.actual),
+ set: Math.round(message?.current?.temps[0]?.bed?.target),
+ unit: '°C',
+ };
+ this.printerStatus.tool0 = {
+ current: Math.round(message?.current?.temps[0]?.tool0?.actual),
+ set: Math.round(message?.current?.temps[0]?.tool0?.target),
+ unit: '°C',
+ };
+ }
+ this.printerStatus.status = PrinterState[message.current.state.text.toLowerCase()];
+
+ if (this.printerStatus.status === PrinterState.printing && this.lastState === PrinterEvent.UNKNOWN) {
+ this.extractPrinterEvent({
+ event: {
+ type: 'PrintStarted',
+ payload: null,
+ },
+ } as OctoprintSocketEvent);
+ } else if (this.printerStatus.status === PrinterState.paused && this.lastState === PrinterEvent.UNKNOWN) {
+ this.extractPrinterEvent({
+ event: {
+ type: 'PrintPaused',
+ payload: null,
+ },
+ } as OctoprintSocketEvent);
+ }
+
+ this.printerStatusSubject.next(this.printerStatus);
+ }
+
+ public extractFanSpeed(message: DisplayLayerProgressData): void {
+ this.printerStatus.fanSpeed =
+ message.fanspeed === 'Off' ? 0 : message.fanspeed === '-' ? 0 : Number(message.fanspeed.replace('%', '').trim());
+ }
+
+ //==== Job Status ====//
+
+ public extractJobStatus(message: OctoprintSocketCurrent): void {
+ const file = message?.current?.job?.file?.display?.replace('.gcode', '').replace('.ufp', '');
+ if (this.jobStatus.file !== file) {
+ this.initJobStatus();
+ }
+
+ this.jobStatus.file = file;
+ this.jobStatus.fullPath = '/' + message?.current?.job?.file?.origin + '/' + message?.current?.job?.file?.path;
+ this.jobStatus.progress = Math.round(message?.current?.progress?.completion);
+ this.jobStatus.timePrinted = {
+ value: this.conversionService.convertSecondsToHours(message.current.progress.printTime),
+ unit: 'h',
+ };
+
+ if (message.current.job.filament) {
+ this.jobStatus.filamentAmount = this.getTotalFilamentWeight(message.current.job.filament);
+ }
+
+ if (message.current.progress.printTimeLeft) {
+ this.jobStatus.timeLeft = {
+ value: this.conversionService.convertSecondsToHours(message.current.progress.printTimeLeft),
+ unit: 'h',
+ };
+ this.jobStatus.estimatedEndTime = this.calculateEndTime(message.current.progress.printTimeLeft);
+ }
+
+ if (message.current.job.estimatedPrintTime) {
+ this.jobStatus.estimatedPrintTime = {
+ value: this.conversionService.convertSecondsToHours(message.current.job.estimatedPrintTime),
+ unit: 'h',
+ };
+ }
+
+ if (!this.configService.isDisplayLayerProgressEnabled() && message.current.currentZ) {
+ this.jobStatus.zHeight = message.current.currentZ;
+ }
+
+ this.jobStatusSubject.next(this.jobStatus);
+ }
+
+ private getTotalFilamentWeight(filament: OctoprintFilament) {
+ let filamentLength = 0;
+ _.forEach(filament, (tool): void => {
+ filamentLength += tool.length;
+ });
+ return this.conversionService.convertFilamentLengthToWeight(filamentLength);
+ }
+
+ private calculateEndTime(printTimeLeft: number) {
+ const date = new Date();
+ date.setSeconds(date.getSeconds() + printTimeLeft);
+ return `${('0' + date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}`;
+ }
+
+ public extractLayerHeight(message: DisplayLayerProgressData): void {
+ this.jobStatus.zHeight = {
+ current: message.currentLayer === '-' ? 0 : Number(message.currentLayer),
+ total: message.totalLayer === '-' ? 0 : Number(message.totalLayer),
+ };
+ }
+
+ //==== Event ====//
+
+ public extractPrinterEvent(state: OctoprintSocketEvent): void {
+ let newState: PrinterEvent;
+
+ switch (state.event.type) {
+ case 'PrintStarted':
+ case 'PrintResumed':
+ newState = PrinterEvent.PRINTING;
+ break;
+ case 'PrintPaused':
+ newState = PrinterEvent.PAUSED;
+ break;
+ case 'PrintFailed':
+ case 'PrintDone':
+ case 'PrintCancelled':
+ newState = PrinterEvent.IDLE;
+ break;
+ case 'Connected':
+ newState = PrinterEvent.CONNECTED;
+ break;
+ case 'Disconnected':
+ newState = PrinterEvent.CLOSED;
+ break;
+ case 'Error':
+ newState = PrinterEvent.CLOSED;
+ break;
+ default:
+ break;
+ }
+
+ if (newState !== undefined) {
+ this.lastState = newState;
+ this.eventSubject.next(newState);
+ }
+ }
+
+ //==== Subscribables ====//
+
+ public getPrinterStatusSubscribable(): Observable {
+ return this.printerStatusSubject.pipe(startWith(this.printerStatus));
+ }
+
+ public getJobStatusSubscribable(): Observable {
+ return this.jobStatusSubject.pipe(startWith(this.jobStatus));
+ }
+
+ public getEventSubscribable(): Observable {
+ return this.eventSubject;
+ }
+}
diff --git a/src/app/services/socket/socket.service.ts b/src/app/services/socket/socket.service.ts
new file mode 100644
index 000000000..259fa5ceb
--- /dev/null
+++ b/src/app/services/socket/socket.service.ts
@@ -0,0 +1,15 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { JobStatus, PrinterEvent, PrinterStatus } from '../../model';
+
+@Injectable()
+export abstract class SocketService {
+ abstract connect(): Promise;
+
+ abstract getPrinterStatusSubscribable(): Observable;
+
+ abstract getJobStatusSubscribable(): Observable;
+
+ abstract getEventSubscribable(): Observable;
+}
diff --git a/src/app/services/system/system.octoprint.service.ts b/src/app/services/system/system.octoprint.service.ts
new file mode 100644
index 000000000..6e6d68b8a
--- /dev/null
+++ b/src/app/services/system/system.octoprint.service.ts
@@ -0,0 +1,56 @@
+/* eslint-disable camelcase */
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+import { ConfigService } from '../../config/config.service';
+import { SocketAuth } from '../../model';
+import { ConnectCommand, OctoprintLogin } from '../../model/octoprint';
+import { NotificationService } from '../../notification/notification.service';
+import { SystemService } from './system.service';
+
+@Injectable()
+export class SystemOctoprintService implements SystemService {
+ constructor(
+ private configService: ConfigService,
+ private notificationService: NotificationService,
+ private http: HttpClient,
+ ) {}
+
+ public getSessionKey(): Observable {
+ return this.http
+ .post(
+ this.configService.getApiURL('login'),
+ { passive: true },
+ this.configService.getHTTPHeaders(),
+ )
+ .pipe(
+ map(octoprintLogin => {
+ return {
+ user: octoprintLogin.name,
+ session: octoprintLogin.session,
+ } as SocketAuth;
+ }),
+ );
+ }
+
+ public sendCommand(command: string): void {
+ this.http
+ .post(this.configService.getApiURL(`system/commands/core/${command}`), null, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError(`Can't execute ${command} command!`, error.message)))
+ .subscribe();
+ }
+
+ public connectPrinter(): void {
+ const payload: ConnectCommand = {
+ command: 'connect',
+ save: false,
+ };
+
+ this.http
+ .post(this.configService.getApiURL('connection'), payload, this.configService.getHTTPHeaders())
+ .pipe(catchError(error => this.notificationService.setError("Can't connect to printer!", error.message)))
+ .subscribe();
+ }
+}
diff --git a/src/app/services/system/system.service.ts b/src/app/services/system/system.service.ts
new file mode 100644
index 000000000..c449cf24e
--- /dev/null
+++ b/src/app/services/system/system.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { SocketAuth } from '../../model';
+
+@Injectable()
+export abstract class SystemService {
+ abstract getSessionKey(): Observable;
+
+ abstract sendCommand(command: string): void;
+
+ abstract connectPrinter(): void;
+}
diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html
index b59e6156e..22a7e2939 100644
--- a/src/app/settings/settings.component.html
+++ b/src/app/settings/settings.component.html
@@ -310,15 +310,21 @@