#! /usr/bin/env python3

#    User-Space driver for the Altaros ATH 8-position Tool Turret

#    Copyright 2025 Andy Pugh

#    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 2 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, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""
This script defines a LinuxCNC userspace HAL component that is intended
to control the Altaros ATH 8-position tool turret for CNC lathes.

A typical HAL file configuration would appear as follows

| loadusr -W ./altaros_ath
| # pre-selecting a tool isn't applicable on a lathe 
| net tool-prep-loop iocontrol.0.tool-prepare => iocontrol.0.tool-prepared
| # tool change loop is handled in the altaros_ath component
| net tool-change iocontrol.0.tool-change => altaros_ath.tool-change
| net tool-changed altaros_ath.tool-changed => iocontrol.0.tool-changed
| net pocket iocontrol.0.tool-prep-pocket => altaros_ath.pocket-num
| net toolsensor sim_toolchanger.strobe => altaros_ath.strobe
| net tool-step altaros_ath.actuate => sim_toolchanger.actuate

altaros_ath.actuate:
	Connect this HAL pin to the GPIO pin that controls the turret actuation
	solenoid.
	
altaros_ath.strobe:
	Connect this input pin to the turrent position feedback sensor.
	
altaros_ath.tool-change:
	Net to iocontrol.0.tool-change. This is the signal from LinuxCNC to
	this HAL component that a tool change is requested.

altaros_ath.tool-changed:
	The HAL component sets this pin high to indicate to the LinuxCNC
	system that the tool-change is complete and that machining can
	re-start. Net to iocontrol.0.tool-changed.
	
altaros_ath.pocket-num:
	This provides the requested tool-changer position for the selected
	tool. G-code specifies a tool-number and the LinuxCNC system then
	maps this with a pocket-number from the tool table. 
	
alaros_ath.position:
	This is a debugging pin that indicates what position the driver
	currently thinks that the turret is in. 

The tool turret has an odd/even sensor that is able to detect failed
tool changes, but this does not allow the system to know the initial
starting position. 

On the first tool-change a dialog box will appear requesting the current
turret position and from that point will work incrementally from that
starting position. The odd/even sensor will detect any failed
tool change and this HAL component abort program execution if it detects
a miss-match

"""

import hal, linuxcnc
from tkinter import simpledialog
import time

h = hal.component("altaros_ath")
h.newpin("actuate", hal.HAL_BIT, hal.HAL_OUT)
h.newpin("strobe", hal.HAL_BIT, hal.HAL_IN)
h.newpin("tool-change", hal.HAL_BIT, hal.HAL_IN)
h.newpin("pocket-num", hal.HAL_S32, hal.HAL_IN)
h.newpin("tool-changed", hal.HAL_BIT, hal.HAL_OUT)
h.newpin("position", hal.HAL_S32, hal.HAL_OUT)
h.ready()

s = linuxcnc.stat()
c = linuxcnc.command()

pos_valid = False
current_pocket = 0

def init_tool_pos():
	global pos_valid
	global current_pocket
	entry = simpledialog.askinteger("Toolchanger", "Enter the current active tool pocket:")
	if entry is None:
		c.error_msg("toolchange cancelled")
		c.abort()
		return
	if 1 <= entry <= 8 :
		current_pocket = entry
		pos_valid = True

try:
	while 1:
		h["position"] = current_pocket
		if h["tool-change"]:
			while pos_valid == False:
				init_tool_pos()
				if not h["tool-change"]: # indicative of cancel pressed / abort 
					break
			else: # pos is valid
				if h["pocket-num"] > 8 or h["pocket-num"] <= 0:
					c.error_msg("tool pocket number out of range")
					c.abort()
				if h["pocket-num"] != current_pocket:
					h["actuate"] = True
					time.sleep(0.7)
					h["actuate"] = False
					time.sleep(0.6)
					current_pocket += 1
					if current_pocket > 8:
						current_pocket -= 8
					if h["strobe"] != (current_pocket % 2 == 0):
						c.error_msg("tool position sensor does not match expected carousel position")
						c.abort()
				if h["pocket-num"] == current_pocket: #signal in-position
					h["tool-changed"] = True
					time.sleep(0.05)
					h["tool-changed"] = False
				
		else: # don't be too busy 
			time.sleep(0.5)
		
except KeyboardInterrupt:
    raise SystemExit
