#!/usr/bin/env python3
#
#    This is stepconf, a graphical configuration editor for LinuxCNC
#    Copyright 2007 Jeff Epler <jepler@unpythonic.net>
#    stepconf 1.1 revamped by Chris Morley 2014
#
#    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 builds the HAL files from the collected data.
#
import os
import time
import shutil

class HAL:
    def __init__(self,app):
        # access to:
        self.d = app.d  # collected data
        global SIG
        SIG = app._p    # private data (signal names)
        self.a = app    # The parent, stepconf

    def write_halfile(self, base):
        inputs = self.a.build_input_set()
        outputs = self.a.build_output_set()

        filename = os.path.join(base, self.d.machinename + ".hal")
        # make a backup copy if hal file exists
        if os.path.exists(filename):
            shutil.copy2(filename, filename.replace(".hal", "_" + str(int(time.time())) + ".hal"))
        file = open(filename, "w")
        print(_("# Generated by stepconf 1.1 at %s") % time.asctime(), file=file)
        print(_("# If you make changes to this file, they will be"), file=file)
        print(_("# overwritten when you run stepconf again"), file=file)

        print("loadrt [KINS]KINEMATICS", file=file)
        # qtplasmac requires 3 spindles
        if self.d.select_qtplasmac:
            print("loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS num_spindles=[TRAJ]SPINDLES", file=file)
        else:
            print("loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS", file=file)
        port3name=port2name=port2dir=port3dir=""
        if self.d.number_pports>2:
             port3name = ' '+self.d.ioaddr3
             if self.d.pp3_direction: # Input option
                port3dir =" in"
             else: 
                port3dir =" out"
        if self.d.number_pports>1:
             port2name = ' '+self.d.ioaddr2
             if self.d.pp2_direction: # Input option
                port2dir =" in"
             else: 
                port2dir =" out"
        if not self.d.sim_hardware:
            print("loadrt hal_parport cfg=\"%s out%s%s%s%s\"" % (self.d.ioaddr, port2name, port2dir, port3name, port3dir), file=file)
        else:
            name='parport.0'
            if self.d.number_pports>1:
                name='parport.0,parport.1'
            print("loadrt sim_parport names=%s"%name, file=file)
        if self.a.doublestep():
            print("setp parport.0.reset-time %d" % self.d.steptime, file=file)
        if not self.d.select_qtplasmac:
            encoder = SIG.PHA in inputs
            counter = SIG.PHB not in inputs
            probe = SIG.PROBE in inputs
            pwm = SIG.PWM in outputs
            pump = SIG.PUMP in outputs
        else:
            encoder, counter, probe, pwm, pump = False, False, False, False, False, 
        limits_homes = SIG.ALL_LIMIT_HOME in inputs

        stepgens = '0'
        for axis in range(1, len(self.d.axislist)):
            stepgens += ",0"
        print("loadrt stepgen step_type={}".format(stepgens), file=file)

        if encoder:
            print("loadrt encoder num_chan=1", file=file)
        if self.d.pyvcphaltype == 1 and self.d.pyvcpconnect == 1:
            if encoder:
               print("loadrt abs count=1", file=file)
               print("loadrt scale count=1", file=file)
               print("loadrt lowpass count=1", file=file)
               if self.d.usespindleatspeed:
                   print("loadrt near", file=file)
        if pump:
            print("loadrt charge_pump", file=file)
            print("net estop-out charge-pump.enable iocontrol.0.user-enable-out", file=file)
            print("net charge-pump <= charge-pump.out", file=file)

        if limits_homes:
            print("loadrt lut5", file=file)

        if pwm:
            print("loadrt pwmgen output_type=1", file=file)

        if self.d.classicladder:
            print("loadrt classicladder_rt numPhysInputs=%d numPhysOutputs=%d numS32in=%d numS32out=%d numFloatIn=%d numFloatOut=%d" % (self.d.digitsin , self.d.digitsout , self.d.s32in, self.d.s32out, self.d.floatsin, self.d.floatsout), file=file)

        if self.d.select_qtplasmac:
            print("loadrt  plasmac", file=file)

        print(file=file)
        print("addf parport.0.read base-thread", file=file)
        if self.d.number_pports > 1:
            print("addf parport.1.read base-thread", file=file)
        if self.d.number_pports > 2:
            print("addf parport.2.read base-thread", file=file)
        if self.d.sim_hardware:
            print("source sim_hardware.hal", file=file)
            if encoder:
                print("addf sim-encoder.make-pulses base-thread", file=file)
        print("addf stepgen.make-pulses base-thread", file=file)
        if encoder: print("addf encoder.update-counters base-thread", file=file)
        if pump: print("addf charge-pump base-thread", file=file)
        if pwm: print("addf pwmgen.make-pulses base-thread", file=file)
        print("addf parport.0.write base-thread", file=file)
        if self.a.doublestep():
            print("addf parport.0.reset base-thread", file=file)
        if self.d.number_pports > 1:
            print("addf parport.1.write base-thread", file=file)
        if self.d.number_pports > 2:
            print("addf parport.2.write base-thread", file=file)
        print(file=file)
        print("addf stepgen.capture-position servo-thread", file=file)
        if self.d.sim_hardware:
            print("addf sim-hardware.update servo-thread", file=file)
            if encoder:
                print("addf sim-encoder.update-speed servo-thread", file=file)
        if encoder: print("addf encoder.capture-position servo-thread", file=file)
        print("addf motion-command-handler servo-thread", file=file)
        print("addf motion-controller servo-thread", file=file)
        if self.d.classicladder:
            print("addf classicladder.0.refresh servo-thread", file=file)
        if self.d.select_qtplasmac:
            print("addf plasmac servo-thread", file=file)

        print("addf stepgen.update-freq servo-thread", file=file)

        if limits_homes:
            print("addf lut5.0 servo-thread", file=file)

        if pwm: print("addf pwmgen.update servo-thread", file=file)
        if self.d.pyvcphaltype == 1 and self.d.pyvcpconnect == 1:
            if encoder:
               print("addf abs.0 servo-thread", file=file)
               print("addf scale.0 servo-thread", file=file)
               print("addf lowpass.0 servo-thread", file=file)
               if self.d.usespindleatspeed:
                   print("addf near.0 servo-thread", file=file)
        if pwm:
            x1 = self.d.spindlepwm1
            x2 = self.d.spindlepwm2
            y1 = self.d.spindlespeed1
            y2 = self.d.spindlespeed2
            scale = (y2-y1) / (x2-x1)
            offset = x1 - y1 / scale
            print(file=file)
            print("net spindle-cmd-rpm => pwmgen.0.value", file=file)
            print("net spindle-on <= spindle.0.on => pwmgen.0.enable", file=file)
            print("net spindle-pwm <= pwmgen.0.pwm", file=file)
            print("setp pwmgen.0.pwm-freq %s" % self.d.spindlecarrier, file=file)        
            print("setp pwmgen.0.scale %s" % scale, file=file)
            print("setp pwmgen.0.offset %s" % offset, file=file)
            print("setp pwmgen.0.dither-pwm true", file=file)

            print("net spindle-cmd-rpm     <= spindle.0.speed-out", file=file)
            print("net spindle-cmd-rpm-abs <= spindle.0.speed-out-abs", file=file)
            print("net spindle-cmd-rps     <= spindle.0.speed-out-rps", file=file)
            print("net spindle-cmd-rps-abs <= spindle.0.speed-out-rps-abs", file=file)
            print("net spindle-at-speed    => spindle.0.at-speed", file=file)
            if SIG.ON in outputs and not pwm:
                print("net spindle-on <= spindle.0.on", file=file)
            if SIG.CW in outputs:
                print("net spindle-cw <= spindle.0.forward", file=file)
            if SIG.CCW in outputs:
                print("net spindle-ccw <= spindle.0.reverse", file=file)
            if SIG.BRAKE in outputs:
                print("net spindle-brake <= spindle.0.brake", file=file)

        if SIG.MIST in outputs:
            print("net coolant-mist <= iocontrol.0.coolant-mist", file=file)

        if SIG.FLOOD in outputs:
            print("net coolant-flood <= iocontrol.0.coolant-flood", file=file)

        # do the qtplasmac connections and preferences file
        if self.d.select_qtplasmac:
            self.qtplasmac_connections(file, base)
            prefsfile = os.path.join(base, self.d.machinename + ".prefs")
            self.qtplasmac_prefs(prefsfile)

        if encoder:
            print(file=file)
            if SIG.PHB not in inputs:
                print("setp encoder.0.position-scale %f"\
                     % self.d.spindlecpr, file=file)
                print("setp encoder.0.counter-mode 1", file=file)
            else:
                print("setp encoder.0.position-scale %f" \
                    % ( 4.0 * int(self.d.spindlecpr)), file=file)
            print("net spindle-position encoder.0.position => spindle.0.revs", file=file)
            print("net spindle-velocity-feedback-rps encoder.0.velocity => spindle.0.speed-in", file=file)
            print("net spindle-index-enable encoder.0.index-enable <=> spindle.0.index-enable", file=file)
            print("net spindle-phase-a encoder.0.phase-A", file=file)
            print("net spindle-phase-b encoder.0.phase-B", file=file)
            print("net spindle-index encoder.0.phase-Z", file=file)
        if probe:
            print(file=file)
            print("net probe-in => motion.probe-input", file=file)
        for i in range(4):
            dout = "dout-%02d" % i
            if dout in outputs:
                comment = ""
                if self.d.select_qtplasmac and i > 0:
                    comment = "#qtplasmac uses this digital output:   "
                print("%snet %s <= motion.digital-out-%02d" % (comment, dout, i), file=file)
        for i in range(4):
            din = "din-%02d" % i
            if din in inputs:
                comment = ""
                if self.d.select_qtplasmac and i > 0:
                    comment  = "#qtplasmac uses this digital input:    "
                print("%snet %s => motion.digital-in-%02d" % (comment, din, i), file=file)
        self.tandemsigs = {}
        if self.d.tandemjoints:
            for j in self.d.tandemjoints:
                self.tandemsigs["{}step".format(j)] = 1
                self.tandemsigs["{}dir".format(j)] = 1

        print(file=file)
        self.outputlist = []
        for o in (1,2,3,4,5,6,7,8,9,14,16,17): self.connect_output(file, o)
        if self.d.number_pports>1:
            if self.d.pp2_direction:# Input option
                pinlist = (1,14,16,17)
            else:
                pinlist = (1,2,3,4,5,6,7,8,9,14,16,17)
            print(file=file)
            for i in pinlist: self.connect_output(file, i, 1)
            print(file=file)

        self.inputlist = []
        for i in (10,11,12,13,15): self.connect_input(file, i)
        if self.d.number_pports>1:
            if self.d.pp2_direction: # Input option
                pinlist = (2,3,4,5,6,7,8,9,10,11,12,13,15)
            else:
                pinlist = (10,11,12,13,15)
            print(file=file)
            for i in pinlist: self.connect_input(file, i, 1)
            print(file=file)

        if limits_homes:
            print("setp lut5.0.function 0x10000", file=file)
            print("net all-limit-home => lut5.0.in-4", file=file)
            print("net all-limit <= lut5.0.out", file=file)
            for j in self.d.axislist:
                print("net {0}homing <= joint.{1}.homing => lut5.0.in-{1}".format(j, self.d.axislist.index(j)), file=file)

        # connect the joints
        for j in self.d.axislist:
            self.connect_joint(file, self.d.axislist.index(j), j)

        print(file=file)
        # wacky estop handling for qtplasmac with sim hardware
        if self.d.select_qtplasmac and self.d.sim_hardware:
            print("\n# ---QTPLASMAC SIM ESTOP HANDLING---", file=file)
            print("loadrt or2 names=estop_or", file=file)
            print("loadrt not names=estop_not", file=file)
            print("addf estop_or servo-thread", file=file)
            print("addf estop_not servo-thread", file=file)
            print("net sim:estop-raw estop_or.out estop_not.in", file=file)
            print("net sim:estop-out estop_not.out iocontrol.0.emc-enable-in", file=file)
        else:
        # standard estop handling
            print("net estop-out <= iocontrol.0.user-enable-out", file=file)
            if  self.d.classicladder and self.d.ladderhaltype == 1 and self.d.ladderconnect: # external estop program
                print(file=file) 
                print(_("# **** Setup for external estop ladder program -START ****"), file=file)
                print(file=file)
                print("net estop-out => classicladder.0.in-00", file=file)
                print("net estop-ext => classicladder.0.in-01", file=file)
                print("net estop-strobe classicladder.0.in-02 <= iocontrol.0.user-request-enable", file=file)
                print("net estop-outcl classicladder.0.out-00 => iocontrol.0.emc-enable-in", file=file)
                print(file=file)
                print(_("# **** Setup for external estop ladder program -END ****"), file=file)
            elif SIG.ESTOP_IN in inputs:
                print("net estop-ext => iocontrol.0.emc-enable-in", file=file)
            else:
                print("net estop-out => iocontrol.0.emc-enable-in", file=file)

        print(file=file)
        if self.d.manualtoolchange:
            if not self.d.select_qtdragon:
                print("loadusr -W hal_manualtoolchange", file=file)
                print("net tool-change iocontrol.0.tool-change => hal_manualtoolchange.change", file=file)
                print("net tool-changed iocontrol.0.tool-changed <= hal_manualtoolchange.changed", file=file)
                print("net tool-number iocontrol.0.tool-prep-number => hal_manualtoolchange.number", file=file)
            else:
                print("net tool-change  <= iocontrol.0.tool-change", file=file)
                print("net tool-changed  => iocontrol.0.tool-changed", file=file)
                print("net tool-number <= iocontrol.0.tool-prep-number", file=file)
                qt = os.path.join(base, "qtvcp_postgui.hal")
                if not os.path.exists(qt):
                    f1 = open(qt, "w")
                    print("net tool-change => hal_manualtoolchange.change", file=f1)
                    print("net tool-changed <= hal_manualtoolchange.changed", file=f1)
                    print("net tool-number  => hal_manualtoolchange.number", file=f1)
                    f1.close()
        else:
            print("net tool-number <= iocontrol.0.tool-prep-number", file=file)
            print("net tool-change-loopback iocontrol.0.tool-change => iocontrol.0.tool-changed", file=file)
        print("net tool-prepare-loopback iocontrol.0.tool-prepare => iocontrol.0.tool-prepared", file=file)
        if self.d.classicladder:
            print(file=file)
            if self.d.modbus:
                print(_("# Load Classicladder with Modbus master included (GUI must run for Modbus)"), file=file)
                print("loadusr classicladder --modmaster custom.clp", file=file)
            else:
                print(_("# Load Classicladder without GUI (can reload LADDER GUI in AXIS GUI"), file=file)
                print("loadusr classicladder --nogui custom.clp", file=file)
        if self.d.pyvcp:
            vcp = os.path.join(base, "custompanel.xml")
            if not os.path.exists(vcp):
                f1 = open(vcp, "w")

                print("<?xml version='1.0' encoding='UTF-8'?>", file=f1)

                print("<!-- ", file=f1)
                print(_("Include your PyVCP panel here.\n"), file=f1)
                print("-->", file=f1)
                print("<pyvcp>", file=f1)
                print("</pyvcp>", file=f1)

        # Same as from pncconf
        # the jump list allows multiple hal files to be loaded postgui
        # this simplifies the problem of overwriting the users custom HAL code
        # when they change pyvcp sample options
        # if the user picked existing pyvcp option and the postgui_call_list is present
        # don't overwrite it. otherwise write the file.
        # qtplasmac doesn't use call list
        if not self.d.select_qtplasmac:
            calllist_filename = os.path.join(base, "postgui_call_list.hal")
            f1 = open(calllist_filename, "w")
            print(_("# These files are loaded post GUI, in the order they appear"), file=f1)
            print(_("# Generated by stepconf 1.1 at %s") % time.asctime(), file=f1)
            print(_("# If you make changes to this file, they will be"), file=f1)
            print(_("# overwritten when you run stepconf again"), file=f1)
            print(file=f1)
            if self.d.pyvcp and not self.d.select_qtplasmac:
                print("source pyvcp_options.hal", file=f1)
            if self.d.manualtoolchange and self.d.select_qtdragon:
                print("source qtvcp_postgui.hal", file=f1)
            print("source custom_postgui.hal", file=f1)
            f1.close()

        # qtplasmac doesn't use pyvcp_options
        if not self.d.select_qtplasmac:
            pyfilename = os.path.join(base, "pyvcp_options.hal")
            f1 = open(pyfilename, "w")
            # If the user asked for pyvcp sample panel add the HAL commands too
            if self.d.pyvcp and self.d.pyvcphaltype == 1 and self.d.pyvcpconnect: # spindle speed/tool # display
                print(_("# These files are loaded post GUI, in the order they appear"), file=f1)
                print(_("# Generated by stepconf 1.1 at %s") % time.asctime(), file=f1)
                print(_("# If you make changes to this file, they will be"), file=f1)
                print(_("# overwritten when you run stepconf again"), file=f1)
                print(_("# **** Setup of spindle speed display using pyvcp -START ****"), file=f1)
                if encoder:
                    print(_("# **** Use ACTUAL spindle velocity from spindle encoder"), file=f1)
                    print(_("# **** spindle-velocity-feedback-rps bounces around so we filter it with lowpass"), file=f1)
                    print(_("# **** spindle-velocity-feedback-rps is signed so we use absolute component to remove sign"), file=f1) 
                    print(_("# **** ACTUAL velocity is in RPS not RPM so we scale it."), file=f1)
                    print(file=f1)
                    print(("setp scale.0.gain 60"), file=f1)
                    print(("setp lowpass.0.gain %f")% self.d.spindlefiltergain, file=f1)
                    print(("net spindle-velocity-feedback-rps               => lowpass.0.in"), file=f1)
                    print(("net spindle-fb-filtered-rps      lowpass.0.out  => abs.0.in"), file=f1)
                    print(("net spindle-fb-filtered-abs-rps  abs.0.out      => scale.0.in"), file=f1)
                    print(("net spindle-fb-filtered-abs-rpm  scale.0.out    => pyvcp.spindle-speed"), file=f1)
                    print(file=f1)
                    print(_("# **** set up spindle at speed indicator ****"), file=f1)
                    if self.d.usespindleatspeed:
                        print(file=f1)
                        print(("net spindle-cmd-rps-abs             =>  near.0.in1"), file=f1)
                        print(("net spindle-velocity-feedback-rps   =>  near.0.in2"), file=f1)
                        print(("net spindle-at-speed                <=  near.0.out"), file=f1)
                        print(("setp near.0.scale %f")% self.d.spindlenearscale, file=f1)
                    else:
                        print(("# **** force spindle at speed indicator true because we chose no feedback ****"), file=f1)
                        print(file=f1)
                        print(("sets spindle-at-speed true"), file=f1)
                    print(("net spindle-at-speed       => pyvcp.spindle-at-speed-led"), file=f1)
                else:
                    print(_("# **** Use COMMANDED spindle velocity from LinuxCNC because no spindle encoder was specified"), file=f1)
                    print(file=f1)
                    print(("net spindle-cmd-rpm-abs    => pyvcp.spindle-speed"), file=f1)
                    print(file=f1)
                    print(("# **** force spindle at speed indicator true because we have no feedback ****"), file=f1)
                    print(file=f1)
                    print(("net spindle-at-speed => pyvcp.spindle-at-speed-led"), file=f1)
                    print(("sets spindle-at-speed true"), file=f1)
                f1.close()
            else:
                print(_("# These files are loaded post GUI, in the order they appear"), file=f1)
                print(_("# Generated by stepconf 1.1 at %s") % time.asctime(), file=f1)
                print(_("# If you make changes to this file, they will be"), file=f1)
                print(_("# overwritten when you run stepconf again"), file=f1)
                print(("sets spindle-at-speed true"), file=f1)
            f1.close()

        # stepconf adds custom.hal and custom_postgui.hal file if one is not present
        if self.d.customhal or self.d.classicladder or self.d.halui:
            for i in ("custom","custom_postgui"):
                custom = os.path.join(base, i+".hal")
                if not os.path.exists(custom):
                    f1 = open(custom, "w")
                    print(_("# Include your %s HAL commands here")%i, file=f1)
                    print(_("# This file will not be overwritten when you run stepconf again"), file=f1)
                    print(file=f1)
                    f1.close()

        file.close()
        self.sim_hardware_halfile(base)
        self.d.add_md5sum(filename)

#******************
# HELPER FUNCTIONS
#******************

    def connect_joint(self, file, num, let):
        axnum = "xyzabcuvw".index(let[0])
        lat = self.d.latency
        print(file=file)
        print("setp stepgen.%d.position-scale [JOINT_%d]SCALE" % (num, num), file=file)
        print("setp stepgen.%d.steplen 1" % num, file=file)
        if self.a.doublestep():
            print("setp stepgen.%d.stepspace 0" % num, file=file)
        else:
            print("setp stepgen.%d.stepspace 1" % num, file=file)
        print("setp stepgen.%d.dirhold %d" % (num, self.d.dirhold + lat), file=file)
        print("setp stepgen.%d.dirsetup %d" % (num, self.d.dirsetup + lat), file=file)
        print("setp stepgen.%d.maxaccel [JOINT_%d]STEPGEN_MAXACCEL" % (num, num), file=file)
        print("net %spos-cmd joint.%d.motor-pos-cmd => stepgen.%d.position-cmd" % (let, num, num), file=file)
        print("net %spos-fb stepgen.%d.position-fb => joint.%d.motor-pos-fb" % (let, num, num), file=file)
        print("net %sstep <= stepgen.%d.step" % (let, num), file=file)
        print("net %sdir <= stepgen.%d.dir" % (let, num), file=file)
        print("net %senable joint.%d.amp-enable-out => stepgen.%d.enable" % (let, num, num), file=file)
        homesig = self.a.home_sig(let)
        if homesig:
            print("net %s => joint.%d.home-sw-in" % (homesig, num), file=file)
        min_limsig = self.min_lim_sig(let)
        if min_limsig:
            print("net %s => joint.%d.neg-lim-sw-in" % (min_limsig, num), file=file)
        max_limsig = self.max_lim_sig(let)
        if max_limsig:
            print("net %s => joint.%d.pos-lim-sw-in" % (max_limsig, num), file=file)

    def sim_hardware_halfile(self,base):
        custom = os.path.join(base, "sim_hardware.hal")
        if self.d.sim_hardware:
            f1 = open(custom, "w")
            print(_("# This file sets up simulated limits/home/spindle encoder hardware."), file=f1)
            print(_("# This is a generated file do not edit."), file=f1)
            print(file=f1)
            inputs = self.a.build_input_set()
            if SIG.PHA in inputs and not self.d.select_qtplasmac:
                print("loadrt sim_encoder names=sim-encoder", file=f1)
                print("setp sim-encoder.ppr %d"%int(self.d.spindlecpr), file=f1)
                print("setp sim-encoder.scale 1", file=f1)
                print(file=f1)
                print("net spindle-cmd-rps            sim-encoder.speed", file=f1)
                print("net fake-spindle-phase-a       sim-encoder.phase-A", file=f1)
                print("net fake-spindle-phase-b       sim-encoder.phase-B", file=f1)
                print("net fake-spindle-index         sim-encoder.phase-Z", file=f1)
                print(file=f1)
            print("loadrt sim_axis_hardware names=sim-hardware", file=f1)
            print(file=f1)
            for j in self.d.axislist:
                print("net {:16}joint.{}.pos-fb   sim-hardware.{}current-pos".format(j.upper() + "joint-pos-fb", self.d.axislist.index(j), j.upper()), file=f1)
            print(file=f1)
            for j in self.d.axislist:
                if j[0] == 'a':
                    limit = 20000
                else:
                    limit = 1000
                print("setp sim-hardware.{:14}{}".format(j.upper() + "maxsw-upper", limit), file=f1)
                print("setp sim-hardware.{:14}[JOINT_{}]MAX_LIMIT".format(j.upper() + "maxsw-lower", self.d.axislist.index(j)), file=f1)
                print("setp sim-hardware.{:14}[JOINT_{}]MIN_LIMIT".format(j.upper() + "minsw-upper", self.d.axislist.index(j)), file=f1)
                print("setp sim-hardware.{:14}-{}".format(j.upper() + "minsw-lower", limit), file=f1)
                print("setp sim-hardware.{:14}[JOINT_{}]HOME_OFFSET".format(j.upper() + "homesw-pos", self.d.axislist.index(j)), file=f1)
                if self.d.units: # change sim home switch hysteresis for metric configs
                    print("setp sim-hardware.{:14}{}".format(j.upper() + "homesw-hyst", 0.6), file=f1)
                print(file=f1)
            for port in range(0,self.d.number_pports):
                if port==0 or not self.d.pp2_direction: # output option
                    pinlist = (10,11,12,13,15)
                else:
                    pinlist = (2,3,4,5,6,7,8,9,10,11,12,13,15)
                self.inputlist = []
                for i in pinlist:
                    self.connect_input(f1, i, port, True)
                print(file=f1)
                if port==0 or not self.d.pp2_direction: # output option
                    pinlist = (1,2,3,4,5,6,7,8,9,14,16,17)
                else:
                    pinlist = (1,14,16,17)
                self.tandemsigs = {}
                if self.d.tandemjoints:
                    for j in self.d.tandemjoints:
                        self.tandemsigs["{}step".format(j)] = 1
                        self.tandemsigs["{}dir".format(j)] = 1
                self.outputlist = []
                for o in pinlist:
                    self.connect_output(f1, o, port, True) 
            print(file=f1)
            print("net fake-all-home        sim-hardware.homesw-all", file=f1)
            print("net fake-all-limit       sim-hardware.limitsw-all", file=f1)
            print("net fake-all-limit-home  sim-hardware.limitsw-homesw-all", file=f1)
            print(file=f1)
            for j in ["x","x2","y","y2","z","a","u","v"]:
                print("net {:21}sim-hardware.{}bothsw-out".format("fake-both-" + j, j.upper()), file=f1)
                print("net {:21}sim-hardware.{}maxsw-out".format("fake-max-" + j, j.upper()), file=f1)
                print("net {:21}sim-hardware.{}minsw-out".format("fake-min-" + j, j.upper()), file=f1)
            print(file=f1)
            for j in ["x","x2","y","y2","z","a","u","v"]:
                print("net {:21}sim-hardware.{}homesw-out".format("fake-home-" + j, j.upper()), file=f1)
            print(file=f1)
            for j in ["x","x2","y","y2","z","a","u","v"]:
                print("net {:21}sim-hardware.{}bothsw-homesw-out".format("fake-both-home-" + j, j.upper()), file=f1)
                print("net {:21}sim-hardware.{}maxsw-homesw-out".format("fake-max-home-" + j, j.upper()), file=f1)
                print("net {:21}sim-hardware.{}minsw-homesw-out".format("fake-min-home-" + j, j.upper()), file=f1)

#        if self.d.sim_hardware:
            print(file=f1)
            for j in self.d.axislist:
                if SIG.ALL_LIMIT_HOME in inputs:
                    print("net {}homing => sim-hardware.{}homing".format(j, j.upper()), file=f1)
                else:
                    print("net {}homing  joint.{}.homing => sim-hardware.{}homing".format(j, self.d.axislist.index(j), j.upper()), file=f1)

            f1.close()
        else:
            if os.path.exists(custom):
                os.remove(custom)

    def connect_input(self, file, num, port=0, fake=False):
        ending=''
        if port == 0:
            p = self.d['pin%d' % num]
            i = self.d['pin%dinv' % num]
        else:
            p = self.d['pp2_pin%d_in' % num]
            i = self.d['pp2_pin%d_in_inv' % num]
        if p == SIG.UNUSED_INPUT: return
        if p not in self.inputlist:
            self.inputlist.append(p)
        else:
            if not fake:
                print("duplicate input \"%s\" will not be connected to \"parport.%d.pin-%02d-in%s\"" % \
                      (SIG.human_input_names[SIG.hal_input_names.index(p)], port, num, ending))
            return
        if fake:
            p='fake-'+p
            ending='-fake'
            p ='{0:<20}'.format(p)
        else:
            p ='{0:<15}'.format(p)
        # comment out inputs for a qtplasmac sim
        comment = ""
        if self.d.select_qtplasmac:
            if p.strip()[-6:] in ["din-01", "din-02", "din-03"]:
                comment = "#qtplasmac uses this digital input:    "
            elif "spindle-" in p:
                comment = "#qtplasmac doesn't use a spindle:      "
            elif "probe-" in p:
                comment = "#qtplasmac doesn't use a probe:        "
            elif self.d.sim_hardware and "plasmac:" in p:
                comment = "#qtplasmac disabled for sim mode:      "
        if i and not fake:
            print("%snet %s <= parport.%d.pin-%02d-in-not%s" \
                % (comment, p, port, num, ending), file=file)
        else:
            print("%snet %s <= parport.%d.pin-%02d-in%s" \
                % (comment, p, port, num, ending), file=file)

    def connect_output(self, file, num, port=0, fake=False):
        ending=''
        if port == 0:
            p = self.d['pin%d' % num]
            i = self.d['pin%dinv' % num]
        else:
            p = self.d['pp2_pin%d' % num]
            i = self.d['pp2_pin%dinv' % num]
        if p == SIG.UNUSED_OUTPUT: return
        if p not in self.outputlist:
            self.outputlist.append(p)
        else:
            if not fake:
                print("duplicate output \"%s\" will not be connected to \"parport.%d.pin-%02d-out%s\"" % \
                      (SIG.human_output_names[SIG.hal_output_names.index(p)], port, num, ending))
            return
        if fake:
            signame ='fake-'+p
            ending='-fake'
            signame ='{0:<20}'.format(signame)
        else:
            signame ='{0:<15}'.format(p)
        if i and not fake: print("setp parport.%d.pin-%02d-out-invert%s 1" %(port, num, ending), file=file)
        # check for a tandem joint
        sig = signame.strip().replace("fake-", "")
        if sig in self.tandemsigs:
            value = self.tandemsigs[sig]
            if value == 2:
                signame = signame.replace('{} '.format(sig), '{}{}{}'.format(sig[0], value, sig[1:]))
            self.tandemsigs[sig] = value + 1
        # comment out inputs for a qtplasmac sim
        comment = ""
        if self.d.select_qtplasmac:
            if signame.strip()[-7:] in ["dout-01", "dout-02", "dout-03"]:
                comment = "#qtplasmac uses this digital output:   "
            elif "spindle-" in signame:
                comment = "#qtplasmac doesn't use a spindle:      "
            elif self.d.sim_hardware and "plasmac:" in signame:
                comment = "#qtplasmac disabled for sim mode:      "
        print("%snet %s => parport.%d.pin-%02d-out%s" % (comment, signame, port, num, ending), file=file)
        if self.a.doublestep() and not fake:
            if p in (SIG.XSTEP, SIG.YSTEP, SIG.ZSTEP, SIG.ASTEP, SIG.USTEP, SIG.VSTEP, SIG.X2STEP, SIG.Y2STEP):
                print("setp parport.0.pin-%02d-out-reset%s 1" % (num,ending), file=file)

    def min_lim_sig(self, axis):
        inputs = self.a.build_input_set()
        thisaxisminlimits = set((SIG.ALL_LIMIT, SIG.ALL_LIMIT_HOME, "min-" + axis, "min-home-" + axis,
                               "both-" + axis, "both-home-" + axis))
        for i in inputs:
            if i in thisaxisminlimits:
                if i==SIG.ALL_LIMIT_HOME:
                    # ALL_LIMIT is reused here as filtered signal
                    return SIG.ALL_LIMIT
                else:
                    return i

    def max_lim_sig(self, axis):
        inputs = self.a.build_input_set()
        thisaxismaxlimits = set((SIG.ALL_LIMIT, SIG.ALL_LIMIT_HOME, "max-" + axis, "max-home-" + axis,
                               "both-" + axis, "both-home-" + axis))
        for i in inputs:
            if i in thisaxismaxlimits:
                if i==SIG.ALL_LIMIT_HOME:
                    # ALL_LIMIT is reused here as filtered signal
                    return SIG.ALL_LIMIT
                else:
                    return i

    # qtplasmac hal connections
    def qtplasmac_connections(self, file, base):
        print("\n# ---PLASMA INPUT DEBOUNCE---", file=file)
        print("loadrt dbounce names=db_breakaway,db_float,db_ohmic,db_arc-ok", file=file)
        print("addf db_float     servo-thread", file=file)
        print("addf db_ohmic     servo-thread", file=file)
        print("addf db_breakaway servo-thread", file=file)
        print("addf db_arc-ok    servo-thread", file=file)
        print("\n# ---JOINT ASSOCIATED WITH THE Z AXIS---", file=file)
        jnum = 2
        if not self.d.sim_hardware:
            print("net plasmac:axis-position joint.{:d}.pos-fb => plasmac.axis-z-position".format(jnum), file=file)
        else:
            print("net Zjoint-pos-fb => plasmac.axis-z-position", file=file)
        comment = ""
        if self.d.sim_hardware:
            comment = "#qtplasmac disabled for sim mode:      "
        print("\n# ---PLASMA INPUTS---", file=file)
        print("# ---all modes---", file=file)
        print("{}net plasmac:float-switch   => db_float.in".format(comment), file=file)
        print("{}net plasmac:breakaway      => db_breakaway.in".format(comment), file=file)
        print("{}net plasmac:ohmic-probe    => db_ohmic.in".format(comment), file=file)
        print("net plasmac:ohmic-sense-in   => plasmac.ohmic-sense-in", file=file)
        print("# ---modes 0 & 1", file=file)
        print("{}net plasmac:arc-voltage-in => plasmac.arc-voltage-in".format(comment), file=file)
        print("# ---modes 1 & 2", file=file)
        print("{}net plasmac:arc-ok-in      => db_arc-ok.in".format(comment), file=file)
        print("# ---mode 2", file=file)
        print("{}net plasmac:move-up        => plasmac.move-up".format(comment), file=file)
        print("{}net plasmac:move-down      => plasmac.move-down".format(comment), file=file)
        print("\n# ---PLASMA OUTPUTS---", file=file)
        print("# ---all modes---", file=file)
        print("net plasmac:ohmic-enable   <= plasmac.ohmic-enable", file=file)
        print("net plasmac:scribe-arm     <= plasmac.scribe-arm", file=file)
        print("net plasmac:scribe-on      <= plasmac.scribe-on", file=file)
        # if encoder required for thcad
        if self.d.thcadenc:
            print("\n# ---ARC VOLTAGE ENCODER---", file=file)
            print("{}loadrt encoder num_chan=1".format(comment), file=file)
            print("{}addf encoder.update-counters base-thread".format(comment), file=file)
            print("{}addf encoder.capture-position servo-thread".format(comment), file=file)
            print("{}setp encoder.0.counter-mode 1".format(comment), file=file)
            print("{}setp encoder.0.position-scale 1".format(comment), file=file)
            print("{}net plasmac:arc-voltage-raw encoder.0.phase-A".format(comment), file=file)
            print("{}net plasmac:arc-voltage-in encoder.0.velocity".format(comment), file=file)
        # if ohmic sensing contact
        if self.d.ohmiccontact:
            print("\n{}net plasmac:ohmic-probe <= plasmac.ohmic-sense-out".format(comment), file=file)
        # add custom.hal if not existing
        chfilename = os.path.join(base, "custom.hal")
        if os.path.exists(chfilename):
            # else make a file with new values
            chfilename = os.path.join(base, "custom.hal.new_values")
        f1 = open(chfilename, "w")
        print(_("# Include your custom HAL commands here"), file=f1)
        print(_("# This file will not be overwritten when you run stepconf again"), file=f1)
        print("\n# for the float and ohmic inputs each increment in delay is", file=f1)
        print("# is a 0.001mm (0.00004\") increase in any probed height result", file=f1)
        print("setp db_float.delay     5", file=f1)
        if self.d.thcadenc:
            print("# set to zero if using internal ohmic sensing", file=f1)
            print("setp db_ohmic.delay     0", file=f1)
        else:
            print("setp db_ohmic.delay     5", file=f1)
        print("setp db_breakaway.delay 5", file=f1)
        print("setp db_arc-ok.delay    5", file=f1)
        # if ohmic sensing contact
        if self.d.ohmiccontact:
            print("\n# ---OHMIC SENSE CONTACT DEBOUNCE---", file=f1)
            print("setp plasmac.ohmic-sense-off-delay  3", file=f1)
            print("setp plasmac.ohmic-sense-on-delay   3", file=f1)
        # hints for fine tuning
        print(_("\n\n\n########################################"), file=f1)
        print(_("# The following variables are available for fine tuning some parameters."), file=f1)
        print(_("# To use any of these, uncomment the required setp line and set an appropriate value.\n"), file=f1)
        print(_("# Dampen excessive noise on the arc voltage input"), file=f1)
        print(_("# default = 0 (volts)"), file=f1)
        print("#setp plasmac.lowpass-frequency 0\n", file=f1)
        print(_("# The time delay from losing the arc ok signal until QtPlasmaC reacts to the arc loss."), file=f1)
        print(_("# default = 0.0 (seconds)"), file=f1)
        print("#setp plasmac.arc-lost-delay 0.0\n", file=f1)
        print(_("# For mode 0 Arc-OK only, the number of consecutive readings within the threshold that are required to set the Arc-OK signal."), file=f1)
        print(_("# default = 6"), file=f1)
        print("#setp plasmac.arc-ok-counts 6\n", file=f1)
        print(_("# For mode 0 Arc-OK only, the maximum voltage deviation that is allowed for a valid voltage to set the Arc OK signal."), file=f1)
        print(_("# default = 10 (volts)"), file=f1)
        print("#setp plasmac.arc-ok-threshold 10\n", file=f1)
        print(_("# The voltage above and below 0V that will display as 0V. Prevents small fluctuations from flickering the voltage display."), file=f1)
        print(_("# default = 0 (volts)"), file=f1)
        print("#setp plasmac.zero-window 0\n", file=f1)
        print(_("# The distance (in millimeters) away from the Z MAX_LIMIT that QtPlasmaC will allow the Z axis to travel while under machine control."), file=f1)
        print(_("# default = 5 (mm)"), file=f1)
        print("#setp plasmac.max-offset 5\n", file=f1)
        print(_("# The required number of consecutive times that the threshold has been exceeded before applying the void lock to the THC."), file=f1)
        print(_("# default = 2"), file=f1)
        print("#setp plasmac.kerf-error-max 2\n", file=f1)
        f1.close()
        # add custom_postgui.hal if not existing
        custom = os.path.join(base, "custom_postgui.hal")
        if not os.path.exists(custom):
            f1 = open(custom, "w")
            print(_("# Include your custom_postgui HAL commands here"), file=f1)
            print(_("# This file will not be overwritten when you run stepconf again"), file=f1)
            print(file=f1)
            f1.close()
        self.d.qtplasmacvscale = 1
        self.d.qtplasmacvoffset = 0
        # if using thcad for arc voltage and not a sim config
        if self.d.thcadenc & 1 and not self.d.sim_hardware:
            if self.d.voltsrdiv < 150:
                dratio = self.d.voltsrdiv
            else:
                dratio = (self.d.voltsrdiv + 100000) / 100000
            self.d.qtplasmacvscale = dratio / (((self.d.voltsfullf - self.d.voltszerof) * 1000) / int(self.d.voltsfjumper) / int(self.d.voltsmodel))
            self.d.qtplasmacvoffset = self.d.voltszerof * 1000 / int(self.d.voltsfjumper)
        # qtplasmac has a shutdown.hal
        sdfilename = os.path.join(base, "shutdown.hal")
        f1 = open(sdfilename, "w")
        print("# Include your shutdown HAL commands here", file=f1)
        print("# This file will not be overwritten when you run stepconf again", file=f1)
        f1.close()
        # if a sim config
        if self.d.sim_hardware:
            # make a sim_postgui.hal file
            spfilename = os.path.join(base, "sim_postgui.hal")
            f1 = open(spfilename, "w")
            print("# QTPLASMAC SIMULATOR PANEL", file=f1)
            print("\n# load the simulated torch", file=f1)
            print("loadusr -Wn sim-torch sim-torch", file=f1)
            print("\n# load the sim GUI", file=f1)
            print("loadusr -Wn qtplasmac_sim qtvcp qtplasmac_sim.ui", file=f1)
            print("\n# connect to existing plasmac connections", file=f1)
            print("net plasmac:torch-on        =>  qtplasmac_sim.torch_on  sim-torch.start", file=f1)
            print("net plasmac:cut-volts       =>  sim-torch.voltage-in", file=f1)
            print("\n# create new sim connections", file=f1)
            print("net sim:arc-ok              qtplasmac_sim.arc_ok                =>  db_arc-ok.in", file=f1)
            print("net sim:arc-voltage-in      sim-torch.voltage-out               =>  plasmac.arc-voltage-in", file=f1)
            print("net sim:arc_voltage_offset  qtplasmac_sim.arc_voltage_offset-f  =>  sim-torch.offset-in", file=f1)
            print("net sim:breakaway           qtplasmac_sim.sensor_breakaway      =>  db_breakaway.in", file=f1)
            print("net sim:float               qtplasmac_sim.sensor_float          =>  db_float.in", file=f1)
            print("net sim:move-down           qtplasmac_sim.move_down             =>  plasmac.move-down", file=f1)
            print("net sim:move-up             qtplasmac_sim.move_up               =>  plasmac.move-up", file=f1)
            print("net sim:ohmic               qtplasmac_sim.sensor_ohmic          =>  db_ohmic.in", file=f1)
            f1.close()

    def qtplasmac_prefs(self, prefsfile):
        def putPrefs(prefs, file, section, option, value):
            if prefs.has_section(section):
                prefs.set(section, option, str(value))
                prefs.write(open(prefsfile, "w"))
            else:
                prefs.add_section(section)
                prefs.set(section, option, str(value))
                prefs.write(open(prefsfile, "w"))
        from configparser import RawConfigParser
        prefs = RawConfigParser()
        prefs.optionxform = str
        putPrefs(prefs, prefsfile, 'PLASMA_PARAMETERS', 'Arc Voltage Offset', '{:.3f}'.format(self.d.qtplasmacvoffset))
        putPrefs(prefs, prefsfile, 'PLASMA_PARAMETERS', 'Arc Voltage Scale', '{:.6f}'.format(self.d.qtplasmacvscale))
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'Mode', self.d.qtplasmacmode)
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'Estop type', self.d.qtplasmacestop)
        dro = 'top' if self.d.qtplasmacdro else 'bottom'
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'DRO position', dro)
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'Flash error', self.d.qtplasmacerror)
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'Hide run', self.d.qtplasmacstart)
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'Hide pause', self.d.qtplasmacpause)
        putPrefs(prefs, prefsfile, 'GUI_OPTIONS', 'Hide abort', self.d.qtplasmacstop)
        for ub in range(1, 21):
            putPrefs(prefs, prefsfile, 'BUTTONS', '{} Name'.format(ub), self.d.qtplasmac_bnames[ub-1])
            putPrefs(prefs, prefsfile, 'BUTTONS', '{} Code'.format(ub), self.d.qtplasmac_bcodes[ub-1])
        putPrefs(prefs, prefsfile, 'POWERMAX', 'Port', self.d.qtplasmacpmx)

    # Boiler code
    def __getitem__(self, item):
        return getattr(self, item)
    def __setitem__(self, item, value):
        return setattr(self, item, value)
