From 32cc893102b493c11de4c58327046a8ea39518bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 12:23:56 +0200 Subject: [PATCH 01/10] python3, single command-line tool --- enter_dfu.py | 27 -------- fw_update.py | 40 ------------ load_filament.py | 41 ------------ modt.py | 152 +++++++++++++++++++++++++++++++++++++++++++++ modt_status.py | 32 ---------- readme.md | 29 ++++----- requirements.txt | 1 + send_gcode.py | 129 -------------------------------------- unload_filament.py | 41 ------------ 9 files changed, 168 insertions(+), 324 deletions(-) delete mode 100644 enter_dfu.py delete mode 100644 fw_update.py delete mode 100644 load_filament.py create mode 100644 modt.py delete mode 100644 modt_status.py create mode 100644 requirements.txt delete mode 100644 send_gcode.py delete mode 100644 unload_filament.py diff --git a/enter_dfu.py b/enter_dfu.py deleted file mode 100644 index 084d90c..0000000 --- a/enter_dfu.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/python -import sys -import os -import usb.core -import usb.util -import time -#This script just finds a mod-t and puts it in DFU mode. - - -dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) - -#If we didn't find a Mod-T we need to throw an error -if dev is None: - raise ValueError('No Mod-T detected') - -#Set active configuration (first is default) -dev.set_configuration() - -#First we mimmick the Mod-T desktop utility -#The initial packet is not human readable -#The second packet puts the Mod-T into DFU mode -dev.write(2, bytearray.fromhex('246a0095ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":7},"data":{"command":{"idx":53,"name":"Enter_dfu_mode"}}};') - -#Wait for the Mod-T to reattach in DFU mode -time.sleep(2) - diff --git a/fw_update.py b/fw_update.py deleted file mode 100644 index 38752a9..0000000 --- a/fw_update.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/python -import sys -import os -import usb.core -import usb.util -import time -#This script *SHOULD* eventually be the all-encompassing firmware update -#It should call the check FW script, place the Mod-T into DFU mode and flash the firmware -#It should have a command-line arg to flash older firmware versions, none of this is really implemented yet -#Make sure this was called correctly -if not len(sys.argv)==2: - print("Usage: fw_update.py filename.dfu") - quit() - -#Find the Mod-T - we should probably see if it's in DFU mode, too -#That way we can do emergency flashes from recovery mode -dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) - -#If we didn't find a Mod-T we need to throw an error -if dev is None: - raise ValueError('No Mod-T detected') - -#Make sure the filename supplied is actually a file, and error out appropriately -fname=str(sys.argv[1]) -if not os.path.isfile(fname): - print(fname + " not found") - quit() - -#Set active configuration (first is default) -dev.set_configuration() - -#First we mimmick the Mod-T desktop utility -#The initial packet is not human readable -#The second packet puts the Mod-T into DFU mode -dev.write(2, bytearray.fromhex('246a0095ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":7},"data":{"command":{"idx":53,"name":"Enter_dfu_mode"}}};') - -#Wait for the Mod-T to reattach in DFU mode -time.sleep(2) - diff --git a/load_filament.py b/load_filament.py deleted file mode 100644 index c7218e7..0000000 --- a/load_filament.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python - -#Just tells the mod-t to enter load filament mode. It also polls the mod-t for status -#This status information can be used to prompt the user to proceed as intended. - -import sys -import os -import usb.core -import usb.util -import time - -# Read pending data from MOD-t (bulk reads of 64 bytes) -def read_modt(ep): - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = text - while len(text)==64: - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = fulltext + text - return fulltext - -#Find the Mod-T - we should probably see if it's in DFU mode, too -#That way we can do emergency flashes from recovery mode -dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) - -#If we didn't find a Mod-T we need to throw an error -if dev is None: - raise ValueError('No Mod-T detected') - -#Set active configuration (first is default) -dev.set_configuration() - -#Same as all the other files, this packet is not readable -#The second packet however, is - -dev.write(2, bytearray.fromhex('24690096ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":9},"data":{"command":{"idx":52,"name":"load_initiate"}}};') - -while True: - dev.write(4, '{"metadata":{"version":1,"type":"status"}}') - print(read_modt(0x83)) - time.sleep(5) diff --git a/modt.py b/modt.py new file mode 100644 index 0000000..470ca03 --- /dev/null +++ b/modt.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +import argparse +import usb.core +import usb.util +import time +from zlib import adler32 + +class ModT: + class PAYLOADS: + BIO_GET_VERSION = (2, '{"transport":{"attrs":["request","twoway"],"id":3},"data":{"command":{"idx":0,"name":"bio_get_version"}}};') + ENTER_DFU_MODE = (2, '{"transport":{"attrs":["request","twoway"],"id":7},"data":{"command":{"idx":53,"name":"Enter_dfu_mode"}}};') + LOAD_INITIATE = (2, '{"transport":{"attrs":["request","twoway"],"id":9},"data":{"command":{"idx":52,"name":"load_initiate"}}};') + STATUS = (4, '{"metadata":{"version":1,"type":"status"}}') + UNLOAD_INITIATE = (2, '{"transport":{"attrs":["request","twoway"],"id":11},"data":{"command":{"idx":51,"name":"unload_initiate"}}};') + WIFI_CLIENT_GET_STATUS = (2, '{"transport":{"attrs":["request","twoway"],"id":5},"data":{"command":{"idx":22,"name":"wifi_client_get_status","args":{"interface_t":0}}}};') + + def __init__(self): + #Find the Mod-T - we should probably see if it's in DFU mode, too + #That way we can do emergency flashes from recovery mode + self.dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) + + #If we didn't find a Mod-T we need to throw an error + if self.dev is None: + raise ValueError('No Mod-T detected') + + #Set active configuration (first is default) + self.dev.set_configuration() + + self.dev.write(2, bytearray.fromhex('246c0093ff')) + + def write(self, endpoint, message): + self.dev.write(endpoint, message) + + def write_gcode(self, gcode, print_status=False, print_blocks=False, encoding='utf8'): + if not isinstance(gcode, bytes): + gcode = bytes(gcode, encoding) + gcode_len = len(gcode) + + def adler32_hash(): + blocksize = 256*1024*1024 + hash = 0 + for ptr in range(0, gcode_len, blocksize): + end = min(ptr+blocksize, gcode_len) + data = gcode[ptr:end] + hash = adler32(data, hash) + if hash < 0: + hash += 2**32 + return hash + + hash = adler32_hash() + self.write(4, '{"metadata":{"version":1,"type":"file_push"},"file_push":{"size":'+str(gcode_len)+',"adler32":'+str(hash)+',"job_id":""}}') + + # submit gcode in block and retrieve status every 20 blocks + status_frequency = 20 + blocksize = 5120 + status_ctx = 0 + for ptr in range(0, gcode_len, blocksize): + status_ctx += 1 + if status_ctx > 20: + status = self.read(0x83) + if print_status: + print('# printer-status:') + print(status) + status_ctx = 0 + + end = min(gcode_len, ptr+blocksize) + block = gcode[ptr:end] + print(f'# block: {ptr}-{end}, block-size: {len(block)}') + if print_blocks: + print(block) + + self.write(4, block) + + def write_gcode_file(self, filename, *args, **kwargs): + with open(filename, 'rb') as f: + gcode = f.read() + self.write_gcode(gcode, *args, **kwargs) + + def read(self, ep): + text = ''.join(map(chr, self.dev.read(ep, 64))) + fulltext = text + while len(text) == 64: + text = ''.join(map(chr, self.dev.read(ep, 64))) + fulltext = fulltext + text + return fulltext + + def get_status(self): + self.write(*self.PAYLOADS.STATUS) + return self.read(0x83) + + def print_status(self, loop=False, loop_sleep=5): + first_run = True + while loop or first_run: + print(self.get_status()) + if not loop: + break + time.sleep(loop_sleep) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Interact with a NewMatter Mod-T printer.') + parser.add_argument('--no-status-loop', help='Do not print the printers status in a loop', action='store_true') + + subparsers = parser.add_subparsers(title='available sub-commands', dest='subcmd') + subparsers.add_parser('bio_version', help='Get bio version. Seems to be equal to status.') + subparsers.add_parser('enter_dfu', help='Enter dfu mode') + + parser_fwupdate = subparsers.add_parser('firmware_update', help='Update firmware. Not implemented. Check https://github.com/tripflex/MOD-t for firmware-versions.') + parser_fwupdate.add_argument('file', help='DFU file containing the firmware.') + + subparsers.add_parser('load_filament', help='Load filament') + + parser_gcode = subparsers.add_parser('send_gcode', help='Send the contents of a gcode-file to the printer') + parser_gcode.add_argument('file', help='Path to the gcode-file. Must be utf8 encoded.') + parser_gcode.add_argument('--print-blocks', help='Print submitted blocks to screen', action='store_true') + parser_gcode.add_argument('--print-status', help='Print the printers status every 20 blocks', action='store_true') + + subparsers.add_parser('status', help='Retrieve the printers status') + subparsers.add_parser('unload_filament', help='Unload filament') + subparsers.add_parser('wifi_status', help='Get wifi client status. Seems to be equal to status.') + + args = parser.parse_args() + + cmd_map = { + 'bio_version': (ModT.PAYLOADS.BIO_GET_VERSION, 0x81), + 'enter_dfu': (ModT.PAYLOADS.ENTER_DFU_MODE, None), + 'load_filament': (ModT.PAYLOADS.LOAD_INITIATE, None), + #'status': ModT.PAYLOADS.STATUS, + 'unload_filament': (ModT.PAYLOADS.UNLOAD_INITIATE, None), + 'wifi_stats': (ModT.PAYLOADS.WIFI_CLIENT_GET_STATUS, 0x81) + } + + try: + printer = ModT() + except ValueError as err: + print(str(err)) + quit(1) + + if args.subcmd in cmd_map: + wargs, rendpoint = cmd_map[args.subcmd] + printer.write(*wargs) + if rendpoint is not None: + print(printer.read(rendpoint)) + elif args.subcmd == 'send_gcode': + printer.write_gcode_file(args.file, args.print_status, args.print_blocks) + elif args.subcmd == 'firmware_update': + #This script *SHOULD* eventually be the all-encompassing firmware update + #It should call the check FW script, place the Mod-T into DFU mode and flash the firmware + #It should have a command-line arg to flash older firmware versions, none of this is really implemented yet + printer.write(*ModT.PAYLOADS.ENTER_DFU_MODE) + + printer.print_status(loop=(not args.no_status_loop)) diff --git a/modt_status.py b/modt_status.py deleted file mode 100644 index 19277a7..0000000 --- a/modt_status.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python - -# Requires pyusb and permissions to read/write the mod-t via USB. -# Just polls the Mod-T for status JSON - -import sys -import os -import usb.core -import usb.util -import time - -# Read pending data from MOD-t (bulk reads of 64 bytes) -def read_modt(ep): - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = text - while len(text)==64: - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = fulltext + text - return fulltext - -# Find MOD-t usb device -dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) - -# was it found? -if dev is None: - raise ValueError('Device not found') - -#Finally, loop and query mod-t status every 5 seconds -while True: - dev.write(4, '{"metadata":{"version":1,"type":"status"}}') - print(read_modt(0x83)) - time.sleep(5) \ No newline at end of file diff --git a/readme.md b/readme.md index 9c73a5c..201d401 100644 --- a/readme.md +++ b/readme.md @@ -1,32 +1,33 @@ -##First and foremost: +# First and foremost: **USE THESE SCRIPTS AT YOUR OWN RISK!** These scripts aren't very well tested and almost none of them are completely finished. They work to interface with the printer but it's all very manual. -##Overview and license +# Overview and license This is a basic set of utility scripts to interface with the New Matter Mod-T 3d printer on Linux. You are free to fork and contribute as you see fit. This work falls under the MIT license. See the LICENSE file for more information. -I must also credit /u/modtdev on reddit for the work on getting the gcode sent to the Mod-T +I must also credit /u/modtdev on reddit for the work on getting the gcode sent to the Mod-T. +# Depdencies +1. `curl` (only for maintenance) +2. `dfu-util` (only for maintenance) +3. `python3` +4. `python3-pyusb` (`python3 -m pip install pyusb`) -###Depdencies -1. curl -2. *dfu-util -3. *python3 -4. *python3-pyusb - - -##Usage +# Usage First, I recommend creating a udev rule for the Mod-T similar to the following: +``` /etc/udev/rules.d/51-modt.rules: SUBSYSTEM=="usb", ATTR{idVendor}=="2b75", ATTR{idProduct}=="0002", GROUP="users", MODE="0674" -SUBSYSTEM=="usb", ATTR{idVendor}=="2b75", ATTR{idProduct}=="0003", GROUP="users", MODE=0674" + SUBSYSTEM=="usb", ATTR{idVendor}=="2b75", ATTR{idProduct}=="0003", GROUP="users", MODE="0674" +``` In the above I have everyone in the `users` group enabled, however you can restrict access as needed. This allows a regular user to run the scripts and still have them operate as intended. +To interact with the ModT-printer just mark `modt.py` as executable or invoke it with `python3 modt.py`. A list of available commands can be retrieved with `python3 modt.py -h`. The tool will print the printers status every 5 seconds in an endless loop to console unless you set the `--no-status-loop` argument. -From there most scripts are pretty straightforward. Simply flag them executable and run them. The `send_gcode.py`, `flash_firmware.sh`, and `fw_update.py` are the only scripts requiring arguments, each of them expect a single argument containing the path to an appropriate file for the function requested. +`flash_firmware.sh` requires one argument specifying the DFU-update file. -If you do choose to use these scripts to print things by sending gcode, monitor the output of the `send_gcode.py` script until you see `STATE_JOB_QUEUED` as the printer status. If you press the front panel button prior to seeing this state you will have a broken print job, which will not print correctly. +If you do choose to use these scripts to print things by sending gcode, monitor the output of the `send_gcode` command until you see `STATE_JOB_QUEUED` as the printer status. If you press the front panel button prior to seeing this state you will have a broken print job, which will not print correctly. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6513d5e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyusb diff --git a/send_gcode.py b/send_gcode.py deleted file mode 100644 index 9786c8d..0000000 --- a/send_gcode.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python - -# USAGE: send_gcode.py file.gcode -# Requires read/write permission to the Mod-T via USB. - -import sys -import os -import usb.core -import usb.util -import time -from zlib import adler32 - -# Adler32 checksum function -# Based on https://gist.github.com/kofemann/2303046 -# For some reason, mod-t uses 0, not 1 as the basis of the adler32 sum -BLOCKSIZE=256*1024*1024 - -def adler32_checksum(fname): - asum = 0 - f = open(fname, "rb") - while True: - data = f.read(BLOCKSIZE) - if not data: - break - asum = adler32(data, asum) - if asum < 0: - asum += 2**32 - f.close() - return asum - -# Read pending data from MOD-t (bulk reads of 64 bytes) -def read_modt(ep): - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = text - while len(text)==64: - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = fulltext + text - return fulltext - -# Main program -if __name__ == '__main__': - - if not len(sys.argv)==2: - print("Usage: send_gcode.py filename.gcode") - quit() - -# Read filename argument and check that the file exists -fname=str(sys.argv[1]) -if not os.path.isfile(fname): - print(fname + " not found") - quit() - -# Get the size of the gcode file -size = os.path.getsize(fname) - -# Get the adler32 checksum of the gcode file -checksum=adler32_checksum(fname) - -# Open gcode file and read into buffer -f = open(fname, "rb") -gcode = f.read() -f.close() - -# Find MOD-t usb device -dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) - -# was it found? -if dev is None: - raise ValueError('Device not found') - -# set the active configuration. With no arguments, the first -# configuration will be the active one -dev.set_configuration() - -# These came from usb dump. -# Some commands are human readable some are maybe checksums -dev.write(2, bytearray.fromhex('246a0095ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":3},"data":{"command":{"idx":0,"name":"bio_get_version"}}};') -print(read_modt(0x81)) - -dev.write(4, '{"metadata":{"version":1,"type":"status"}}') -print(read_modt(0x83)) - -dev.write(2, bytearray.fromhex('248b0074ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":5},"data":{"command":{"idx":22,"name":"wifi_client_get_status","args":{"interface_t":0}}}};') -print(read_modt(0x81)) - -dev.write(2, bytearray.fromhex('246a0095ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":7},"data":{"command":{"idx":0,"name":"bio_get_version"}}};') -print(read_modt(0x81)) - -dev.write(4, '{"metadata":{"version":1,"type":"status"}}') -print(read_modt(0x83)) - -dev.write(4, '{"metadata":{"version":1,"type":"status"}}') -print(read_modt(0x83)) - -# Start writing actual gcode -# File size and adler32 checksum calculated earlier -dev.write(4, '{"metadata":{"version":1,"type":"file_push"},"file_push":{"size":'+str(size)+',"adler32":'+str(checksum)+',"job_id":""}}') - -# Write gcode in batches of 20 bulk writes, each 5120 bytes. -# Read mod-t status between these 20 bulk writes - -start=0 -counter=0 -while True: - if (start+5120-1>size-1): - end=size - else: - end=start+5120 - block = gcode[start:end] - print(str(counter)+':' +str(start)+'-'+str(end-1)+'\t'+str(len(block))) - counter += 1 - if counter>=20: - temp=read_modt(0x83) - counter = 0 - dev.write(4, block) - if (start == 0): - temp=read_modt(0x83) - start = start + 5120 - if (start>size): - break; - -# Gcode sent. Finally, loop and query mod-t status every 5 seconds -while True: - dev.write(4, '{"metadata":{"version":1,"type":"status"}}') - print(read_modt(0x83)) - time.sleep(5) \ No newline at end of file diff --git a/unload_filament.py b/unload_filament.py deleted file mode 100644 index 296d457..0000000 --- a/unload_filament.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python - -#Just tells the mod-t to enter unload filament mode. It also polls the mod-t for status -#This status information can be used to prompt the user to proceed as intended. - -import sys -import os -import usb.core -import usb.util -import time - -# Read pending data from MOD-t (bulk reads of 64 bytes) -def read_modt(ep): - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = text - while len(text)==64: - text=''.join(map(chr, dev.read(ep, 64))) - fulltext = fulltext + text - return fulltext - -#Find the Mod-T - we should probably see if it's in DFU mode, too -#That way we can do emergency flashes from recovery mode -dev = usb.core.find(idVendor=0x2b75, idProduct=0x0002) - -#If we didn't find a Mod-T we need to throw an error -if dev is None: - raise ValueError('No Mod-T detected') - -#Set active configuration (first is default) -dev.set_configuration() - -#Same as all the other files, this packet is not readable -#The second packet however, is - -dev.write(2, bytearray.fromhex('246c0093ff')) -dev.write(2, '{"transport":{"attrs":["request","twoway"],"id":11},"data":{"command":{"idx":51,"name":"unload_initiate"}}};') - -while True: - dev.write(4, '{"metadata":{"version":1,"type":"status"}}') - print(read_modt(0x83)) - time.sleep(5) From a1603690172081df82cdc8382a0b00f8fb29b934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 12:24:55 +0200 Subject: [PATCH 02/10] flash_firmware: dfu-file as parameter, not hardcoded --- flash_firmware.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/flash_firmware.sh b/flash_firmware.sh index 8e5b6fe..b2adc76 100644 --- a/flash_firmware.sh +++ b/flash_firmware.sh @@ -2,7 +2,13 @@ #This script actually runs dfu-util to flash firmware to the Mod-T #Currently the firmware version and location is hard-coded for testing purposes. Eventually this should be changed to $1 #Actually start writing the firmware, in the background, and log to a file. -dfu-util -d 2b75:0003 -a 0 -s 0x0:leave -D /home/xaero/Downloads/firmware_modt_override.dfu > /tmp/dfu & + +if [ "$1" == "" ]; then + echo "No DFU-file specified" + exit 1 +fi + +dfu-util -d 2b75:0003 -a 0 -s 0x0:leave -D $1 > /tmp/dfu & #Loop until the firmware has been written while true; do @@ -25,4 +31,3 @@ done #cleanup our temporary file rm /tmp/dfu - From 057a627cc32cc8a68d6c63beaad77f85d7067ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 12:25:29 +0200 Subject: [PATCH 03/10] rm scripts to download firmware Servers seem to be non-functional atm. Download firmware from https://github.com/tripflex/MOD-t --- curl.sh | 39 ----------------------------- latest_firmware.py | 62 ---------------------------------------------- 2 files changed, 101 deletions(-) delete mode 100644 curl.sh delete mode 100644 latest_firmware.py diff --git a/curl.sh b/curl.sh deleted file mode 100644 index b05d38b..0000000 --- a/curl.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -#This script just downloads the firmware from the new matter store -#This script is adapted for a websocket fronted and processes -#$_POST data expected ?url= -#Evaluating that query string results in setting the variable "url" to the actual intended download url -#kind of hacky but really the only way to interface with websockets. Needs sanitization etc. -#echo for debugging, eval to actually put the variable in -echo $QUERY_STRING - -eval $QUERY_STRING - -#Another echo for debugging, let's make sure we have a url variable now -echo $url - -#start downloading, log the download progress, and fork into the background -curl -# --limit-rate 5K --output /dev/null $url > /tmp/curl.log 2>&1 & - -#capture the PID for the while loop -pid=$! - -#Loop while curl is still alive -while kill -0 $pid 2>/dev/null; do - #We just need the progress percentage - progress=`grep -ao '[^ ]*%' /tmp/curl.log | tail -1` - #Send that over to the webpage - echo $progress - #Doesn't need to update very often - sleep 0.1 -done - -#We won't always capture the 100% mark, so we force it if we make it this far. -#Suggested fix: check if $pid curl exits gracefully. use try...catch statement to handle HTTP response codes -echo "100%" - -/* - *TODO: The above is ugly, and breaks easily - *Need to implement error handling, etc, what if the server is down? - *No internet? What if the data passed to the url variable is malformed? - */ diff --git a/latest_firmware.py b/latest_firmware.py deleted file mode 100644 index 5bbcee0..0000000 --- a/latest_firmware.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/python2 - -# This script just reaches out to the New Matter server and asks what the latest firmware is. -# Since their page returns JSON we get the name of the firmware as well as the URL to it as result. -import re -import os.path -import urllib2 -import base64 -import gzip -import zlib -from StringIO import StringIO -from io import BytesIO - -def make_requests(): - response = [None] - responseText = None - - if(request_de_newmatter_com(response)): - responseText = read_response(response[0]) - print responseText - response[0].close() - - -def read_response(response): - if response.info().get('Content-Encoding') == 'gzip': - buf = StringIO(response.read()) - return gzip.GzipFile(fileobj=buf).read() - - elif response.info().get('Content-Encoding') == 'deflate': - decompress = zlib.decompressobj(-zlib.MAX_WBITS) - inflated = decompress.decompress(response.read()) - inflated += decompress.flush() - return inflated - - return response.read() - - -def request_de_newmatter_com(response): - response[0] = None - - try: - req = urllib2.Request("https://de.newmatter.com/api/fw/racingmoon/version?v=2&sid=C14BUVBXBlNWUgRVBgQKCFNTBwUHBQMA") - req.add_header("Authorization", "Basic NDVkMDJiZGU1NjhkNmQwMWJmNjM3ZmNmZWJjM2FjODU6OTkwMzZjZTQzZTZiNTk5ZTNhNDc2NTJjZjRiNTc3ZWUyNGQyOTI0NzIxNGU2M2UxM2UwZDQ0N2IwMzg4NzY3ZA==") - req.add_header("User-Agent", "Mozilla 5.0 (MOD-t printer tool 1.4.2)") - req.add_header("Content-Type", "application/json") - req.add_header("Connection", "Keep-Alive") - req.add_header("Accept-Encoding", "gzip, deflate") - req.add_header("Accept-Language", "en-US,*") - - response[0] = urllib2.urlopen(req) - - except urllib2.URLError, e: - if not hasattr(e, "code"): - return False - response[0] = e - except: - return False - - return True - - -make_requests() From f64a83911fff6259efefc490217ecf66518f8f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 12:27:56 +0200 Subject: [PATCH 04/10] readme: rm curl dependency --- readme.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 201d401..1f38231 100644 --- a/readme.md +++ b/readme.md @@ -8,10 +8,9 @@ This work falls under the MIT license. See the LICENSE file for more information I must also credit /u/modtdev on reddit for the work on getting the gcode sent to the Mod-T. # Depdencies -1. `curl` (only for maintenance) -2. `dfu-util` (only for maintenance) -3. `python3` -4. `python3-pyusb` (`python3 -m pip install pyusb`) +1. `dfu-util` (only for maintenance) +2. `python3` +3. `python3-pyusb` (`python3 -m pip install pyusb`) # Usage From 02177d0f36e33a02b39c530a225fad47585c2b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 19:29:59 +0200 Subject: [PATCH 05/10] add clear_nozzle command --- modt.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/modt.py b/modt.py index 470ca03..5ca84cd 100644 --- a/modt.py +++ b/modt.py @@ -6,6 +6,16 @@ import time from zlib import adler32 +class GCODES: + CLEAR_NOZZLE = ''' + M82 + M109 S230.0 ;set temp and wait + G0 Z100 F300 ; move to Z 100 when @ temp + G1 E10.0000 F9000.00000 ; extrude some filament + G0 Z50 F100 ; move down slowly after extruding + ; https://github.com/ajfoul/MOD-t/blob/master/clearnozzle.gcode + ''' + class ModT: class PAYLOADS: BIO_GET_VERSION = (2, '{"transport":{"attrs":["request","twoway"],"id":3},"data":{"command":{"idx":0,"name":"bio_get_version"}}};') @@ -30,6 +40,7 @@ def __init__(self): self.dev.write(2, bytearray.fromhex('246c0093ff')) def write(self, endpoint, message): + print(endpoint, message) self.dev.write(endpoint, message) def write_gcode(self, gcode, print_status=False, print_blocks=False, encoding='utf8'): @@ -103,6 +114,7 @@ def print_status(self, loop=False, loop_sleep=5): subparsers = parser.add_subparsers(title='available sub-commands', dest='subcmd') subparsers.add_parser('bio_version', help='Get bio version. Seems to be equal to status.') + subparsers.add_parser('clear_nozzle', help='Execute gcode to clear the nozzle') subparsers.add_parser('enter_dfu', help='Enter dfu mode') parser_fwupdate = subparsers.add_parser('firmware_update', help='Update firmware. Not implemented. Check https://github.com/tripflex/MOD-t for firmware-versions.') @@ -127,7 +139,7 @@ def print_status(self, loop=False, loop_sleep=5): 'load_filament': (ModT.PAYLOADS.LOAD_INITIATE, None), #'status': ModT.PAYLOADS.STATUS, 'unload_filament': (ModT.PAYLOADS.UNLOAD_INITIATE, None), - 'wifi_stats': (ModT.PAYLOADS.WIFI_CLIENT_GET_STATUS, 0x81) + 'wifi_status': (ModT.PAYLOADS.WIFI_CLIENT_GET_STATUS, 0x81) } try: @@ -138,11 +150,21 @@ def print_status(self, loop=False, loop_sleep=5): if args.subcmd in cmd_map: wargs, rendpoint = cmd_map[args.subcmd] + print(f'endpoint: {wargs[0]},', f'request: {wargs[1]}') printer.write(*wargs) if rendpoint is not None: print(printer.read(rendpoint)) + + elif args.subcmd == 'clear_nozzle': + print('Clearing nozzle.') + print('Please press the start button when you see the STATE_JOB_QUEUED message') + print('using gcode:') + print(GCODES.CLEAR_NOZZLE) + printer.write_gcode(GCODES.CLEAR_NOZZLE) + elif args.subcmd == 'send_gcode': printer.write_gcode_file(args.file, args.print_status, args.print_blocks) + elif args.subcmd == 'firmware_update': #This script *SHOULD* eventually be the all-encompassing firmware update #It should call the check FW script, place the Mod-T into DFU mode and flash the firmware From 3b634e206a798d2a47df0f650cb2ae8d2a8fe494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 19:31:32 +0200 Subject: [PATCH 06/10] mark status as default command --- modt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modt.py b/modt.py index 5ca84cd..2e823fd 100644 --- a/modt.py +++ b/modt.py @@ -127,7 +127,7 @@ def print_status(self, loop=False, loop_sleep=5): parser_gcode.add_argument('--print-blocks', help='Print submitted blocks to screen', action='store_true') parser_gcode.add_argument('--print-status', help='Print the printers status every 20 blocks', action='store_true') - subparsers.add_parser('status', help='Retrieve the printers status') + subparsers.add_parser('status', help='Retrieve the printers status. This is the default action.') subparsers.add_parser('unload_filament', help='Unload filament') subparsers.add_parser('wifi_status', help='Get wifi client status. Seems to be equal to status.') From bb77e06dd06806b7f566bc3682495c29dde3675c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 19:32:28 +0200 Subject: [PATCH 07/10] rm print from ModT.write --- modt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modt.py b/modt.py index 2e823fd..8d64138 100644 --- a/modt.py +++ b/modt.py @@ -40,7 +40,6 @@ def __init__(self): self.dev.write(2, bytearray.fromhex('246c0093ff')) def write(self, endpoint, message): - print(endpoint, message) self.dev.write(endpoint, message) def write_gcode(self, gcode, print_status=False, print_blocks=False, encoding='utf8'): From 7e31560af9c4420505dbd2186b18d626760bcd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Sun, 28 Jun 2020 19:40:06 +0200 Subject: [PATCH 08/10] send_gcode: printing progress is optional, show total length --- modt.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modt.py b/modt.py index 8d64138..d07f90a 100644 --- a/modt.py +++ b/modt.py @@ -42,7 +42,7 @@ def __init__(self): def write(self, endpoint, message): self.dev.write(endpoint, message) - def write_gcode(self, gcode, print_status=False, print_blocks=False, encoding='utf8'): + def write_gcode(self, gcode, print_progress=False, print_status=False, print_blocks=False, encoding='utf8'): if not isinstance(gcode, bytes): gcode = bytes(gcode, encoding) gcode_len = len(gcode) @@ -76,7 +76,8 @@ def adler32_hash(): end = min(gcode_len, ptr+blocksize) block = gcode[ptr:end] - print(f'# block: {ptr}-{end}, block-size: {len(block)}') + if print_progress: + print(f'# block: {ptr}-{end} of {gcode_len}, block-size: {len(block)}') if print_blocks: print(block) @@ -124,6 +125,7 @@ def print_status(self, loop=False, loop_sleep=5): parser_gcode = subparsers.add_parser('send_gcode', help='Send the contents of a gcode-file to the printer') parser_gcode.add_argument('file', help='Path to the gcode-file. Must be utf8 encoded.') parser_gcode.add_argument('--print-blocks', help='Print submitted blocks to screen', action='store_true') + parser_gcode.add_argument('--print-progress', help='Print the current progress for every submitted block', action='store_true') parser_gcode.add_argument('--print-status', help='Print the printers status every 20 blocks', action='store_true') subparsers.add_parser('status', help='Retrieve the printers status. This is the default action.') @@ -162,7 +164,12 @@ def print_status(self, loop=False, loop_sleep=5): printer.write_gcode(GCODES.CLEAR_NOZZLE) elif args.subcmd == 'send_gcode': - printer.write_gcode_file(args.file, args.print_status, args.print_blocks) + printer.write_gcode_file( + args.file, + print_progress = args.print_progress, + print_status = args.print_status, + print_blocks = args.print_blocks + ) elif args.subcmd == 'firmware_update': #This script *SHOULD* eventually be the all-encompassing firmware update From 1a3b65f9fc5120f723a1131a317777db9f0d801f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Tue, 30 Jun 2020 16:49:31 +0200 Subject: [PATCH 09/10] Clear nozzle at 220deg (was 230) --- modt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modt.py b/modt.py index d07f90a..5acf76e 100644 --- a/modt.py +++ b/modt.py @@ -9,7 +9,7 @@ class GCODES: CLEAR_NOZZLE = ''' M82 - M109 S230.0 ;set temp and wait + M109 S220.0 ;set temp and wait G0 Z100 F300 ; move to Z 100 when @ temp G1 E10.0000 F9000.00000 ; extrude some filament G0 Z50 F100 ; move down slowly after extruding From 8420de53dd63b2f5f1106bc882263e1c32553aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Jan=C3=9Fen?= Date: Tue, 30 Jun 2020 16:50:16 +0200 Subject: [PATCH 10/10] send_gcode: print progress in percent --- modt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modt.py b/modt.py index 5acf76e..11b5c4f 100644 --- a/modt.py +++ b/modt.py @@ -77,7 +77,7 @@ def adler32_hash(): end = min(gcode_len, ptr+blocksize) block = gcode[ptr:end] if print_progress: - print(f'# block: {ptr}-{end} of {gcode_len}, block-size: {len(block)}') + print(f'# progress: {round(100*ptr/gcode_len, 3)}%, block: {ptr}-{end} of {gcode_len}, block-size: {len(block)}') if print_blocks: print(block)