├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── etc
├── init.d
│ └── touchscreen
└── systemd
│ └── system
│ └── touchscreen.service
├── img
├── logo.png
└── tentacle-20x20.png
├── main.py
├── octoprint.cfg.sample
├── os_utils.py
├── panels.kv
├── pics
├── screenshot_control.png
├── screenshot_job.png
├── screenshot_osutils.png
├── screenshot_status.png
└── screenshot_temps.png
└── start.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | octoprint.cfg
3 | libs
4 | requests
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Octoprint",
4 | "Popen",
5 | "ifconfig",
6 | "ifdown",
7 | "ifup",
8 | "inet",
9 | "nicname"
10 | ]
11 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OctoPiTouchPanel
2 | Touchscreen interface for OctoPrint using the kivy gui framework
3 |
4 | The layout is designed for the official Raspberry Pi Foundation 7" touchscreen. I would recommend using the Raspberry Pi 2 or 3 Model B for the Octoprint server. I think the Pi 1 might be a bit under powered for running this touchscreen app.
5 |
6 | # Installation
7 | Big thanks to Tim Vaillemans for documenting this installation process.
8 | ## Installation for python2
9 |
10 | Install OctoPi (https://octopi.octoprint.org/)
11 | ssh to your Octoprint server
12 | sudo su -
13 | apt-get update
14 | apt-get upgrade
15 | apt-get install python-pip
16 | sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
17 | pkg-config libgl1-mesa-dev libgles2-mesa-dev \
18 | python-setuptools libgstreamer1.0-dev git-core \
19 | gstreamer1.0-plugins-{bad,base,good,ugly} \
20 | gstreamer1.0-{omx,alsa} python-dev libmtdev-dev \
21 | xclip
22 | pip install -U Cython==0.27.3
23 | pip install git+https://github.com/kivy/kivy.git@master
24 | pip install kivy-garden
25 | garden install graph
26 |
27 |
28 | ## Installation for python3
29 |
30 | Install OctoPi 0.16 or higher (https://octopi.octoprint.org/)
31 | ssh to your Octoprint server
32 | sudo apt-get update
33 | sudo apt-get upgrade
34 | sudo apt-get install python3 python3-pip python3-dev python3-setuptools
35 | sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
36 | pkg-config libgl1-mesa-dev libgles2-mesa-dev git-core \
37 | gstreamer1.0-plugins-{bad,base,good,ugly} libgstreamer1.0-dev \
38 | gstreamer1.0-{omx,alsa} libmtdev-dev xclip
39 |
40 | python3 -m pip install --upgrade --user pip wheel setuptools
41 | python3 -m pip install --upgrade --user Cython==0.29.10 pillow PySDL2
42 | python3 -m pip install --user git+https://github.com/kivy/kivy.git@master
43 | ~/.local/bin/garden install --upgrade graph
44 |
45 |
46 | ## Configure kivy to use the touchscreen input
47 | [Copied from https://kivy.org/docs/installation/installation-rpi.html]
48 | If you are using the official Raspberry Pi touch display, you need to configure Kivy to use it as an input source. To do this, edit the file ~/.kivy/config.ini and go to the [input] section. Add this:
49 |
50 | mouse = mouse
51 | mtdev_%(name)s = probesysfs,provider=mtdev
52 | hid_%(name)s = probesysfs,provider=hidinput
53 |
54 |
55 | ## Disable console screen blanking
56 | Raspbian Jessie has console screen blanking turned on by default. It is set to 600 seconds. To disable this you must edit this file:
57 |
58 | vi /boot/cmdline.txt
59 |
60 |
61 | After 'console=tty1' add 'consoleblank=0'. Your cmdline.txt should look something like this:
62 |
63 | dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 consoleblank=0 root=PARTUUID=17e26144-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
64 |
65 | Note the cmdline.txt file must be a single line of text. After saving this file reboot your Pi.
66 |
67 | ## Clone this repo
68 |
69 | cd ~
70 | git clone https://github.com/matt448/OctoPiTouchPanel.git
71 |
72 |
73 | ## Configure OctoPiTouchPanel
74 |
75 | cd OctoPiTouchPanel
76 | cp octoprint.cfg.sample octoprint.cfg
77 | edit octoprint.cfg (with vim, nano, etc.)
78 | The important items to edit are in the APISettings section.
79 | - host: should almost always be 127.0.0.1
80 | - apikey: get API key from your octoprint webpage. Looks something like AADDEEDD0BA48F891F3966F856765FB
81 | - nicname: Use ifconfig to identify network interface Looks something like eth0 or wlan0
82 |
83 |
84 |
85 | ## Configure system to start touchscreen app on boot
86 | Older Linux distros use start scripts in /etc/init.d. Newer Linux distros use start scripts in /etc/systemd/system.
87 | I've included both with this project but since systemd is now the standard I will detail how to set that up.
88 |
89 | sudo cp OctoPiTouchPanel/etc/systemd/system/touchscreen.service /etc/systemd/system
90 | sudo chmod +x /etc/systemd/system/touchscreen.service
91 | sudo systemctl enable touchscreen
92 |
93 | The touchscreen app should now launch when the Pi boots up.
94 |
95 |
96 | ## Manually starting and stopping the app
97 |
98 | sudo systemctl start touchscreen
99 | sudo systemctl stop touchscreen
100 |
101 |
102 | # Screenshots
103 |
104 | 
105 |
106 |
107 |
108 | 
109 |
110 |
111 |
112 | 
113 |
114 |
115 |
116 | 
117 |
118 |
119 |
120 | 
121 |
--------------------------------------------------------------------------------
/etc/init.d/touchscreen:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ### BEGIN INIT INFO
3 | # Provides: touchscreen
4 | # Required-Start: $remote_fs $syslog
5 | # Required-Stop: $remote_fs $syslog
6 | # Default-Start: 2 3 4 5
7 | # Default-Stop: 0 1 6
8 | # Short-Description: Octoprint touchscreen
9 | # Description: Octoprint touchscreen
10 | ### END INIT INFO
11 |
12 | # Author: Matthew McMillan
13 | #
14 |
15 | # Do NOT "set -e"
16 |
17 | # PATH should only include /usr/* if it runs after the mountnfs.sh script
18 | PATH=/sbin:/usr/sbin:/bin:/usr/bin
19 | DESC="Description of the service"
20 | NAME=touchscreen
21 | DAEMON=/root/OctoPiTouchPanel/main.py
22 | DAEMON_ARGS=""
23 | PIDFILE=/var/run/$NAME.pid
24 | SCRIPTNAME=/etc/init.d/$NAME
25 | CHDIR='/root/OctoPiTouchPanel'
26 | CHUID='root'
27 |
28 | # Exit if the package is not installed
29 | [ -x "$DAEMON" ] || exit 0
30 |
31 | # Read configuration variable file if it is present
32 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME
33 |
34 | # Load the VERBOSE setting and other rcS variables
35 | . /lib/init/vars.sh
36 |
37 | # Define LSB log_* functions.
38 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present
39 | # and status_of_proc is working.
40 | . /lib/lsb/init-functions
41 |
42 | #
43 | # Function that starts the daemon/service
44 | #
45 | do_start()
46 | {
47 | # Return
48 | # 0 if daemon has been started
49 | # 1 if daemon was already running
50 | # 2 if daemon could not be started
51 | log_daemon_msg "Starting $NAME daemon"
52 | sleep 5
53 | start-stop-daemon --start --quiet --background --chuid $CHUID --chdir $CHDIR --pidfile $PIDFILE --make-pidfile --exec $DAEMON --test > /dev/null \
54 | || return 1
55 | start-stop-daemon --start --quiet --background --chuid $CHUID --chdir $CHDIR --pidfile $PIDFILE --make-pidfile --exec $DAEMON -- \
56 | $DAEMON_ARGS \
57 | || return 2
58 | log_end_msg $?
59 | # Add code here, if necessary, that waits for the process to be ready
60 | # to handle requests from services started subsequently which depend
61 | # on this one. As a last resort, sleep for some time.
62 | }
63 |
64 | #
65 | # Function that stops the daemon/service
66 | #
67 | do_stop()
68 | {
69 | # Return
70 | # 0 if daemon has been stopped
71 | # 1 if daemon was already stopped
72 | # 2 if daemon could not be stopped
73 | # other if a failure occurred
74 | log_daemon_msg "Stopping $NAME daemon"
75 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
76 | RETVAL="$?"
77 | log_end_msg $RETVAL
78 | [ "$RETVAL" = 2 ] && return 2
79 | # Wait for children to finish too if this is a daemon that forks
80 | # and if the daemon is only ever run from this initscript.
81 | # If the above conditions are not satisfied then add some other code
82 | # that waits for the process to drop all resources that could be
83 | # needed by services started subsequently. A last resort is to
84 | # sleep for some time.
85 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
86 | [ "$?" = 2 ] && return 2
87 | # Many daemons don't delete their pidfiles when they exit.
88 | rm -f $PIDFILE
89 | return "$RETVAL"
90 | }
91 |
92 | #
93 | # Function that sends a SIGHUP to the daemon/service
94 | #
95 | do_reload() {
96 | #
97 | # If the daemon can reload its configuration without
98 | # restarting (for example, when it is sent a SIGHUP),
99 | # then implement that here.
100 | #
101 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
102 | return 0
103 | }
104 |
105 | case "$1" in
106 | start)
107 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
108 | do_start
109 | case "$?" in
110 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
111 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
112 | esac
113 | ;;
114 | stop)
115 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
116 | do_stop
117 | case "$?" in
118 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
119 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
120 | esac
121 | ;;
122 | status)
123 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
124 | ;;
125 | #reload|force-reload)
126 | #
127 | # If do_reload() is not implemented then leave this commented out
128 | # and leave 'force-reload' as an alias for 'restart'.
129 | #
130 | #log_daemon_msg "Reloading $DESC" "$NAME"
131 | #do_reload
132 | #log_end_msg $?
133 | #;;
134 | restart|force-reload)
135 | #
136 | # If the "reload" option is implemented then remove the
137 | # 'force-reload' alias
138 | #
139 | log_daemon_msg "Restarting $DESC" "$NAME"
140 | do_stop
141 | case "$?" in
142 | 0|1)
143 | do_start
144 | case "$?" in
145 | 0) log_end_msg 0 ;;
146 | 1) log_end_msg 1 ;; # Old process is still running
147 | *) log_end_msg 1 ;; # Failed to start
148 | esac
149 | ;;
150 | *)
151 | # Failed to stop
152 | log_end_msg 1
153 | ;;
154 | esac
155 | ;;
156 | *)
157 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
158 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
159 | exit 3
160 | ;;
161 | esac
162 |
163 | :
164 |
--------------------------------------------------------------------------------
/etc/systemd/system/touchscreen.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Octoprint Touchscreen Interface
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User=pi
8 | WorkingDirectory=/home/pi/OctoPiTouchPanel
9 | ExecStart=/usr/bin/python3 /home/pi/OctoPiTouchPanel/main.py
10 | Restart=on-abort
11 |
12 | [Install]
13 | WantedBy=multi-user.target
14 |
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/img/logo.png
--------------------------------------------------------------------------------
/img/tentacle-20x20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/img/tentacle-20x20.png
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # kivy imports
4 | from kivy.app import App
5 | from kivy.uix.tabbedpanel import TabbedPanel
6 | from kivy.uix.floatlayout import FloatLayout
7 | from kivy.uix.boxlayout import BoxLayout
8 | from kivy.uix.gridlayout import GridLayout
9 | from kivy.lang import Builder
10 | from kivy.config import Config
11 | from kivy.core.window import Window
12 | from kivy.clock import Clock
13 | from kivy.utils import get_color_from_hex as rgb
14 | from kivy.garden.graph import Graph, MeshLinePlot, SmoothLinePlot
15 |
16 | # other imports
17 | import time
18 | import os
19 | from math import sin, cos
20 | import requests
21 | import json
22 | import configparser
23 | import sys
24 | from subprocess import *
25 | import pprint
26 | from collections import deque # Fast pops from the ends of lists
27 | import os_utils
28 |
29 | # Temperature lists
30 | hotendactual_list = deque([])
31 | hotendtarget_list = deque([])
32 | bedactual_list = deque([])
33 | bedtarget_list = deque([])
34 |
35 | g_hotend_actual = 0
36 | g_hotend_target = 0
37 | g_bed_actual = 0
38 | g_bed_target = 0
39 |
40 | # Initialize Temperature lists
41 | temperature_list_size = 201
42 | temperature_graph_time_max = 30000 # Minutes * 10000
43 | for i in range(temperature_list_size):
44 | hotendactual_list.append(0)
45 | hotendtarget_list.append(0)
46 | bedactual_list.append(0)
47 | bedtarget_list.append(0)
48 | # Fill timestamp list with 1000x time vals in seconds
49 | graphtime_list = []
50 | for i in range(temperature_list_size): # Fill the list with zeros
51 | graphtime_list.append(0)
52 |
53 | graphtime_list[0] = temperature_graph_time_max
54 | for i in range(temperature_list_size - 1): # Replace values with decreasing seconds from 30 to 0
55 | val = graphtime_list[i] - (temperature_graph_time_max / (temperature_list_size - 1))
56 | graphtime_list[i + 1] = (val)
57 |
58 |
59 | # Read settings from the config file
60 | settings = configparser.ConfigParser()
61 | settings.read('octoprint.cfg')
62 | host = settings.get('APISettings', 'host')
63 | nicname = settings.get('APISettings', 'nicname')
64 | apikey = settings.get('APISettings', 'apikey')
65 | debug = int(settings.get('Debug', 'debug_enabled'))
66 | hotend_max = int(settings.get('MaxTemps', 'hotend_max'))
67 | bed_max = int(settings.get('MaxTemps', 'bed_max'))
68 | invert_X = int(settings.get('AxisInvert', 'invert_X'))
69 | invert_Y = int(settings.get('AxisInvert', 'invert_Y'))
70 | invert_Z = int(settings.get('AxisInvert', 'invert_Z'))
71 |
72 | # Define Octoprint constants
73 | httptimeout = 3 # http request timeout in seconds
74 | printerapiurl = 'http://' + host + '/api/printer'
75 | printheadurl = 'http://' + host + '/api/printer/printhead'
76 | bedurl = 'http://' + host + '/api/printer/bed'
77 | toolurl = 'http://' + host + '/api/printer/tool'
78 | jobapiurl = 'http://' + host + '/api/job'
79 | connectionurl = 'http://' + host + '/api/connection'
80 | commandurl = 'http://' + host + '/api/printer/command'
81 | headers = {'X-Api-Key': apikey, 'content-type': 'application/json'}
82 |
83 | if debug:
84 | print("*********** DEBUG ENABLED ************")
85 | print(headers)
86 |
87 | bed_temp_val = 0.0
88 | hotend_temp_val = 0.0
89 | jogincrement = 10
90 |
91 | platform = sys.platform # Grab platform name for platform specific commands
92 |
93 | start_time = time.time()
94 |
95 | ################################
96 | # Load the Kivy widget layout
97 | Builder.load_file('panels.kv')
98 |
99 |
100 | class Panels(TabbedPanel):
101 | global bed_max
102 | global hotend_max
103 |
104 | def gettemps(self, *args):
105 | global g_hotend_actual
106 | global g_hotend_target
107 | global g_bed_actual
108 | global g_bed_target
109 | self.ids.hotendslider.max = hotend_max
110 | self.ids.bedslider.max = bed_max
111 | try:
112 | if debug:
113 | print('[GET TEMPS] Trying /printer API request to Octoprint...')
114 | r = requests.get(printerapiurl, headers=headers, timeout=httptimeout)
115 | if debug:
116 | print('[GET TEMPS] STATUS CODE: ', str(r.status_code))
117 | except requests.exceptions.RequestException as e:
118 | r = False
119 | if debug:
120 | print('[GET TEMPS] ERROR: Couldn\'t contact Octoprint /printer API')
121 | print(e)
122 | if r and r.status_code == 200:
123 | if debug:
124 | print('[GET TEMPS] JSON Data: ' + str(r.json()))
125 | if r and r.status_code == 200 and 'tool0' in r.json()['temperature']:
126 | printeronline = True
127 | g_hotend_actual = int(r.json()['temperature']['tool0']['actual'])
128 | g_hotend_target = int(r.json()['temperature']['tool0']['target'])
129 | g_bed_actual = int(r.json()['temperature']['bed']['actual'])
130 | g_bed_target = int(r.json()['temperature']['bed']['target'])
131 | printing = r.json()['state']['flags']['printing']
132 | paused = r.json()['state']['flags']['paused']
133 | operational = r.json()['state']['flags']['operational']
134 |
135 | if debug:
136 | print(' BED ACTUAL: ' + str(g_bed_actual))
137 | print('HOTEND ACTUAL: ' + str(g_hotend_actual))
138 | print(' PRINTING: ' + str(printing))
139 | print(' PAUSED: ' + str(paused))
140 | print(' OPERATIONAL: ' + str(operational))
141 |
142 | # Update text color on Temps tab if values are above 40C
143 | if g_bed_actual > 40:
144 | self.ids.tab3_bed_actual.color = [1, 0, 0, 1]
145 | else:
146 | self.ids.tab3_bed_actual.color = [1, 1, 1, 1]
147 |
148 | if g_bed_target > 40:
149 | self.ids.tab3_bed_target.color = [1, 0, 0, 1]
150 | else:
151 | self.ids.tab3_bed_target.color = [1, 1, 1, 1]
152 |
153 | if g_hotend_actual > 40:
154 | self.ids.tab3_hotend_actual.color = [1, 0, 0, 1]
155 | else:
156 | self.ids.tab3_hotend_actual.color = [1, 1, 1, 1]
157 |
158 | if g_hotend_target > 40:
159 | self.ids.tab3_hotend_target.color = [1, 0, 0, 1]
160 | else:
161 | self.ids.tab3_hotend_target.color = [1, 1, 1, 1]
162 |
163 | # Enable/Disable extruder buttons
164 | if g_hotend_actual < 130 or printing or paused:
165 | self.ids.extrude.disabled = True
166 | self.ids.retract.disabled = True
167 | else:
168 | self.ids.extrude.disabled = False
169 | self.ids.retract.disabled = False
170 |
171 | # Set pause/resume label on pause button
172 | if paused:
173 | self.ids.pausebutton.text = 'Resume'
174 | else:
175 | self.ids.pausebutton.text = 'Pause'
176 |
177 | # Enable/Disable print job buttons
178 | if printing or paused:
179 | self.ids.printbutton.disabled = True
180 | self.ids.cancelbutton.disabled = False
181 | self.ids.pausebutton.disabled = False
182 | else:
183 | self.ids.printbutton.disabled = False
184 | self.ids.cancelbutton.disabled = True
185 | self.ids.pausebutton.disabled = True
186 |
187 | # Set position of slider pointer
188 | self.ids.hotendpb.value = (g_hotend_actual / self.ids.hotendslider.max) * 100
189 | self.ids.bedpb.value = (g_bed_actual / self.ids.bedslider.max) * 100
190 |
191 | # Update temperature values with new data
192 | self.ids.bed_actual.text = str(g_bed_actual) + u"\u00b0" + ' C'
193 | self.ids.hotend_actual.text = str(g_hotend_actual) + u"\u00b0" + ' C'
194 | if g_bed_target > 0:
195 | self.ids.bed_target.text = str(g_bed_target) + u"\u00b0" + ' C'
196 | else:
197 | self.ids.bed_target.text = 'OFF'
198 | if g_hotend_target > 0:
199 | self.ids.hotend_target.text = str(g_hotend_target) + u"\u00b0" + ' C'
200 | else:
201 | self.ids.hotend_target.text = 'OFF'
202 | else:
203 | if r:
204 | print('Error. API Status Code: ', str(r.status_code)) # Print API status code if we have one
205 | # If we can't get any values from Octoprint just fill values with not available.
206 | self.ids.bed_actual.text = 'N/A'
207 | self.ids.hotend_actual.text = 'N/A'
208 | self.ids.bed_target.text = 'N/A'
209 | self.ids.hotend_target.text = 'N/A'
210 |
211 | def home(self, *args):
212 | axis = args[0]
213 | print('HOME AXIS: ' + axis)
214 | if axis == 'xy':
215 | homedata = {'command': 'home', 'axes': ['x', 'y']}
216 | else:
217 | homedata = {'command': 'home', 'axes': ['z']}
218 | try:
219 | if debug:
220 | print('[HOME ' + axis + '] Trying /API request to Octoprint...')
221 | r = requests.post(printheadurl, headers=headers, json=homedata, timeout=httptimeout)
222 | if debug:
223 | print('[HOME ' + axis + '] STATUS CODE: ' + str(r.status_code))
224 | except requests.exceptions.RequestException as e:
225 | r = False
226 | if debug:
227 | print('ERROR: Couldn\'t contact Octoprint /job API')
228 | print(e)
229 |
230 | def jogaxis(self, *args):
231 | axis = args[0]
232 | direction = args[1]
233 | global invert_X
234 | global invert_Y
235 | global invert_Z
236 | global jogincrement
237 | invert_axis = {'x': invert_X, 'y': invert_Y, 'z': invert_Z}
238 |
239 | print('AXIS: ' + axis)
240 | print('DIRECTION: ' + direction)
241 | print('INCREMENT: ' + str(jogincrement))
242 |
243 | if direction == 'up' or direction == 'forward' or direction == 'left':
244 | if invert_axis[axis]:
245 | inc = jogincrement * -1
246 | else:
247 | inc = jogincrement
248 |
249 | if direction == 'down' or direction == 'backward' or direction == 'right':
250 | if invert_axis[axis]:
251 | inc = jogincrement
252 | else:
253 | inc = jogincrement * -1
254 |
255 | jogdata = {'command': 'jog', axis: inc}
256 | print('JOGDATA: ' + str(jogdata))
257 | try:
258 | if debug:
259 | print('[JOG ' + axis + ' ' + direction + '] Trying /API request to Octoprint...')
260 | r = requests.post(printheadurl, headers=headers, json=jogdata, timeout=httptimeout)
261 | if debug:
262 | print('STATUS CODE: ' + str(r.status_code))
263 | except requests.exceptions.RequestException as e:
264 | r = False
265 | if debug:
266 | print('ERROR: Couldn\'t contact Octoprint /job API')
267 | print(e)
268 |
269 | def jogincrement(self, *args):
270 | global jogincrement
271 | if debug:
272 | print('[JOG INCREMENT] Button pressed')
273 | print('[JOG INCREMENT] INC: ' + str(args[0]))
274 | jogincrement = args[0]
275 |
276 | def connect(self, *args):
277 | connectiondata = {'command': 'connect', 'port': '/dev/ttyACM0', 'baudrate': 250000,
278 | 'save': False, 'autoconnect': False}
279 | try:
280 | if debug:
281 | print('[CONNECT] Trying /job API request to Octoprint...')
282 | print('[CONNECT] ' + connectionurl + str(connectiondata))
283 | r = requests.post(connectionurl, headers=headers, json=connectiondata, timeout=httptimeout)
284 | if debug:
285 | print('[CONNECT] STATUS CODE: ' + str(r.status_code))
286 | print('[CONNECT] RESPONSE: ' + r.text)
287 | except requests.exceptions.RequestException as e:
288 | r = False
289 | if debug:
290 | print('[CONNECT] ERROR: Couldn\'t contact Octoprint /job API')
291 | print(e)
292 |
293 | def disconnect(self, *args):
294 | disconnectdata = {'command': 'disconnect'}
295 | try:
296 | if debug:
297 | print('[DISCONNECT] Trying /job API request to Octoprint...')
298 | print('[DISCONNECT] ' + connectionurl + str(disconnectdata))
299 | r = requests.post(connectionurl, headers=headers, json=disconnectdata, timeout=httptimeout)
300 | if debug:
301 | print('[DISCONNECT] STATUS CODE: ' + str(r.status_code))
302 | print('[DISCONNECT] RESPONSE: ' + r.text)
303 | except requests.exceptions.RequestException as e:
304 | r = False
305 | if debug:
306 | print('[DISCONNECT] ERROR: Couldn\'t contact Octoprint /job API')
307 | print(e)
308 |
309 | def setbedtarget(self, *args):
310 | bedsliderval = args[0]
311 | bedtargetdata = {'command': 'target', 'target': bedsliderval}
312 | if debug:
313 | print('[BED TARGET] New Value: ' + str(bedsliderval) + ' C')
314 | try:
315 | if debug:
316 | print('[BED TARGET] Trying /API request to Octoprint...')
317 | r = requests.post(bedurl, headers=headers, json=bedtargetdata, timeout=httptimeout)
318 | if debug:
319 | print ('[BED TARGET] STATUS CODE: ' + str(r.status_code))
320 | except requests.exceptions.RequestException as e:
321 | print('[BED TARGET] ERROR: Couldn\'t contact Octoprint /job API')
322 | print(e)
323 | r = False
324 |
325 | def sethotendtarget(self, *args):
326 | hotendsliderval = args[0]
327 | hotendtargetdata = {'command': 'target', 'targets': {'tool0': hotendsliderval}}
328 | if debug:
329 | print('[HOTEND TARGET] New Value: ' + str(hotendsliderval) + ' C')
330 | try:
331 | if debug:
332 | print('[HOTEND TARGET] Trying /API request to Octoprint...')
333 | r = requests.post(toolurl, headers=headers, json=hotendtargetdata, timeout=httptimeout)
334 | if debug:
335 | print('[HOTEND TARGET] STATUS CODE: ' + str(r.status_code))
336 | except requests.exceptions.RequestException as e:
337 | r = False
338 | if debug:
339 | print('[HOTEND TARGET] ERROR: Couldn\'t contact Octoprint /job API')
340 | print(e)
341 |
342 | def extrudefilament(self, *args):
343 | posneg = args[0]
344 | extrudeamount = (int(self.ids.extrudeamount.text) * posneg)
345 | if debug:
346 | print('[EXTRUDE FILAMENT] Amount: ' + str(extrudeamount))
347 | extrudedata = {'command': 'extrude', 'amount': extrudeamount}
348 | if debug:
349 | print('[EXTRUDE FILAMENT] Extruding: ' + str(extrudeamount) + ' mm')
350 | try:
351 | if debug:
352 | print ('[EXTRUDE FILAMENT] Trying /API request to Octoprint...')
353 | r = requests.post(toolurl, headers=headers, json=extrudedata, timeout=httptimeout)
354 | if debug:
355 | print('[EXTRUDE FILAMENT] STATUS CODE: ' + str(r.status_code))
356 | except requests.exceptions.RequestException as e:
357 | r = False
358 | if debug:
359 | print('[EXTRUDE FILAMENT] ERROR: Couldn\'t contact Octoprint /job API')
360 | print(e)
361 |
362 | def fanspeed(self, *args):
363 | speed_percent = int(args[0])
364 | speed_pwm = int(speed_percent * 2.551)
365 | fan_gcode = 'M106 S' + str(speed_pwm)
366 | fancmd = {"commands": [fan_gcode]}
367 | if debug:
368 | print('[FAN CONTROL] Speed: ' + str(speed_pwm))
369 | print('[FAN CONTROL] ' + str(fancmd))
370 | try:
371 | r = requests.post(commandurl, headers=headers, json=fancmd, timeout=httptimeout)
372 | except requests.exceptions.RequestException as e:
373 | r = False
374 |
375 | def jobcontrol(self, *args):
376 | jobcommand = args[0]
377 | jobdata = {'command': jobcommand}
378 | try:
379 | if debug:
380 | print ('[JOB COMMAND] Trying /API request to Octoprint...')
381 | # Send job request to the job api
382 | r = requests.post(jobapiurl, headers=headers, json=jobdata, timeout=httptimeout)
383 | if debug:
384 | print ('[JOB COMMAND] STATUS CODE: ' + str(r.status_code))
385 | print ('[JOB COMMAND] COMMAND: ' + str(jobcommand))
386 | print ('[JOB COMMAND] BUTTON TEXT: ' + self.ids.pausebutton.text)
387 | # Update pause button text
388 | if r.status_code == 204 and jobcommand == 'pause' and self.ids.pausebutton.text == 'Pause':
389 | self.ids.pausebutton.text = 'Resume'
390 | elif r.status_code == 204 and jobcommand == 'pause' and self.ids.pausebutton.text == 'Resume':
391 | self.ids.pausebutton.text = 'Pause'
392 |
393 | except requests.exceptions.RequestException as e:
394 | r = False
395 | if debug:
396 | print ('[JOB COMMAND] ERROR: Couldn\'t contact Octoprint /job API')
397 | print(e)
398 |
399 | def getstats(self, *args):
400 | try:
401 | if debug:
402 | print ('[GET STATS] Trying /job API request to Octoprint...')
403 | r = requests.get(jobapiurl, headers=headers, timeout=1)
404 | except requests.exceptions.RequestException as e:
405 | r = False
406 | if debug:
407 | print('[GET STATS] ERROR: Couldn\'t contact Octoprint /job API')
408 | print(e)
409 | if r and r.status_code == 200:
410 | if debug:
411 | print ('[GET STATS] JSON Data: ' + str(r.json()))
412 | printerstate = r.json()['state']
413 | jobfilename = r.json()['job']['file']['name']
414 | jobpercent = r.json()['progress']['completion']
415 | jobprinttime = r.json()['progress']['printTime']
416 | jobprinttimeleft = r.json()['progress']['printTimeLeft']
417 | if debug:
418 | print ('[GET STATS] Printer state: ' + printerstate)
419 | print ('[GET STATS] Job percent: ' + str(jobpercent) + '%')
420 | if jobfilename is not None:
421 | jobfilenamefull = jobfilename
422 | jobfilename = jobfilename[:25] # Shorten filename to 25 characters
423 | self.ids.jobfilename.text = jobfilename
424 | self.ids.jobfilenamefull.text = jobfilenamefull
425 | else:
426 | self.ids.jobfilename.text = '-'
427 | if printerstate is not None:
428 | self.ids.printerstate.text = printerstate
429 | self.ids.printerstate2.text = printerstate
430 | self.ids.printerstate3.text = printerstate
431 | else:
432 | self.ids.printerstate.text = 'Unknown'
433 | self.ids.printerstate2.text = 'Unknown'
434 | self.ids.printerstate3.text = 'Unknown'
435 | if jobpercent is not None:
436 | jobpercent = int(jobpercent)
437 | self.ids.jobpercent.text = str(jobpercent) + '%'
438 | self.ids.progressbar.value = jobpercent
439 | else:
440 | self.ids.jobpercent.text = '---%'
441 | self.ids.progressbar.value = 0
442 | if jobprinttime is not None:
443 | hours = int(jobprinttime / 60 / 60)
444 | if hours > 0:
445 | minutes = int(jobprinttime / 60) - (60 * hours)
446 | else:
447 | minutes = int(jobprinttime / 60)
448 | seconds = int(jobprinttime % 60)
449 | self.ids.jobprinttime.text = str(hours).zfill(2) + ':' + \
450 | str(minutes).zfill(2) + ':' + str(seconds).zfill(2)
451 | else:
452 | self.ids.jobprinttime.text = '00:00:00'
453 | if jobprinttimeleft is not None:
454 | hours = int(jobprinttimeleft / 60 / 60)
455 | if hours > 0:
456 | minutes = int(jobprinttimeleft / 60) - (60 * hours)
457 | else:
458 | minutes = int(jobprinttimeleft / 60)
459 | seconds = int(jobprinttimeleft % 60)
460 | self.ids.jobprinttimeleft.text = str(hours).zfill(2) + \
461 | ':' + str(minutes).zfill(2) + ':' + str(seconds).zfill(2)
462 | else:
463 | self.ids.jobprinttimeleft.text = '00:00:00'
464 |
465 | else:
466 | if r:
467 | print ('Error. API Status Code: ' + str(r.status_code)) # Print API status code if we have one
468 | # If we can't get any values from Octoprint API fill with these values.
469 | self.ids.jobfilename.text = 'N/A'
470 | self.ids.printerstate.text = 'Unknown'
471 | self.ids.printerstate2.text = 'Unknown'
472 | self.ids.printerstate3.text = 'Unknown'
473 | self.ids.jobpercent.text = 'N/A'
474 | self.ids.progressbar.value = 0
475 | self.ids.jobprinttime.text = '--:--:--'
476 | self.ids.jobprinttimeleft.text = '--:--:--'
477 |
478 | def update_ip_addr(self, *args):
479 | ip_addr = os_utils.get_ip_address(platform, nicname, debug)
480 | if ip_addr:
481 | self.ids.ip_addr.text = str(ip_addr)
482 | else:
483 | self.ids.ip_addr.text = 'Unknown Platform'
484 |
485 | def button_restart_os(self, *args):
486 | command = args[0]
487 | os_utils.restart_os(platform, command, debug)
488 |
489 | def button_exit_app(self, *args):
490 | os_utils.exit_app()
491 |
492 | def button_restart_networking(self, *args):
493 | os_utils.restart_networking(platform, nicname, debug)
494 |
495 | def graphpoints(self, *args):
496 | global g_hotend_actual
497 | global g_hotend_target
498 | global g_bed_actual
499 | global g_bed_target
500 | hotendactual_plot = SmoothLinePlot(color=[1, 0, 0, 1])
501 | hotendtarget_plot = MeshLinePlot(color=[1, 0, 0, .75])
502 | bedactual_plot = SmoothLinePlot(color=[0, 0, 1, 1])
503 | bedtarget_plot = MeshLinePlot(color=[0, 0, 1, .75])
504 |
505 | # Update temperature graph arrays with new data
506 | hotendactual_list.popleft()
507 | hotendactual_list.append(g_hotend_actual)
508 | hotendtarget_list.popleft()
509 | hotendtarget_list.append(g_hotend_target)
510 | bedactual_list.popleft()
511 | bedactual_list.append(g_bed_actual)
512 | bedtarget_list.popleft()
513 | bedtarget_list.append(g_bed_target)
514 |
515 | # Build list of plot points tuples from temp and time lists
516 | hotendactual_points_list = []
517 | hotendtarget_points_list = []
518 | bedactual_points_list = []
519 | bedtarget_points_list = []
520 | for i in range(temperature_list_size):
521 | hotendactual_points_list.append((graphtime_list[i] / 1000.0 * -1, int(hotendactual_list[i])))
522 | hotendtarget_points_list.append((graphtime_list[i] / 1000.0 * -1, int(hotendtarget_list[i])))
523 | bedactual_points_list.append((graphtime_list[i] / 1000.0 * -1, int(bedactual_list[i])))
524 | bedtarget_points_list.append((graphtime_list[i] / 1000.0 * -1, int(bedtarget_list[i])))
525 |
526 | # Remove all old plots from the graph before drawing new ones
527 | while(len(self.my_graph.plots) != 0):
528 | # TODO - add a counter so we can abort after a certain number of tries.
529 | self.my_graph.remove_plot(self.my_graph.plots[0])
530 |
531 | # Draw the new graphs
532 | hotendactual_plot.points = hotendactual_points_list
533 | self.my_graph.add_plot(hotendactual_plot)
534 | hotendtarget_plot.points = hotendtarget_points_list
535 | self.my_graph.add_plot(hotendtarget_plot)
536 | bedactual_plot.points = bedactual_points_list
537 | self.my_graph.add_plot(bedactual_plot)
538 | bedtarget_plot.points = bedtarget_points_list
539 | self.my_graph.add_plot(bedtarget_plot)
540 |
541 |
542 | class TabbedPanelApp(App):
543 | def build(self):
544 | Window.size = (800, 480)
545 | panels = Panels()
546 | Clock.schedule_once(panels.gettemps, 0.5) # Update bed and hotend at startup
547 | Clock.schedule_interval(panels.gettemps, 5) # Update bed and hotend temps every 5 seconds
548 |
549 | Clock.schedule_interval(panels.getstats, 5) # Update job stats every 5 seconds
550 |
551 | Clock.schedule_once(panels.update_ip_addr, 0.5) # Update IP addr once right away
552 | Clock.schedule_interval(panels.update_ip_addr, 30) # Then update IP every 30 seconds
553 |
554 | Clock.schedule_once(panels.graphpoints, 1) # Update graphs right away at startup
555 | graph_interval = ((temperature_graph_time_max / 1000) /(temperature_list_size - 1)) * 60
556 | print('Graph update interval:', graph_interval, ' seconds')
557 | Clock.schedule_interval(panels.graphpoints, graph_interval) # Update graphs every 10 seconds
558 | return panels
559 |
560 |
561 | if __name__ == '__main__':
562 | TabbedPanelApp().run()
563 |
--------------------------------------------------------------------------------
/octoprint.cfg.sample:
--------------------------------------------------------------------------------
1 | [APISettings]
2 | host: 127.0.0.1
3 | apikey: PUTYOUROCTOPRINTAPIKEYHERE
4 | nicname: eth0
5 |
6 | [Debug]
7 | #0 = Debug output disabled
8 | #1 = Debug output enabled
9 | debug_enabled: 0
10 |
11 | [MaxTemps]
12 | #This is the max temp in celsius for the sliders
13 | #that set the hot end and bed temps.
14 | hotend_max: 300
15 | bed_max: 100
16 |
17 | [AxisInvert]
18 | #Enable if the jog buttons move the wrong direction.
19 | #0 = disable axis invert
20 | #1 = enable axis invert
21 | invert_X: 0
22 | invert_Y: 0
23 | invert_Z: 0
24 |
--------------------------------------------------------------------------------
/os_utils.py:
--------------------------------------------------------------------------------
1 | ##########################################
2 | # These are items for the 'OS Utils' tab
3 | from subprocess import Popen
4 | from subprocess import PIPE
5 | from subprocess import os
6 |
7 | ###################################
8 | # Exit the touchscreen application
9 | def exit_app():
10 | exit()
11 |
12 | ####################################
13 | # Ask the OS to restart networking
14 | # This will restart the nic listed in the config file
15 | def restart_networking(platform, nicname, debug):
16 | if 'linux' in platform or 'Linux' in platform:
17 | cmd = 'sudo ifconfig ' + nicname + ' down'
18 | p = Popen(cmd, shell=True, stdout=PIPE)
19 | cmd_output = p.communicate()[0].decode('utf-8')
20 | if debug:
21 | print('[RESTART NETWORK]: ' + cmd_output)
22 | cmd = 'sudo ifconfig ' + nicname + ' up'
23 | p = Popen(cmd, shell=True, stdout=PIPE)
24 | cmd_output = p.communicate()[0].decode('utf-8')
25 | if debug:
26 | print('[RESTART NETWORK]: ' + cmd_output)
27 | else:
28 | if debug:
29 | print('Unknown Platform. Not restarting network interface')
30 |
31 |
32 | ##########################################
33 | # Request an OS restart
34 | def restart_os(platform, command, debug):
35 | if 'linux' in platform or 'Linux' in platform:
36 | print ('[RESTART] OS is going to ' + str(command))
37 | if command == 'reboot':
38 | cmd = "sudo shutdown now -r"
39 | elif command == 'shutdown':
40 | cmd = "sudo shutdown now -h"
41 | else:
42 | cmd = "true"
43 | os.system(cmd)
44 | else:
45 | print ('[RESTART] ' + str(command) + ' Unsupported OS')
46 |
47 |
48 | #############################################
49 | # Detect IP address of the OS
50 | def get_ip_address(platform, nicname, debug):
51 | ip = ''
52 | if 'linux' in platform or 'Linux' in platform:
53 | cmd = "ip addr show " + nicname + " | grep inet | grep -v inet6 | awk '{print $2}' | cut -d/ -f1"
54 | p = Popen(cmd, shell=True, stdout=PIPE)
55 | cmd_output = p.communicate()[0].decode('utf-8')
56 | ip = cmd_output
57 | else:
58 | if debug:
59 | print('Unknown platform. Can\'t detect IP address')
60 | return(ip)
--------------------------------------------------------------------------------
/panels.kv:
--------------------------------------------------------------------------------
1 | #:kivy 1.11.1
2 | #:import rgb kivy.utils.get_color_from_hex
3 | :
4 | size_hint: 1, 1
5 | pos_hint: {'center_x': .5, 'center_y': .5}
6 | do_default_tab: False
7 | tab_height: '60dp'
8 | my_graph: my_graph
9 | ##############
10 | # Tab1
11 | ##############
12 | TabbedPanelItem:
13 | id: tab1
14 | text: 'Status'
15 | font_size: '20sp'
16 | BoxLayout:
17 | orientation: 'horizontal'
18 | ######################
19 | # Left Stats Area
20 | ######################
21 | BoxLayout:
22 | size_hint: (.4, 1)
23 | orientation: 'vertical'
24 | padding: 10
25 | Image:
26 | source: 'img/logo.png'
27 | size_hint_y: None
28 | height: 100
29 | GridLayout:
30 | cols: 2
31 | Label:
32 | text: 'IP Address:'
33 | Label:
34 | id: ip_addr
35 | size_hint_x: 1.75
36 | text: 'N/A'
37 | Label:
38 | text: 'Machine State:'
39 | Label:
40 | id: printerstate
41 | text: 'Unknown'
42 | Label:
43 | text: 'File:'
44 | Label:
45 | id: jobfilename
46 | text: ' '
47 | Label:
48 | text: 'Time Elapsed:'
49 | Label:
50 | id: jobprinttime
51 | text: '00:00:00'
52 | Label:
53 | text: 'Est. Time Left:'
54 | Label:
55 | id: jobprinttimeleft
56 | text: '00:00:00'
57 | Label:
58 | text: 'Printed: '
59 | Label:
60 | id: jobpercent
61 | text: '---%'
62 | ProgressBar:
63 | id: progressbar
64 | size_hint_x: .97
65 | size_hint_y: None
66 | height: '15dp'
67 | value: 0
68 | ######################
69 | # Right Stats Area
70 | ######################
71 | BoxLayout:
72 | size_hint: (.6, 1)
73 | orientation: 'vertical'
74 | GridLayout:
75 | cols: 3
76 | size_hint: (1, .3)
77 | Label:
78 | text: ' '
79 | size_hint_y: None
80 | height: 20
81 | Label:
82 | text_size: self.size
83 | halign: 'center'
84 | valign: 'middle'
85 | text: 'Actual'
86 | Label:
87 | text_size: self.size
88 | halign: 'center'
89 | valign: 'middle'
90 | text: 'Target'
91 | Label:
92 | size_hint_y: None
93 | height: 40
94 | text_size: self.size
95 | bold: True
96 | halign: 'right'
97 | valign: 'middle'
98 | text: 'Hot End:'
99 | Label:
100 | id: hotend_actual
101 | text_size: self.size
102 | font_size: 30
103 | bold: True
104 | halign: 'center'
105 | valign: 'middle'
106 | text: 'N/A' + u"\u00b0" + ' C'
107 | Label:
108 | id: hotend_target
109 | text_size: self.size
110 | font_size: 30
111 | bold: True
112 | halign: 'center'
113 | valign: 'middle'
114 | text: 'N/A' + u"\u00b0" + ' C'
115 | Label:
116 | size_hint_y: None
117 | height: 40
118 | bold: True
119 | text_size: self.size
120 | halign: 'right'
121 | valign: 'middle'
122 | text: 'Bed:'
123 | Label:
124 | id: bed_actual
125 | text_size: self.size
126 | font_size: 30
127 | bold: True
128 | halign: 'center'
129 | valign: 'middle'
130 | text: 'N/A' + u"\u00b0" + ' C'
131 | Label:
132 | id: bed_target
133 | text_size: self.size
134 | font_size: 30
135 | bold: True
136 | halign: 'center'
137 | valign: 'middle'
138 | text: 'N/A' + u"\u00b0" + ' C'
139 | Graph:
140 | id:my_graph
141 | xlabel: 'Time'
142 | ylabel: 'Temp'
143 | x_ticks_minor: 5
144 | x_ticks_major: 5
145 | y_ticks_major: 50
146 | y_grid_label: True
147 | x_grid_label: True
148 | padding: 5
149 | xlog: False
150 | ylog: False
151 | x_grid: True
152 | y_grid: True
153 | xmin: -30
154 | xmax: 0
155 | ymin: 0
156 | ymax: 300
157 | label_options: { 'color': rgb('444444'), 'bold': True}
158 | background_color: rgb('f8f8f2') # back ground color of canvas
159 | tick_color: rgb('808080') # ticks and grid
160 | border_color: rgb('808080') # border drawn around each graph
161 | ##############
162 | # Tab2
163 | ##############
164 | TabbedPanelItem:
165 | text: 'Control'
166 | font_size: '20sp'
167 | FloatLayout:
168 | #X/Y Axis buttons
169 | Label:
170 | text: 'X/Y'
171 | font_size: '35sp'
172 | pos: -265, 95
173 | Button:
174 | text: '^'
175 | id: jogforward
176 | disabled: False
177 | on_press: root.jogaxis('y', 'forward')
178 | pos: 100, 200
179 | size_hint: .10, .15
180 | Button:
181 | text: '<'
182 | id:jogleft
183 | disabled: False
184 | on_press: root.jogaxis('x', 'left')
185 | pos: 10, 130
186 | size_hint: .10, .15
187 | Button:
188 | text: 'H'
189 | id: homexy
190 | disabled: False
191 | on_press: root.home('xy')
192 | pos: 100, 130
193 | size_hint: .10, .15
194 | Button:
195 | text: '>'
196 | id: jogright
197 | disabled: False
198 | on_press: root.jogaxis('x', 'right')
199 | pos: 190, 130
200 | size_hint: .10, .15
201 | Button:
202 | text: 'v'
203 | id: jogbackward
204 | disabled: False
205 | on_press: root.jogaxis('y', 'backward')
206 | pos: 100, 60
207 | size_hint: .10, .15
208 | #Z axis buttons
209 | Label:
210 | text: 'Z'
211 | font_size: '35sp'
212 | pos: -65, 95
213 | Button:
214 | size_hint_x: None
215 | width: '25dp'
216 | id: jogzup
217 | disabled: False
218 | text: '^'
219 | on_press: root.jogaxis('z', 'up')
220 | pos: 300, 200
221 | size_hint: .10, .15
222 | Button:
223 | text: 'H'
224 | id: homez
225 | disabled: False
226 | on_press: root.home('z')
227 | pos: 300, 130
228 | size_hint: .10, .15
229 | Button:
230 | text: 'v'
231 | id: jogzdown
232 | disabled: False
233 | on_press: root.jogaxis('z', 'down')
234 | pos: 300, 60
235 | size_hint: .10, .15
236 | #Connect Button
237 | Label:
238 | id: printerstate2
239 | text: 'Unknown'
240 | #pos: 310, 195
241 | pos: 335, 195
242 | Button:
243 | text: 'Connect'
244 | id: connect
245 | on_press: root.connect()
246 | pos: 675, 345
247 | size_hint: .15, .10
248 | Button:
249 | text: 'Disconnect'
250 | id: connect
251 | on_press: root.disconnect()
252 | pos: 675, 295
253 | size_hint: .15, .10
254 | #####################
255 | # Jog increments
256 | #####################
257 | ToggleButton:
258 | text: '0.1'
259 | id: joginc01
260 | on_press: root.jogincrement(0.1)
261 | group: 'jogincrement'
262 | state: 'normal'
263 | pos: 10, 0
264 | size_hint: .1, .09
265 | ToggleButton:
266 | text: '1'
267 | id: joginc1
268 | on_press: root.jogincrement(1)
269 | group: 'jogincrement'
270 | state: 'normal'
271 | pos: 105, 0
272 | size_hint: .1, .09
273 | ToggleButton:
274 | text: '10'
275 | id: joginc10
276 | on_press: root.jogincrement(10)
277 | group: 'jogincrement'
278 | state: 'down'
279 | pos: 200, 0
280 | size_hint: .1, .09
281 | ToggleButton:
282 | text: '100'
283 | id: joginc100
284 | on_press: root.jogincrement(100)
285 | group: 'jogincrement'
286 | state: 'normal'
287 | pos: 300, 0
288 | size_hint: .1, .09
289 | ######################
290 | # Extrude/Retract
291 | ######################
292 | Spinner:
293 | id: extrudeamount
294 | text: '5'
295 | values: '1', '5', '10', '20', '100'
296 | pos: 450, 0
297 | size_hint: .15, .09
298 | Label:
299 | text: 'mm'
300 | pos: 138, -189
301 | Button:
302 | text: 'Extrude'
303 | id: extrude
304 | disabled: False
305 | on_press: root.extrudefilament(1)
306 | pos: 450, 60
307 | size_hint: .15, .15
308 | Button:
309 | text: 'Retract'
310 | id: retract
311 | disabled: False
312 | on_press: root.extrudefilament(-1)
313 | pos: 450, 130
314 | size_hint: .15, .15
315 | ######################
316 | # Fan Controls
317 | ######################
318 | Slider:
319 | id: fanslider
320 | max: 100
321 | value: int(100)
322 | orientation: 'vertical'
323 | on_value: fanslider.value = int(self.value)
324 | size_hint: .15, .42
325 | pos: 620, 0
326 | Button:
327 | size_hint_x: None
328 | width: '25dp'
329 | id: fanpercent
330 | disabled: False
331 | text: 'FAN ' + str(int(fanslider.value)) + '%'
332 | on_press: root.fanspeed(fanslider.value)
333 | pos: 700, 100
334 | size_hint: .10, .15
335 | Button:
336 | text: 'FAN OFF'
337 | id: fanoff
338 | disabled: False
339 | on_press: root.fanspeed(0)
340 | pos: 700, 12
341 | size_hint: .10, .15
342 |
343 | ###############################
344 | # Tab3 - Temperature Controls
345 | ###############################
346 | TabbedPanelItem:
347 | text: 'Temps'
348 | font_size: '20sp'
349 | FloatLayout:
350 | #################
351 | #Bed temp slider
352 | #################
353 | Slider:
354 | id: bedslider
355 | max: 100
356 | value: int(0)
357 | on_value: bedslider.value = int(self.value)
358 | size_hint: .80, .15
359 | pos: 160, 295
360 | ProgressBar:
361 | id: bedpb
362 | size_hint: .76, .15
363 | pos: 176, 265
364 | value: 0
365 | Label:
366 | text: 'Selected Bed Target: ' + str(bedslider.value) + u"\u00b0" + ' C'
367 | font_size: '20sp'
368 | halign: 'left'
369 | pos: 50, 150
370 | Button:
371 | id: setbedtarget
372 | text: 'Set Bed Target'
373 | on_press: root.setbedtarget(bedslider.value)
374 | size_hint: .18, .12
375 | pos: 10, 325
376 | Button:
377 | id: setbedoff
378 | text: 'Turn Off Bed'
379 | on_press: root.setbedtarget(0)
380 | size_hint: .18, .12
381 | pos: 10, 270
382 | Label:
383 | id: tab3_bed_actual
384 | text: 'Bed Actual: ' + bed_actual.text
385 | font_size: '16sp'
386 | pos: -60, 75
387 | Label:
388 | id: tab3_bed_target
389 | text: 'Bed Target: ' + bed_target.text
390 | font_size: '16sp'
391 | pos: 200, 75
392 |
393 | ####################
394 | #Hot end temp slider
395 | ####################
396 | Slider:
397 | id: hotendslider
398 | max: 300
399 | value: int(0)
400 | on_value: hotendslider.value = int(self.value)
401 | size_hint: .80, .15
402 | pos: 160, 120
403 | ProgressBar:
404 | id: hotendpb
405 | size_hint: .76, .15
406 | pos: 176, 90
407 | value: 0
408 | Label:
409 | text: 'Selected Hot End Target: ' + str(hotendslider.value) + u"\u00b0" + ' C'
410 | font_size: '20sp'
411 | halign: 'left'
412 | pos: 50, -15
413 | Button:
414 | id: sendhotendtarget
415 | text: 'Set Hot End Target'
416 | on_press: root.sethotendtarget(hotendslider.value)
417 | size_hint: .18, .12
418 | pos: 10, 150
419 | Button:
420 | id: sethotendoff
421 | text: 'Turn Off Hot End'
422 | on_press: root.sethotendtarget(0)
423 | size_hint: .18, .12
424 | pos: 10, 95
425 | Label:
426 | id: tab3_hotend_actual
427 | text: 'Hot End Actual: ' + hotend_actual.text
428 | font_size: '16sp'
429 | pos: -60, -100
430 | Label:
431 | id: tab3_hotend_target
432 | text: 'Hot End Target: ' + hotend_target.text
433 | font_size: '16sp'
434 | pos: 200, -100
435 |
436 | #######################
437 | # Tab4 - Job Controls
438 | #######################
439 | TabbedPanelItem:
440 | text: 'Job'
441 | font_size: '20sp'
442 | FloatLayout:
443 | GridLayout:
444 | cols: 2
445 | pos: 0, 320
446 | size_hint: .75, .2
447 | Label:
448 | text: 'Loaded File: '
449 | text_size: self.size
450 | size_hint: .2, 1
451 | halign: 'right'
452 | valign: 'middle'
453 | Label:
454 | id: jobfilenamefull
455 | text: 'N/A'
456 | text_size: self.size
457 | halign: 'left'
458 | valign: 'middle'
459 | Label:
460 | text: 'Status: '
461 | text_size: self.size
462 | size_hint: .2, 1
463 | halign: 'right'
464 | valign: 'middle'
465 | Label:
466 | id: printerstate3
467 | text: 'Unknown'
468 | text_size: self.size
469 | halign: 'left'
470 | valign: 'middle'
471 |
472 | Button:
473 | id: printbutton
474 | text: 'Print'
475 | disabled: False
476 | on_press: root.jobcontrol('start')
477 | size_hint: .18, .18
478 | pos: 20, 220
479 | Button:
480 | id: pausebutton
481 | text: 'Pause'
482 | disabled: False
483 | on_press: root.jobcontrol('pause')
484 | size_hint: .18, .18
485 | pos: 20, 120
486 | Button:
487 | id: cancelbutton
488 | text: 'Cancel'
489 | disabled: False
490 | on_press: root.jobcontrol('cancel')
491 | size_hint: .18, .18
492 | pos: 20, 20
493 |
494 |
495 | #######################
496 | # Tab5 - OS Utils
497 | #######################
498 | TabbedPanelItem:
499 | text: 'OS Utils'
500 | font_size: '20sp'
501 | FloatLayout:
502 | Button:
503 | id: restartOS
504 | text: 'Reboot'
505 | on_press: root.button_restart_os('reboot')
506 | size_hint: .18, .12
507 | pos: 10, 95
508 | Button:
509 | id: shutdownOS
510 | text: 'Shutdown'
511 | on_press: root.button_restart_os('shutdown')
512 | size_hint: .18, .12
513 | pos: 10, 30
514 | Button:
515 | id: exitApp
516 | text: 'Exit'
517 | on_press: root.button_exit_app()
518 | size_hint: .18, .12
519 | pos: 10, 195
520 | Button:
521 | id: restartNetworking
522 | text: 'Restart Networking'
523 | on_press: root.button_restart_networking()
524 | size_hint: .18, .12
525 | pos: 210, 195
526 |
527 |
--------------------------------------------------------------------------------
/pics/screenshot_control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/pics/screenshot_control.png
--------------------------------------------------------------------------------
/pics/screenshot_job.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/pics/screenshot_job.png
--------------------------------------------------------------------------------
/pics/screenshot_osutils.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/pics/screenshot_osutils.png
--------------------------------------------------------------------------------
/pics/screenshot_status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/pics/screenshot_status.png
--------------------------------------------------------------------------------
/pics/screenshot_temps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matt448/OctoPiTouchPanel/dd90f76aa475212dab89b36ab38f0b288f52df4b/pics/screenshot_temps.png
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script can be used to start the touchscreen app in the background.
4 | # If you want to start and stop the app with init use the script etc/init.d/touchscreen
5 |
6 | DAEMON='/root/OctoPiTouchPanel/main.py'
7 | PIDFILE=/var/run/touchscreen.pid
8 | CHDIR='/root/OctoPiTouchPanel'
9 | start-stop-daemon --start --quiet --chdir $CHDIR --pidfile $PIDFILE --exec $DAEMON
10 |
--------------------------------------------------------------------------------