Skip to content

Commit

Permalink
feat: moonraker power plugin support
Browse files Browse the repository at this point in the history
  • Loading branch information
cadriel committed Oct 21, 2020
1 parent 18e89a2 commit be67ba0
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 15 deletions.
33 changes: 23 additions & 10 deletions src/components/cards/ToolsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,48 @@
fixed-tabs
background-color="quaternary"
>
<v-tab :key="0">
<v-tab :key="'macros'">
<v-icon left>{{ icons.fileCode }}</v-icon>
Macros
</v-tab>
<v-tab :key="1">
<v-tab :key="'power'" v-if="gpioPowerPluginEnabled">
<v-icon left>{{ icons.power }}</v-icon>
Power
</v-tab>
<v-tab :key="'syscommands'">
<v-icon left>{{ icons.tools }}</v-icon>
Sys Commands
</v-tab>
<v-tab :key="2">
<v-tab :key="'jobs'">
<v-icon left>{{ icons.files }}</v-icon>
Jobs
</v-tab>
<v-tab :key="3">
<v-tab :key="'console'">
<v-icon left>{{ icons.console }}</v-icon>
Console
</v-tab>
</v-tabs>
<v-divider></v-divider>

<v-tabs-items v-model="activeTab" class="mb-auto rounded">
<v-tab-item :key="0" class="tertiary rounded">
<v-tab-item :key="'macros'" class="tertiary rounded">
<macros-widget></macros-widget>
</v-tab-item>
<v-tab-item :key="1" class="tertiary rounded">
<v-tab-item :key="'power'" class="tertiary rounded" v-if="gpioPowerPluginEnabled">
<power-control-widget></power-control-widget>
</v-tab-item>
<v-tab-item :key="'syscommands'" class="tertiary rounded">
<system-commands-widget></system-commands-widget>
</v-tab-item>
<v-tab-item :key="2" class="tertiary rounded max-height">
<v-tab-item :key="'jobs'" class="tertiary rounded max-height">
<file-system-widget
root="gcodes"
accept=".gcode"
:show-title="false"
:show-meta-data="false"
></file-system-widget>
</v-tab-item>
<v-tab-item :key="3" class="tertiary rounded max-height">
<v-tab-item :key="'console'" class="tertiary rounded max-height">
<console-widget></console-widget>
</v-tab-item>
</v-tabs-items>
Expand All @@ -55,17 +62,23 @@ import MacrosWidget from '@/components/widgets/MacrosWidget.vue'
import FileSystemWidget from '@/components/widgets/filesystem/FileSystemWidget.vue'
import SystemCommandsWidget from '@/components/widgets/SystemCommandsWidget.vue'
import ConsoleWidget from '@/components/widgets/ConsoleWidget.vue'
import PowerControlWidget from '@/components/widgets/PowerControlWidget.vue'
@Component({
components: {
MacrosWidget,
FileSystemWidget,
SystemCommandsWidget,
ConsoleWidget
ConsoleWidget,
PowerControlWidget
}
})
export default class ToolsCard extends Mixins(UtilsMixin) {
activeTab = 0
activeTab = 'macros'
get gpioPowerPluginEnabled () {
return (this.$store.state.socket.plugins.includes('power'))
}
}
</script>

Expand Down
34 changes: 34 additions & 0 deletions src/components/widgets/PowerControlWidget.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<v-card-text class="d-flex flex-wrap align-center justify-start pt-5">
<v-btn
v-for="(device, index) in devices"
:key="index"
@click="toggleDevice(device, `${waits.onGpio}${device.id}`)"
:loading="hasWait(`${waits.onGpio}${device.id}`)"
color="secondary"
class="me-2 mb-2">Toggle {{ device.name }} {{ (device.state === 1) ? 'Off' : 'On' }}</v-btn>
</v-card-text>
</template>

<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator'
import UtilsMixin from '@/mixins/utils'
import { Waits } from '@/globals'
import { SocketActions } from '@/socketActions'
import { Device } from '@/store/gpio/types'
@Component({})
export default class PowerControlWidget extends Mixins(UtilsMixin) {
waits = Waits
get devices () {
return this.$store.state.gpio.devices
}
toggleDevice (device: Device, wait?: string) {
const state = (device.state === 1) ? 0 : 1
if (wait) this.$store.dispatch('socket/addWait', wait)
SocketActions.machineGpioPowerToggle(device.id, state, wait)
}
}
</script>
5 changes: 4 additions & 1 deletion src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ import {
mdiDeleteAlertOutline,
mdiCogs,
mdiContentSaveOutline,
mdiAlert
mdiAlert,
mdiPowerPlug
} from '@mdi/js'

/**
Expand All @@ -70,6 +71,7 @@ export const Globals = Object.freeze({
})

export const Icons = Object.freeze({
power: mdiPowerPlug,
home: mdiHome,
close: mdiClose,
refresh: mdiRefresh,
Expand Down Expand Up @@ -128,6 +130,7 @@ export const Icons = Object.freeze({
})

export const Waits = Object.freeze({
onGpio: 'onGpio',
onHomeAll: 'onHomeAll',
onHomeXY: 'onHomeXY',
onHomeZ: 'onHomeZ',
Expand Down
30 changes: 30 additions & 0 deletions src/socketActions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Vue from 'vue'
import { Waits } from '@/globals'
import { Device } from './store/gpio/types'

export const SocketActions = {
async printerInfo () {
Expand Down Expand Up @@ -30,6 +31,35 @@ export const SocketActions = {
)
},

async machineGpioPowerDevices () {
Vue.$socket.emit(
'machine.gpio_power.devices', {
action: 'gpio/init'
}
)
},

async machineGpioPowerStatus () {
Vue.$socket.emit(
'machine.gpio_power.status', {
action: 'gpio/onStatus'
}
)
},

async machineGpioPowerToggle (id: string, state: number, wait?: string) {
const emit = (state === 1)
? 'machine.gpio_power.on'
: 'machine.gpio_power.off'
Vue.$socket.emit(
emit, {
action: 'gpio/onToggle',
params: { [id]: null },
wait
}
)
},

async printerQueryEndstops () {
Vue.$socket.emit(
'printer.query_endstops.status', {
Expand Down
40 changes: 40 additions & 0 deletions src/store/gpio/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ActionTree } from 'vuex'
import { GpioState } from './types'
import { RootState } from '../types'
import { SocketActions } from '@/socketActions'

export const actions: ActionTree<GpioState, RootState> = {

/**
* Inits the list of available devices.
*/
async init ({ commit }, payload) {
if (
payload.devices &&
payload.devices.length > 0
) {
commit('onDevices', payload)
SocketActions.machineGpioPowerStatus()
}
},

/**
* Loads the current status of each power device defined.
*/
async onStatus ({ commit }, payload) {
commit('onStatus', payload)
},

/**
* On a toggling a power device.
*/
async onToggle ({ commit, dispatch }, payload) {
dispatch('onStatus', payload)

// Remove a wait if defined.
if (payload.__request__ && payload.__request__.wait && payload.__request__.wait.length) {
commit('socket/removeWait', payload.__request__.wait, { root: true })
}
}

}
6 changes: 6 additions & 0 deletions src/store/gpio/getters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { GetterTree } from 'vuex'
import { GpioState } from './types'
import { RootState } from '../types'

export const getters: GetterTree<GpioState, RootState> = {
}
22 changes: 22 additions & 0 deletions src/store/gpio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/camelcase */

import { Module } from 'vuex'
import { getters } from './getters'
import { actions } from './actions'
import { mutations } from './mutations'
import { GpioState } from './types'
import { RootState } from '../types'

export const state: GpioState = {
devices: []
}

const namespaced = true

export const gpio: Module<GpioState, RootState> = {
namespaced,
state,
getters,
actions,
mutations
}
19 changes: 19 additions & 0 deletions src/store/gpio/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Vue from 'vue'
import { MutationTree } from 'vuex'
import { GpioState } from './types'

export const mutations: MutationTree<GpioState> = {
onDevices (state, payload) {
state.devices = payload.devices
},

onStatus (state, payload) {
for (const key in payload) {
const i = state.devices.findIndex(device => device.id === key)
if (i >= 0) {
// Vue.set(state.devices, i, payload[key] === 'off' ? 0 : 1)
Vue.set(state.devices[i], 'state', payload[key] === 'off' ? 0 : 1)
}
}
}
}
9 changes: 9 additions & 0 deletions src/store/gpio/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface GpioState {
devices: Device[];
}

export interface Device {
id: string;
name: string;
state?: 1 | 0;
}
4 changes: 3 additions & 1 deletion src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Vuex from 'vuex'
import { socket } from './socket'
import { files } from './files'
import { config } from './config'
import { gpio } from './gpio'
import { RootState } from './types'
import { FileConfig } from './config/types'

Expand All @@ -17,7 +18,8 @@ export default new Vuex.Store<RootState>({
modules: {
config,
socket,
files
files,
gpio
},
mutations: {
setVersion (state, payload) {
Expand Down
23 changes: 23 additions & 0 deletions src/store/socket/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,34 @@ export const actions: ActionTree<SocketState, RootState> = {
}, Globals.KLIPPY_RETRY_DELAY)
} else {
// We're good, move on. Start by loading the temperature and console history.
SocketActions.serverInfo()
SocketActions.serverTemperatureStore()
SocketActions.serverGcodeStore()
}
},

async onServerInfo ({ commit }, payload) {
// This payload should return a list of enabled plugins.
const plugins = [
'power'
]
if (
payload.plugins &&
payload.plugins.length > 0
) {
commit('onPlugins', payload.plugins)
plugins.forEach((plugin) => {
if (payload.plugins.includes(plugin)) {
switch (plugin) {
case 'power':
SocketActions.machineGpioPowerDevices()
break
}
}
})
}
},

/**
* Once a gcode script has run, the
* socket notifies us of the result of
Expand Down
1 change: 1 addition & 0 deletions src/store/socket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const getDefaultState = (): SocketState => {
console: [],
chart: [],
macros: {},
plugins: [],
temperature_fans: [],
heater_fans: [],
heater_generics: [],
Expand Down
4 changes: 3 additions & 1 deletion src/store/socket/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Globals, chartConfiguration } from '@/globals'

export const mutations: MutationTree<SocketState> = {
resetState (state) {
console.log('resetting state...')
const newState = getDefaultState()
Object.keys(newState).forEach((key: string) => {
// Some properties we may not want to reset.
Expand Down Expand Up @@ -73,6 +72,9 @@ export const mutations: MutationTree<SocketState> = {
onPrinterInfo (state, payload) {
state.printer.info = payload
},
onPlugins (state, payload) {
payload.forEach((plugin: string) => state.plugins.push(plugin))
},
onPrinterObjectsList (state, payload) {
if (!state.printer.objects.includes(payload)) {
state.printer.objects.push(payload)
Expand Down
5 changes: 3 additions & 2 deletions src/store/socket/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ export interface SocketState {
connecting: boolean; // if the socket is down, are we still attempting to reconnect?
acceptingNotifications: boolean;
error: SocketError | null;
waits: string[]; // list of things that we might be waiting on, like a gcode script to finish.
waits: string[]; // list of things that we might be waiting on, like a gcode script to finish
endstops: EndStops;
macros: Macros;
console: string[]; // console stream.
plugins: string[]; // active plugins (gpio_power)
console: string[]; // console stream
chart: ChartDataSet[]; // chart data
temperature_fans: string[]; // maintains a list of available temp fans
temperature_sensors: string[]; // maintains a list of available sensors
Expand Down
2 changes: 2 additions & 0 deletions todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
- Performance / memory heap checks
-
## Known Bugs:
- No console data / history on first load causes the send textbox to be at the top of the panel
(instead of the bottom)
- if you complete a print, then delete the original gcode;
- then you can still attempt to reprint something that's no longer there and;
- the metadata load fails because the file is no longer there.
Expand Down

0 comments on commit be67ba0

Please sign in to comment.