#!/usr/bin/env python3

import linuxcnc
import hal

import time
import sys
import subprocess
import os
import signal
import glob
import re


def print_status(status):
    status.poll()
    print("status.axis[0]: {}".format(status.axis[0]))
    print("status.axis[1]: {}".format(status.axis[1]))
    print("status.joint[0]: {}".format(status.joint[0]))
    print("status.joint[1]: {}".format(status.joint[1]))
    print("status.current_vel: {}".format(status.current_vel))
    print("status.echo_serial_number: {}".format(status.echo_serial_number))
    print("status.enabled: {}".format(status.enabled))
    print("status.estop: {}".format(status.estop))
    print("status.exec_state: {}".format(status.exec_state))
    print("status.inpos: {}".format(status.inpos))
    print("status.interp_state: {}".format(status.interp_state))
    print("status.interpreter_errcode: {}".format(status.interpreter_errcode))
    print("status.limit: {}".format(status.limit))
    print("status.motion_mode: {}".format(status.motion_mode))
    print("status.motion_type: {}".format(status.motion_type))
    print("status.position: {}".format(status.position))
    print("status.state: {}".format(status.state))
    print("status.task_mode: {}".format(status.task_mode))
    print("status.task_state: {}".format(status.task_state))
    print("status.velocity: {}".format(status.velocity))
    sys.stdout.flush()


def assert_wait_complete(command):
    r = command.wait_complete()
    print("wait_complete() returns {}".format(r))
    assert((r == linuxcnc.RCS_DONE) or (r == linuxcnc.RCS_ERROR))


#
# connect to HAL
#

comp = hal.component("test-ui")
comp.newpin("x-neg-lim-sw", hal.HAL_BIT, hal.HAL_OUT)
comp.ready()

hal.connect('test-ui.x-neg-lim-sw', 'x-neg-lim-sw')


#
# connect to LinuxCNC
#

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


#
# Come out of E-stop, turn the machine on, switch to Manual mode, and home.
#

c.state(linuxcnc.STATE_ESTOP_RESET)
c.state(linuxcnc.STATE_ON)
c.mode(linuxcnc.MODE_MANUAL)
c.home(0)
c.home(1)
c.home(2)
c.wait_complete()

# wait for homing to complete
start_time = time.time()
s.poll()
all_homed = s.homed[0]+s.homed[1]+s.homed[2]
while (all_homed != 3) and (time.time() - start_time < 5):
    time.sleep(0.100)
    s.poll()
    all_homed = s.homed[0]+s.homed[1]+s.homed[2]

if all_homed != 3:
    print("failed to home")
    print("s.homed: {}".format(s.homed))
    sys.exit(1)

c.teleop_enable(0)


#
# run the test: start a jog on X, then trip a limit switch
#

# jog arguments are: (jog_type, joint_flag, axis, velocity)
c.jog(linuxcnc.JOG_CONTINUOUS, 1, 0, -0.1)

# verify that we're starting to move
s.poll()
old_x = s.position[0]
start_time = time.time()
while (old_x == s.position[0]) and (time.time() - start_time < 5):
    time.sleep(0.1)
    s.poll()

if old_x == s.position[0]:
    print("no jog movement")
    sys.exit(1)

print("x started moving (%.6f to %.6f)" % (old_x, s.position[0]))
print_status(s)

# verify that Status reflects the situation
assert(s.joint[0]['min_soft_limit'] == False)
assert(s.joint[0]['min_hard_limit'] == False)
assert(s.joint[0]['max_soft_limit'] == False)
assert(s.joint[0]['max_hard_limit'] == False)
assert(s.joint[0]['inpos'] == False)
assert(s.joint[0]['enabled'] == True)

assert(not (1 in s.limit))
assert(s.inpos == False)
assert(s.enabled == True)

# trip the limit switch
comp['x-neg-lim-sw'] = True

# let linuxcnc react to the limit switch
expected_error = 'joint 0 on limit switch error'
start_time = time.time()
while (time.time() - start_time < 5):
    error = e.poll()
    if error != None:
        if error[1] == expected_error:
            break
        else:
            print("linuxcnc sent other error %d: %s" % (error[0], error[1]))
    time.sleep(0.1)

if error == None or error[1] != expected_error:
    print("no limit switch error from LinuxCNC")
    sys.exit(1)

print("linuxcnc sent error %d: %s" % (error[0], error[1]))
print_status(s)

# verify that we're stopping
s.poll()
start_time = time.time()
while (s.joint[0]['velocity'] != 0.0) and (time.time() - start_time < 5):
    time.sleep(0.1)
    s.poll()

if s.joint[0]['velocity'] != 0.0:
    print("limit switch didn't stop movement")
    sys.exit(1)

print("x stopped moving (pos=%.6f, vel=%.6f)" % (s.position[0], s.joint[0]['velocity']))
print_status(s)

# verify that Status reflects the situation
assert(s.joint[0]['min_soft_limit'] == False)
assert(s.joint[0]['min_hard_limit'] == True)
assert(s.joint[0]['max_soft_limit'] == False)
assert(s.joint[0]['max_hard_limit'] == False)
assert(s.joint[0]['inpos'] == True)
assert(s.joint[0]['enabled'] == False)

assert(s.limit[0] == 1)
assert(s.inpos == True)
assert(s.enabled == False)

# turn the machine back on with Override Limits enabled
c.override_limits()
time.sleep(1)
s.poll()
print_status(s)
print("command.serial: {}".format(c.serial))
# this fails in 2.6.12 due to the stat RCS message having a status of
# RCS_EXEC...  as if though the override_limits command didn't set status
# back to RCS_DONE when it finished.
# assert_wait_complete(c)

c.state(linuxcnc.STATE_ON)
assert_wait_complete(c)

# verify that Status reflects the new situation
s.poll()
assert(s.joint[0]['min_soft_limit'] == False)
assert(s.joint[0]['min_hard_limit'] == True)
assert(s.joint[0]['max_soft_limit'] == False)
assert(s.joint[0]['max_hard_limit'] == False)
assert(s.joint[0]['inpos'] == True)
assert(s.joint[0]['enabled'] == True)

assert(s.limit[0] == 1)
assert(s.inpos == True)
assert(s.enabled == True)

# jog X in the positive direction, off the negative limit switch
c.jog(linuxcnc.JOG_CONTINUOUS, 1, 0, 1)

# verify that we're starting to move
s.poll()
old_x = s.position[0]
start_time = time.time()
while (old_x == s.position[0]) and (time.time() - start_time < 5):
    time.sleep(0.1)
    s.poll()

if old_x == s.position[0]:
    print("no jog movement")
    sys.exit(1)

print("x started moving (%.6f to %.6f)" % (old_x, s.position[0]))
print_status(s)

# un-trip the limit switch
comp['x-neg-lim-sw'] = False

# let linuxcnc react to the limit switch untripping
start_time = time.time()
while (time.time() - start_time < 5):
    s.poll()
    if (s.joint[0]['min_hard_limit'] == False) and (s.limit[0] == 0):
        break
    time.sleep(0.1)

# verify that Status reflects the new situation
assert(s.joint[0]['min_soft_limit'] == False)
assert(s.joint[0]['min_hard_limit'] == False)
assert(s.joint[0]['max_soft_limit'] == False)
assert(s.joint[0]['max_hard_limit'] == False)
assert(s.joint[0]['inpos'] == False)
assert(s.joint[0]['enabled'] == True)

assert(s.limit[0] == 0)
assert(s.inpos == False)
assert(s.enabled == True)

# stop the jog
c.jog(linuxcnc.JOG_STOP, 1, 0)

# verify that we're stopping
s.poll()
old_x = s.position[0]
start_time = time.time()
while (old_x != s.position[0]) and (time.time() - start_time < 5):
    time.sleep(0.1)
    s.poll()

if old_x != s.position[0]:
    print("JOG_STOP didn't stop movement")
    sys.exit(1)

print("x stopped moving (%.6f)" % s.position[0])
print_status(s)

# verify that Status reflects the new situation
assert(s.joint[0]['min_soft_limit'] == False)
assert(s.joint[0]['min_hard_limit'] == False)
assert(s.joint[0]['max_soft_limit'] == False)
assert(s.joint[0]['max_hard_limit'] == False)
# FIXME: another bug
#assert(s.joint[0]['inpos'] == True)
assert(s.joint[0]['enabled'] == True)

assert(s.limit[0] == 0)
# FIXME: another bug
#assert(s.inpos == True)
assert(s.enabled == True)

# success!
sys.exit(0)
