esrf

Beamline Instrument Software Support
SPEC Macro documentation: [ Macro Index | BCU Home ]

"""
#%TITLE% eurotherm2400.mac
#$Revision: 4.2 $
#%NAME% Macros for a the use of a EuroTherm 2400 temperature controller.
#%DESCRIPTION%
# The macros provide users with an interface between SPEC and a EuroTherm 2400
# temperature controller.
#%CATEGORY% temperature
#%INTERNALS%
# You may declare a motor and a counter, even for the same
# device! The macro motor/counter macros can handle that.
#%BR%The temperature setpoint (or any other writable modbus tag) is driven as a
# (macro) motor, the process value "PV" and other tags can be read as (macro)
# counter.
#%SETUP%
# When used through a serial line, this macro motor set is using a macro set
# called modbus-rtu.mac. The communication with the Eurotherm controller is done
# using the modbus binary, called RTU, protocol!
#%BR%%BR%
# Only one controller is allowed in a Spec session. If need arises, that might
# be changed.
#
#
#Now declare a %B%motor\0controller%B%:
#
#
#%PRE%
#MOTORS    DEVICE                ADDR  <>MODE  NUM             <>TYPE
#   YES     E2400                   3          999       Macro Motors
#%PRE%
# or
#%PRE%
#MOTORS\0\0\0\0DEVICE\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0ADDR\0\0<>MODE\0\0NUM\0\0\0\0\0\0\0\0\0\0\0\0\0<>TYPE
#\0\0\0YES\0\0\0\0\0E2400\0\0\0id00/modbus/e2408\0\0\0\0\0\0\0\0\0\0999\0\0\0\0\0\0\0Macro Motors
#%PRE%

# %BR%
# If you use a serial line access, please put the index number of your serial line into the ADDR field (alphanum,  hit "'" first). Optionally the Modbus %B%instrument%B% address can be added, seperated from the serial line number with blank.
#
# If you access your instrument through a Modbus Tango device server, please
# enter its address. The Modbus instrument address is a property of the server,
# you can't change it in Spec.
# %BR% %BR%
#Then create the macro motor:
#%BR% %BR%Unit is index num of controller, second is the Modbus %B%TAG%B%
# address.
#%PRE%
#Number:\0<>Controller\0\0\0\0\0\0\0\0\0\0\00:\0MAC_MOT
#Unit/[Module/]Channel\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00/1
#Name\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0Eurot SP
#Mnemonic\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0m_euro
#Steps per degree/mm\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Sign of user * dial\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Backlash [steps]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00
#Steady-state rate [Hz]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Base rate [Hz]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Acceleration time [msec]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Motor accumulator\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00
#Restrictions <>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0NONE
#
#Dial = accumulator / steps
#\0\0High limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01000.0000
#\0\0Current\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#\0\0Low limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#
#User = sign * dial + offset
#\0\0Offset\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#\0\0`High' limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01000.0000
#\0\0Current\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#\0\0`Low' limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#%PRE%
# The value steps per degree should reflect, if the controller accepts floating
# point numbers or not (check Modbus tag address 525).
#%BR%
# Then hit 'm' twice. Set the following for your motor:
#%PRE%
#Hardware\0read\0mode\0<>\0\0\0\0\0\0\0\0PR\0+\0AL\0+\0NQ
#%PRE%
#%BR% %BR%Now declare a %B%scaler\0controller%B%:
#%PRE%
#SCALERS\0\0\0\0\0\0\0\0\0DEVICE\0\0\0\0\0\0\0\0\0\0ADDR\0\0<>MODE\0\0NUM\0\0\0\0\0\0\0\0\0\0\0\0\0<>TYPE
#\0\0\0YES\0\0\0\0\0\0\0\0\0\0\0\0POLL\0\0\0\0\0\0\0\0\0\0\0\0\01\0\0\0\0\0\0\0\0\0\0\0\02\0\0\0\0\0Software Timer
#\0\0\0YES\0\0\0\0\0\0\0\0\0\0\0E2400\0\0\0\0\0\0\0\0\0\0\03\01\0\0\0\0\0\0\0\0\0\0999\0\0\0\0\0\0Macro Counter
#%PRE%%BR%
#%BR% %BR%
# The %B%counter%B% is then configured as :
#%PRE%
#Number\0\0\0\0\0\0\0\0Name\0\0Mnemonic\0\0<>Device\0\0Unit\0\0Chan\0\0\0<>Use\0As\0\0Scale\0Factor
#\0\0\0\0\03\0\0\0\0\0\0T_euro\0\0\0\0t_euro\0\0\0MAC_CNT\0\0\0\0\00\0\0\0\0\01\0\0\0\0counter\0\0\0\0\0\0\0\0\0\0\0\0\01
#\0\0\0\0\04\0\0\0\0\0\0europv\0\0\0\0europv\0\0\0MAC_CNT\0\0\0\0\00\0\0\0901\0\0\0\0counter\0\0\0\0\0\0\0\0\0\0\0\0\01
#%PRE%%BR%
#%BR% %BR%
# This reads the Process Value (tag address 1) and places the value in the
# counter t_euro. One can read tag addresses as IEEE floating point numbers. To
# read a tag address as floating point number, add 900 to its tag address. To
# read the PV use 901.
#%BR%Other modbus tag addresses are:
#  %UL%
#  %LI%\0\01 - Read Process value
#  %LI%\0\02 - Change Setpoint
#  %LI%273 - Select Manual Mode
#  %LI%\0\03 - Change Output Power
#  %LI%\0\05 - Working Set Point
#  %LI%163 - Programmer setpoint
#  %LI%\036 - Segment time remaining (secs)
#  %LI%160 - Target setpoint (current segment)
#  %LI%161 - Ramp rate
#  %LI%\0\06 - Proportional band PID1
#  %LI%\0\08 - Integral time PID1
#  %LI%\0\09 - Derivative time PID1
#  %XUL%
# But please have a look at the documentation, if you have more specific needs:
# http://wikiserv.esrf.fr/bliss/images/5/52/Series_2000_Modbus_and_Ei-Bisynch_Digital_Communications_Handbook.pdf
#%EXAMPLE%
# As application example, one could create two separate spec session. The first
# session would start a previously defined program in the controller and then
# start a %B%timescan.%B% The timescan updates the process value continuously. A client
# spec session can now fetch the counter value, using the prop_get() builtin.
# Assume the counter number 3 reads you Eurotherm:
#%PRE%
# 16.HOLG> y = prop_get("localhost:spec", "var/S[3]")
# 17.HOLG> p y
# y["3"] = 100.3
#%PRE%
# The same can be accomplished with a simple remote counter :-), but you still
# have to count to read the instrument!%BR%
# Alternatively, one may trigger the read-out when needed by executing the
# %B%euro_read%B% macro. This macro will write the result to S[3], too.
#%PRE%
# remote_async("localhost:spec", "euro_read 3")
#%PRE% If you use a remote counter, then you can even just say
#%B%euro_read\0mne%B%.
#%BR% %BR%
# If you read your instrument through a %B%Eurotherm2400\0Tango\0device\0server%B%,
# you can declare the same instrument in several Spec sessions and just read out
# the process value in a timescan, while the first session could serve to define
# new programs in the controller.
#%BR% %BR%
# Due to the slow serial line connection, it has to be accounted for the long
# read out time. For faster handling, it is planned to write a device server,
# which does a PV polling.
#%BR% %BR%
#%B%For\0your\0information\0from\0the\0documentation:%B%%BR%%BR%
#There are four different Holdback types.   The choice of type is made by setting
#a parameter when creating a program, and may be one of the following:
#%DL%
#%DT%OFF%DD% Disables Holdback - therefore no action is taken.
#%DT%Lo%DD%  Deviation Low Holdback holds the program back when the process
# variable deviates below the setpoint by more than the holdback value.
#%DT%Hi%DD%  Deviation High Holdback holds the program back when the process
# variable deviates above the setpoint by more than the holdback value.
#%DT%bAnd%DD% Deviation Band Holdback is a combination of the two.  It holds the
# program back when the process variable deviates either above, or below, the
# setpoint by more than the holdback value.
#%XDL%
#%BR%%BR%There is a single Holdback Value which applies to the whole program.
# However, the Holdback type and whether or not it is enabled, can be applied to
# the program as a whole, or individually in each segment.
#%BR%%BR%
#%INTERNALS%
# Great care has to be taken, when reading selected addresses from the Modbus.
# Some of the mb addresses are given in 10th. This concerns the values for ramp
# rate/time and dwell time in segment programming. Also the status values for
# the remaining segment time in seconds (36) and minutes (63) are return as 10
# times of the real value.
#%END%
#%HISTORY%
#$Log: eurotherm2400.mac,v $
#Revision 4.2  2018/05/22 09:40:35  witsch
#Macros should now accept arguments unit=1 or Unit=1, as suggested by Alejandro
#to avoid confusion about camelback argument names. Now `unit` is being
#cased to lower and thus cases are no longer important.
#
#Revision 4.1  2017/07/06 14:28:26  witsch
#Refactoring of _euro_mne2unit, as it didn't work, when the controller
#index number was 0. It basically always chose the one with zero.
#
#Revision 4.0  2017/02/01 10:33:44  witsch
#Several changes asked by the beam lines:
#1 - when using europrogreset occasionally the setpoint wasn`t written
#    correctly and lead to the controller to go to unwished setpoints.
#2 - guess unit number or accept unit number or mnemonic as argument:
#    unit=3 or mne=euro. Mnemonic can be motor or counter. However, if
#    nothing it specified, the macros will just go and find the first
#    of the controllers in the config.
#3 - a futil attempt to make a kind of test with all user macros, where
#    the variable STARTUP_TEST is set and all user macros have an if to
#    quit from this test for STARTUP_TEST.
#
#Revision 3.8  2016/12/02 10:34:32  guilloud
#changed TANGO_ERR test.
#
#Revision 3.7  2016/08/05 14:23:25  witsch
#Small change in the europrog reset part, where a printed line was deleted
#and when resetting isn't wanted at the question, return to europrog, rather
#than the spec prompt.
#
#Revision 3.6  2016/01/05 16:06:35  guilloud
#fix strange character : ΒΌ ---> 1/4
#
#Revision 3.5  2014/10/22 08:29:15  witsch
#very small changes, to allow europrog to work again.
#
#When did this stop to work ? basically when you store "$1" in a variable, it becomes 0.
#Before it was "".
#
#Also the figure checking the controller number was increased to 100.
#
#Revision 3.4  2013/04/10 08:58:26  witsch
#error found by Roberto:
#when in the interactive part of europrog, after going to ".hold", the following run
#yielded an error about being unable to write to modbus address 22.
#
#This address is the program number (inside the e2400) to be executed.
#
#Bug has been corrected to only execute that part of the macro, when the controller is
#in RESET.
#
#Also corrected when the dection of the unit number went wrong and showed odd strings like
#Try one of the following arguments: 1max_progs, 1setpoint, 1pnum, 1max_segments, 1scale, 1firmwareversion, 1ident.
#
#Revision 3.3  2013/02/07 15:09:50  witsch
#bugfix: where one physical controller could be defined as counter and motor controller.
#if different controllers had the MAC_MOT index number, there could be a clash. Corrected!
#
#feature request from Roberto, for a motor_par(x, "ramprate") done!
#
#Revision 3.2  2012/11/12 15:55:19  witsch
#Bug fixed, where the read out of ramp rates and parameters were wrong, when controller
#was in Full/Integer Precision.
#
#Macro euro2400showpid allows to use other than unit 0.
#Macro euro2400menu did not declare bla[] local, which lead to an error, when bla was used
#globally.
#
#Revision 3.1  2012/11/09 13:36:52  witsch
#Fixed bug, where it was impossible to do a reset from europrog, when the E2400 macro motor
#controller was not in index position 0.
#Added printout of TANGO_ERR[0][desc] in case of a tango error.
#Changed the behaviour of the set point motor position. When not using the "eurowaitformove"
#
#feature, the position will be the value returned from the modbus address 2
#
#Revision 3.0  2012/06/20 08:00:58  witsch
#after a number of changes, a major release to mark the
##step, that has been made in making the macros more reliable.
#
#Revision 2.11  2012/05/14 12:30:00  witsch
#make the skipsegement work.
#
#make sure there is no ramping in the normal setpoint movement, when
#trying to run a program.
#
#Revision 2.10  2011/11/10 13:05:50  witsch
#Rewrite of the europrogreset macro. Apparently a bug had slipped into it.
#
#
#The new version will allow for a non-interactive setting of the setpoint
#The unit is set as "unit=<unit>" option.
#
#Please consult help local eurotherm2400 and search to europrogreset.
#
#Revision 2.9  2011/07/06 10:32:16  witsch
#some of the __europrog_show calls were missing the unit as 4 arg.
#
#Reprogram the europrogreset macro to allow a smooth setting of the
#temperature setpoint, to avoid the connected power supply plunging deep
#only to pump high power after a second.
#
#Revision 2.8  2011/02/16 15:06:14  witsch
#To treat the conflict between europrog from temperature.mac or
#eurotherm2400.mac all macros concerned with europrog were moved to the
#end of the file.
#Eurotherm2400.mac will overwrite the europrog macros, if they come from
#temperature.mac.
#
#Revision 2.7  2010/12/03 15:36:55  witsch
#A bug in E2400_config, where a MAC_CNT controller was declared unusable, was corrected.
#
#Added a list of Modbus addresses to the documentation.
#
#Added functions _euro_set_serial_parameters and _euro_set_serial_parameters
#to allow a search for a possible BiSync configuration of the controller. Thi
#is a often asked for feature, although the bad serial line cable is in 99%
#of the cases the problem!
#
#Revision 2.6  2010/05/07 13:38:56  perez
#Fix another bug in europrog
#
#Revision 2.5  2010/05/07 10:27:35  perez
#Fix bug on europrog/write missing unit field
#
#Revision 2.4  2009/12/11 10:22:27  witsch
#problem with setting a program number in tag address 22, when the programmer
#type only offered a single possible program.
#surrounded by an if () {}
#
#Revision 2.4  2009/12/09 11:22:51  witsch
#problem with setting a program number in tag address 22, when the programmer
#type only offered a single possible program.
#surrounded by an if () {}
#
#Revision 2.3  2009/12/01 16:39:01  witsch
#there was a problem with a variable declared global (loop) which already
#existed as a macro. Change variable name to _e2400_loop and others as well.
#
#Revision 2.2  2009/12/01 13:44:01  witsch
#perform reset during E2400_config to make sure the tango interface works.
#
#Revision 2.1  2009/11/17 13:53:46  witsch
#bug in euro2400parameters :
#local ushort array data[120] was declared only if there were no args. Moved
#it up to the beginning of the macro.
#
#Revision 2.0  2009/07/17 09:43:28  witsch
#added "abort_one" part to E2400_cmd, which allows to stop the macro motor, when a ^C is hit.
#Added setting the second set of PID values in euro2400menu.
#
#Revision 1.17  2009/07/06 08:07:47  witsch
#Several major changes:
#* changed E2400_config to check for cell 12550 for scaling. Happened on BM01
#  that 12550 was 1 and the values read were all integers.
#* Added macro euro2400menu, which allows to set ramprate and timeunit, as
#  well as the holdback behaviour of a simple setpoint change. This will allow
#  a simple "mv" to act as a ramp, which in turn will allow simple scans, with
#  ccd integration to work.
#* added macro eurowaitformove, which will switch the motor to wait for the
#  end of movement or not. (former motors_run)
#* some cosmetics, like make sure that data is always local and not global.
#
#Revision 1.16  2009/06/16 09:15:38  witsch
#* improved the lack of tango_[io|get] in older versions of spec.
#* include a firmware version check, as the version 4.11 don't do a
#  [rw]_float treatment of the temperature values.
#* fixed bug, where the max_segment member wasn't addressed correctly.
#* reintroduce the "get_status " function in E2400_cmd. This will allow
#  for umv to show the set point move, with the changes below.
#* new function "euro2400parameters", which allows to move the setpoint at
#  at ramprate with a ramprateunit. umv will show the moving setpoint with
#  the previous change.
#* in function "_europrog_status()" deleted possibility to skip segment.
#
#Revision 1.15  2009/06/09 12:17:08  witsch
#some little cosmetic changes.
#
#change the usage of a motor returning .error. from the _config function, when it had no programmable segments. This is the case,
#when the secondary E2400 is used.
#
#Revision 1.14  2009/05/18 14:29:51  witsch
#many little changes and bugfixes.
#
#Most notably the problem with the ramp rate and dwell times, where the
#value had to be multiplied with 10 to be correct, has been solved.
#
#Added macro euro2400_configuration, which allowes to change some of the
#configuration in config mode. This comprises: Resolution,
#DecimalPlaces, LowRangeLimit, HighRangeLimit and InstrumentUnits.
#
#Revision 1.13  2009/05/04 15:35:34  witsch
#put the ident treatment into the E2400_config function.
#
#the config now configures the controller and then each counter/motor.
#
#_europrog_write() will now offer an error message, if the input value is
#higher than the allow value programmed in mb address 12.
#
#deleted output power macro.
#
#Revision 1.12  2009/04/28 15:14:25  witsch
#Introduction of the Eurotherm tango server into the macros.
#
#Couple the access routines to the access point (device server or serial line)
#, rather than the controller unit number. The latter might create
#multiple entries into the global associative array (__E2400) for a single
#access point.
#
#The macro motor command function with argument "count" will read the polled
#value of the device server rather than read any modbus tag addresses from
#the device.
#
#Revision 1.11  2009/04/21 08:32:03  witsch
#after testing multiple controllers can be used.
#
#Change in _config, to set max_segments for controllers with only counters.
#
#Revision 1.10  2009/04/08 15:30:53  witsch
#quite some bug fixes, thanks to Roberto and Wouter.
#
#Revision 1.9  2009/03/31 15:05:16  witsch
#large changes program editing. Added saving a program after its definition
#as a scan in the data file. (and perhaps more).
#
#Revision 1.7  2008/12/08 10:30:52  witsch
#more cosmetics
#
#Revision 1.5  2008/10/06 12:07:40  witsch
#added a euro2400status macro. plus some cosmetic work.
#
#Revision 1.4  2008/09/29 08:31:22  witsch
#the channel number of a counter can now be the modbus tag address.
#In the _config, that means that the modbus address is fished out of
#E2400_ADDR.
#
#Revision 1.3  2008/09/25 15:34:00  witsch
#lots of bug fixes, help local eurotherm2400 should yield something
#intelligible.
#
#Revision 1.2  2008/09/18 06:51:14  witsch
#rename file and macros and internal varialbes to E2400. Handbook says, the
#definition of programs is only for 2400 family.
#
#Revision 1.1  2008/09/10 11:43:10  witsch
#Initial revision
#
"""


if (!(whatis("__e2400debug")  & 2)) rdef __e2400debug \'#$*\'

def __e2400_put(unit, fwaddr, value) '{
    if (__E2400[__E2400[unit]]["type"]) {
        return __e2400_put_tango(__E2400[unit], fwaddr, value)
    }
    else {
        return __e2400_put_serial(__E2400[unit], fwaddr, value)
    }
}
'

def __e2400_blockput(unit, fwaddr, num, data) '{
    if (__E2400[__E2400[unit]]["type"]) {
        return __e2400_blockput_tango(__E2400[unit], fwaddr, num, data)
    }
    else {
        return __e2400_blockput_serial(__E2400[unit], fwaddr, num, data)
    }
}
'

def __e2400_get(unit, fwaddr, num, data) '{
    if (__E2400[__E2400[unit]]["type"]) {
        return __e2400_get_tango(__E2400[unit], fwaddr, num, data)
    }
    else {
        return __e2400_get_serial(__E2400[unit], fwaddr, num, data)
    }
}
'

# some modbus functions might need to be declared although not needed, when
# connecting through a Modbus tango server. When working with over serial line,
# it is crucial, that people see this!!!
if (!(whatis("modb_rtu_addnode")   & 2)) {
    global _hw_bla
    rdef modb_rtu_addnode()  \'
    eprint "##########################################################"
    eprint "##########################################################"
    eprint "#####                                                #####"
    eprint "#####  Sorry, your lacking the modbus-rtu.mac file!  #####"
    eprint "#####                                                #####"
    eprint "##########################################################"
    eprint "##########################################################"
    return -1
\'
    _hw_bla=1
}
# only here, modbus_rtu_addnode is a valid macro function
if (_hw_bla) {
    modb_rtu_addnode("bla")
    unglobal _hw_bla
}

# older versions of Spec don`t have tango_io(), but the macros need it, even
# when not using it.
if (whatis("tango_io") == 0) {
    __StR__ = "def tango_io() \'{print \"no tango_io\"}\'"
    eval(__StR__)
    __StR__ = "def tango_get() \'{print \"no tango_get\"}\'"
    eval(__StR__)
    unglobal __StR__
}


#%UU%
#%MDESC% toggle debug mode for the present macros.
def e2400_debug '{
    if ((whatis("__e2400debug")>>16) <= 5) { # just a # sign -> off
        rdef __e2400debug "eprint \"Euro: \""
        print "e2400 debug is ON"
    } else {
        rdef __e2400debug \'#$*\'
        print "E2400 debug is OFF"
    }
}
'

#%IU%(node, fwaddr, value)
#%MDESC%
# Called by spec
def __e2400_put_serial(node, fwaddr, value) '{
    __e2400debug "__e2400_put_serial("  node "," fwaddr "," value ")"
    if (modb_rtu_write_a_word(node, fwaddr, value) < 0) {
        return(-1)
    }
    return(0)
}
'

#%IU%(node, fwaddr, value)
#%MDESC%
# Called by spec
def __e2400_blockput_serial(node, fwaddr, nwords, data) '{
    __e2400debug "__e2400_blockput_serial(" node "," fwaddr "," nwords ", data)"
    if (modb_rtu_write_n_words(node, fwaddr, nwords, data) < 0) {
        return(-1)
    }
    return(0)
}
'

#%IU%(fwaddr, fwaddr, num, data)
#%MDESC%
# Called by spec
def __e2400_get_serial(node, fwaddr, num, data) '{
    __e2400debug "__e2400_get_serial(" node "," fwaddr "," num ", data)"
    if (modb_rtu_read_n_words(node, fwaddr, num, data) < 0) {
        return(-1)
    }
    return(0)
}
'


#%IU%(fwaddr, value)
#%MDESC%(server, fwaddr, value)
# Called by spec
def __e2400_put_tango(server, fwaddr, value) '{
    local short array x[2];
    x[0]=fwaddr; x[1]=value;
    __e2400debug "__e2400_put_tango(" server ", " fwaddr ", " value ")"
    tango_io(server, "PresetSingleRegister", x);
    if (TANGO_ERR != "0") {
        eprint "FAILED: Writing", value, "to modbus tag address", fwaddr
        eprint TANGO_ERR_STACK["0"]["desc"];
        return(-1)
    }
    return(0)
}
'

#%IU%(server, fwaddr, nwords, data)
#%MDESC%
# Called by spec
def __e2400_blockput_tango(server, fwaddr, nwords, data) '{
    local short array x[2+nwords]
    x[0]=fwaddr; x[1]=nwords; x[2:] = data
    __e2400debug "__e2400_blockput_tango(" server ", " fwaddr ", " nwords ", data)"
    __e2400debug x
    whats x
    tango_io(server, "PresetMultipleRegisters", x);
    if (TANGO_ERR != "0") {
        eprint "FAILED: Writing", value, "to modbus tag address", fwaddr
#       eprint TANGO_ERR_STACK["0"]["desc"];
        return(-1)
    }
    return(0)
}
'


#%IU%(server, fwaddr, num, data)
#%MDESC%
# Called by spec.<BR>data is ushort array
#
def __e2400_get_tango(server, fwaddr, num, data) '{
    local short array x[2], y[num];
    local i, got
    x[0]=fwaddr; x[1]=num;
    got = tango_io(server, "ReadHoldingRegisters", x, y);
    if (TANGO_ERR != "0") {
#       eprint TANGO_ERR_STACK["0"]["desc"];
        return(-1)
    }
    data  = y
    __e2400debug "__e2400_get_tango(" server ", " fwaddr ", " num ", data) =", y
    return(0)
}
'

def __euro_rfloat(unit, val) '{
    __e2400debug "__euro_rfloat(" unit ", " val ")"
    return(val)
}
'

def __euro_wfloat(unit, val) '{
    return(int(val))
}
'


#%IU%(mne, type, unit, mod, chan)
#%MDESC%
# Called by spec
def E2400_config(mne, type, unit, mod, chan) '{
    # chan will be used for counters to designate the tag address. Leave alone.
    __e2400debug "Configuring Eurotherm", mne, type, unit, mod, chan

    global __E2400[]
    # list_init __E2400 never do that. It deletes our array!
    __E2400["savedir"] = BLISSADM "/local/userconf/" SPEC "/"
    local ushort array data[120]
    """
    # Using unit as the serial line number, must be already defined, and chan as
    # modbus address, use E2400_ADDR to grab Modbus address.
    # E2400_ADDR must contain a string with serial line index number and the
    # modbus address separated by a blank. If there is only one string, it will be
    # the serial line index number!

    # New addition, where the connection to a Eurotherm 2400 is made through a
    # Tango Modbus ds.
    # Here we need to detect, whether the address field contains a serial index
    # number or a device domain name. Sorry, no taco device here, it is always
    # assued to have a tango ds. Simply check the lenght to be bigger than 2!

    # Warning: the modbus device address is hidden as property in the tango ds.
    # Try to work out how to change it from Spec.
    """
    if (mne == "..") {
        local x, aux[], modbaddr
        if ((x = split(E2400_ADDR, aux)) == 2) {
            modbaddr  = aux[1]  # only used with direct serial line, tango ds has a
                                # property for that.
            E2400_ADDR = aux[0]
        } else if (x == 1) {
            modbaddr = 1
        }
        if (length(E2400_ADDR) > 2) { # modbus tango ds !
            if (tango_io(E2400_ADDR, "State") == -1) {
                eprint "Eurotherm: can`t reach", E2400_ADDR "!!!"
                return ".error."
            }
            # perform reset to make sure the interface will work.
            if (tango_io(E2400_ADDR, "Init") == -1) {
                eprint "Eurotherm: can`t reach", E2400_ADDR "!!!"
                return ".error."
            }
            # If a counter controller and a motor controller have the same
            # index number but differenct addresses, we need to create an
            # escape unit, which should allow to use both. This needs, of
            # course, to be exploited in the E2400_{cmd,par} macro functions!
            # Usually the counter controllers should be configured after the
            # motor controllers.
            if ((__E2400[unit] != 0) && (__E2400[unit] != E2400_ADDR)) {
                unit = "C" unit # should make a C0 or C1 or C2
            }
            __E2400[unit] = E2400_ADDR
            # now check if is a naked Modbus or a eurotherm ds.
            TANGO_ERR = -1
            if (tango_get(E2400_ADDR, "Temperature") != -1) {
                __E2400[__E2400[unit]]["type"] = 2 # use tango_get Temperature to read PV
            } else {
                # naked modbus server
                __E2400[__E2400[unit]]["type"] = 1 # which put/get function to use
            }
        } else {                # assume a Spec serial line.
            need modbus-rtu       # load the necessary modbus rtu macros
            local name
            name = unit ":" modbaddr
            if ((__E2400[unit] = modb_rtu_addnode(E2400_ADDR, modbaddr, name)) < 0) {
                tty_cntl("updated?"); tty_cntl("ce"); printf("\r")
                global ___e2400_check_bisync
                if ((time() - ___e2400_check_bisync) < 20) {
                    return ".error."
                }
                ___e2400_check_bisync = time()
                if (ser_par(E2400_ADDR,  "data_bits") != 8) {
                    # give it a shot at checking BiSync prot.
                    tty_cntl("mr")
                    local aux[]
                    eprint "E2400_config: No answer from Eurotherm, checking if it is on BiSync:"
                    aux = _euro_search_bisync(E2400_ADDR)
                    if (aux["addr"] == -1) {
                        eprint "E2400_config: Still no answer from Eurotherm, check cable!"
                    }
                    else {
                        eprint "E2400_config: Controller answered via BiSync protocol!"
                        eprint "Please switch to modbus ..."
                        eprint "(http://wikiserv.esrf.fr/bliss/index.php/Eurotherm_BySync_Configuration)"
                    }
                }
                tty_cntl("se")
                return ".error."
            }
        __E2400[__E2400[unit]]["type"] = 0
        }
        delete __E2400[__E2400[unit]]["max_progs"]
        if (__e2400_get(unit, 122, 1, data)) {
            return ".error."
        }
        local ident
        ident = data[0]
        # ident now contains a number in hex format which will identify your
        # controller.
        # mine was 0x2480 :-)
        #  Controller identifier
        #    in format >ABCD (hex),
        #    A = 2 (series 2000) B = Range number  C = Size       D = Type
        #                          2: 2200           3: 1/32 din    0: PID/on-off
        #                          4: 2400           6: 1/16 din    2: VP
        #                                            8: 1/8 din
        #                                            4: 1/4 din
        if(((ident >> 12) != 2) && (((data[0]>>8 & 0x0f) == 4) || ((data[0]>>8 & \
                    0x0f) == 2))) {
            print "This is not an Eurotherm 2000 series."
            print "Check you connected the right controller "
            return ".error."
        }
        __E2400[__E2400[unit]]["ident"] = sprintf("%x00", ident >> 8)
        """
        # There is the possibility to config the 2400 series with floating point
        # or integer values. Tag address 525 tells you how many digits appear
        # after the radix character. BUT !!!!! there was one controller with
        # firmware version 0x0411, on which this cell`s contents has no influence
        # on the modbus readings. The sample environment controllers have the
        # 0x0461 version.
        """
        if (__e2400_get(unit, 107, 1, data)) {
            return ".error."
        }
        __E2400[__E2400[unit]]["firmwareversion"] = data[0]
        # full (floating) or integer resolution, decides on wheather the modbus
        # values are like on the display or not.
        if (__e2400_get(unit, 12550, 1, data)) {
            return ".error."
        }
        if (data[0] == 1) { # Full resolution
            rdef __euro_ramp_rate_read_helper(unit,val)     \'{return val}\'
            rdef __euro_ramp_rate_write_helper(unit,val)    \'{return val}\'
            rdef __euro_rfloat(unit, val)                   \'{return val}\'
            rdef __euro_wfloat(unit, val)                   \'{return val}\'
            __E2400[__E2400[unit]]["scale"] = 1
        } else {            # Integer resolution
            rdef __euro_ramp_rate_read_helper(unit,val)     \'{return val / 10}\'
            rdef __euro_ramp_rate_write_helper(unit,val)    \'{return val * 10}\'
            if (__e2400_get(unit, 525, 1, data)) {
                return ".error."
            }
            __E2400[__E2400[unit]]["scale"] = pow(10, data[0])
            local str
            str = "{return(val / " __E2400[__E2400[unit]]["scale"] ")}"
            rdef __euro_rfloat(unit, val)   str
            str = "{return(int(val * " __E2400[__E2400[unit]]["scale"] "))}"
            rdef __euro_wfloat(unit, val)   str
        }
        """
        # catch the number of definable programs. Possible are 1, 4 and 20.
        # reading 20 segments in one go is not possible, as the modbus protocol
        # allows only 512 byte in one read. To avoid splitting this into two
        # read cycles, we only read 16 segments. This sounds sufficient.
        """
        if (__e2400_get(unit, 211, 1, data)) {
            return ".error."
        }
        if (data[0] == 32768) { # not controlling a heater or cooler ?!?
            ;
        } else {
            __E2400[__E2400[unit]]["max_segments"] = x > 16 ? 16 : data[0]
            # set to auto mode
            if (__e2400_put(unit, 273, 0)) {
            return ".error."
            }
        }
        if (__e2400_get(unit, 517, 1, data)) {
        return ".error."
        }
        __E2400[__E2400[unit]]["max_progs"] = data[0]
    }
    else if (type == "mot") {
        return E2400_cmd(mne, "position")
    }
}
'

#%IU%
#%MDESC%
# Called by spec
def E2400_cmd(mne, cmd, p1, p2, p3) '{
    local unit, module, channel, x, ieeevals, retval
    local ushort array data[120]
    unglobal ___e2400_check_bisync
    ieeevals = retval = 0
    __e2400debug "Command Eurotherm", mne, cmd, p1, p2, p3
    if (mne != "..") {
        if (cmd == "start_one") {
            # start counter, when it is one
            if (p2 == 0) {
                return
            }
            # must be a motor then
            unit    = motor_par(mne, "unit")
            channel = motor_par(mne, "channel")
            if (channel > 900) {
            channel  -= 900
                ieeevals  = 1
            }
        # make a move
        # check status, only answer if status is RESET
        if (__e2400_get(unit, 23, 1, data)) {
            eprint "Communication error with Eurotherm"
            return ".error."
        }
        if (data[0] != 1) {
            eprint "This command is not allowed! Try to set the Eurotherm to RESET!"
            return ".error."
        }
        # this is address 2, Target setpoint
        if (__e2400_put(unit, 2, __euro_wfloat(unit, p1))) {
            return ".error."
        }
        __E2400[__E2400[unit]]["setpoint"]  = p1
        }
        else if (cmd == "position") {
            unit    = motor_par(mne, "unit")
            channel = motor_par(mne, "channel")
            if (channel > 900) {
            channel  -= 900
                ieeevals  = 1
            }
            if ((!__E2400[unit][channel]) && (channel = 2)) {
                channel = 5 # return working setpoint rather than setpoint
            }
            # get angle
            # please consult manual to find out about Eurotherms way to offer
            # floating point values. Basically they are at addresses 0x8000 + original
            # address * 2
            if (ieeevals) {
                local ushort array bla[2]
                if (__e2400_get(unit, 0x8000 + 2*channel, 2, data)) {
                    return(".error.")
                }
                # take 32 bit just read and turns into IEEE float
                retval = __euro__float(data)
            }
            else {
                if (__e2400_get(unit, channel, 1, data)) {
                    return ".error."
                }
                retval = __euro_rfloat(unit, data[0])
            }
            # return position
            return(retval)
        }
        else if (cmd == "get_status") {
            # this get_status is targeted only to the SP and working SP. Any other channel
            # will get the not-busy right from the start.
            unit    = motor_par(mne, "unit")
            channel = motor_par(mne, "channel")
            if (!__E2400[unit][channel]) {
                return 0
            }
            if (channel > 900) {
                channel  -= 900
            }
            if (channel != 2) {
                return(0)
            }
            # check status, only answer if status is RESET
            if (__e2400_get(unit, 23, 1, data)) {
                return ".error."
            }
            if (data[0] != 1) {
                return ".error."
            }
            # compare with where to go
            local wsp, sp
            wsp = sp = 0
            if (__e2400_get(unit, 5, 1, data)) {
                return ".error."
            }
            wsp = __euro_rfloat(unit, data[0])
            if (__e2400_get(unit, 2, 1, data)) {
                return ".error."
            }
            sp = __euro_rfloat(unit, data[0])
            # now this might seem risky, to compare to "=", but as the modbus protocol only
            # delivers shorts, which might then be treated with the [rw]float function, this
            # should not be a problem.
            if (wsp != sp) {
                return 2
            }
            return(0)
        }
        else if (cmd == "counts") { # this is for the counter
            # a bit dirty, but why not, address 1 delivers the PV (process value),
            # address 2 the SP (set point) and address 3 the OP (Output power).
            # and for that matter, it would allow to set any tag address as a counter.
            # just set a sufficient number of channels in the E2400 counter
            # controller. there seems, however, to be a limit of 999 in Spec
            unit    = counter_par(mne, "unit")
            # goodness, this is tedious. If there is a counter controller with
            # the same index number as a motor controller, the unit will be
            # preceeded with a "C". But only if the units are different. So
            # check if C1 or whatever it is here exists and if not, go on with
            # the unit number without the C.
            local cunit
            cunit = "C" unit
            if (__E2400[cunit] != 0) {
                unit = cunit
            }
            channel = counter_par(mne, "channel")
            if (channel > 900) {
                channel  -= 900
                ieeevals  = 1
            }
            # please consult manual to find out about Eurotherms way to offer
            # floating point values. Basically they are at addresses 0x8000 +
            # original address * 2, answer is two shorts
            if (ieeevals) {
                # Temperature reading with the Eurotherm2400 tango server
                if((__E2400[__E2400[unit]]["type"] == 2) && (channel = 1)) {
                    return(tango_get(__E2400[unit], "Temperature"))
                }
                # and the rest is for Modbus servers and serial line comm.
                local y
                if (__e2400_get(unit, 0x8000 + 2*channel, 2, data)) {
                    return ".error."
                }
                retval = __euro__float(data) # takes 32 bit just read and returns IEEE float
            }
            else {
                if (__e2400_get(unit, channel, 1, data)) {
                return ".error."
                }
                retval = __euro_rfloat(unit, data[0])
            }
            # return count
            #
            return(retval)
            # end of count, this will read the value and return it!
        }
        else if (cmd == "abort_one") {
            unit    = motor_par(mne, "unit")
            channel = motor_par(mne, "channel")
            if (channel > 900) {
                channel  -= 900
            }
            if (channel != 2) {
                return(0)
            }
            # check status, only answer if status is RESET
            if (__e2400_get(unit, 23, 1, data)) {
                return ".error."
            }
            if (data[0] != 1) {
                return ".error."
            }
            # set the current temperature as setpoint
            if (__e2400_get(unit, 1, 1, data)) {
                return ".error."
            }
            if (__e2400_put(unit, 2, data[0])) {
                return ".error."
            }
            return(0)
        }
    }
}
'


#%IU%
#%MDESC%
# Called by spec with different keys to handle motor_par(mot,"key",new_value)
# actions.
def E2400_par(num, key, action, p1, p2) '{
    __e2400debug "Parameters Eurotherm", num, key, action, p1, p2
    unit    = motor_par(num, "unit")
    channel = motor_par(num, "channel")
    if ((key == "?") && (action == "get")) {
        return("waitformv, ramprate")
    }
    else if (key == "waitformv") {
        if (action == "get") {
            return __E2400[unit][channel]
        }
        else {
            __E2400[unit][channel] = p1
        }
    }
    else if (key == "ramprate") {
        local ushort array data[120]
        if (action == "get") {
            if (__e2400_get(unit, 35, 1, data)) {
                return ".error."
            }
            return data[0]
        }
        else {
            if (__e2400_put(unit, 35, p1)) {
                return ".error."
            }
        }
    }

}
'


#%IU% (<data>)
#%MDESC%
#  Converts 4 byte size values into IEEE floating point value and returns it.
#%BR%
#  The 2408 can be put into a floating point mode (read cell 12550) which then
#  displays values with variable number of floating point numbers. To avoid
#  getting in trouble with that, the protocol allows to read 4 bytes from
#  adresses over 0x8000. Thus, the cell 2 reads from %BR%
#  2 x 2 + 8000h = 8004h = 32772 decimal. %BR%
def __euro__float(inarr) '{
    # so s should contain 32 bits! but I have 2 shorts
    local i j retval, s[]
    __e2400debug "Eurotherm IEEE float conversion"
    local   f1, x, e
    # this would be with 4 bytes, independant of the array type
    s[0] = inarr[0] >> 8 & 0xff; s[1] = inarr[0] & 0xff
    s[2] = inarr[1] >> 8 & 0xff; s[3] = inarr[1] & 0xff
    f1 = 0x800000 | (s[1]&0x7f) << 16 | s[2] << 8 | s[3]
    e = ((s[0]&0x7F)<<1) | ((s[1]&0x80)>>7)
    e -= 127
    x = f1 * pow(2., -23. + e)
    if (s[0]&0x80)
        x = -x
    # and this is with 2 16 bit values, independant of the array type
    #  s = inarr
    #  f1 = 0x800000 | (s[0]&0x7f) << 16 | s[1] & 0xff00 | s[1] & 0xff
    #  e = ((s[0] >> 8 &0x7F)<<1) | ((s[1]>>8&0x80)>>7)
    #  e -= 127
    #  x = f1 * pow(2., -23. + e)
    #  if (s[0]>>8&0x80)
    #    x = -x
    __e2400debug sprintf("converting %04x and %04x to %g\n", inarr[0]&0xffff, inarr[1]&0xffff, x)
    return(x)
}
'



#%UU%mne
#%MDESC%
# read PV (process value) from the counter with mnemonic mne. Allows to trigger
# a read-out without counting. Value is returned, i.e. x = euro_read euro01
def euro_read '  E2400_cmd("$1", "counts")'

#%UU% <manual_setpoint> [unit=<unit>]
#%MDESC%
# Gives information about the status of the controller. Not to confuse with
# europrogstatus, which gives information about a program execution in the
# controller. This one reads the status bytes on a controller and puts them out
# in human readable form. Saves you to rtfm.
# The bits of the fast status read (tag 75) are as follows:%BR%
#%PRE%
# 0 Alarm 1 State ( 0 = Safe, 1 = Alarm )%BR%
# 1 Alarm 2 State ( 0 = Safe, 1 = Alarm )%BR%
# 2 Alarm 3 State ( 0 = Safe, 1 = Alarm )%BR%
# 3 Alarm 4 State ( 0 = Safe, 1 = Alarm )%BR%
# 4 Manual Mode ( 0 = Auto, 1 = Manual )%BR%
# 5 Sensor Break ( 0 = Good PV, 1 = Sensor Broken )%BR%
# 6 Loop Break ( 0 = Good closed loop, 1 = Open Loop )%BR%
# 7 Heater Fail ( 0 = No Fault, 1 = Load fault detected )%BR%
# 8 Tune Active ( 0 = Auto Tune disabled, 1 = Auto Tune active)%BR%
# 9 Ramp/Program Complete ( 0 = Running/Reset, 1 = Complete )%BR%
# 10 PV out of range ( 0 = PV within table range, 1 = PV out of table range )%BR%
# 11 DC control module fault (0= Good,. 1= BAD)%BR%
# 12 Programmer Segment Synchronise (0= Waiting, 1 = Running)%BR%
# 13 Remote input sensor break (0 = Good, 1 = Bad)%BR%
#%PRE%
# After reflexion, Ill give you all that is there :-), 74  to 77
def euro2400status '{
    local ushort array data[4]
    local unit, x
    _euro_mne2unit $*
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr, prgnum, prgsavefile
        unglobal STARTUP_TEST
    } else {
        if (__e2400_get(unit, 74, 4, data)) {
            return(-1)
        }
        eprint "The 2400s of Eurotherm offer some diagnostics. Here are the 4 bytes"
        eprint "one can look at with their bit`s meaning."
        # tag address 74: Fast Status Byte
        x = data[0]
        if (x & 0x01)   eprint "Alarm 1 State"
        if (x & 0x02)   eprint "Alarm 2 State"
        if (x & 0x04)   eprint "Alarm 3 State"
        if (x & 0x08)   eprint "Alarm 4 State"
        if (x & 0x10)   eprint "Manual Mode"
        else            eprint "Auto Mode"
        if (x & 0x20)   eprint "Sensor Broken"
        else            eprint "Good PV"
        if (x & 0x40)   eprint "Open 1G"
        else            eprint "Good closed loop"
        if (x & 0x80)   eprint "Heater Fail: Load fault detected"
        else            eprint "Heater Fail: No Fault"
        # Summary Output Status Word, tag address 75
        x = data[1]
        # skip all information already present from byte 74
        eprintf("Tune Active: Auto Tune active: ")
        if (x & 0x100)  eprint "active"
        else            eprint "disabled"
        eprintf("Ramp/Program Complete: ")
        if (x & 0x200)  eprint "Complete"
        else            eprint "Running/Reset"
        if (x & 0x400)  eprintf("PV out of")
        else            eprintf("PV within")
        eprint " table range"
        eprintf("DC control module fault: ")
        if (x & 0x800)  eprint "BAD"
        else            eprint "Good"
        eprintf("Programmer Segment Synchronise: ")
        if (x & 0x1000) eprint "Running"
        else            eprint "Waiting"
        eprintf("Remote input sensor break: ")
        if (x & 0x2000) eprint "Bad"
        else            eprint "Good"
        # tag address 76: Control Status Word
        x = data[2]
        if (x & 0x0001) eprint "Control algorithm Freeze"
        if (x & 0x0002) eprint "PV input sensor broken"
        if (x & 0x0004) eprint "PV out of sensor range"
        if (x & 0x0008) eprint "Self Tune failed"
        if (x & 0x0010) eprint "PID servo signal"
        if (x & 0x0020) eprint "PID debump signal"
        if (x & 0x0040) eprint "Fault detected in closed loop behaviour (loop break)"
        if (x & 0x0080) eprint "Freezes the integral accumulator"
        if (x & 0x0100) eprint "Indicates that a tune has completed successfully"
        if (x & 0x0200) eprint "Direct/reverse acting control"
        if (x & 0x0400) eprint "Algorithm Initialisation flag"
        if (x & 0x0800) eprint "PID demand has been limited."
        if (x & 0x1000) eprint "Autotune enabled"
        if (x & 0x2000) eprint "Adaptive tune enabled"
        if (x & 0x4000) eprint "Automatic Droop compensation enabled"
        if (x & 0x8000) eprint "Manual / Auto mode switch"
        # tag address 77: Instrument Status Word
        x = data[3]
        if (x & 0x0001) eprint "Config/Oper mode switch"
        if (x & 0x0002) eprint "Disables limit checking"
        if (x & 0x0004) eprint "SRL ramp running (Read Only)"
        if (x & 0x0008) eprint "Remote setpoint active"
        if (x & 0x0010) eprint "Alarm acknowledge switch."
    }
}
'


#%UU% program_number [file_name] <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC%
# Save the program with number program_number. Optional argument is the
# filename. Files are saved in ~blissadm/local/spec/userconf.
def euro2400saveprogram '{
    local unit, prgnum, prgsavefile
    prgnum = 1
    _euro_mne2unit $*
    if (!__E2400[__E2400[unit]]["max_segments"]) {
        eprint "$0: you have no controller defined yet!"
        eprint "   Please correct config."
        exit
    }
    if (! numargs ) {
        eprint "$0: use with arguments: program_number [file_name]"
    } else {
        if (numargs == 1) {
            prgnum = _myarr[$# - numargs]
            while (prgsavefile == "") {
                prgsavefile = getval("Save program under which name","")
            }
        }
        else if (numargs == 2) {

            prgsavefile = _myarr[$# - numargs + 1]
        }

        if (index(prgsavefile, "/") == 1) { # that`s an absolute pathname
            prgsavefile = prgsavefile
        } else {
            prgsavefile = __E2400["savedir"] prgsavefile
        }
        local str
        str = "_" USER
        if (!index(prgsavefile, str)) {     # already contains user name
            prgsavefile = prgsavefile str
        }

        # attempt for a kind of startup test
        if (STARTUP_TEST) {
            print "startup test: unit", unit, numargs, _myarr, prgnum, prgsavefile
            unglobal STARTUP_TEST
        } else {
            # array_dump appends data, so we have make sure, the file doesn`t exit
            unix(sprintf("/bin/rm -rf %s",  prgsavefile))
            # read the program to be sure we save the real thing
            global ushort array __E2400SAVE[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
            __E2400SAVE = _europrog_read(unit, prgnum)
            __e2400debug "euro2400saveprogram", __E2400SAVE
            # now array containes data
            create_directory_tree(__E2400["savedir"])
            array_dump(prgsavefile, __E2400SAVE)
            unglobal __E2400SAVE
            # note that if this program calls another one, it would have to be saved
            # separately, but this seems rare, so for now, we keep it simple.
            close(prgsavefile)
            print "Program", prgnum, "saved to", prgsavefile
        }
    }
}
'

need  editfilename
need  spec_utils

#%UU% [file_name] [program number] <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC%
# Restore a program previously saved. Only and optional argument is the
# filename. Files are restored from ~blissadm/local/spec/userconf.
#%BR%
def euro2400restoreprogram '{
    local unit, prgnum, prgsavefile
    prgnum = -1
    _euro_mne2unit $*
    if (!__E2400[__E2400[unit]]["max_segments"]) {
        eprint "$0: you have no controller defined yet!"
        eprint "   Please correct config."
        exit
    }
    if (numargs > 0) {
        prgnum = _myarr[$# - numargs]
    }
    if (numargs > 1) {
        prgsavefile = _myarr[$# - numargs +1]
    }
    if (!prgsavefile) {
        while (1) {
            if (whatis("editfilename")  & 2) {
                print "Restore program from which file (use TAB to show files):"
                prgsavefile = __E2400["savedir"]
                prgsavefile = editfilename(prgsavefile)
                print
            } else {
                prgsavefile = getval("Restore program from which name","")
                prgsavefile = __E2400["savedir"]
            }
            if (!file_info(prgsavefile, "-f")) {
                eprint "No such file!"
            } else {
                break
            }
        }
    }
    if (prgnum == -1) {
        while(1) {
            prgnum = getval("Enter the program number you want to restore \
your saved program to:", __E2400[__E2400[unit]]["pnum"])
            if ((prgnum >= 0) && (prgnum < __E2400[__E2400[unit]]["max_progs"])) {
                break
            }
        }
    }

    if ((prgnum < 0) || (prgnum > __E2400[__E2400[unit]]["max_progs"])) {
        eprint "$0: Program number is out of bounds, can be 1 to", __E2400[__E2400[unit]]["max_progs"]
        exit
    }

    if (!file_info(prgsavefile, "-f")) {
        eprint "No such file!"
        exit
    }

    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr, prgnum, prgsavefile
        unglobal STARTUP_TEST
    } else {
        # read the program to be sure we save the real thing
        global ushort array __E2400PRESTORED[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
        array_read(prgsavefile, __E2400PRESTORED)
        # note that if this program calls another one, it would have to be save
        # separately, but this seems rare, so for now, we keep it simple.
        local astr, __i, __aux[]
        __i = split(prgsavefile, __aux, "/"); __i --
        astr = "Stored program with name \"" __aux[__i] "\" (without sub-programs!)"
        unglobal _europrog_show_call
        _europrog_show(astr , 20, __E2400PRESTORED, nosubprog, 1)
        _europrog_write(unit, __E2400PRESTORED, prgnum)
    }
}
'

#%IU%(starttemp, memarr, nosubprg)
#%MDESC% Save eurotherm 2400 program from array `memarr` into the SPEC
# datafile.
def __europrog_save_as_scan(unit, starttemp, memarr, nosubprg) '{
    if (DATAFILE != "") {
        ond ; offt
        local _mysegtype, _segtstr[], title
        local __units[]
        __units[0] = "sec"; __units[1] = "min"; __units[2] = "hour";
        __units[3] =     1; __units[4] =    60; __units[5] =  3600 ;
        _segtstr[0] = "End"
        _segtstr[1] = "Ramp(rate)"
        _segtstr[2] = "Ramp(time)"
        _segtstr[3] = "Dwell"
        _segtstr[4] = "Step"
        _segtstr[5] = "CallProgram"

        if (_europrog_save_call == 0) {
            global _europrog_save_call __e2400_loop[]
            global __e2400_pli, __e2400_iiisave, _time_units
            _time_units  = memarr[2]

            global float array pleuro[50000][2]
            local iii, ii
            __e2400_pli = 0
            pleuro[0][0]     = __e2400_pli
            pleuro[0][1] = starttemp
            __e2400_pli ++
            title = "Eurotherm program definition " memarr[4] " cycle(s)"
            # HEADER
            printf("\n#S %d  %s\n#D %s\n", ++SCAN_N, title, date())
            printf("#T %g  (%s)\n", __units[_time_units + 3], "Seconds")

            _head_par G 0
            _head_par U 1
            _head_par M 2

            printf("#Q %s\n", _hkl_val)

            print "#P0"
            Fheader

            print "#N 2"
            print "#L Time  Eurotherm program"
            _time_units  = __units[memarr[2]+3]
        }
        _europrog_save_call ++
        tstart = starttemp

        # segments
        for (ii = 1 ; ii < __E2400[__E2400[unit]]["max_segments"] ; ii++ ) {
            _mysegtype = memarr[ii * 8]
            __e2400debug "__europrog_save_as_scan: segment type", _segtstr[_mysegtype], "start at", tstart
            if (_mysegtype == 0 ) {
            plisave = __e2400_pli
                break
            }
            else if (_mysegtype == 1 ) {
                local tstart, tstep, tend, tloop
                tend  = __euro_rfloat(unit,  memarr[(ii * 8) + 1]) # target temp
                tstep = __euro_rfloat(unit,  memarr[(ii * 8) + 2]) # ramp rate
                __e2400debug " __europrog_save_as_scan: ramp/rate!", tend, tstep
                tstart = pleuro[__e2400_pli-1][1]  # starting temp
                # tstep reading is always positiv, even if ramp goes down!
                tstep   = (tend - tstart) > 0 ? tstep : -tstep
                __e2400debug "from", tstart, "to", tend, "stepping", tstep, "degrees"
                __e2400debug "starting pli", __e2400_pli
                tloop = tstart
                for (tloop   = tstart; fabs(tend - tloop) > fabs(tstep);) {
                pleuro[__e2400_pli][0] = __e2400_pli * _time_units
                    tloop         += tstep
                    pleuro[__e2400_pli][1] = tloop
                    __e2400_pli ++
                }
                if (pleuro[__e2400_pli-1][1] != tend) {
                    pleuro[__e2400_pli][0] = __e2400_pli * _time_units
                    pleuro[__e2400_pli][1] = tend
                    __e2400_pli++
                }
                __e2400debug "     end pli", __e2400_pli
            }
            else if (_mysegtype == 2 ) {
                local tstart, tstep, tend, tloop
                tend   = __euro_rfloat(unit,  memarr[(ii * 8) + 1]) # ramp target
                tloop  = __euro_rfloat(unit,  memarr[(ii * 8) + 2]) # ramp time
                __e2400debug "__europrog_save_as_scan: ramp/time!", tend, tloop
                tstart = pleuro[__e2400_pli-1][1]  # starting temp
                tstep  = (tend - tstart) / tloop
                __e2400debug "from", tstart, "to", tend, "stepping", tstep, tloop, "times"
                __e2400debug "starting pli", __e2400_pli
                for (_iii = 0; _iii < tloop; _iii++) {
                pleuro[__e2400_pli][0] = __e2400_pli * _time_units
                    pleuro[__e2400_pli][1] = pleuro[__e2400_pli-1][1] + tstep
                    __e2400_pli ++
                }
                if (pleuro[__e2400_pli-1][1] != tend) {
                    pleuro[__e2400_pli][0] = __e2400_pli * _time_units
                    pleuro[__e2400_pli][1] = tend
                    __e2400_pli++
                }
                __e2400debug "     end pli", __e2400_pli
                }
                else if (_mysegtype == 3 ) {
                local x2, bla, _iii
                    x2 = __euro_rfloat(unit,  memarr[(ii * 8) + 2]) # dwell time
                    __e2400debug "__europrog_save_as_scan: dwell!", x2
                    __e2400debug "starting pli", __e2400_pli
                    for (_iii = 0; _iii < x2; _iii++) {
                    pleuro[__e2400_pli][0] = __e2400_pli * _time_units
                        pleuro[__e2400_pli][1] = pleuro[__e2400_pli-1][1]
                        __e2400_pli ++
                    }
                __e2400debug "     end pli", __e2400_pli
            }
            else if (_mysegtype == 4 ) { # segment type: step
                local x1
                x1 = __euro_rfloat(unit,  memarr[(ii * 8) + 1])
                __e2400debug "__europrog_save_as_scan: step!", x1
                pleuro[__e2400_pli][0] = __e2400_pli * _time_units
                pleuro[__e2400_pli][1] = x1
                __e2400debug "pli", __e2400_pli
                __e2400_pli++
            }
            else if (_mysegtype == 5 ) {
                local cycle subprog
                cycle   = memarr[(ii * 8) + 4]
                subprog = memarr[(ii * 8) + 3]
                if(nosubprg) {
                print "#C The displayed program demands the subprogram", subprog, "for",\
                    cycle, "cycles"
                    print "#C They are not included here!!!"
                } else {
                    __e2400debug "subprogram", cycle, "times"
                    print "#C The displayed program demands the subprogram", subprog, "for",\
                    cycle, "cycles"
                    __e2400_loop[_europrog_save_call] = ii
                    for (; iii < cycle; iii ++ ) {
                    local bstr
                        bstr = "__E2400PDATA" subprog
                        cyclesave = cycle
                        __e2400_iiisave   = iii
                        __europrog_save_as_scan(unit, pleuro[__e2400_pli-1][1], @bstr)
                        # after the recursive function call, the on and offs are gone
                        ond ; offt
                        # as are the local variables.
                        __e2400_pli = plisave
                        iii = __e2400_iiisave
                        cycle = cyclesave
                        __units[0] = "sec"; __units[1] = "min"; __units[2] = "hour";
                        __units[3] =     1; __units[4] =    60; __units[5] =  3600 ;
                        _segtstr[0] = "End"
                        _segtstr[1] = "Ramp(rate)"
                        _segtstr[2] = "Ramp(time)"
                        _segtstr[3] = "Dwell"
                        _segtstr[4] = "Step"
                        _segtstr[5] = "CallProgram"
                    }
                    ii = __e2400_loop[_europrog_save_call]
                }
            }
        }

        _europrog_save_call --
        if (_europrog_save_call == 0) {
            __e2400debug "Dumping pleuro[0:" __e2400_pli-1 "]"
            array_dump(pleuro[0:__e2400_pli-1])
            unglobal _europrog_save_call __e2400_loop[] __e2400_pli pleuro plisave __e2400_iiisave
            unglobal cyclesave _time_units
        }
        offd ; ont
    }
}
'

def __e2400_simple '{
    # only for holger
    # $1 address
    # $2  # of bytes
    # $3 unit
    local _myarr, option, unit, aux[], numargs
    _euro_mne2unit $*
    local mbaddr
    if (!numargs) {
        eprint "Give me at least a Modbus address to read!"
        exit
    } else {
        mbaddr = _myarr[$# - numargs + 1]
    }
    local ushort array data[120]
    __e2400_get(unit, mbaddr, 2, data)
    numb--
    print data[0:numb]
}
'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC%
# Show the PID values for unit
#%BR%
def euro2400showpid '{
    local _myarr, option, unit, aux[], bla
    _euro_mne2unit $*
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr
        unglobal STARTUP_TEST
    } else {
        local ushort array data[120]
        local prop, inte, deri
        __e2400_get(unit, 6, 1, data)
        prop = __euro_rfloat(unit, data[0])
        __e2400_get(unit, 8, 2, data)
        inte = data[0]
        deri = data[1]
        print "The PID values are : ", prop, inte, deri
    }
}
'

#%UU%  <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC%
# Please run without arguments to find out about the possible actions.
#
# Put controller into configuration mode and set one of several special cells.
# The following values influence the macros considerably. They can be set from
# the computer to make the user experience less painful.
#%BR%
# Note: no spaces are allowed around the `=` signs!
#%BR%
# Note: the decimal display will only be valid, if the Resolution is set to 0 (full)!!!
#
#%DL%
#%DT% Decimal places displayed
# (mb address 525)
#%DL%
#%DT%0:    nnnn
#%DT%1:    nnn.n
#%DT%2:    nn.nn
#%XUL%
#
#%DT% Resolution
#(mb address 12550)
#%DL%
#%DT%0:    Full
#%DT%1:    Integer
#%XUL%
#%DT% Low Range Limit
#(mb address 11)
#%DT% High Range Limit
#(mb address 12)
#%DT% Instrument Units
#(mb address 516)
#%DL%
#%DT%0:    oC
#%DT%1:    oF
#%DT%2:    oK
#%DT%3:    None
#%XUL%
#%XUL%
def euro2400configuration '{
    local currstat, _states, option, unit, x, i, good, usage, bla
    _states[0] = "NORMAL"; _states[1]  = "STANDBY"; _states[2] = "CONFIGURATION"
    # the variable usine refers to the French word `usine a gaz`, which characterizes an excessive
    # complexity, which remains incomprehsible for the uninitiated :-)
    # local _myarr[], aux[], bla[], varnam, usine[], doit
    local bla[], usine[], doit
    # I just hate to have the same strings twice, so use x as first index in usine[].
    x = "Resolution";      usine[x]["address"]  = 12550; usine[x]["poss"] = "0(Full)|1(Integer)"
    x = "DecimalPlaces";   usine[x]["address"]  =   525; usine[x]["poss"] = "0|1|2"
    x = "LowRangeLimit";   usine[x]["address"]  =    11; usine[x]["poss"] = "value"
    x = "HighRangeLimit";  usine[x]["address"]  =    12; usine[x]["poss"] = "value"
    x = "InstrumentUnits"; usine[x]["address"]  =   516; usine[x]["poss"] = "0(C)|1(F)|2(K)|3(None)"
    x = "RampRateUnits";   usine[x]["address"]  =   531; usine[x]["poss"] = "0(Sec)|1(Min)|2(Hour)"
    doit = 0 # any action necessary ?
    # unit recognition is done by the _euro_mne2unit macro
    _euro_mne2unit $*
    local aux[], bla[]
    for (option in _myarr) {
        split(_myarr[option], aux, "=")
        varnam = aux[0]
        if (string_tolower(varnam) == "unit")
            continue
        usine[varnam]["newval"] = aux[1]
        @varnam = aux[1]
        bla[varnam] = aux[1]
    }
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr
        unglobal STARTUP_TEST
    } else {
        local ushort array data[120]
        if(!numargs) {
            local x, y[], z, oldz
            printf("usage: %s \\\n", "$0")
            for (x in usine) {
                split(x, y, "\034")
                z = y[0]
                if (z != oldz) {
                    if (usine[z]["address"]) {
                        str = sprintf("\t%s=%s \\ ", z, usine[z]["poss"])
                    }
                    len = length(str)
                    for(i = len; i < 50; i++) {
                        str = str " "
                    }
                    if (__e2400_get(unit, usine[z]["address"], 1, data)) {
                        eprint "Couldn`t read value, try a different unit"
                        exit
                    }
                    str = str "(current value: " data[0] ")"
                    print str
                    oldz = z
                }
            }
            # Now do some checking.
            # Note: the decimal display will only be valid, if the Resolution is set to 0 (full)!!!
            # do this any time DecimalDisplay is changed, the overhead is minimal
            if (("DecimalDisplay" in bla) && !("Resolution" in bla)) {
                bla["Resolution"] = "" # make sure Resolution is set with the DecimalDisplay
                usine["Resolution"]["newval"] = 0
            }
            # the following checks if anything at all needs doing
            for (x in bla) {
                if (usine[x]["address"]) {
                    if (__e2400_get(unit, usine[x]["address"], 1, data)) {
                        eprint "Can`t read address", usine[x]["address"], "from unit", unit, "!"
                        exit
                    }
                    usine[x]["value"] = data[0]
                    if (usine[x]["value"] != usine[x]["newval"]) {
                        doit = 1 # yes, we need to go into config mode.
                        usine[x]["doit"] = 1 # and change this value
                    }
                }
            }
            if (doit) {
                # put controller into config mode
                if (__e2400_get(unit, 199, 1, data)) {
                    eprint "Can`t read address 199 from unit", unit, "!"
                    exit
                }
                currstat = data[0]
                    print "The controller was in", _states[currstat], "mode."
                    if (__e2400_put(unit, 199, 2)) {
                    eprint "Can`t write 2 to address 199 on unit", unit, "!"
                        exit
                    }
                if (__e2400_put(unit, 138, 2)) {
                    eprint "Can`t write Configuration Level Password to unit", unit, "!"
                    exit
                }
                for (x in bla) {
                    if (string_tolower(x) == "unit") {
                    continue
                    }
                    if (usine[x]["doit"]) {
                    if (__e2400_put(unit, usine[x]["address"], usine[x]["newval"])) {
                        eprint "Can`t write", usine[x]["newval"], "to address", usine[x]["address"],\
                        "on unit", unit, "!"
                    }
                    else if (__e2400_get(unit, usine[x]["address"], 1, data)) {
                        eprint "Can`t read address", usine[x]["address"], "from unit", unit, "!"
                    }
                    print x "`s old value was", usine[x]["value"] ", the new value is", data[0]
                    }
                }
                # set controller back into normal mode
                if (__e2400_get(unit, 199, 1, data)) {
                    eprint "Can`t read address 199 from unit", unit, "!"
                    exit
                }
                currstat = data[0]
                if (__e2400_put(unit, 199, 0)) {
                    eprint "Can`t write 0 to address 199 on unit", unit, "!"
                    exit
                }
                # here the controller is going to go offline for a time.
                print
                print "The controller will go through a reset now, this will take less than a minute!"
                print "Please wait until the controller becomes responsive."
                good = 0
                for (i = 0; i < 60; i ++) {
                    sleep(5)
                    if (__e2400_get(unit, 199, 1, data) == 0) {
                        good = 1
                        break
                    } else {
                        tty_cntl("updated?"); tty_cntl("ce"); printf("\r")
                    }
                }
                if (! good && __E2400[__E2400[unit]]["type"]) {
                    print "Apparently there is a problem with the communication with the controller."
                    print "Please restart the device server!"
                } else if (! good) {
                    # I found that when using the modbus-rtu macros a good bash at the serial line
                    # will solve the problem. Usually a ct 1 will get it back to work.
                    COUNT_TIME = 1
                    count_em
                } else {
                    if (__e2400_get(unit, 199, 1, data)) {
                    eprint "Can`t read address 199 from unit", unit, "!"
                        exit
                    }
                    currstat = data[0]
                    print "Now the controller is in", _states[currstat], "mode!"
                }
                print "After these changes, it is probable you have to reconfigure."
                    reconfig
            }
            else {
                print  "No action necessary!"
            }
        }
    }
}
'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC%
# Please run without arguments to find out about the possible actions.
#
# Set one of several special cells.
# The following values influence the macros considerably. They can be set from
# the computer to make the user experience less painful.
#%BR%
# Note: no spaces are allowed around the `=` signs!
#%BR%

def euro2400parameters '{
    local currstat, _states, option, unit, x, i, good
    # the variable usine refers to the French word `usine a gaz`, which
    # characterizes an excessive complexity, which remains incomprehsible for the
    # uninitiated :-)
    local bla[], usine[], doit
    local ushort array data[120]
    # I just hate to have the same strings twice, so use x as first index in usine[].
    x = "Proportional";    usine[x]["address"]  =     6; usine[x]["poss"] = "value"
    x = "Integral";        usine[x]["address"]  =     8; usine[x]["poss"] = "value"
    x = "Derivate";        usine[x]["address"]  =     9; usine[x]["poss"] = "value"
    x = "RampHoldBack";    usine[x]["address"]  =    70; usine[x]["poss"] = "0(off)|1(low)|2(high)|3(Band)"
    x = "RampHoldValue";   usine[x]["address"]  =    65; usine[x]["poss"] = "value"
    x = "RampRate";        usine[x]["address"]  =    35; usine[x]["poss"] = "value"
    # this parameter has been found to be part of the system configuration.
    # one needs to go to the configuration mode.
    #  x = "RampRateUnits";   usine[x]["address"]  =   531; usine[x]["poss"] = "0(Sec)|1(Min)|2(Hour)"
    _euro_mne2unit $*
    # ~ split("$*", myarr)
    local aux[], bla[]
    for (option in _myarr) {
        split(_myarr[option], aux, "=")
        varnam = aux[0]
        if (string_tolower(varnam) == "unit")
            continue
        usine[varnam]["newval"] = aux[1]
        @varnam = aux[1]
        bla[varnam] = "" # as a placeholder for the next for()
    }
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr
        unglobal STARTUP_TEST
    } else {
        doit = 0 # any action necessary ?
        if(!numargs) {
            local x, y[], z, oldz, str, len
            printf("usage: %s \\\n", "$0")
            for (x in usine) {
                split(x, y, "\034")
                z = y[0]
                if (z != oldz) {
                    if (usine[z]["address"]) {
                        str = sprintf("\t%s=%s \\ ", z, usine[z]["poss"])
                    }
                    len = length(str)
                    for(i = len; i < 50; i++) {
                        str = str " "
                    }
                    if (__E2400[__E2400[unit]]["max_segments"]) {
                        if (__e2400_get(unit, usine[z]["address"], 1, data)) {
                            eprint "Can`t access unit", unit, "!"
                            exit
                        }
                        str = str "(current value: " data[0] ")"
                    }
                    print str
                    oldz = z
                }
            }
            exit
        }
        # the following checks if anything at all needs doing
        for (x in bla) {

            if (usine[x]["address"]) {
                if (__e2400_get(unit, usine[x]["address"], 1, data)) {
                    eprint "Can`t access unit", unit, "!"
                    exit
                }
                usine[x]["value"] = data[0]
                # if (usine[x]["value"] != usine[x]["newval"]) {
                if (usine[x]["value"] != @x) {
                    doit = 1 # yes, we need to go into config mode.
                    usine[x]["doit"] = 1 # and change this value
                }
            }
        }
        if (doit) {
            for (x in bla) {
                if (string_tolower(x) == "unit") {
                continue
                }
                if (usine[x]["doit"]) {
                    if (__e2400_put(unit, usine[x]["address"], usine[x]["newval"])) {
                        eprint "Can`t access unit", unit, "!"
                        exit
                    }
                    if (__e2400_get(unit, usine[x]["address"], 1, data)) {
                        eprint "Can`t access unit", unit, "!"
                        exit
                    }
                    print x "`s old value was", usine[x]["value"] ", the new value is", data[0]
                }
            }
        }
        else {
            print  "No action necessary!"
        }
    }
}
'


#%IU%(memarr, unit)
#%MDESC% this is a special macro for europrog
# shows the values in a eurotherm program, just the values without conversion.
def _europrog_show_values(memarr) '{
    printf("%-20s %3s %3s", "Holdback type :", memarr[0], memarr[1])
    print
    printf("%-20s %3s", "Time units    :", memarr[2])
    print
    printf("%-20s %3s", "Run (cycles)  :", memarr[4])
    print

    local _mysegtype
    # segments
    for (ii = 1 ; ii < __E2400[__E2400[unit]]["max_segments"] ; ii++ ) {
        printf("%-10s", "Segment # "); _europrog_print_so(ii); printf(" : ")
        _mysegtype = memarr[(ii * 8)]
        if (_mysegtype > 5 || _mysegtype < 0 ) {
            _europrog_print_error("Type has to be between 0 and 5")
            ii --
            break
        } else {
            _mysegtype = memarr[ii * 8]
            if (_mysegtype == 0 ) {
                local __type[], str
                __type[0] = "Reset"; __type[1] = "Indefinite_Dwell"
                __type[2] = "SetOutput"
                printf("%-11s %3s ", "End", memarr[(ii * 8) + 3])
                print
                break
            }
            else if (_mysegtype == 1 ) { # ramp (rate)
                local x1, x2
                x1 = memarr[(ii * 8) + 1]
                x2 = memarr[(ii * 8) + 2]
                printf("%-11s ", "Ramp (rate)")
                printf(  "%3s ", x1)
                printf(  "%3s ", x2)
                print
            }
            else if (_mysegtype == 2 ) { # ramp (time)
                local x1, x2
                x1 = memarr[(ii * 8) + 1]
                x2 = memarr[(ii * 8) + 2]
                printf("%-11s ", "Ramp (time)")
                printf(  "%3s ", x1)
                printf(  "%3s ", x2)
                print
            }
            else if (_mysegtype == 3 ) { # dwell
                x2 = __euro_rfloat(unit,  memarr[(ii * 8) + 2])
                printf("%-11s ", "Dwell")
                printf(  "%3s ", " ")
                printf(  "%3s ", x2)
                print
            }
            else if (_mysegtype == 4 ) { # Step
                local x1
                x1 = __euro_rfloat(unit, memarr[(ii * 8) + 1])
                printf("%-11s ", "Step")
                printf(  "%3s ", x1)
                print
            }
            else if (_mysegtype == 5 ) { # Call
                local cycle subprog
                cycle   = memarr[(ii * 8) + 4]
                subprog = memarr[(ii * 8) + 3]
                printf( "%s", "Call subprog")
                printf(  "%3s ", subprog)
                printf(  "%3s ", cycle)
                print
            }
        }
    }
}
'

#%UU% <mne>
#%MDESC%
# toggle behaviour for motor mne to wait for the end of a move, if a simple mv
# command is using a ramp rate.
#%END%
# call this macro euro... instead of euro2400 as I will try to make macros for
# both types 2400s and 2700s.
def eurowaitformove '{
    local mne, unit
    _euro_mne2unit $*
    local num
    num = motor_num(mne)
    channel = motor_par(num, "channel")
    x = motor_par(num, "waitformv")
    motor_par(num, "waitformv", x ? 0 : 1)
    print "Motor", motor_mne(num), "will", motor_par(num, "waitformv") ? "NOT " : \
        "" "go back to the prompt after a mv command"
}
'

#%IU%<unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Set various parameters in the controller. Only possible for unit number 0 in
# this spec session.
def euro2400menu '{
    # the function assumes that the _show function was used and the
    # display started from line 0.
    local __units[], _time_units, holdbacktype[], answer[], x, usine[], bla[]
    local unit, mne
    __units[0] = "seconds"; __units[1] = "minutes"; __units[2] = "hours"
    holdbacktype[0] = "OFF"; holdbacktype[1] = "Low"
    holdbacktype[2] = "High"; holdbacktype[3] = "Band"
    _euro_mne2unit $*
    local ii, line, lines
    if (COLS < 80) {
        eprint "Your window is too small!"
        eprint "Please increase size!"
        return(-1)
    }
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr
        unglobal STARTUP_TEST
    } else {
        for(ii = 0; ii < ROWS; ii ++) {
            print
        } # don`t do a clear screen as often visual information is lost.
        _time_units  = memarr[2]
        x = "Proportional2"; bla[x] = ""; usine[x]["address"]  =  48; usine[x]["dp"] = 1; usine[x]["poss"] = "value"
        x = "Integral2";     bla[x] = ""; usine[x]["address"]  =  49; usine[x]["dp"] = 0; usine[x]["poss"] = "value"
        x = "Derivate2";     bla[x] = ""; usine[x]["address"]  =  51; usine[x]["dp"] = 0; usine[x]["poss"] = "value"
        x = "Proportional1"; bla[x] = ""; usine[x]["address"]  =   6; usine[x]["dp"] = 1; usine[x]["poss"] = "value"
        x = "Integral1";     bla[x] = ""; usine[x]["address"]  =   8; usine[x]["dp"] = 0; usine[x]["poss"] = "value"
        x = "Derivate1";     bla[x] = ""; usine[x]["address"]  =   9; usine[x]["dp"] = 0; usine[x]["poss"] = "value"
        x = "Current PID set";bla[x] = "";usine[x]["address"]  =  72; usine[x]["dp"] = 0; usine[x]["poss"] = "0(Set 1)|1(Set 2)"
        x = "RampHoldBack";  bla[x] = ""; usine[x]["address"]  =  70; usine[x]["dp"] = 0; usine[x]["poss"] = "0(off)|1(low)|2(high)|3(Band)"
        x = "RampHoldValue"; bla[x] = ""; usine[x]["address"]  =  65; usine[x]["dp"] = 1; usine[x]["poss"] = "value"
        x = "RampRate";      bla[x] = ""; usine[x]["address"]  =  35; usine[x]["dp"] = 1; usine[x]["poss"] = "value"
        x = "RampRateUnits"; bla[x] = ""; usine[x]["address"]  = 531; usine[x]["dp"] = 0; usine[x]["poss"] = "0(Sec)|1(Min)|2(Hour)"

        local ushort array data[120]
        for (x in bla) {
            if (usine[x]["address"]) {
                if (__e2400_get(unit, usine[x]["address"], 1, data)) {
                    eprint "Can`t access unit", unit, "!"
                    exit
                }
                usine[x]["value"] = usine[x]["dp"] ? __euro_rfloat(unit, data[0]) : data[0]
            }
        }
        #build the screen
        local twid, fwid, format
        twid = 17; fwid = 8; format = "%-" twid "s%-2s"
        twid += 2 # for the :
        tty_cntl("ho") # go home
        print "       Eurotherm 2400 setpoint parameters" # line 0
        print # line 1
        x = "RampHoldBack"
        printf(format, "Holdback type", ":")
        _europrog_print_so(holdbacktype[usine[x]["value"]], fwid)
        if (usine[x]["value"]) {
            local y
            y = "RampHoldValue"
            printf("   with  ")
            _europrog_print_so(usine[y]["value"], 4)
            print "   degree(s) width"
        } else {
            print
        }
        # that was line 2
        printf(format, "Time units", ": ")
        _europrog_print_so(__units[usine["RampRateUnits"]["value"]], fwid)
        print # line 3
        printf(format, "Ramp rate", ": ");
        _europrog_print_so(usine["RampRate"]["value"], fwid)
        print # line 4
        print # 5

        x = "Current PID set"
        printf(format, x, ": ")
        _europrog_print_so(usine[x]["value"], fwid)
        print # 6
        printf(format, "P1", ": ")
        _europrog_print_so(usine["Proportional1"]["value"], fwid)
        print # 7
        printf(format, "I1", ": ")
        _europrog_print_so(usine["Integral1"]["value"], fwid)
        print # 8
        printf(format, "D1", ": ")
        _europrog_print_so(usine["Derivate1"]["value"], fwid)
        print # 9
        printf(format, "P2", ": ")
        _europrog_print_so(usine["Proportional2"]["value"], fwid)
        print # 10
        printf(format, "I2", ": ")
        _europrog_print_so(usine["Integral2"]["value"], fwid)
        print # 11
        printf(format, "D2", ": ")
        _europrog_print_so(usine["Derivate2"]["value"], fwid)
        print # 12

        line  = 2
        answer[1] = "HWnext field"
        while(1) {
            if (line == 2) {
                x = "RampHoldBack"
                usine[x]["newvalue"] = usine[x]["value"]
                if (answer[1] == "HWnext field") { # coming from next field
                answer = europrog_multiplechoice(usine[x]["value"], holdbacktype, fwid, twid, line)
                    usine[x]["newvalue"] = answer[0]
                    if (answer[1] == "HWprevious field") { # goto previous edit
                    line --
                    } # else stay on the same line
                }
                if (line == 2 && usine["RampHoldBack"]["newvalue"]) {
                    x = "RampHoldValue"
                    _europrog_print_so(holdbacktype[usine["RampHoldBack"]["newvalue"]], fwid)
                    printf("   with  ")
                    _europrog_print_so(usine[x]["value"], 4)
                    print "   degree(s) width"
                    answer = europrog_editfield(usine[x]["value"], fwid, 2*twid, line)
                    usine[x]["newvalue"] = answer[0] * 1
                    if (answer[1] == "HWnext field") { # goto next edit
                        line ++
                    } else { # previous is the first one on same line
                        answer[1] = "HWnext field"
                    }
                } else { # no second value to edit
                    line ++
                }
            }
            if (line == 3) {
                x = "RampRateUnits"
                answer = europrog_multiplechoice(usine[x]["value"], __units, fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line --
                }
            }
            if (line == 4) {
                x = "RampRate"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line = 6
                } else {
                line --
                }
            }
            if (line == 6) {
                x = "Current PID set"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line = 4
                }
            }
            if (line == 7) {
                x = "Proportional1"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line --
                }
            }
            if (line == 8) {
                x = "Integral1"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line --
                }
            }
            if (line == 9) {
                x = "Derivate1"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line --
                }
            }
            if (line == 10) {
                x = "Proportional2"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line --
                }
            }
            if (line == 11) {
                x = "Integral2"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line --
                }
            }
            if (line == 12) {
                x = "Derivate2"
                answer = europrog_editfield(usine[x]["value"], fwid, twid, line)
                usine[x]["newvalue"] = answer[0] * 1
                if (answer[1] == "HWnext field") { # goto next edit
                line ++
                } else {
                line  = 4
                }
            }
            if (line == 13) {
                print
                break
            }
        }

        for (x in bla) {
            if (usine[x]["address"]) {
                if (usine[x]["address"] == 531 && \
                    usine[x]["newvalue"] != usine[x]["value"]) {
                    local str
                    str = "euro2400configuration RampRateUnits=" usine[x]["newvalue"]
                    eval(str)
                }
                else if (usine[x]["newvalue"] != usine[x]["value"]) {
                    if ((usine[x]["address"] == 65) && usine[x]["newvalue"] == 0 ) {
                        ; # add 65 doesn`t like 0
                    }
                    else {
                        if (__e2400_put(unit, usine[x]["address"], usine[x]["dp"] ? __euro_wfloat(unit, usine[x]["newvalue"])\
                            : usine[x]["newvalue"])) {
                            eprint "Can`t write", usine[x]["newvalue"], "to address", usine[x]["address"],\
                            "on unit", unit, "!"
                        }
                    }
                }
            }
        }
        _europrog_print_so("Use \"eurowaitformove mne\" to set a motor to wait for\
            the end of a move!")
    }
}
'

#-----------------------------------------------------------
#%IU%(mne)
#%MDESC% Setting serial line parameters
def _euro_set_serial_parameters(serialline, parity, speed) '{
    global ESRF_ERR
    local params[], device, lspeed, lparity, lbytes
    device = ser_par(serialline, "device_id")
    if (index(device, "/dev/")) {
        eprint "_euro_set_serial_parameters(): can`t set parameters on serial line", \
                           device
        return(-1)
    }
    if (!speed)  { lspeed = 9600 }
    else         { lspeed = speed }
    if (parity)  { lparity = 3; lbytes = 1 }
    else         { lparity = 0; lbytes = 0 }
    # setting the parameters as wished by the Tango DS
    params[ 0] =     3 # SL_TIMEOUT    /* timeout parameter */
    params[ 1] =   500 # timeout value
    params[ 2] =     4 # SL_PARITY     /* number of parity bits parameter */
    params[ 3] =lparity# even parity
    params[ 4] =     5 # SL_CHARLENGTH /* number of data bits parameter */
    params[ 5] =lbytes # 7 data bits
    params[ 6] =     6 # SL_STOPBITS   /* number of stop bits parameter */
    params[ 7] =     0 # 1 stop bit
    params[ 8] =     7 # SL_BAUDRATE   /* baud rate parameter */
    params[ 9] =lspeed # baud

    ESRF_ERR = -1
    esrf_io(device, "DevSerSetParameter", params)
    if (ESRF_ERR){
        __e2400debug  "_euro_set_serial_parameters: esrf_io(\"" device "\", \"DevSerSetParameter\", params) failed!"
        return(0)
    } else {
        return(1)
    }
}
'



# this is all a little tedious, I have no way to interrogate the server, what config it
# is running. But I can set it. However, I can ask the spec serial line how many data
# bits it is using. Usually 8 databits mean no parity, 7 databits mean even parity.
# Let`s hope, this assumption is right.
def _euro_search_bisync(serialline, enda) '{
    local sl, i, answer, retval[], oldslconf
    retval["addr"] = -1
    retval["answer"] = ".error."
    sl = serialline
    if (! enda) end = 4
    else end = enda
        oldslconf = ser_par(serialline, "data_bits") # issues 7 or 8, 7 is evenp (likely!)
        if (oldslconf == 8) { # 8 databits, switsch to raw evenp
            _euro_set_serial_parameters(serialline, 1)
        }
    for (i = 0; i < end; i++) {
        ser_put(sl, sprintf("\00400%d%dPV\005", i, i))
        sleep(1)
        answer = ser_get(sl)
        if (answer != "") {
        retval["addr"] = i
            local l
            l = length(answer)
            retval["answer"] = substr(answer, 2, l - 3)
            break
        }
    }
    if (oldslconf == 8) { # was 8 databits, switsch back to raw evenp
        _euro_set_serial_parameters(serialline, 0)
    }
    return retval
}
'

if (whatis("europrog") & 2) { #macro present
    # some cosmetics
    tty_cntlred    = ""
    tty_cntlnotred = ""

    ### introduce some code for the unfortunate choice to have a second europrog
    ### in eurotherm2400.mac on top of temperature.mac. Mea culpa,  Holger
    global grepeuroprog
    local fname, str
    pname   = "/tmp/" SPEC rand()
    str     = "/bin/rm -f " pname
    unix(str)
    on(pname)
    offt
    prdef europrog
    close(pname)
    ont
    str     = "head -2 " pname
    unix(str, grepeuroprog)
    if (index(grepeuroprog, "temperature.mac")) {
        eprint tty_cntlred \
        "    europrog is now loaded from eurotherm2400.mac !!!" \
        tty_cntlnotred
    }
    unglobal grepeuroprog tty_cntlred tty_cntlnotred
}



#%IU% (unit, state)
#%MDESC% this is a special macro for europrog
# Sends a program control command to a Eurotherm 2400 series instrument.
def _europrog_cmd(unit, state) '{
    local _ret _states
    local ushort array data[120]
    #  1: Off
    #  2: Run
    #  4: Hold
    #  16 End
    #  32: Dwell
    #  64 Ramp
    _states[1] = "RESET"   ; _states[2]  = "RUN"       ; _states[4] = "HOLD"
    _states[8] = "HOLDBACK"; _states[16] = "COMPLETED" ; _states[32] = "Dwell"
    _states[64] = "Ramp"
    if (__e2400_get(unit, 23, 1, data)) {
        return -1
    }
    _ret = data[0]
    printf("\rProgram state is ")

    # _ret is not power of 2
    if ((_ret-1) & _ret) {
        print "UNKNOWN=" _ret
    } else {
        print _states[_ret]
    }
    if (!state) return(_ret)
    # returns the state
    if ((state-1) & state) {
    # state is not power of 2
        _europrog_status(unit)
    } else {
    # write the new state
        if (__e2400_put(unit, 23, state)) {
        eprint
            eprint "This state might not be allowed to be set from the state"
            eprint "you are in right now!"
        }
    }
    # and read back
    if (__e2400_get(unit, 23, 1, data)) {
    return -1
    }
    _ret = data[0]
    print "New program state is", _states[_ret]
    return _ret
}
'

#%IU% (unit)
#%MDESC% this is a special macro for europrog
# Reads the status of a program on a Eurotherm 2400 series instrument.

# This macro has been adapted from the former one in temperature.mac. There
# seems to be more testing necessary!
def _europrog_status(unit) '{
    _states[1] = "RESET"   ; _states[2]  ="RUN"        ; _states[4] = "HOLD";
    _states[8] = "HOLDBACK"; _states[16] = "COMPLETED"
    local ushort array data[120]

    if (__e2400_get(unit, 23, 1, data)) {
        return -1
    }
    _ret = data[0]
    printf("\rProgram state is ")

    # _ret is not power of 2
    if ((_ret-1) & _ret) {
        print "UNKNOWN=" _ret
    } else {
        print _states[_ret]
    }

    local _tags _ii _val _mytype
    _tags[0][1] = \
    "    Programmer # active              :  "; _tags[1][1] =  "22"
    _tags[0][2] = \
    "    Programmer setpoint              :  "; _tags[1][2] = "163"; _tags[2][2] = 1
    _tags[0][3] = \
    "    Program cycles remaining         :  "; _tags[1][3] =  "59"
    _tags[0][4] = \
    "    Current segment number           :  "; _tags[1][4] =  "56"
    _tags[0][5] = \
    "    Current segment type             :  "; _tags[1][5] =  "29"
    _tags[0][6] = \
    "    Segment time remaining (secs)    :  "; _tags[1][6] =  "36"; _tags[2][6] = 2
    _tags[0][7] = \
    "    Segment time remaining (mins)    :  "; _tags[1][7] =  "63"; _tags[2][7] = 2
    _tags[0][8] = \
    "    Target setpoint (current segment):  "; _tags[1][8] = "160"; _tags[2][8] = 1

    for (_ii = 1;_ii < 9; _ii++) {
        __e2400debug "_europrog_status" i

        if (__e2400_get(unit, _tags[1][_ii], 1, data)) {
            return -1
        }
        if (_tags[2][_ii]) {
            _tags[2][_ii] = __euro_rfloat(unit, data[0])
        } else {
            _tags[2][_ii] = data[0]
        }
        print _tags[0][_ii] _tags[2][_ii]
        sleep(.1)
    }
}
'

#%IU%(label, starttemp, memarr, nosubprg, unit)
#%MDESC% this is a special macro for europrog
# Reads a program from a Eurotherm 2400 series instrument.
def _europrog_show(label, starttemp, memarr, nosubprg, unit) '{
    local _mysegtype
    local __units[], _time_units, holdbacktype[]
    __units[0] = "sec";     __units[1] = "min";     __units[2] = "hour"
    __units[3] = "seconds"; __units[4] = "minutes"; __units[5] = "hours"
    holdbacktype[0] = "OFF"; holdbacktype[1] = "Low";
    holdbacktype[2] = "High"; holdbacktype[3] = "Band";
    if (_europrog_show_call == 0) {
        #print " Reading parameters for" , label
        global _europrog_show_call, __e2400_loop[], __e2400_pli
        global float array pleuro[512][2]
        local x0, x1, y0, y1, iii, ii
        __e2400_pli = x0 = y0 = iii = 0
        pleuro[0][0] = 0 # first x value is 0
        pleuro[0][1] = starttemp
        # due to a problem with the data handling with this function, which calls
        # itself recursively, the data in __E2400PDATA are lost in a call to self.
        if (COLS < 80) {
        eprint "Your window is too small!"
            eprint "Please increase size!"
            return(-1)
        }
        for(ii = 0; ii < ROWS; ii ++) {
            print
        } # don`t do a clear screen as often visual information is lost.
        _time_units  = memarr[2]
        tty_cntl("ho") # go home
        print "       Eurotherm 2400 program definition"
        printf("%s", "   "); _europrog_print_so(label); print
        printf("%-20s", "Holdback type :")
        _europrog_print_so(holdbacktype[memarr[0]])
        if (memarr[0]) {
            printf("   with  ");
            _europrog_print_so(__euro_rfloat(unit,  memarr[1]))
            print "   degree(s) width"
        } else {
            print
        }
        printf("%-20s", "Time units    :"); _europrog_print_so(__units[memarr[2]+3])
        print
        printf("%-20s", "Run (cycles)  :"); _europrog_print_so(memarr[4], 2)
        printf("   time(s)");
    }
    _europrog_show_call ++
    # segments
    for (ii = 1 ; ii < __E2400[__E2400[unit]]["max_segments"] ; ii++ ) {
        printf("\n%10s ", label)
        printf("%-10s", "Segment # "); _europrog_print_so(ii); printf(" : ")
        _mysegtype = memarr[(ii * 8)]
        if (_mysegtype > 5 || _mysegtype < 0 ) {
            _europrog_print_error("Type has to be between 0 and 5")
            ii --
            break
        } else {
            _mysegtype = memarr[ii * 8]
            if (_mysegtype == 0 ) {
                local __type[], str
                __type[0] = "Reset"; __type[1] = "Indefinite_Dwell"
                __type[2] = "SetOutput"
                printf("%s %-9s %s ", "End", label, "with:")
                _europrog_print_so(__type[memarr[(ii * 8) + 3]], 5)
                print
                break
            }
            else if (_mysegtype == 1 ) { # ramp (rate)
                local x1, x2
                x1 = __euro_rfloat(unit,  memarr[(ii * 8) + 1])
                x2 = __euro_ramp_rate_read_helper(unit, memarr[(ii * 8) + 2])
                printf("%-10s", "Ramp (rate)")
                printf(  "%9s", "target: ")
                _europrog_print_so(x1, 5)
                printf( "%-9s", " degrees")
                printf(  "%4s", "at: ")
                _europrog_print_so(x2,  5)
                printf(" degrees/%s", __units[memarr[2]+3])
                __e2400_pli++
                pleuro[__e2400_pli][1] = x1
                if (pleuro[__e2400_pli][1] > y1) y1 = pleuro[__e2400_pli][1]
                local bla
                # make sure the ramp rate is not 0! otherwise division by zero error
                bla = x2
                x2 = bla ? bla : 1
                bla  = fabs(pleuro[__e2400_pli][1] - pleuro[__e2400_pli-1][1])  # new value - old value
                bla /= x2         # devide by ramp rate
                pleuro[__e2400_pli][0] = pleuro[__e2400_pli-1][0] + bla   # and add to former time
            }
            else if (_mysegtype == 2 ) { # ramp (time)
                local x1, x2
                x1 = __euro_rfloat(unit,  memarr[(ii * 8) + 1])
                x2 = __euro_ramp_rate_read_helper(unit, memarr[(ii * 8) + 2])
                printf("%-10s", "Ramp (time)")
                printf(  "%9s", "target: ")
                _europrog_print_so(x1, 5)
                printf( "%-9s", " degrees")
                printf(  "%4s", "in: ")
                _europrog_print_so(x2 , 5)
                printf(" %s", __units[memarr[2]+3])
                __e2400_pli++
                pleuro[__e2400_pli][1] = x1
                if (pleuro[__e2400_pli][1] > y1) y1 = pleuro[__e2400_pli][1]
                local bla
                bla  = x2                                 # ramp time
                pleuro[__e2400_pli][0] = pleuro[__e2400_pli-1][0] + bla   # and add to former time
            }
            else if (_mysegtype == 3 ) { # dwell
                local x2
                x2 = __euro_ramp_rate_read_helper(unit, memarr[(ii * 8) + 2])
                printf("%-33s", "Dwell")
                printf("%5s", "for: ")
                _europrog_print_so(x2, 5)
                printf(" %s", __units[memarr[2]+3])
                __e2400_pli++
                pleuro[__e2400_pli][1] = pleuro[__e2400_pli-1][1]         # new value is old value
                if (pleuro[__e2400_pli][1] > y1) y1 = pleuro[__e2400_pli][1] * 1.01
                local bla
                bla  = x2                                 # dwell time
                pleuro[__e2400_pli][0] = pleuro[__e2400_pli-1][0] + bla   # and add to former time
            }
            else if (_mysegtype == 4 ) { # Step
                local x1
                x1 = __euro_rfloat(unit, memarr[(ii * 8) + 1])
                printf("%-11s", "Step")
                printf(  "%9s", "to: ")
                _europrog_print_so(x1, 5)
                printf( "%-9s", " degrees")
                __e2400_pli++
                pleuro[__e2400_pli][1] = x1
                if (pleuro[__e2400_pli][1] > y1) y1 = pleuro[__e2400_pli][1] * 1.01
                    pleuro[__e2400_pli][0] = pleuro[__e2400_pli-1][0] + .001
                # add a little bit to see ups and downs
            }
            else if (_mysegtype == 5 ) { # Call
                local cycle subprog
                cycle   = memarr[(ii * 8) + 4]
                subprog = memarr[(ii * 8) + 3]
                printf( "%s", "Call subprog")
                printf("%8s", "# ")
                _europrog_print_so(subprog, 5)
                printf( "%8s", "")
                printf( "%5s", "")
                _europrog_print_so(cycle, 5)
                printf( " %s", "times")
                if(!nosubprg) {
                    __e2400_loop[_europrog_show_call] = ii
                    # do not read here, as the plotting, showing and saving are confused
                    #_europrog_read(unit, subprog)
                    local ushort array subprgdat[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
                    subprgdat = _europrog_read(unit, subprog)
                    for (; iii < cycle; iii ++ ) {
                        local astr, bstr
                        astr = "Subprog " subprog
                        _europrog_show(astr, 0, subprgdat, nosubprog, unit)
                    }
                    ii = __e2400_loop[_europrog_show_call]
                }
            }
        }
    }

    # plotting part
    if (pleuro[1][1]) { # second point is non null, so plot
        x0 = -.2
        x1 = pleuro[__e2400_pli][0] + .2
        y1 *= 1.01
        local cntlstr
        cntlstr = "filter2,open,geometry=+50-50,title=Eurotherm 2400 " label
        plot_cntl(cntlstr)
        plot_cntl("erase")
        plot_cntl("lines")
        plot_cntl("-ebars")
        plot_cntl("colors=49:16:9:3:2:3:11:6")
        plot_move(0,2,"Degrees")
        plot_cntl("colors=::2")
        cntlstr = "Eurotherm 2400 Definition: " label
        plot_move(20, 1, cntlstr)
        #plot_cntl("colors=::3")
        #plot_move(20, 2, "setpoint")
        #plot_cntl("colors=49:16:9:3:2:3:11:6")
        # this is necessary, as Spec forget the local variables after the first return
        __units[0] = "sec"; __units[1] = "min"; __units[2] = "hour";
        __units[3] =     1; __units[4] =    60; __units[5] =  3600 ;
        local tstr
        tstr = "Time (" __units[memarr[2]] ")"
        plot_move(0,-1, tstr)
        plot_range("auto","auto","auto","auto")
        #plot
        #plot_cntl("erase")
        #plot_range(0, x1, 0, y1)
        array_plot(pleuro[0:__e2400_pli])
    }
    _europrog_show_call --
    if (_europrog_show_call == 0) {
        printf("\n    End type... this is the last segment !!!\n")
        if(!nosubprg) {
            print "The time for each cycle should be close to", \
            int(pleuro[__e2400_pli][0]), __units[memarr[2]]
        }
        unglobal _europrog_show_call, iii, __e2400_pli
    }
    plot_cntl("filter1")
}
'

#%IU% (memarr, label, unit)
#%MDESC% this is a special macro for europrog
# Edit a program
#
# Adress calculation is base address 8192 plus number of program (skip 0) times
# 17*8
def _europrog_edit(memarr, label, unit) '{
    # the function assumes that the _show function was used and the
    # display started from line 0.
    local __units[], _time_units, holdbacktype[], answer[], _segt[]
    __units[0] = "seconds"; __units[1] = "minutes"; __units[2] = "hours"
    holdbacktype[0] = "OFF"; holdbacktype[1] = "Low"
    holdbacktype[2] = "High"; holdbacktype[3] = "Band"
    _segt[0] = "End"   ; _segt[1] = "Ramp (rate)"; _segt[2] = "Ramp (time)"
    _segt[3] = "Dwell "; _segt[4] = "Step";        _segt[5] = "Call program"
    local ii _mysegtype, line, lines, numseg
    line  = 2
    answer[1] = "HWnext field"
    while(1) {
        if (line == 2) {
            if (answer[1] == "HWnext field") { # coming from next field
                answer = europrog_multiplechoice(memarr[0], holdbacktype, 4, 20, line)
                memarr[0] = answer[0]
                if (answer[1] == "HWprevious field") { # goto previous edit
                    line --
                } # else stay on the same line
            }
            if (line == 2 && memarr[0]) {
                answer = europrog_editfield(__euro_rfloat(unit, memarr[1]), 2, 33, line)
                memarr[1] = __euro_wfloat(unit, answer[0])
                if (answer[1] == "HWnext field") { # goto next edit
                    line ++
                } else { # previous is the first one on same line
                    answer[1] = "HWnext field"
                }
            } else { # no second value to edit
                line ++
            }
        }
        if (line == 3) {
            answer = europrog_multiplechoice(memarr[2], __units, 8, 20, line)
            memarr[2] = answer[0]
            memarr[3] = memarr[2]
            if (answer[1] == "HWnext field") { # goto next edit
            line ++
            } else {
            line --
            }
        }
        if (line == 4) {
            answer = europrog_editfield(memarr[4], 2, 20, line)
            memarr[4] = answer[0]
            if (answer[1] == "HWnext field") { # goto next edit
            line ++
            } else {
            line --
            }
        }
        # segments
        if (line > 4) {
            ii =  line - 4
            _mysegtype = memarr[(ii * 8)]
            # it would be nice to offer to insert a segment here. One could perhaps
            # shift the data of the current and following segments up.
            line = 4 + ii
            printf("\n%10s ", label)
            printf("%-10s", "Segment # "); _europrog_print_so(ii); printf(" : ")
            # this will be common for all segments
            answer = europrog_multiplechoice(_mysegtype, _segt, 14, 25, line)
            memarr[ii * 8] = _mysegtype = answer[0]
            if (answer[1] == "HWprevious field") { # goto previous edit
                line --
                continue
                #} else {
                #  line ++
                #  continue
            }
            if (_mysegtype == 0 ) {
                local _endt[]
                _endt[0] = "Reset"; _endt[1] = "Indefinite_Dwell"
                _endt[2] = "SetOutput"
                x1 = memarr[(ii * 8) + 3] > 2 ? 0 : memarr[(ii * 8) + 3]
                printf("%s %-9s %s ", "End", label, "with:")
                _europrog_print_so(_endt[x1], 5)
                answer = europrog_multiplechoice(x1, _endt, 9, 45, line)
                memarr[(ii * 8) + 3] = answer[0]
                _europrog_print_so(_endt[answer[0]], 5)
                if (answer[1] == "HWprevious field") { # goto previous edit
                    line --
                } else {
                    tty_cntl("cd"); print; print
                    break
                }
            }
            else if (_mysegtype == 1 ) { # ramp (rate)
                local x1, x2
                x1 = __euro_rfloat(unit, memarr[(ii * 8) + 1])
                x2 = __euro_ramp_rate_read_helper(unit, memarr[(ii * 8) + 2])
                tty_cntl("ce")
                printf("%-10s", "Ramp (rate)")
                printf(  "%9s", "target: ")
                _europrog_print_so(x1, 5)
                printf( "%-9s", " degrees")
                printf(  "%4s", "at: ")
                _europrog_print_so(x2, 5)
                printf(" degrees/%s", __units[memarr[2]])
                answer = europrog_editfield(x1, 5, 45, line)
                memarr[(ii * 8) + 1] = __euro_wfloat(unit, answer[0])
                answer = europrog_editfield(x2, 5, 63, line)
                memarr[(ii * 8) + 2] = __euro_ramp_rate_write_helper(unit, answer[0])
            }
            else if (_mysegtype == 2 ) { # ramp (time)
                local x1, x2
                x1 = __euro_rfloat(unit, memarr[(ii * 8) + 1])
                x2 = __euro_ramp_rate_read_helper(unit, memarr[(ii * 8) + 2])
                tty_cntl("ce")
                printf("%-10s", "Ramp (time)")
                printf(  "%9s", "target: ")
                _europrog_print_so(x1, 5)
                printf( "%-9s", " degrees")
                printf(  "%4s", "in: ")
                _europrog_print_so(x2 , 5)
                printf(" %s", __units[memarr[2]])
                answer = europrog_editfield(x1, 5, 45, line)
                memarr[(ii * 8) + 1] = __euro_wfloat(unit, answer[0])
                answer = europrog_editfield(x2, 5, 63, line)
                memarr[(ii * 8) + 2] = __euro_ramp_rate_write_helper(unit, answer[0])
            }
            else if (_mysegtype == 3 ) { # dwell
                local x2
                x2 = __euro_ramp_rate_read_helper(unit, memarr[(ii * 8) + 2])
                tty_cntl("ce")
                printf("%-33s", "Dwell")
                printf("%5s", "for: ")
                _europrog_print_so(x2, 5)
                printf(" %s", __units[memarr[2]])
                answer = europrog_editfield(x2, 5, 63, line)
                memarr[(ii * 8) + 2] = __euro_ramp_rate_write_helper(unit, answer[0])
            }
            else if (_mysegtype == 4 ) { # Step
                local x1
                x1 = __euro_rfloat(unit, memarr[(ii * 8) + 1])
                tty_cntl("ce")
                printf("%-11s", "Step")
                printf(  "%9s", "to: ")
                _europrog_print_so(x1, 5)
                printf( "%-9s", " degrees")
                answer = europrog_editfield(x1, 5, 45, line)
                memarr[(ii * 8) + 1] = __euro_wfloat(unit, answer[0])
            }
            else if (_mysegtype == 5 ) { # Call
                local cycle subprog
                subprog = memarr[(ii * 8) + 3]
                cycle   = memarr[(ii * 8) + 4]
                tty_cntl("ce")
                printf( "%s", "Call subprog")
                printf("%8s", "# ")
                _europrog_print_so(subprog, 5)
                printf( "%8s", "")
                printf( "%5s", "")
                _europrog_print_so(cycle, 5)
                printf( " %s", "times")
                answer = europrog_editfield(subprog, 5, 45, line)
                memarr[(ii * 8) + 3] = answer[0]
                answer = europrog_editfield(cycle, 5, 63, line)
                memarr[(ii * 8) + 4] = answer[0]
            }
            if (answer[1] == "HWprevious field") { # goto previous edit
                line --
            } else {
                line ++
            }
        }
#        if (line < 2) line = lines
#        if (line > lines) line = 2
#        if (line == lines) {
#          local __ok[], __x
#          __ok[0] = "Done"; __ok[1] = "Continue"
#          tty_move (3, line, "Program Editing")
#          answer = europrog_multiplechoice(__x, __ok, 8, 20, line)
#          if (!answer[0]) {
#            print
#            break
#          } else {
#            line = 2
#          }
#        }
    }
    sleep(.1)
    return(memarr)
}
'


#%IU% (unit, euro_program_number)
#%MDESC% this is a special macro for europrog
# Reads a program from a Eurotherm 2400 series instrument.
def _europrog_read(unit, program_number) '{
    local pdata # name of data array
    pdata = "__E2400PDATA" program_number
    global ushort array @pdata[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
    # above, array name and array itself must be global to be retained through
    # recursive calls.
    local _mysegtype, __pb
    local ushort array data[41]
    __E2400["pb"] = 8192 # programming base
    __pb = __E2400["pb"] + (program_number * 17 * 8)

    if (program_number > __E2400[__E2400[unit]]["max_progs"]) {
        eprint "Program number too high!"
        return -1
    }
    #  print " Reading parameters from program number" , program_number

    # One program has 16 segments plus general data. 17 * 8 = 136. As 125 words
    # is max read (3 times 5 plus 2) times 8.
    if (__e2400_get(unit, __pb, 5 * 8, @pdata)) {
        eprint "\n\n_europrog_read: Problem reading data from unit", unit
        eprint "Exited ! Please try again!"
        exit
    }
    if (__e2400_get(unit, __pb + 5 * 8,  5 * 8, data)) {
        eprint "\n\n_europrog_read: Problem reading data from unit", unit
        eprint "Exited ! Please try again!"
        exit
    }
    @pdata[5 * 8:] = data[0:5*8]
    if (__e2400_get(unit, __pb + 10 * 8, 5 * 8, data)) {
        eprint "\n\n_europrog_read: Problem reading data from unit", unit
        eprint "Exited ! Please try again!"
        exit
    }
    @pdata[10 * 8:] = data[0:5*8]
    if (__e2400_get(unit, __pb + 15 * 8, 2 * 8, data)) {
        eprint "\n\n_europrog_read: Problem reading data from unit", unit
        eprint "Exited ! Please try again!"
        exit
    }
    @pdata[15 * 8:] = data[0:5*8]

    # declare local array to be returned, if declared earlier, it will be lost
    local ushort array returnarr[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
    # copy the data array to the return array

    returnarr = @pdata
    # return the data of the demanded segment. Of course there might be a call
    # to a sub program, but that has to be defined separately and won`t be needed
    # by the _europrog()
    return(returnarr)
}
'

#%IU% (unit, memarr, program_number)
#%MDESC% this is a special macro for europrog
# Writes a program on a Eurotherm 2400 series instrument.
#
# Adress calculation is base address 8192 plus number of program (skip 0) times
# 17*8
#%BR%%BR%
# writing a program has to be done in a selective manner. One can`t just write a
# block of modbus adresses. Each segment type has arguments, which can be
# written other
def _europrog_write(unit, memarr, program_number) '{
    if (!program_number) {
    program_number = 1
    }
    __e2400debug "_europrog_write"

    local ii _mysegtype, __pb
    __E2400["pb"] = 8192 # programming base
    # Attention, the values read from the controller are already treated to
    # reflect its floating point representation!!!
    __pb = __E2400["pb"] + (program_number * 17 * 8)
    # Holdback type (0:OFF, 1:Low, 2:High, 3:Band)
    if (__e2400_put(unit, __pb, memarr[0])) {
        return -1
    }
    if (memarr[0] ) {
    # Holdback value
    if (__e2400_put(unit, __pb + 1, memarr[1])) {
        return -1
    }
    }
    # Time units  (0:secs, 1:mins, 2:hours)
    if (__e2400_put(unit, __pb + 2, memarr[2])) {
    return -1
    }
    memarr[3] = memarr[2]
    # just get on with your lifes! So what, you can`t have both!
    if (__e2400_put(unit, __pb + 3, memarr[3])) {
        return -1
    }
    # Cycles
    if (__e2400_put(unit, __pb + 4, memarr[4])) {
    return -1
    }
    # segments
    __e2400debug "maxsegments", unit, __E2400[__E2400[unit]]["max_segments"]
    for (ii = 1 ; ii <= __E2400[__E2400[unit]]["max_segments"] ; ii++ ) {
        #    Type 0:End, 1:Ramp(rate) 2:Ramp(time)
        #     3:Dwell 4:Step 5:Call program"
        _mysegtype = memarr[(ii * 8)]
        __e2400debug "segment type", _mysegtype
        if (__e2400_put(unit, __pb + (ii * 8), \
                memarr[(ii * 8)]) == -1) {
            return -1
        }
        if (_mysegtype == 0 ) {     # End
            # End action (0:Reset, 1:Indefinite_Dwell, 2:SetOutput)
            if (__e2400_put(unit, __pb + (ii * 8) + 3, \
                    memarr[(ii * 8) + 3]) == -1) {
                astr = "Segment " ii ", type " _mysegtype
                _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 3])
            }
            break
        }
        else if (_mysegtype == 1 ) { # ramp (rate)
            __e2400debug "type ramp/rate values", memarr[(ii * 8) + 1], memarr[(ii * 8) + 2]
            if (__e2400_put(unit, __pb + (ii * 8) + 1, memarr[(ii * 8) + 1])) {
                astr = "Segment " ii ", type " _mysegtype
                _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 1])
            }
            if (__e2400_put(unit, __pb + (ii * 8) + 2, memarr[(ii * 8) + 2])) {
                astr = "Segment " ii ", type " _mysegtype
                _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 2])
            }
        }
        else if (_mysegtype == 2 ) { # ramp (time)
            __e2400debug "type ramp/time values", memarr[(ii * 8) + 1], memarr[(ii * 8) + 2]
            if (__e2400_put(unit, __pb + (ii * 8) + 1, memarr[(ii * 8) + 1])) {
            astr = "Segment " ii ", type " _mysegtype
                _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 1])
            }
        if (__e2400_put(unit, __pb + (ii * 8) + 2, memarr[(ii * 8) + 2])) {
            astr = "Segment " ii ", type " _mysegtype
            _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 2])
        }
        }
        else if (_mysegtype == 3 ) { # dwell
            __e2400debug "type dwell values", memarr[(ii * 8) + 2]
            if (__e2400_put(unit, __pb + (ii * 8) + 2, memarr[(ii * 8) + 2])) {
            astr = "Segment " ii ", type " _mysegtype
                _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 2])
            }
        }
        else if (_mysegtype == 4 ) { # Step
            __e2400debug "type step values", memarr[(ii * 8) + 1]
            if (__e2400_put(unit, __pb + (ii * 8) + 1, memarr[(ii * 8) + 1])) {
            astr = "Segment " ii ", type " _mysegtype
                _europrog_value_too_high(unit, astr, memarr[(ii * 8) + 1])
            }
        }
        else if (_mysegtype == 5 ) { # Call
            __e2400debug "type call values", memarr[(ii * 8) + 3], memarr[(ii * 8) + 4]
            if (__e2400_put(unit, __pb + (ii * 8) + 3, \
                memarr[(ii * 8) + 3])) {
            return -1
            }
            if (__e2400_put(unit, __pb + (ii * 8) + 4, \
                    memarr[(ii * 8) + 4])) {
                return -1
            }
        }
    }
}
'

def _europrog_value_too_high(unit, astr, value) '{
    local ushort array data[10]
    local eurolimit, estr
    if (__e2400_get(unit, 12, 1, data)) {
        return -1
    }
    eurolimit = data[0]
    estr = astr ": Writing " value " is too big. Maximum is " eurolimit "!"
    _europrog_print_error(estr)
}
'


def _europrog_print_error(str) '{
    tty_move(0, -2)
        tty_cntl("mb")
        print str
        tty_cntl("se")
}
'

def _europrog_print_so(str, width) '{
    tty_cntl("so")
    fmt = "%s"
    if (width) fmt = "%" width "s"
        printf(fmt, str)
        tty_cntl("se")
}
'

#%IU% (str, width, xpos, ypos)
#%MDESC% this is a special macro for europrog
# Edit a field
def europrog_editfield(str, width, xpos, line) '{
    local key, oldkey, akey, escape
    local retarr[], domv, curpos, strlen, tmpstr
    key = ""
    outstr = str
    strlen = length(outstr)
    if (!width) width = strlen
        fmt = "%s"
    if (width) fmt = "%" width "s"
    tty_cntl("so")
    tty_move(xpos, line)
    printf(fmt, str)
    tty_move(xpos + width, line)
    curpos = strlen
    # wait until user hits a key
    while (1) {
        key  = input(-1)
        akey = asc(key)
        #if (key) printf("%x", akey)
        sleep(.05)
        if (akey == 0x08 || akey == 0x7f || akey == 0x7e) {
            outstr = substr(outstr, 1, --strlen); curpos --
        } else if (akey == 0x9 || akey == 0x0a) {
            domv = "HWnext field"
            break
        } else if (akey == 0x01) { #beginning of string
            curpos = 0
        } else if (akey == 0x05) { #end of string
            curpos = strlen
        } else if (akey == 0x15) { # ctrl-u, delete line
            outstr = ""; curpos =  0
        } else if (akey == 0x1b) {
            oldkey = akey
        } else if ((oldkey == 0x1b) && (akey == 0x5b)) {
            escape = 1
        } else if (akey) {
            if (escape) {
                if (akey == 0x41 || akey == 0x5a) {
                    domv = "HWprevious field"
                    break
                } else if (akey == 0x42) {
                    domv = "HWnext field"
                    break
                } else if (akey == 0x44) { #right
                    curpos = curpos > 0 ? curpos - 1 : 0
                } else if (akey == 0x43) { #left
                    curpos = curpos < strlen ? curpos + 1 : strlen
                }
                escape = 0
                tty_move(xpos + width - strlen + curpos, line)
                continue
            }
            tty_move(xpos + width - strlen + curpos, line)
            if (curpos == strlen) {
                outstr = outstr key
                strlen ++; curpos ++
            } else {
                tmpstr = substr(outstr, 1, curpos) key substr(outstr, ++curpos)
                strlen++
                outstr = tmpstr
            }
        }
        if (curpos < 0) curpos = 0
        tty_move(xpos, line)
        printf(fmt, outstr)
        tty_move(xpos + width - strlen + curpos, line)
    }
    input(1)
    tty_cntl("se")
    retarr[0] = outstr
    retarr[1] = domv
    return(retarr)
}
'


#%IU% (choice, charr, width, xpos, ypos)
#%MDESC% this is a special macro for europrog
# Edit a field
def europrog_multiplechoice(choice, charr, width, xpos, ypos) '{
    # choice is the current choice, charr is the choice array
    local key, oldkey, akey, escape, line, thischoice
    local retarr[], domv, strlen
    key = ""
    curpos = strlen = length(charr[choice])
    line = xpos
    fmt = "%s"
    if (width) fmt = "%" width "s"
    tty_cntl("so")
    tty_move(line, ypos)
    printf(fmt, charr[choice])
    thischoice = choice
    # wait until user hits a key
    while (1) {
        key  = input(-1)
        akey = asc(key)
        #if (key) printf("%x", akey)
        sleep(.01)
        if (akey == 0x9 || akey == 0x0a) {
            domv = "HWnext field"
            break
        } else if (akey == 0x1b) {
            oldkey = akey
        } else if ((oldkey == 0x1b) && (akey == 0x5b)) {
            escape = 1
        } else if (akey) {
            if (escape) {
            if (akey == 0x41) { # up
                if(charr[thischoice + 1] != "") {
                thischoice ++
                    strlen = length(charr[thischoice])
                }
            } else if (akey == 0x42) { # down
                if(thischoice > 0) {
                thischoice--
                    strlen = length(charr[thischoice])
                }
            } else if (akey == 0x44 || akey == 0x5a) { #right
                domv = "HWprevious field"
                break
            } else if (akey == 0x43) { #left
                domv = "HWnext field"
                break
            }
            escape = 0
                tty_move(line, ypos)
                continue
            }
            tty_move(line, ypos)
        }
        tty_move(line, ypos)
        printf(fmt, charr[thischoice])
        tty_move(line, ypos)
    }
    input(1)
    retarr[0] = thischoice
    retarr[1] = domv
    tty_cntl("se")
    return(retarr)
}
'

#%UU% <manual_setpoint> <unit=[0|1|2|..] or mne=some-counter-or-motor>  [noninteractive]
#%MDESC% Forces a reset of running program (if any) and sends a new setpoint
# to the eurotherm.%BR%
# Without argument, the macro will set the last used setpoint.%BR%
# With "unit=1" unit declared as 1 will be reset. No blanks please.
# With option "noninteractive", no questions asked.
#%END%
# Asked by ID01 to have a macro that one can call with
# no need of user interaction (calling "europrog status" first, and then reset)
# Useful to concatenate many programs from a spec macro.
def europrogreset '{
    local unit, setpoint, num, doit, ask
    num     = split("$*", aux)#; print "num", num
    unit    = 0
    doit    = 0
    ask     = 1
    setpoint= "n.a."
    _euro_mne2unit $*

    if (numargs > 0) {
        setpoint = _myarr[$# - numargs ]
    }
    if (numargs > 1 && _myarr[1] == "noninteractive") {
        ask = 0; doit = 1
    }
    if (setpoint == "n.a.") {
        setpoint = __E2400["MANSETPOINT"] # no setpoint given, use from array
    } else {
        __E2400["MANSETPOINT"] = setpoint # was given, store it in array.
    }
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr, setpoint, __E2400["MANSETPOINT"]
        unglobal STARTUP_TEST
    } else {
        if (ask) {
            printf("Doing this you will GO TO THE MANUAL SETPOINT : %.1f\n", \
            __E2400["MANSETPOINT"])
            doit = yesno("Reset anyway", 0)
        }
        if (doit) {
            # send the reset
            __eurotherm_wrapper reset unit
            sleep(.5)
            # write the setpoint
            if (__e2400_put(unit, 2, __euro_wfloat(unit, __E2400["MANSETPOINT"]))) {
                return -1
            }
        }
    }
}
'

# On request of someone on ID11 :-) I added all those macros, although all
# actions can be achieved in the interactive version. They need to control
# the EurothermProgrammer from remote.

def __eurotherm_wrapper '{

    _euro_mne2unit  $*

    if (!__E2400[__E2400[unit]]["max_segments"]) {
        eprint "There is no such controller registered. Either use eurosetup"
        eprint "or use the controller as motor and/or counter."
        eprint "Try: argument 1 or 2"
        exit
    }
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr, cmd, prgnum, resettemp
        unglobal STARTUP_TEST
    } else {

        local ushort array data[120]
        local rr
        # see if set point ramp rate is set
        if (__e2400_get(unit, 35, 1, data)) {
            return -1
        }
        rr = data[0]
        if (rr) {
            eprint "The controller is set to ramp, when the set point is changed!"
            eprint "Use euro2400parameters RampRate=0, before you can run a program."
            exit
        }

        if ("$1" == "status")           _europrog_status(unit)
        else if ("$1" == "reset")       _europrog_cmd(unit, 1)
        else if ("$1" == "hold") {  # check for state. No point in holding
                                    # if not running, i.e. reset
            local state
            if (__e2400_get(unit, 23, 1, data)) {
                return -1
            }
            state = data[0]
            if (state != 2) {
                eprint "Controller is not running any program."
                eprint "Can`t hold it !!!"
            } else {
                _europrog_cmd(unit, 4)
            }
        }
        else if ("$1" == "holdback") {  # check for state. No point in holding
                                        # if not running, i.e. reset
            local state
            if (__e2400_get(unit, 23, 1, data)) {
                return -1
            }

            state = data[0]
            if (state != 2) {
                eprint "Controller is not running any program."
                eprint "Can`t hold it !!!"
            } else {
                _europrog_cmd(unit, 8)
            }
        }
        else if ("$1" == "run") {
            _europrog_cmd(unit, 2)
        }
        else if ("$1" == "skipsegment") {   # check for state. No point in holding
                                            # if not running, i.e. reset
            local state
            if (__e2400_get(unit, 23, 1, data)) {
                return -1
            }
            state = data[0]
            if (state != 2) {
                eprint "Controller is not running any program."
                eprint "Can`t hold it !!!"
            } else {
                _europrog_cmd(unit, 4)  # put into hold state
                __e2400_put(unit, 36, 0)# set remaining seg time secs to zero
                                        # but that only works from hold state!
                _europrog_cmd(unit, 2)  # and
            }
        }
        else if ("$1" == "complete") {
            eprint "Completing a program is not possible."
        }
        # else if ("$1" == "complete") _europrog_cmd(unit, 16)
    }
}
'


#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Gives a status of the program execution in the Eurotherm
def europrogstatus '__eurotherm_wrapper status'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Forces the running program into hold mode. Please consult docmentation
# for the exact meaning of that state.
def europroghold '__eurotherm_wrapper hold'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Forces the running program into holdback mode.
#%BR%%BR%
#%B%Holdback%B%\0(from the documentation)%BR%%BR%
#As the setpoint ramps up, or down (or dwells), the measured value may lag
#behind, or deviate from, the setpoint by an undesirable amount. %B%Holdback%B%
#is available to freeze the program at its current state, should this occur.
#The action of Holdback is the same as a deviation alarm. It can be enabled, or
#disabled.   Holdback has two parameters - a value and a type. If the error from
#the setpoint exceeds the set "holdback" value, then the Holdback feature, if
#enabled, will automatically freeze the program at its current point and flash
#the HOLD light.   When the error comes within the holdback value, the program
#will resume normal running.

def europrogholdback '__eurotherm_wrapper holdback'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Forces the running program to complete.
def europrogcomplete '__eurotherm_wrapper complete'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Executes the defined program.
def europrogrun '__eurotherm_wrapper run'

#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor>
#%MDESC% Ends the current segment.
def europrogskipsegment '__eurotherm_wrapper skipsegment'


#%UU% <unit=[0|1|2|..] or mne=some-counter-or-motor> <run|reset|hold|holdback|complete|skip|status> <argument>]
#%MDESC%
# Macro that runs an interactive loop. It allows to define programs inside
# the controller, read them, read the status, reset the controller.
# The command run can have an additional argument containing the program number.
# %BR%
def europrog '{
    # the following two ifs will allow the user to enter the controller number and
    # command in any order.
    local x, unit, cmd, prgnum, _myarr, option, aux[], numargs
    local resettemp
    numargs = $#
    _euro_mne2unit $*

    if (numargs) {
        if (length(_myarr[$# - numargs]) > 2) { # controller # shorter than 2 :-)
            cmd = _myarr[$# - numargs]
        } else {
            # could there be a unit number to start with
            unit = _myarr[$# - numargs]
            numargs --
            cmd = _myarr[$# - numargs]
        }
        if (cmd == "run") {
            if (numargs == 2)
                prgnum = _myarr[$# - numargs +1]
            else
                prgnum = getval("Program number to run?", 0)
        }
        else if (cmd == "reset") {
            if (numargs == 2)
                resettemp = _myarr[$# - numargs +1]
        }
    }
    if (!__E2400[__E2400[unit]]["max_segments"]) {
        local x, str, aux
        eprint "There is no such controller registered. Either use eurosetup"
        eprint "or use the controller as motor and/or counter."
        for (x in __E2400) {
            if (x < 10) {
                # continue if not a single index
                if (split(x, aux, "\034") >1) continue
                # concatenate eligible numbers
                str = x ", " str
            }
        }
        str = substr(str, 1, length(str) - 2)
        eprintf("Try one of the following arguments: ");
        tty_cntl("md"); print str; tty_cntl("me")

        exit
    }
    # attempt for a kind of startup test
    if (STARTUP_TEST) {
        print "startup test: unit", unit, numargs, _myarr, cmd, prgnum, resettemp
        unglobal STARTUP_TEST
    } else {
        if (cmd == 0) {
            euro_main(unit, cmd, prgnum)
        }
        else if (cmd == "run" ) {
            europrogrun unit
        }
        else if (cmd == "reset" ) {
            eval(sprintf("europrogreset unit=%d %f", unit, resettemp))
            # well, prgnum is temp, but the variable should be ok.
        }
        else if (cmd == "hold" ) {
            europroghold unit
        }
        else if (cmd == "holdback" ) {
            europrogholdback unit
        }
        else if (cmd == "complete" ) {
            europrogcomplete unit
        }
        else if (cmd == "skip" ) {
            europrogskipsegment unit
        }
        else if (cmd == "status" ) {
            europrogstatus unit
        }
        else if (cmd == "explain" ) {
            print "you may use europrog without argument, then you get to a kind of"
            print "shell. Otherwise, you may add a controller number (controller as in"
            print "defined in the config). On top you can then use commands from the"
            print "list of: run, reset, hold, holdback, complete, skip and status"
        }
        else {
            eprint "europrog [controller <run|reset|hold|holdback|complete|status> <program_number>]"
        }
    }
}
'

#%IU%()
#%MDESC%
# This macro function runs a loop to provide interactive communication to an
# eurotherm device. %BR%
# It reads keyboard input and executes the wished command.
#
def euro_main(unit, cmd, prgnum) '{
    if (!(whatis("__E2400") & 0x01000000)) {
    eprint  "$0: You must first configure a macro motor or counter!"
        exit
    }
    local _cmd euro___pr c helpstr aux[]
    local message, starttemp
    local ushort array data[120]

    # if not yet defined, set to 1
    __E2400[__E2400[unit]]["pnum"] = \
    __E2400[__E2400[unit]]["pnum"] ? \
    __E2400[__E2400[unit]]["pnum"] : 1
    __E2400[__E2400[unit]]["pnum"] = \
    prgnum ? prgnum : __E2400[__E2400[unit]]["pnum"]

    euro___pr = "EURO"__E2400[__E2400[unit]]["ident"] " unit " unit " : "
    helpstr = "europrog: programming Eurotherm " euro___pr "\n"
    helpstr = helpstr "  Device commands:\n"
    helpstr = helpstr "  ---------------\n"
    helpstr = helpstr "  .h, .help, .     Display available commands\n"
    helpstr = helpstr "  .r, .read        Read program\n"
    helpstr = helpstr "  .w, .write       Edit/write program\n"
    helpstr = helpstr "  .s, .status      Instrument status\n"
    helpstr = helpstr "  .run             Run program <number>\n"
    helpstr = helpstr "  .hold            Set program status to hold\n"
    helpstr = helpstr "  .holdback        Set program status to holdback\n"
    helpstr = helpstr "  .complete        Set program status to complete\n"
    helpstr = helpstr "  .ss, .skip       Skip current segment\n"
    helpstr = helpstr "  .t, .reset       Reset instrument status\n"
    helpstr = helpstr "  .q, .quit        Leave europrog\n"
    print helpstr
    while(1) {
        tty_cntl("md")
        line = input(euro___pr)
#       if (substr(line,1,2) == "\033\133") { print substr(line,3,1);continue}
        if (line == "\033\133A") { print ; continue }
        tty_cntl("me")
        c = split(line, aux)
        if (aux[0] == ".h" || aux[0] == "." || aux[0] == ".help") {
            print helpstr
        }
        else if (aux[0] == ".q" || aux[0] == ".quit") {
            exit
        }
        else if (aux[0] == ".r" || aux[0] == ".read") {
            if (aux[1]) {
            __E2400[__E2400[unit]]["pnum"] = aux[1]
            }
            if (!__E2400[__E2400[unit]]["pnum"]) {
            __E2400[__E2400[unit]]["pnum"] = 1
            }
            if (__E2400[__E2400[unit]]["pnum"] > \
                __E2400[__E2400[unit]]["max_progs"]) {
            _europrog_print_error("Program number too big!")
                continue
            }
            local ushort array prgdat[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
            prgdat = _europrog_read(unit, __E2400[__E2400[unit]]["pnum"])
            local astr
            __e2400_get(unit, 1, 1, data)
            starttemp = __euro_rfloat(unit, data[0])
            astr = "Program " __E2400[__E2400[unit]]["pnum"]
            unglobal _europrog_show_call
            _europrog_show(astr , starttemp, prgdat, nosubprog, unit)
        }
        else if (aux[0] == ".w" || aux[0] == ".write") {
            if (aux[1]) {
            __E2400[__E2400[unit]]["pnum"] = aux[1]
            }
            if (_europrog_cmd(unit, 0) != 1) { #if not RESET
            eprint "In this state the programmer cannot be programmed!!!"
                continue
            }
            local ushort array prgdat[(__E2400[__E2400[unit]]["max_segments"] + 1) * 8]
            prgdat = _europrog_read(unit, __E2400[__E2400[unit]]["pnum"])
            astr = "Program " __E2400[__E2400[unit]]["pnum"]
            local astr
            astr = "Program " __E2400[__E2400[unit]]["pnum"]
            __e2400_get(unit, 1, 1, data)
            starttemp = __euro_rfloat(unit, data[0])
            unglobal _europrog_show_call
            _europrog_show(astr , starttemp, prgdat, 1, unit)
            prgdat = _europrog_edit(prgdat, astr, unit)
            unglobal _europrog_show_call
            _europrog_show(astr , starttemp, prgdat, 1, unit)
            _europrog_write(unit, prgdat, __E2400[__E2400[unit]]["pnum"])
            __europrog_save_as_scan(unit, starttemp, prgdat)
        }
        else if (aux[0] == ".t" || aux[0] == ".reset") {
            print \
            "To modify the currently running program type use: \"europrog status \" "
            if (yesno("Reset", 0) == 0)
                continue
            __E2400["MANSETPOINT"] = getval("Manual setpoint after reset", \
                __E2400["MANSETPOINT"])
            __eurotherm_wrapper reset unit
            if (__e2400_put(unit, 2, __euro_wfloat(unit, \
                    __E2400["MANSETPOINT"]))) {
                return -1
            }
        }
        else if (aux[0] == ".hold") {
            __eurotherm_wrapper hold unit
        }
        else if (aux[0] == ".holdback" ) {
            __eurotherm_wrapper holdback unit
        }
        else if (aux[0] == ".complete" ) {
            __eurotherm_wrapper complete unit
        }
        else if (aux[0] == ".s" || aux[0] == ".status" ) {
            __eurotherm_wrapper status  unit}
        else if (aux[0] == ".ss" || aux[0] == ".skip" ) {
            __eurotherm_wrapper skipsegment  unit}
        else if (aux[0] == ".run") {
            if (aux[1]) {
                __E2400[__E2400[unit]]["pnum"] = aux[1]
                """
                this only works if the controller has more than single program and
                if no program is running
                """
                # check status, only answer if status is RESET
                if (__e2400_get(unit, 23, 1, data)) {
                    eprint "Communication error with Eurotherm"
                    return ".error."
                }
                if ((__E2400[__E2400[unit]]["max_progs"] > 1) && (data[0] == 1)) {
                    if (__e2400_put(unit, 22, __E2400[__E2400[unit]]["pnum"])) {
                        eprint "In all likelyhood, the last program left the controller in the"
                        eprint "state \"COMPLETED\" or \"HOLDBACK\"."
                        eprint " Use the command \".reset\"."
                        return -1
                    }
                    print "Program number is", __E2400[__E2400[unit]]["pnum"]
                }
            }

            __eurotherm_wrapper run unit
            print "\nTo observ the controller temperature, consider using timescan"
            print "or uct. Remember to set the counter with plotselect!\n"
        }
        else {
            print "I don`t understand the command \"" aux[0] "\""
        }
    }
}
'

def _euro_mne2unit '
    """ new version of this macro does not allow just putting the unit
        number as an argument. It`s just too difficult to guess.
    """
    local mymne, myunit, __euro_num
    local x, cmd, prgnum, _myarr, option
    local aux[], numargs, varnam, None
    numargs = $#
    split("$*", _myarr)

    __e2400debug "@@@", _myarr

    None = "None"
    mne  = None
    unit = None
    for (option in _myarr) {
        if (split(_myarr[option], aux, "=") == 2) {
            varnam = aux[0]
            __e2400debug "@@@varnam", varnam
            @varnam = aux[1]
            __e2400debug varnam, "value", @varnam
            if (string_tolower(varnam) == "unit" || varnam == "mne") numargs --
        }
    }

    __e2400debug "@@@ mne & unit", mne, unit
    if (unit != None) {     # unit is set, use it
        __e2400debug "@@@ --------------> use unit", unit
    }
    else if (mne != None) {     # no mne neither, search for first motor
                                # with device_id E2400
        __e2400debug "@@@ --------------> use mne", mne
        # mne can be motor|counter mnemonic or number, spec
        # might expand string to number
        if ((mne == motor_mne(mne) || (mne == motor_num(mne)) || \
            (mne == motor_mne(motor_num(mne)))) && (motor_mne(mne) != "?")) {
            __euro_num = motor_num(mne)
            if (motor_par(mne, "device_id") == "E2400") {
                unit = motor_par(__euro_num, "unit")
            }
            __e2400debug "@@@1 mne & unit", mne, unit
        }
        else
        if ((mne == cnt_mne(mne) || (mne == cnt_mne(mne)) || \
            (mne == cnt_mne(cnt_num(mne)))) && \
            (cnt_mne(mne) != "?")) {
            __euro_num = cnt_num(mne)
            if (counter_par(mne, "device_id") == "E2400") {
                unit = counter_par(__euro_num, "unit")
            }
            __e2400debug "@@@2 mne & unit", mne, unit
        }
        if (unit == None) {
            eprint "No eurotherm 2400 with mnemonic", mne "!"
            exit
        }
    }
    # if none is set, go find something motors ?
    if (( mne == None ) && (unit == None)) {
        local i
        for (i = 0; i < MOTORS; i ++) {
            __e2400debug "motor:", motor_name(i), motor_par(i, "device_id")
            if (motor_par(i, "device_id") == "E2400") {
                mne =  motor_mne(i)
                unit = motor_par(i, "unit")
                eprint "Using motor \"" motor_mne(i) "\""
                break
            }
        }
    }
    # or counters
    if (( mne == None ) && (unit == None)) {
        local i
        for (i = 0; i < COUNTERS; i ++) {
            __e2400debug "counter:", cnt_name(i), counter_par(i, "device_id")
            if (counter_par(i, "device_id") == "E2400") {
                mne =  cnt_mne(i)
                unit = counter_par(i, "unit")
                eprint "Using counter \"" cnt_mne(i) "\""
                break
            }
        }
    }
    __e2400debug "mne = ", mne, "unit", unit

    if (!__E2400[unit]) {
        eprint "Please use argument eg. \"mne=eurosp\" to specify your controller"
        exit
    } else {
        __e2400debug "_euro_mne2unit: Using unit", unit
    }
'

def _euro_mne_test '{
    local x, unit, cmd, prgnum, _myarr, option, aux[], numargs, RampRate
    numargs = $#
    _euro_mne2unit $*

    print "MNE:", mne
    print "UNIT:", unit
    print "RR:", RampRate

}
'


def _euro_2400_test_function(cmd) '{

    global STARTUP_TEST
    STARTUP_TEST = 1

    print "Command:", cmd
    eval(cmd)
    # ~ print "argument test: unit", unit, numargs, _myarr, prgnum, prgsavefile

    unglobal STARTUP_TEST
}
'

def testattempt_eurotherm2400 '{

    # now start a series of tests
    # my test system happened to have a motor named euro,
    # which was defined on a controller in 4th position.
    cdef("cleanup_once", "unglobal STARTUP_TEST;\n", "testattempt_eurotherm2400")
    _euro_2400_test_function("euro2400configuration")
    _euro_2400_test_function("euro2400configuration mne=euro")
    _euro_2400_test_function("euro2400configuration unit=3")
    _euro_2400_test_function("euro2400menu mne=euro")
    _euro_2400_test_function("euro2400menu unit=3")
    _euro_2400_test_function("euro2400parameters")
    _euro_2400_test_function("euro2400parameters mne=euro")
    _euro_2400_test_function("euro2400parameters unit=3")
    _euro_2400_test_function("euro2400parameters 3")
    _euro_2400_test_function("euro2400parameters mne=euro RampRate=60")
    _euro_2400_test_function("euro2400parameters unit=3 RampRate=60")
    _euro_2400_test_function("euro2400parameters RampRate=60")
    # save and restore program won`t test, because the filename being
    # absolute is being interpreted as a division
    # ~ _euro_2400_test_function("euro2400saveprogram unit=3 /tmp/test 0")
    # ~ _euro_2400_test_function("euro2400restoreprogram /tmp/test 0")
    # ~ _euro_2400_test_function("euro2400restoreprogram mne=euro")
    # ~ _euro_2400_test_function("euro2400restoreprogram unit=3 /tmp/test 0")
    _euro_2400_test_function("euro2400showpid")
    _euro_2400_test_function("euro2400showpid mne=euro")
    _euro_2400_test_function("euro2400showpid unit=3")
    _euro_2400_test_function("euro2400status")
    _euro_2400_test_function("euro2400status mne=euro")
    _euro_2400_test_function("euro2400status unit=3")
    _euro_2400_test_function("europrogreset")
    _euro_2400_test_function("europrogreset 22")
    _euro_2400_test_function("europrogreset unit=3")
    _euro_2400_test_function("europrogreset 25 unit=3")
    _euro_2400_test_function("europroghold")
    _euro_2400_test_function("europroghold mne=euro")
    _euro_2400_test_function("europroghold unit=3")
    _euro_2400_test_function("europrogholdback")
    _euro_2400_test_function("europrogholdback mne=euro")
    _euro_2400_test_function("europrogholdback unit=3")
    _euro_2400_test_function("europrogcomplete")
    _euro_2400_test_function("europrogcomplete mne=euro")
    _euro_2400_test_function("europrogcomplete unit=3")
    _euro_2400_test_function("europrogrun")
    _euro_2400_test_function("europrogrun mne=euro")
    _euro_2400_test_function("europrogrun unit=3")

}
'

#%MACROS%
#%IMACROS%
#%INTERNALS%
#%AUTHOR% H. Witsch, BLISS - ESRF, derived from the macros in temperature.mac,
#$Revision: 4.2 $, $Date: 2018/05/22 09:40:35 $
#%TOC%