-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtimelapse_duet3.py
154 lines (138 loc) · 6.84 KB
/
timelapse_duet3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# Duet_3_6HC_Time_Lapse
# Copyright (C) {2020} {JimsJump.com}
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
### What does this program do ??? ###
# Python script to collect photos & create video of 3D Printer Layer Changes
# using the Duet 3 - 6HC Mainboard (from Duet3D) and Raspberry Pi SBC
###
#
# Setup libraries
import os
import datetime
import urllib3
import subprocess
import select
import requests
import signal
from systemd import journal
urllib3.disable_warnings()
### SETUP PARAMETERS ###
#
# Requires MJPEG-Streamer & FFMPEG Installed/setup to capture pictures and compile video
# See https://github.com/cncjs/cncjs/wiki/Setup-Guide:-Raspberry-Pi-%7C-MJPEG-Streamer-Install-&-Setup-&-FFMpeg-Recording
#
# URL & Port where webcam is at - verify by visiting in browser (Chrome/Firefox/IE etc.)
webcam_url = 'http://xxx.xxx.xxx.xxx:8081/?action=snapshot' # <-- CHANGE IP & PORT
# polling cycle m-sec, 1000 m-sec = 1 second
cycle = 250
# define the folder access rights
access_rights = 0o777 #use 0o755 for tigher control, 0o777 grants full read/write/modify access
### setup folder locations ###
# folders are created in the same folder the script is started
# folders are have date and time stamp in names to id and locate easily
# folder where this script is stored
current_path = os.getcwd()
# create unique folder for snapshots & videos
# setup directories and notify
current_path = os.getcwd()
now = datetime.datetime.now()
timelapse_path = os.path.join(current_path, 'Video_' + now.strftime("%Y%m%dT%H%M%S") + '')
os.makedirs(timelapse_path, access_rights)
snapshots_path = os.path.join(timelapse_path, 'Images')
os.makedirs(snapshots_path, access_rights)
# announce where folders are located for this run
print ("The current working directory is %s" % current_path)
print ("The time lapse video directory is %s" % timelapse_path)
print ("The snapshot images directory is %s" % snapshots_path)
# Create a systemd.journal.Reader instance
j = journal.Reader()
# Set the reader's default log level
j.log_level(journal.LOG_INFO)
# Only include entries since the current box has booted.
j.this_boot()
j.this_machine()
# Filter log entries to Duet3 Control Server
j.add_match(_SYSTEMD_UNIT="duetcontrolserver.service")
# Move to the end of the journal
j.seek_tail()
# Important! - Discard old journal entries
j.get_previous()
# Create a poll object for journal entries
p = select.poll()
p.register(j, j.get_events())
# Register the journal's file descriptor with the polling object.
journal_fd = j.fileno()
poll_event_mask = j.get_events()
p.register(journal_fd, poll_event_mask)
# announce Time Lapse started
print('Time lapse cycles started... %s' % datetime.datetime.now())
layers = 0
# Poll for new journal entries every 250ms
# Update and take photos when the Duet Control Server announces LAYER_CHANGE, PRINT_STARTED, & PRINT_COMPLETE
# Anouncements are sent via GCODE using M118 P0 S"LAYER_CHANGE" for example
# Adding these announcements to GCODE via SLIC3R or CURA or other STL slicing program is covered in the README
while True :
if p.poll(cycle) :
if j.process() != journal.APPEND :
continue
for entry in j:
if entry['MESSAGE'] != "" :
# Capture Image at Layer Change
if entry['MESSAGE'] == "[info] LAYER_CHANGE" :
r = requests.get(webcam_url, timeout=5, stream=True)
if r.status_code == 200:
now = datetime.datetime.now()
pic = os.path.join(snapshots_path, "layer_" + str(layers) + ".jpg")
with open(pic, 'wb') as f:
for chunk in r:
f.write(chunk)
print((str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE']) + ' ' + 'SNAPSHOT TAKEN AT LAYER No. ' + str(layers) )
layers += 1
else:
print((str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE']) + ' ' + '** SNAPSHOT FAILED!! **.')
# Log / Announce Print Started
if entry['MESSAGE'] == "[info] PRINT_STARTED":
print((str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE']) + ' ' )
# FFMPEG Compile Video File
if entry['MESSAGE'] == "[info] PRINT_COMPLETE":
# capture last layer image
r = requests.get(webcam_url, timeout=5, stream=True)
if r.status_code == 200:
now = datetime.datetime.now()
pic = os.path.join(snapshots_path, "layer_" + str(layers) + ".jpg")
with open(pic, 'wb') as f:
for chunk in r:
f.write(chunk)
# announce Print Completed
print((str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE']) + ' ' + 'SNAPSHOT TAKEN AT LAYER No. ' + str(layers) )
layers += 1
# compile video
now = datetime.datetime.now()
video_file = os.path.abspath(os.path.join(timelapse_path, now.strftime("%Y%m%dT%H%M%S") + ".mp4"))
snapshots_files = os.path.join(snapshots_path, "*.jpg")
print('Compiling Video... %s' % datetime.datetime.now())
subprocess.call(["ffmpeg", "-r", "1", "-y", "-pattern_type", "glob", "-i", snapshots_files, "-vcodec", "libx264", video_file])
# announce Time Lapse completed
print((str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE']) + ' ' )
# give file locations
print('Video located at: ' + video_file)
print('Snapshots located at: ' + snapshots_path)
# stop poll cycle
print('Total layers printed: ' + str(layers))
print('Process completed!! --Goodbye...')
# kill the process
os.kill(p.pid,signal.SIGINT)
# print('waiting ... %s' % datetime.datetime.now()) #un-comment for testing to verify activity