Skip to content

Commit

Permalink
Add fan control from home screen
Browse files Browse the repository at this point in the history
  • Loading branch information
alufers committed Oct 14, 2020
1 parent 3d206d2 commit 33b8eb0
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 23 deletions.
20 changes: 18 additions & 2 deletions core/printer_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package core
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -160,7 +162,7 @@ func (pm *PrinterService) serialWriterGoroutine() {
select {
case c := <-pm.consoleWriteSem:
log.Print("Serial writer: serialConsoleWrite", c)
err := pm.printerLink.Write([]byte(c))
err := pm.sendLineAndSniff(c)
if err != nil {
log.Printf("error while writing from serial console to serial: %v", err)
}
Expand All @@ -180,7 +182,7 @@ func (pm *PrinterService) handleJob(job *PrintJobInternal) {
}
var sendAndMaybeResend func(string)
sendAndMaybeResend = func(l string) {
pm.printerLink.Write([]byte(l))
pm.sendLineAndSniff(l)
select {
case <-pm.okSem:
case num := <-pm.resendSem:
Expand All @@ -206,6 +208,20 @@ func (pm *PrinterService) handleJob(job *PrintJobInternal) {
}
}

func (pm *PrinterService) sendLineAndSniff(line string) error {
fanSpeedRegex := regexp.MustCompile("(L[0-9\\ ]+)?M106 S([0-9]*)")
matches := fanSpeedRegex.FindSubmatch([]byte(line))
log.Printf("%v, len(matches) = %v", line, len(matches))
if len(matches) == 3 {
log.Printf("FOUDN FANCTL")
numValue, err := strconv.Atoi(string(matches[2]))
if err == nil {
pm.app.TrackedValuesService.TrackedValues["fanSpeed"].UpdateValue(float64(numValue))
}
}
return pm.printerLink.Write([]byte(line))
}

/*
parseLine processes lines incoming from the printer's firmware and dispatches them.
*/
Expand Down
10 changes: 10 additions & 0 deletions core/tracked_values_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ func NewTrackedValuesService(app *App) *TrackedValuesService {
MaxHistoryLength: 300,
History: []interface{}{},
}),
"fanSpeed": NewTrackedValueInternal(&TrackedValue{
PlotColor: "#a9a9a9",
PlotDash: []float64{},
Name: "fanSpeed",
Unit: "",
DisplayType: TrackedValueDisplayTypePlot,
Value: 0,
MaxHistoryLength: 300,
History: []interface{}{},
}),
"targetHotbedTemperature": NewTrackedValueInternal(&TrackedValue{
PlotColor: "#b86bff",
PlotDash: []float64{5, 5},
Expand Down
5 changes: 5 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"apexcharts": "^3.17.1",
"apollo-upload-client": "^12.1.0",
"bulma": "^0.8.1",
"bulma-extensions": "^6.2.7",
"core-js": "^3.6.4",
"fuse.js": "^3.6.1",
"graphql": "^14.6.0",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Component from "vue-class-component";
import Navbar from "./components/Navbar.vue";
import Alerts from "./components/Alerts.vue";
import "bulma/css/bulma.css";
import "bulma-extensions/dist/css/bulma-extensions.min.css";
import "@fortawesome/fontawesome-free/css/all.css";
@Component({
Expand Down
249 changes: 249 additions & 0 deletions frontend/src/components/FanSpeedControl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
<!--
HeaterControl is the UI element which shows the current temperature of a heater and allows it to pe controller by the user.
It supports reading temperature presets from the settings as well as entering custom temps.
-->
<template>
<LoaderGuard>
<div class="field">
<label class="label"> {{ name }}</label>

<div class="print-stat">
<div class="controls tags has-addons are-medium">
<span class="tag tag-label">
<span
class="orb"
:style="{
background: trackedValueMeta && trackedValueMeta.plotColor,
}"
></span>
Actual</span
>
<span class="tag temp-value">{{ fanSpeed.toFixed(0) }}</span>
</div>

<div class="controls tags has-addons are-medium">
<span class="tag tag-label">Target</span>
<span class="tag" :class="{ 'is-warning': fanSpeed <= 10 }">
<div class="dropdown" :class="{ 'is-active': showPresetsDropdown }">
<div class="dropdown-trigger">
<input
ref="temperatureInput"
class="slider"
type="range"
min="0"
max="255"
step="1"
v-model.number="targetEdit"
@keyup.enter="setTarget"
@focus="showPresetsDropdown = true"
@blur="hidePresetsDropdown"
/>
<span
class="icon is-small"
@click="$refs.temperatureInput.focus()"
>
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a
href="#"
class="dropdown-item"
v-for="(tp, i) in temperaturePresets"
:key="i"
@click.prevent="selectPreset(i)"
>{{ tp.name }} ({{ tp.value }} °C)</a
>
</div>
</div>
</div>
</span>
</div>
<div class="field has-addons">
<p class="control">
<a
class="button"
@click="setTarget"
:class="{
'is-primary': targetEdit !== fanSpeed,
...isLoadingClass,
}"
>SET</a
>
</p>
<p class="control">
<a class="button" @click="heaterOff" :class="isLoadingClass">OFF</a>
</p>
</div>
</div>
</div>
</LoaderGuard>
</template>
<script lang="ts">
import Vue from "vue";
import Component, { mixins } from "vue-class-component";
import { Prop, Watch } from "vue-property-decorator";
import TrackedValueSubscription from "../decorators/TrackedValueSubscription";
import LoadableMixin from "../LoadableMixin";
import { sendGcode } from "../../../graphql/queries/sendGcode.graphql";
import {
SendGcodeMutation,
SendGcodeMutationVariables,
GetTemperaturePresetsQuery,
TrackedValue,
} from "../graphql-models-gen";
import ApolloQuery from "../decorators/ApolloQuery";
import { getTemperaturePresets } from "../../../graphql/queries/getTemperaturePresets.graphql";
import { setTimeout } from "timers";
import { Presets } from "../types/settings";
import TrackedValueMeta from "../decorators/TrackedValueMeta";
import LoaderGuard from "./LoaderGuard.vue";

@Component({
components: {
LoaderGuard,
},
})
export default class HeaterControl extends mixins(LoadableMixin) {
@Prop({ type: String })
name!: string;
@Prop({ type: String })
fanSpeedTrackedValue!: string;

@Prop({ type: String })
temperatureGcode!: string;
@Prop({ type: String })
temperaturePresetKey: keyof Presets;

temperaturePresetsRaw: Presets[] = null;

showPresetsDropdown = false;

@TrackedValueSubscription(function(this: HeaterControl) {
return this.fanSpeedTrackedValue;
})
fanSpeed = 0;

@TrackedValueMeta(function(this: HeaterControl) {
return this.fanSpeedTrackedValue;
})
trackedValueMeta: TrackedValue = null;

targetEdit = 0;

created() {
this.withLoader(async () => {
let { data } = await this.$apollo.query<GetTemperaturePresetsQuery>({
query: getTemperaturePresets,
});
this.temperaturePresetsRaw = data.settings;
});
}

heaterOff() {
//this.connection.sendMessage("sendGCODE", { data: `${this.temperatureGcode} S0` });
this.withLoader(async () => {
this.targetEdit = 0;
await this.$apollo.mutate<SendGcodeMutation>({
mutation: sendGcode,
variables: <SendGcodeMutationVariables>{
cmd: `${this.temperatureGcode} S0`,
},
});
});
}

setTarget() {
this.withLoader(async () => {
await this.$apollo.mutate<SendGcodeMutation>({
mutation: sendGcode,
variables: <SendGcodeMutationVariables>{
cmd: `${this.temperatureGcode} S${this.targetEdit}`,
},
});
});
}

@Watch("target")
targetWatch(newTarget: number, oldTarget: number) {
if (this.targetEdit === oldTarget) {
this.targetEdit = newTarget;
}
}

get temperaturePresets() {
if (!this.temperaturePresetsRaw) return [];
return this.temperaturePresetsRaw.map((tp) => ({
name: tp.name,
value: <number>tp[this.temperaturePresetKey],
}));
}

selectPreset(index: number) {
this.targetEdit = this.temperaturePresets[index].value;
this.showPresetsDropdown = false;
}

hidePresetsDropdown() {
setTimeout(() => (this.showPresetsDropdown = false), 200); // delay before hiding the dropdown so that the browser has time to register the click event
}
}
</script>

<style scoped>
.slider {
margin-top: 20px !important;
width: 210px;
}
.is-danger .temperature-input {
color: white;
}

.dropdown-trigger {
display: flex;
flex-direction: row;
align-items: center;
}

.print-stat {
height: 35px;
display: flex;
align-items: flex-start;
align-content: center;
justify-content: flex-start;
}

.print-stat .value {
font-weight: bold;
}

.controls {
padding-top: 2px;
margin-right: 8px;
height: 30px;
}

.temp-value {
width: 100px;
}

.tag-label {
color: #838383;
}

.label {
margin-top: 10px;
}

.preset-field {
margin-right: 8px;
}

.orb {
width: 10px;
height: 10px;
border-radius: 1000px;
margin-right: 5px;
}
</style>
3 changes: 2 additions & 1 deletion frontend/src/components/TemperatureDisplay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export default class TemperatureDisplay extends mixins(LoadableMixin) {
"hotendTemperature",
"targetHotendTemperature",
"hotbedTemperature",
"targetHotbedTemperature"
"targetHotbedTemperature",
"fanSpeed"
];
chartOptions: ApexOptions = {
chart: {
Expand Down
Loading

0 comments on commit 33b8eb0

Please sign in to comment.