def test_send_command_error(self):
        s1, s2 = socket.socketpair()
        t = HeadController(s2.fileno(), required_module="EXTRUDER")
        t.bootstrap()
        s1.send(EXTRUDER_HELLO_RESP)
        t.handle_recv()

        s2.close()
        self.assertRaises(IOError, t.ext.set_heater, 0, 210)
Ejemplo n.º 2
0
class MaintainTask(DeviceOperationMixIn, CommandMixIn):
    st_id = -1
    mainboard = None
    toolhead = None
    busying = False
    toolhead_updating = False
    _has_zprobe = False

    def __init__(self, stack, handler):
        super(MaintainTask, self).__init__(stack, handler)

        self.busying = False
        self._has_zprobe = False

        self._macro = None
        self._on_macro_error = None
        self._on_macro_running = None

        def on_mainboard_ready(_):
            self.busying = False
            handler.send_text("ok")

        def on_mainboard_empty(sender):
            if self._macro:
                self._macro.on_command_empty(self)

        def on_mainboard_sendable(sender):
            if self._macro:
                self._macro.on_command_sendable(self)

        def on_mainboard_ctrl(sender, data):
            if self._macro:
                self._macro.on_ctrl_message(self, data)

        self.mainboard = MainController(
            self._sock_mb.fileno(),
            bufsize=14,
            empty_callback=on_mainboard_empty,
            sendable_callback=on_mainboard_sendable,
            ctrl_callback=on_mainboard_ctrl)
        self.toolhead = HeadController(self._sock_th.fileno())

        self.mainboard.bootstrap(on_mainboard_ready)
        self.toolhead.bootstrap(self.on_toolhead_ready)

    def on_toolhead_ready(self, sender):
        if sender.module_name and sender.module_name != "N/A":
            tools.toolhead_on()

    def on_mainboard_message(self, watcher, revent):
        try:
            self.mainboard.handle_recv()

        except IOError as e:
            from errno import EAGAIN
            if e.errno == EAGAIN:
                # TODO: There is a recv bug in C code, this is a quit fix to
                # passthrough it.
                return
            logger.exception("Mainboard connection broken")
            if self.mainboard.ready:
                self.handler.send_text("error SUBSYSTEM_ERROR")
                self.handler.close()
            else:
                self.handler.send_text("error SUBSYSTEM_ERROR")
                self.stack.exit_task(self)
        except (RuntimeError, SystemError) as e:
            if self._macro:
                self._on_macro_error(e)
        except Exception:
            logger.exception("Unhandle Error")

    def on_headboard_message(self, watcher, revent):
        try:
            self.toolhead.handle_recv()
        except IOError:
            logger.exception("Toolhead connection broken")
            self.stack.exit_task(self)

        except (HeadOfflineError, HeadResetError) as e:
            logger.debug("Head reset")
            tools.toolhead_standby()
            self.toolhead.bootstrap(self.on_toolhead_ready)
            if self._macro:
                self._on_macro_error(e)

        except HeadError as e:
            logger.info("Head Error: %s", e)

        except SystemError as e:
            logger.exception("Unhandle Error")
            if self._macro:
                self._on_macro_error(e)

        except Exception:
            logger.exception("Unhandle Error")

    def dispatch_cmd(self, handler, cmd, *args):
        if cmd == "stop_load_filament":
            self.mainboard.send_cmd("@HOME_BUTTON_TRIGGER\n", raw=1)
            return
        elif self.busying:
            raise RuntimeError(RESOURCE_BUSY)

        if cmd == "home":
            self.do_home(handler)

        elif cmd == "move":
            try:
                opt = {
                    k: float(v)
                    for k, v in (arg.split(':', 1) for arg in args)
                }
                self.do_move(handler, **opt)
            except (ValueError, IndexError):
                raise RuntimeError(UNKNOWN_COMMAND)

        elif cmd == "calibration" or cmd == "calibrate":
            try:
                threshold = float(args[0])
                if threshold < 0.01:
                    threshold = 0.01
            except (ValueError, IndexError):
                threshold = float("inf")

            clean = "clean" in args
            self.do_calibrate(handler, threshold, clean=clean)

        elif cmd == "zprobe":
            if len(args) > 0:
                h = float(args[0])
                self.do_zprobe(handler, h=h)
            else:
                self.do_zprobe(handler)

        elif cmd == "load_filament":
            self.do_load_filament(handler, int(args[0]), float(args[1]))

        elif cmd == "load_flexible_filament":
            self.do_load_filament(handler, int(args[0]), float(args[1]), True)

        elif cmd == "unload_filament":
            self.do_unload_filament(handler, int(args[0]), float(args[1]))

        elif cmd == "headinfo":
            self.head_info(handler)

        elif cmd == "headstatus":
            self.head_status(handler)

        elif cmd == "reset_mb":
            reset_mb()
            self.stack.exit_task(self)
            handler.send_text("ok")

        elif cmd == "extruder_temp":
            self.do_change_extruder_temperature(handler, *args)

        elif cmd == "diagnosis_sensor":
            self.diagnosis_sensor(handler)

        elif cmd == "update_head":
            self.update_toolhead_fw(handler, *args)

        elif cmd == "hal_diagnosis":
            self.do_hal_diagnosis(handler)

        elif cmd == "quit":
            self.stack.exit_task(self)
            handler.send_text("ok")
        else:
            logger.debug("Can not handle: '%s'" % cmd)
            raise RuntimeError(UNKNOWN_COMMAND)

    def diagnosis_sensor(self, handler):
        dataset = []

        def on_message_cb(msg):
            if msg.startswith("DATA "):
                kv = msg[5:].split(" ", 1)
                if len(kv) == 2:
                    dataset.append("%s=%s" % (kv[0], kv[1]))
                else:
                    dataset.append(kv)

        def on_success_cb():
            handler.send_text("ok " + "\x00".join(dataset))
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            handler.send_text("error %s" % " ".join(error.args))
            self.busying = False

        self._macro = macro.CommandMacro(on_success_cb, ["M666L1"],
                                         on_message_cb)
        self._on_macro_error = on_macro_error
        self.busying = True
        self._macro.start(self)

    def do_change_extruder_temperature(self, handler, sindex, stemp):
        if not self.toolhead.ready or not self.toolhead.sendable():
            raise HeadError(EXEC_HEAD_ERROR, RESOURCE_BUSY)
        module = self.toolhead.status["module"]
        if module != "EXTRUDER":
            raise HeadTypeError("EXTRUDER", module)

        self.toolhead.ext.set_heater(int(sindex), float(stemp))
        handler.send_text("ok")

    def do_move(self, handler, **kw):
        def on_success_cb():
            handler.send_text("ok")
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            handler.send_text("error %s" % " ".join(error.args))
            self.busying = False

        subcmd = ''.join(MOVE_COMMAND[k] % v for k, v in kw.items()
                         if k in MOVE_COMMAND)
        if 'E' in subcmd:
            cmds = ['T2', 'G92E0', 'G1' + subcmd, 'T0']
        else:
            cmds = ['G1' + subcmd]

        self._macro = macro.CommandMacro(on_success_cb, cmds)
        self._on_macro_error = on_macro_error
        self._macro.start(self)
        self.busying = True

    def do_load_filament(self, handler, index, temp, disable_accelerate=False):
        if not self.toolhead.ready or not self.toolhead.sendable():
            raise HeadError(EXEC_HEAD_ERROR, RESOURCE_BUSY)
        module = self.toolhead.status["module"]
        if module != "EXTRUDER":
            raise HeadTypeError("EXTRUDER", module)

        def on_load_done():
            handler.send_text("ok")
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False
            handler.send_text("error %s" % " ".join(error.args))

        def on_heating_done():
            def on_message(msg):
                try:
                    if msg == "FILAMENT+":
                        handler.send_text("CTRL LOADING")
                    elif msg == "FILAMENT-":
                        handler.send_text("CTRL WAITING")
                except Exception:
                    self.mainboard.send_cmd("@HOME_BUTTON_TRIGGER\n")
                    raise

            opt = Options(head="EXTRUDER")
            if opt.plus_extrusion:
                self.mainboard.send_cmd("M92E145")
            self._macro = macro.LoadFilamentMacro(on_load_done, index,
                                                  opt.filament_detect,
                                                  disable_accelerate,
                                                  on_message)
            self._macro.start(self)

        def on_macro_running():
            if isinstance(self._macro, macro.ControlHeaterMacro):
                rt = self.toolhead.status.get("rt")
                if rt:
                    try:
                        handler.send_text("CTRL HEATING %.1f" % rt[index])
                    except IndexError:
                        pass

        self._macro = macro.ControlHeaterMacro(on_heating_done, index, temp)
        self._on_macro_error = on_macro_error
        self._on_macro_running = on_macro_running
        self.busying = True
        self._macro.start(self)
        handler.send_text("continue")

    def do_unload_filament(self, handler, index, temp):
        if not self.toolhead.ready:
            raise HeadError(EXEC_HEAD_ERROR, RESOURCE_BUSY)
        module = self.toolhead.status["module"]
        if module != "EXTRUDER":
            raise HeadTypeError("EXTRUDER", module)

        def on_load_done():
            handler.send_text("ok")
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_heating_done():
            self._macro = macro.UnloadFilamentMacro(on_load_done, index)
            self._macro.start(self)

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False
            handler.send_text("error %s" % " ".join(error.args))

        def on_macro_running():
            if isinstance(self._macro, macro.WaitHeadMacro):
                st = self.toolhead.status.copy()
                handler.send_text("CTRL HEATING %.1f" % st.get("rt")[index])
            else:
                handler.send_text("CTRL UNLOADING")

        self._macro = macro.ControlHeaterMacro(on_heating_done, index, temp)
        self._on_macro_error = on_macro_error
        self._on_macro_running = on_macro_running
        self.busying = True
        self._macro.start(self)
        handler.send_text("continue")

    def do_home(self, handler):
        def on_success_cb():
            handler.send_text("ok")
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            handler.send_text("error %s" % " ".join(error.args))
            self.busying = False

        cor = Preference.instance().plate_correction
        self._macro = macro.CommandMacro(on_success_cb, [
            "M666X%(X).4fY%(Y).4fZ%(Z).4fR%(R).4fD%(D).5fH%(H).4f" % cor,
            "G28+"
        ])
        self._on_macro_error = on_macro_error
        self.busying = True
        self._macro.start(self)

    def do_calibrate(self, handler, threshold, clean=False):
        def on_success_cb():
            while self._macro.debug_logs:
                handler.send_text("DEBUG " + self._macro.debug_logs.popleft())

            p1, p2, p3 = self._macro.history[-1]
            handler.send_text("ok %.4f %.4f %.4f" % (p1, p2, p3))
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False
            handler.send_text("error %s" % " ".join(error.args))

        def on_macro_running():
            while self._macro.debug_logs:
                handler.send_text("DEBUG " + self._macro.debug_logs.popleft())
            handler.send_text("CTRL POINT %i" % len(self._macro.data))

        opt = Options(head="EXTRUDER")

        correct_at_final = True if threshold == float("inf") else False

        if self._has_zprobe is False:
            self._has_zprobe = True
            opt = Options(head="EXTRUDER")
            self._macro = macro.CorrectionMacro(
                on_success_cb,
                threshold=threshold,
                clean=clean,
                dist=opt.zprobe_dist,
                correct_at_final=correct_at_final)
        else:
            self._macro = macro.CorrectionMacro(
                on_success_cb,
                threshold=threshold,
                clean=clean,
                correct_at_final=correct_at_final)

        self._on_macro_error = on_macro_error
        self._on_macro_running = on_macro_running
        self._on_macro_error = on_macro_error
        self.busying = True
        self._macro.start(self)
        handler.send_text("continue")

    def do_zprobe(self, handler, h=None):
        def on_success_cb():
            while self._macro.debug_logs:
                handler.send_text("DEBUG " + self._macro.debug_logs.popleft())
            handler.send_text("ok %.4f" % self._macro.history[0])
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False

        def on_macro_error(error):
            self._macro.giveup(self)
            self._macro = self._on_macro_error = self._on_macro_running = None
            self.busying = False
            handler.send_text("error %s" % " ".join(error.args))

        def on_macro_running():
            handler.send_text("CTRL ZPROBE")

        if self._has_zprobe is False:
            self._has_zprobe = True
            opt = Options(head="EXTRUDER")
            self._macro = macro.ZprobeMacro(on_success_cb,
                                            threshold=float("inf"),
                                            dist=opt.zprobe_dist)
        else:
            self._macro = macro.ZprobeMacro(on_success_cb,
                                            threshold=float("inf"))
        self._on_macro_running = on_macro_running
        self._on_macro_error = on_macro_error
        self.busying = True
        self._macro.start(self)
        handler.send_text("continue")

    def do_hal_diagnosis(self, handler):
        memory_stack = []

        def on_resp(ret, data):
            try:
                io_w, timer_w, sock = data
                io_w.stop()
                timer_w.stop()
                sock.close()
            finally:
                handler.send_text("ok %s" % ret)
                self.busying = False
                while memory_stack:
                    memory_stack.pop()

        def on_io(watcher, revent):
            ret = tools.hal_diagnosis_result(watcher.data[-1])
            on_resp(ret, watcher.data)

        def on_timeout(watcher, revent):
            on_resp("HAL_TIMEOUT", watcher.data)

        try:
            sock = tools.begin_hal_diagnosis()
            self.busying = True

            io_watcher = self.stack.loop.io(sock, EV_READ, on_io)
            t_watcher = self.stack.loop.timer(90, 0, on_timeout)
            memory_stack.append(io_watcher)
            memory_stack.append(t_watcher)
            memory_stack.append(sock)

            io_watcher.data = t_watcher.data = (io_watcher, t_watcher, sock)
            io_watcher.start()
            t_watcher.start()
        except Exception:
            logger.exception("HAL diagnosis error")
            handler.send_text("error SUBSYSTEM_ERROR")

    def head_info(self, handler):
        dataset = self.toolhead.profile.copy()
        dataset["version"] = dataset.get("VERSION")
        dataset["module"] = dataset.get("TYPE")
        handler.send_text("ok " + dumps(dataset))

    def head_status(self, handler):
        handler.send_text("ok " + dumps(self.toolhead.status))

    def update_toolhead_fw(self, handler, mimetype, sfilesize):
        def ret_callback(success):
            logger.debug("Toolhead update retuen: %s", success)
            self.toolhead_updating = False

        filesize = int(sfilesize)
        if filesize > (1024 * 256):
            raise RuntimeError(TOO_LARGE)

        t = UpdateHbFwTask(self.stack, handler, filesize)
        self.stack.enter_task(t, ret_callback)
        handler.send_text("continue")
        self.toolhead_updating = True

    def on_timer(self, watcher, revent):
        metadata.update_device_status(self.st_id, 0, "N/A",
                                      self.handler.address)

        if self.toolhead_updating:
            return

        try:
            self.mainboard.patrol()
            self.toolhead.patrol()

            if self._on_macro_running:
                self._on_macro_running()

        except (HeadOfflineError, HeadResetError) as e:
            logger.info("%s", e)
            tools.toolhead_standby()
            if self._macro:
                self._on_macro_error(e)
            self.toolhead.bootstrap(self.on_toolhead_ready)

        except RuntimeError as e:
            logger.info("%s", e)
            if self._macro:
                self._on_macro_error(e)

        except SystemError:
            if self.busying:
                self.handler.send_text("error %s" % SUBSYSTEM_ERROR)
                self.stack.exit_task(self)
            else:
                logger.exception("Mainboard dead during maintain")
                self.handler.send_text("error %s" % SUBSYSTEM_ERROR)
                self.handler.close()

        except IOError:
            logger.warn("Socket IO Error")
            self.handler.close()

        except Exception:
            logger.exception("Unhandle error")
            self.handler.close()

    def clean(self):
        try:
            if self.mainboard:
                self.mainboard.send_cmd("@HOME_BUTTON_TRIGGER\n", raw=1)
                self.mainboard.close()
                self.mainboard = None
        except Exception:
            logger.exception("Mainboard error while quit")

        try:
            if self.toolhead:
                if self.toolhead.ready:
                    # > Check should toolhead power deplayoff
                    if self.toolhead.module_name == "EXTRUDER":
                        for t in self.toolhead.status.get("rt", ()):
                            if t > 60:
                                logger.debug("Set toolhead delay off")
                                metadata.delay_toolhead_poweroff = b"\x01"
                                break
                    # ^
                    self.toolhead.shutdown()
                self.toolhead = None
        except Exception:
            logger.exception("Toolhead error while quit")

        try:
            tools.toolhead_standby()
        except Exception:
            logger.exception("HAL control error while quit")

        metadata.update_device_status(0, 0, "N/A", "")
Ejemplo n.º 3
0
class IControlTask(DeviceOperationMixIn):
    st_id = -3  # Device status ID
    main_e_axis = 0  # E axis control
    cmd_index = 0  # Command counter
    cmd_queue = None  # Command store queue
    udp_sock = None  # UDP socket to send status
    handler = None  # Client TCP connection object
    known_position = None  # Is toolhead position is known or not
    mainboard = None  # Mainborad Controller
    toolhead = None  # Headboard Controller
    head_resp_stack = None  # Toolhead raw rasponse stack

    def __init__(self, stack, handler):
        super(IControlTask, self).__init__(stack, handler)
        self.handler = proxy(handler)
        self.handler.binary_mode = True
        self.cmd_queue = deque()
        self.meta = Metadata.instance()

        self._ready = 0

        def on_mainboard_ready(ctrl):
            self._ready |= 1
            self.mainboard.send_cmd("X8F")
            self.mainboard.send_cmd("T0")
            self.mainboard.send_cmd("G90")
            self.mainboard.send_cmd("G92E0")
            handler.send_text("ok")

        self.mainboard = MainController(
            self._sock_mb.fileno(),
            bufsize=14,
            empty_callback=self.on_mainboard_empty,
            sendable_callback=self.on_mainboard_sendable,
            ctrl_callback=self.on_mainboard_result)
        self.toolhead = HeadController(
            self._sock_th.fileno(),
            msg_callback=self.toolhead_message_callback)

        self.mainboard.bootstrap(on_mainboard_ready)
        self.unpacker = Unpacker()

    def on_toolhead_ready(self, ctrl):
        self._ready |= 2

    @property
    def buflen(self):
        return len(self.cmd_queue) + self.mainboard.buffered_cmd_size

    def on_mainboard_empty(self, caller):
        self.fire()

    def on_mainboard_sendable(self, caller):
        self.fire()

    def toolhead_message_callback(self, sender, data):
        if data and self.head_resp_stack is not None and \
                len(self.head_resp_stack) <= 32:
            self.head_resp_stack.append(data)
            self.send_udp1(sender)

    def on_binary(self, buf, handler):
        self.unpacker.feed(buf)
        for payload in self.unpacker:
            self.process_cmd(handler, *payload)

    def process_cmd(self, handler, index, cmd, *params):
        if index != self.cmd_index:
            logger.debug("Ignore %s 0x%02x %s", index, cmd, params)
            return

        fn = CMD_MATRIX.get(cmd)
        try:
            if cmd < 0xf0:
                fn(self, handler, *params)
            else:
                fn(self, handler, *params)

            self.cmd_index += 1

        except InternalError as e:
            self.handler.send(packb((0xff, self.cmd_index, e[1])))

        except Exception:
            logger.exception("Unknown error during processing command")
            self.handler.send(packb((0xff, self.cmd_index, MSG_UNKNOWN_ERROR)))
            self.on_require_kill(handler)

    def fire(self):
        if self.cmd_queue:
            target, cmd = self.cmd_queue[0]
            if target == TARGET_MAINBOARD:
                if self.mainboard.queue_full:
                    return
                else:
                    self.cmd_queue.popleft()
                    self.mainboard.send_cmd(cmd)
            elif target == TARGET_TOOLHEAD:
                if self.mainboard.buffered_cmd_size == 0:
                    if self.toolhead.sendable():
                        self.cmd_queue.popleft()
                        # TODO
                        self.toolhead.send_cmd(cmd, self)
                else:
                    return

    def on_mainboard_message(self, watcher, revent):
        try:
            self.mainboard.handle_recv()
        except IOError:
            logger.error("Mainboard connection broken")
            self.stack.exit_task(self)
            self.send_udp0()
        except Exception:
            logger.exception("Unhandle Error")

    def on_headboard_message(self, watcher, revent):
        try:
            self.toolhead.handle_recv()
            check_toolhead_errno(self.toolhead, self.th_error_flag)
            self.fire()

        except IOError:
            logger.error("Headboard connection broken")
            self.stack.exit_task(self)

        except (HeadResetError, HeadOfflineError, HeadTypeError):
            self._ready &= ~2

        except HeadError as e:
            logger.info("Head Error: %s", e)

        except Exception:
            logger.exception("Unhandle Error")

    def on_mainboard_result(self, controller, message):
        # Note: message will be...
        #   "DATA HOME 12.3 -23.2 122.3"
        if message.startswith("DATA HOME"):
            position = [float(val) for val in message[10:].split(" ")]
            if float("nan") in position:
                self.handler.send(packb((CMD_G028, 1, None)))
            else:
                self.handler.send(packb((CMD_G028, 0, position)))
                self.known_position = [0, 0, 240]

        #   "DATA READ X:0.124 Y:0.234 Z:0.534 F0:1 F1:0 MB:0"
        if message.startswith("DATA READ "):
            output = {}
            for key, val in ((p.split(":") for p in message[10:].split(" "))):
                if key in ("X", "Y", "Z"):
                    output[key] = float(val)
                elif key in ("F0", "F1"):
                    output[key] = (val == "1")
                elif key == "MB":
                    output[key] = (val == "1")
            self.handler.send(packb((CMD_VALU, output)))
        #   "DATA ZPROBE -0.5"
        if message.startswith("DATA ZPROBE "):
            self.handler.send(packb((CMD_G030, float(message[12:]))))

    def send_udp0(self):
        if self.udp_sock:
            try:
                buf = packb((0, "", self.cmd_index, self.buflen))
                self.udp_sock.send(buf)
            except socket.error:
                pass

    def send_udp1(self, toolhead):
        if self.udp_sock:
            try:
                if self.head_resp_stack is not None:
                    buf = packb((2, "", 0, len(self.head_resp_stack)))
                    self.udp_sock.send(buf)

                if toolhead.ready:
                    buf = packb(
                        (1, "", 0, toolhead.error_code, toolhead.status))
                    self.udp_sock.send(buf)

                # elif toolhead.ready_flag > 0:
                #     buf = packb((1, "", 0, -1, {}))
                #     self.udp_sock.send(buf)

                else:
                    buf = packb((1, "", 0, -2, {}))
                    self.udp_sock.send(buf)

            except socket.error:
                pass

    def send_udps(self, signal):
        if self.udp_sock:
            try:
                self.udp_sock.send(packb((signal, )))
            except socket.error:
                pass

    def on_timer(self, watcher, revent):
        self.meta.update_device_status(self.st_id, 0, "N/A",
                                       self.handler.address)

        self.send_udp0()
        if not self._ready & 2:
            self.send_udp1(self.toolhead)

        try:
            self.mainboard.patrol()
        except RuntimeError as e:
            logger.info("%s", e)

        except Exception:
            logger.exception("Mainboard dead")
            self.handler.send_text(packb((0xff, -1, 0xff, SUBSYSTEM_ERROR)))
            self.on_require_kill(self.handler)
            return

        try:
            self.toolhead.patrol()
        except (HeadOfflineError, HeadResetError) as e:
            logger.debug("Head Offline/Reset: %s", e)

        except RuntimeError as e:
            logger.info("%s", e)

        except socket.error:
            logger.warn("Socket IO Error")
            self.handler.close()

        except Exception:
            logger.exception("Toolhead dead")
            self.handler.send_text(packb((0xff, -1, 0xff, SUBSYSTEM_ERROR)))
            self.on_require_kill(self.handler)
            return

    def clean(self):
        self.mainboard.send_cmd("@HOME_BUTTON_TRIGGER\n")

        if self.toolhead:
            if self.toolhead.ready:
                self.toolhead.shutdown()
            self.toolhead = None

        if self.mainboard:
            self.mainboard.close()
            self.mainboard = None

        self.handler.binary_mode = False

    def append_cmd(self, target, cmd):
        self.cmd_queue.append((target, cmd))
        self.fire()

    def create_movement_command(self,
                                F=None,
                                X=None,
                                Y=None,
                                Z=None,
                                E0=None,
                                E1=None,
                                E2=None):  # noqa
        target = self.known_position
        yield "G1"

        if F:
            yield "F%i" % F

        if X is not None or Y is not None or Z is not None:
            if self.known_position:
                if X is not None:
                    target[0] = X
                    yield "X%.5f" % X
                if Y is not None:
                    target[1] = Y
                    yield "Y%.5f" % Y
                if Z is not None:
                    target[2] = Z
                    yield "Z%.5f" % Z

                if (target[0]**2 + target[1]**2) > 28900:
                    raise InternalError(CMD_G001, MSG_OPERATION_ERROR)
                elif target[2] > 240 or target[2] < 0:
                    raise InternalError(CMD_G001, MSG_OPERATION_ERROR)

            else:
                raise InternalError(CMD_G001, MSG_OPERATION_ERROR)

        eflag = False
        for i, e in ((0, E0), (1, E1), (2, E2)):
            if e is not None:
                if eflag:
                    raise InternalError(CMD_G001, MSG_OPERATION_ERROR)
                else:
                    eflag = True
                    if self.main_e_axis != i:
                        yield "T%i" % i
                        self.main_e_axis = i
                    yield "E%.5f" % e

        self.known_position = target

    def on_move(self, handler, kw):
        try:
            cmd = "".join(self.create_movement_command(**kw))
            self.append_cmd(TARGET_MAINBOARD, cmd)
        except TypeError:
            raise InternalError(CMD_G001, MSG_BAD_PARAMS)

    def on_sleep(self, handler, secondes):
        try:
            cmd = "G4S%.4f" % secondes
            self.append_cmd(TARGET_MAINBOARD, cmd)
        except TypeError:
            raise InternalError(CMD_G004, MSG_BAD_PARAMS)

    def on_scan_lasr(self, handler, flags):
        try:
            cmd = "X1E%i" % flags
            self.append_cmd(TARGET_MAINBOARD, cmd)
        except TypeError:
            raise InternalError(CMD_SLSR, MSG_BAD_PARAMS)

    def on_home(self, handler):
        self.append_cmd(TARGET_MAINBOARD, "X6")
        self.known_position = None

    def on_lock_motors(self, handler):
        self.append_cmd(TARGET_MAINBOARD, "M17")

    def on_release_motors(self, handler):
        self.append_cmd(TARGET_MAINBOARD, "M84")
        self.known_position = None

    def on_z_probe(self, handler, x, y):
        try:
            if self.known_position and x**2 + y**2 <= 7225:
                cmd = "G30X%.5fY%.5f" % (x, y)
                self.append_cmd(TARGET_MAINBOARD, cmd)

            else:
                raise InternalError(CMD_G030, MSG_OPERATION_ERROR)
        except TypeError:
            raise InternalError(CMD_G030, MSG_BAD_PARAMS)

    # def on_adjust(self, handler, kw):
    #     pass

    def on_set_toolhead_temperature(self, handler, index, temperature):
        if index == 0 and temperature >= 0 and temperature <= 220:
            cmd = "H%i%.1f" % (index, temperature)
            self.append_cmd(TARGET_TOOLHEAD, cmd)
        else:
            raise InternalError(CMD_M104, MSG_OPERATION_ERROR)

    def on_set_toolhead_fan_speed(self, handler, index, speed):
        if index == 0 and speed >= 0 and speed <= 1:
            cmd = "F%i%.3f" % (index, speed)
            self.append_cmd(TARGET_TOOLHEAD, cmd)
        else:
            raise InternalError(CMD_M106, MSG_OPERATION_ERROR)

    def on_set_toolhead_pwm(self, handler, pwm):
        if pwm >= 0 and pwm <= 1:
            cmd = "X2O" % (pwm * 255)
            self.append_cmd(TARGET_MAINBOARD, cmd)
        else:
            raise InternalError(CMD_HLSR, MSG_OPERATION_ERROR)

    def on_query_value(self, handler, flags):
        self.append_cmd(TARGET_MAINBOARD, "X87F%i" % flags)

    def on_toolhead_profile(self, handler):
        buf = packb((CMD_THPF, self.toolhead.info()))
        self.handler.send(buf)

    def on_toolhead_raw_command(self, handler, cmd):
        self.append_cmd(TARGET_TOOLHEAD, cmd)

    def on_toolhead_raw_response(self, handler):
        buf = packb((CMD_THRR, self.head_resp_stack))
        self.head_resp_stack = []
        self.handler.send(buf)

    def on_require_sync(self, handler, ipaddr, port, salt):
        endpoint = (ipaddr, port)
        logger.debug("Create sync udp endpoint at %s", repr(endpoint))
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(endpoint)
            s.send(packb((0xff, )))
        except (TypeError, OSError):
            raise InternalError(CMD_SYNC, MSG_OPERATION_ERROR)

        try:
            if self.udp_sock:
                self.udp_sock.close()
        finally:
            self.udp_sock = s

    def on_require_head(self, handler, head_type):
        self.toolhead = HeadController(
            self._sock_th.fileno(),
            required_module=head_type,
            msg_callback=self.toolhead_message_callback)

        self.head_resp_stack = [] if head_type == "USER" else None

    def on_bootstrap_toolhead(self, handler):
        self.toolhead.bootstrap(self.on_toolhead_ready)

    def on_clean_toolhead_error(self, handler):
        self.toolhead.errcode = 0

    def on_require_quit(self, handler):
        if self.buflen:
            raise InternalError(CMD_QUIT, MSG_OPERATION_ERROR)

        self.stack.exit_task(self)
        self.handler.send(packb((CMD_QUIT, 0)))

    def on_require_kill(self, handler):
        try:
            self.send_udps(0xfe)
            self.stack.exit_task(self)
        finally:
            from fluxmonitor.hal.tools import reset_mb
            reset_mb()
            self.handler.send(packb((CMD_QUIT, 0)))