esrf

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

"""
#%TITLE% ATTRIBUTES.MAC
#%NAME% ATTRIBUTES.MAC - TANGO attribute macro counters.
#
#%DESCRIPTION%
#     The macro set offers the possibility to set-up a TANGO attribute
#     as macro motor or as macro counter.
#      Only read/write TANGO attributes can be used as macro motor.
#     The macro motor controller defined is called "attr_mot" and
#     The macro counter controller defined is called "attr_ct".
#
#%SETUP%
#Motor and Counter Device Configuration (Not CAMAC)%BR%
#SCALERS\0\0\0\0\0\0\0\0DEVICE\0\0\0ADDR\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0<>MODE\0\0NUM\0\0\0\0\0\0\0\0\0\0\0\0\\0\0\0<>TYPE%BR%
#\0\0\0\0YES\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\0\0\0\0\0\0\0\0\0\0POLL\0\0\0\01\0\0\0\0\0\0\0\0Software\0Timer%BR%
#\0\0\0\0YES\0\0\0\0\0\0\0attr_ct\0\0\0IDXX/wcidxx/tg\0\0\0\0\0\0\0\0\0\0\0\0\0\0\02\0\0\0\0\0\0\0\0\0Macro\0Counter%BR%
#
# Make sure to set the ADDR field with the device name.%BR%
# Make sure to set the NUM field to the good number of macro_counters you need.%BR%
# If you read from an array of scalars, i.e. a name that was defined multiple
#times, make sure to set the channel number as the array index.
#
# One must set (type p, when on counter) the Custom Parameters "attr_name".
# If the Custom Parameters "channel" is set, the macro assumes, that the signal name is used multiple times and
# that the channel is the index in the array of returned values.
#%EXAMPLE%
#%DL%
#
#
#%DT%  attr_mot_setup tilt id14/eh3-motor/mono_tile/Position %DD%
#    Set-up the TANGO attribute macro motor with the mnemonic "tilt"
#
#
#%XDL%
#
#%END%

# Even disabled, attr_ct counters consume (small) counting time ( ~0.5 ms ?)
# to read 3 scalars : ~3 ms overhead ?
"""

# for color printing :)
need spec_utils

# Macro-COUNTERS parameters.
global ATTR_CT
list_test ATTR_CT
# ATTR_CT[mne]["dev_name"]
# ATTR_CT[mne]["attr_name"]

# Macro-MOTORS parameters.
global ATTR_MOT
list_test ATTR_MOT
# ATTR_MOT[mne]["dev_name"]
# ATTR_MOT[mne]["attr_name"]

"""
######################################################################
##############################           #############################
##############################  General  #############################
##############################           #############################
######################################################################
"""

#%UU%
#%MDESC%
#    Removes everything about attributes.mac
def attr_unsetup '{
    local ii _arr_name

    unglobal ATTR_MOT
    unglobal ATTR_CT
}'


#%UU%
#%MDESC%
#    Displays information about counters managed by attributes macros.
def attr_show '{
    attr_ct_show
    print ""
    attr_mot_show
}'


#%IU%
#%MDESC%
#    Cosmetics.
def attr_error_head '{
    tty_cntl("md")
    #printf("ERROR:attribute.mac : ")
    printf("ERROR : ")
    tty_cntl("me")
}'


"""
######################################################################
#######################                         ######################
#######################  Scalar Macro counters  ######################
#######################                         ######################
######################################################################
"""


#%UU%
#%MDESC%
#    Displays informations about macro counters.
def attr_ct_show '{
    local _mne

    print "-------=== macro counters ===------------"
    print "mnemonic         device       attribute  "
    print "-----------------------------------------"

    for (i=0; i< list_n(ATTR_CT); i++){
        _mne = ATTR_CT[i+1]
#         print ATTR_CT[i+1]"      " ATTR_CT[_mne]["dev_name"]"     "\
#             ATTR_CT[_mne]["attr_name"]

        printf("ATTR_CT : Counter %10s linked to %s/%s\n",              \
               _mne, ATTR_CT[_mne]["dev_name"], ATTR_CT[_mne]["attr_name"])


    }
    print "-----------------------------------------"

}'


#%IU%
#%MDESC%
#Called on reconfig.
def attr_ct_config(cntnum, what, p1, p2) '{
    global ATTR_CT

    if((cntnum == "..") && (what == "ctrl")) {
        # Check that the Tango DS is running
        if(ds_is_tango(attr_ct_ADDR) != 1) {
            attr_error_head
            printf("device %s is unresponsive -> disabling it. \n", attr_ct_ADDR)
            return ".error."
        }
        return
    }

    if(what == "cnt") {
        local _mne, _attr, _info[], _arrname

        _mne = cnt_mne(cntnum)
        list_test ATTR_CT
        list_add(ATTR_CT, _mne)

        if((_attr = counter_par(cntnum,"attr_name")) == 0) {
            attr_error_head
            printf("missing \"attr_name\" for counter \"%s\"\n", _mne)
            return ".error."
        }

        tango_get(attr_ct_ADDR, "?", _attr, _info)
        if (TANGO_ERR != "0"){
            tty_cntl("md")
            print "\n" ATTR_CT_ERROR " " _cntr_mne
            printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
            print TANGO_ERR_STACK
            tty_cntl("me")
            return ".error."
        }

        list_setpar(ATTR_CT, _mne, "dev_name", attr_ct_ADDR)
        list_setpar(ATTR_CT, _mne, "attr_name", _attr)

        if (_info["max_dim_x"] > 1) { # this is not a scalar but an array of values
            _arr_name = "ATTR_CT_" attr_ct_ADDR "_" _attr
            list_setpar(ATTR_CT, _mne, "array", _arr_name)
            if (! index(ATTR_CT[attr_ct_ADDR], _attr)) {
                ATTR_CT[attr_ct_ADDR] = ATTR_CT[attr_ct_ADDR] " " _attr
            }
        }
    }
}'


#%IU%
#%MDESC%
#    Main function for macro counter.
def attr_ct_cmd(cntnum, what, p1, p2) '{
    local _cntr _dev _attr _ans _attr_name _cntr_mne
    local _sname _arr_name ii

    if (cntnum == "..") {
        if (what == "prestart_all") {
            local _aux[], numele, i
            numele = split(ATTR_CT[attr_ct_ADDR], _aux)
            for (i = 0; i < numele; i++) {
                _arr_name = "ATTR_CT_" attr_ct_ADDR "_" _aux[i]
                # get all values in the array
                tango_get(attr_ct_ADDR, _aux[i], @_arr_name)
                if (TANGO_ERR != "0"){
                    tty_cntl("md")
                    print "\n" ATTR_CT_ERROR " " _cntr_mne
                    printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
                    print TANGO_ERR_STACK
                    tty_cntl("me")
                    return ".error."
                }
            }
        }
    } else
    if (cntnum != "..") {
        if (what == "counts") {
            local _ans, _num,  _dev, _attr, _ret_val, _arr_name

            _cntr_mne = cnt_mne(cntnum)
            # Tests if _cntr_mne is an attr_ct macro counter.
            if (list_check(ATTR_CT, _cntr_mne)) {
                _dev  = list_getpar(ATTR_CT, _cntr_mne, "dev_name")
                _attr = list_getpar(ATTR_CT, _cntr_mne, "attr_name")
                _arr_name = list_getpar(ATTR_CT, _cntr_mne, "array")

                if (_arr_name != -1) { # this must be an array of scalars
                    # just grab the value from the already existing array
                    # for that we need the channel number
                    local chan
                    chan = counter_par(cntnum, "channel")
                    return @_arr_name[chan]
                }
                else {
                    # print "  reading attribute scalar...  "
                    _ans = tango_get(_dev, _attr)
                    _ret_val = _ans

                    if (TANGO_ERR != "0"){
                        tty_cntl("md")
                        print "\n" ATTR_CT_ERROR " " _cntr_mne
                        printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
                        print TANGO_ERR_STACK
                        tty_cntl("me")
                        return ".error."
                    }

                    return (_ret_val)
                }
            }
            else {
                #  print "Counter \"" _cntr_mne "\" not set up in ATTR_CT."
                return (-1)
            }
        }
    }
}'



"""
########################################################################
########################################################################
########################################################################
#######################                          #######################
#######################  Spectrum Macro Counter  #######################
#######################                          #######################
########################################################################
########################################################################
########################################################################

# SETUP FREE : use config only.
# !!! can be time-consuming even with single read. ??
# bench ct 0.00001 : 13.25 msec. for 2 spectrums (soft timer / POLL)
# bench ct 0.00001 :  4.12 msec. for 2 spectrums (soft timer / INTR)
"""

constant  __Attr_macfname  "attributes.mac"

def __attr_tango_catch_error '{
    if (TANGO_ERR != "0") {
        tty_cntl("md")
        __errstr    =   __Attr_macfname " - " func " - ERROR: " what \
            "(\"" addr "\", \"" attr "\"" add ")\n" \
            TANGO_ERR_STACK["0"]["desc"] "\n"
        eprint __errstr
        __Attr_debug  TANGO_ERR_STACK
        tty_cntl("me")
        return ".error."
    }
}
'

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

#%UU%
#%MDESC% toggle debug mode for the present macros.
def Attr_debug '{
    if ((whatis("__Attr_debug") >> 16) <= 5) {  # just a # sign -> off
        rdef __Attr_debug "eprintf(\"%s: \", __Attr_macfname);eprint "
        print "Attributes debug is ON"
    } else {
        rdef __Attr_debug \'#$*\'
        print "Attributes debug is OFF"
    }
}
'



#%MDESC%
#Macro counter and motor config
#%IU%
def attr_spectrum_config(num, type, unit, nb_chan, channel) '{
    __Attr_debug "attr_spectrum_config:", num, type, unit, nb_chan, channel
    local _idx  _arr_name
    local _ds_name
    local _taa[]

    split(attr_spectrum_ADDR, _taa, "/")
    _ds_name = sprintf("%s/%s/%s", _taa[0], _taa[1], _taa[2])
    __Attr_debug "attr_spectrum_config: _ds_name", _ds_name, _taa[3]
    if(ds_is_tango(_ds_name)) {
        if (num == "..") {
            # Creates a global associative array to cache tango
            # attribute reading.
            # Its name is forged using mac cnt name and unit number.
            _arr_name = sprintf("attr_spectrum_%s", _taa[3])
            global @_arr_name
            @arr_name[0] = 0
            __Attr_debug "attr_spectrum_config: _arr_name", _arr_name
        }
        else {
            _arr_name = sprintf("attr_spectrum_%s", _taa[3])
            local str, parcmd, mnecmd, showstr
            if (type == "mot") {
                parcmd = "motor_par"
                mnecmd = "motor_mne"
                showstr = "Motor "
            } else
            if (type == "cnt") {
                parcmd = "counter_par"
                mnecmd = "cnt_mne"
                showstr = "Counter "
            }
            str = parcmd "("num", \"spectrum\", \"" attr_spectrum_ADDR "\", \"add\")"
            eval(str)
            #counter_par(num, "spectrum", attr_spectrum_ADDR , "add")
            str = parcmd "("num", \"temp_arr\", \"" _arr_name "\", \"add\")"
            eval(str)
            #counter_par(num, "temp_arr", _arr_name , "add")
            str = parcmd "("num", \"channel\")"
            _idx = eval(str)
            #_idx = counter_par(num, "channel")

            __Attr_debug "  spectrum = " attr_spectrum_ADDR
            __Attr_debug "  temp_arr = " _arr_name

            str = mnecmd "(" num ")"
            local mne
            mne = eval(str)
            mne = showstr mne
            printf("ATTR_SPECTRUM : %10s linked to %s[%d]\n",     \
                   mne, attr_spectrum_ADDR, _idx)
        }
    }
    else {
        attr_error_head
        printf("device %s is unresponsive -> disabling it.\n", _ds_name)
        return ".error."
    }

}'


#%IU%
#%MDESC%
#    Macro motor/counter to read TANGO SPECTRUM ATTRIBUTES.
def attr_spectrum_cmd(num, key, p1, p2, unit) '{
    __Attr_debug "attr_spectrum_cmd:", num, key, p1, p2, unit
    local _read_at_end
    local func, attr, what, __errstr
    func = "attr_spectrum_cmd"

	local _dev _arr_name
	#printf ("attr_spectrum_cmd; cnum=\"%2s\"  prestart_all  p1=\"%2s\"  p2=\"%2s\"  unit=\"%2s\"\n", \
	#    num, p1, p2, unit)
	split (attr_spectrum_ADDR, _attr_fields, "/")
	_dev  = sprintf("%s/%s/%s", _attr_fields[0], _attr_fields[1], _attr_fields[2])
	attr = _attr_fields[3]
	_arr_name = sprintf("attr_spectrum_%s", attr)

    if (num == "..") {
        if ((key == "prestart_all") || (key == "preread_all")) {
            what = "tango_get"
            TANGO_ERR = -1
            tango_get(_dev, attr, @_arr_name)
            __attr_tango_catch_error
            __Attr_debug func ": _array", @_arr_name
        }
    }
    else { # mot or cnt, not ".."
        if (key == "start_one") {
            if (p2 != 0) { # this must be a motor, as p2 is the relative movement
                local _arr_name _index _ret_val

                _arr_name = motor_par(num, "temp_arr")
                _index    = motor_par(num, "channel")
                what = "tango_put"
                @_arr_name[_index] = p1
                TANGO_ERR = -1
                tango_put(_dev, attr, @_arr_name)
                __attr_tango_catch_error
                __Attr_debug "setting wago resource", attr "[" _index "]", "to", p1
            }
        }
        else if (key == "halt_one") {
			local _arr_name _index _ret_val
			_arr_name = counter_par(num, "temp_arr")
			what = "tango_get"
			TANGO_ERR = -1
			tango_get(_dev, attr, @_arr_name)
			__attr_tango_catch_error
			__Attr_debug "getting wago resource", attr, @_arr_name
        }
        else if (key == "counts") {
            local _arr_name _index _ret_val
			_arr_name = counter_par(num, "temp_arr")
            _index    = counter_par(num, "channel")
            return @_arr_name[_index]
        }
        else if (key == "position") {
            local _arr_name _index _ret_val
            _arr_name = motor_par(num, "temp_arr")
            _index    = motor_par(num, "channel")
            return @_arr_name[_index]
        }
    }
}
'



def attr_spectrum_read_test '{
    # ~ 0.33 ms
    global TTT[]
    tango_get("sys/database/2", "Timing_average", TTT)
}'


"""
######################################################################
###########################                ###########################
###########################  Macro motors  ###########################
###########################                ###########################
######################################################################

##%UU% <mne> <dev> <attribute>   |  <mne> <dev/attribute>
#%MDESC%
# Configures counter <mne> to read <attribute> of tango device <dev>.
#
# example:    attr_mot_setup  at1  id22eh2/machin/1   X
#          or attr_mot_setup  at2  id22eh2/machin/1/X
#
# example 2  attr_mot_setup rft1  id22/refillsim/1  Refill_time
#         or attr_mot_setup rft1  id22/refillsim/1/Refill_time
#
"""
def attr_mot_setup '{
    local _fields  _dev_name   _nbarg
    local _args_strings  _mne


    _mne   = "$1"
    motnum = motor_num(_mne)

    list_test ATTR_MOT

    list_add(ATTR_MOT, _mne)

    _nbarg = $#


    # Parses arguments to extract device and attribute names.
    # 2 or 3 arguments ?
    if (_nbarg == 2){
        if(motor_par(motnum,"type") != -1){
            list_setpar(ATTR_MOT, _mne, "type", motor_par(motnum,"type"))
        }
        # print "2 parameters given."
        _args_strings[0] = 0
        split ("$2", _args_strings, "/")

        list_setpar(ATTR_MOT, _mne, "dev_name",                     \
                 sprintf("%s/%s/%s", _args_strings[0],          \
                         _args_strings[1], _args_strings[2]))

        list_setpar(ATTR_MOT, _mne, "attr_name", _args_strings[3])
    }
    else if (_nbarg == 3){
        if(motor_par(motnum,"type") != -1){
            list_setpar(ATTR_MOT, _mne, "type", motor_par(motnum,"type"))
        }
        # print "3 parameters given."
        list_setpar(ATTR_MOT, _mne, "dev_name", "$2")
        list_setpar(ATTR_MOT, _mne, "attr_name", "$3")
    }
    else {
        attr_error_head
        print "$0 requires 2 or 3 arguments."
        print "usage -> $0  <mne> <device> <attribute>"
        print "      or $0  <mne> <device/attribute>"
        exit
    }


    # Informative message.
    printf("Configuring macro motor ")
    tty_cntl("md")
    printf ("%s", "$1")
    tty_cntl("me")
    printf (" to reflect attribute ")
    tty_cntl("md")
    printf ("%s", ATTR_MOT["$1"]["attr_name"])
    tty_cntl("me")
    printf (" of device ")
    tty_cntl("md")
    printf ("%s", ATTR_MOT["$1"]["dev_name"])
    tty_cntl("me")
    print "."

    if (motor_num(_mne) < 0){
        printf("Warning: motor \"%s\" is not defined\n", _mne)
    }
}'



#%UU% <mne>
#%MDESC%
#    Removes reading of attribute linked to <mne> motor.
#    ex: attr_mot_unsetup at1
def attr_mot_unsetup '{
    local _nb_param

    _nb_param =$#

    if (_nb_param < 1){
        print "usage : $0 <motor_mnemonic>"
    }
    else{
        list_remove(ATTR_MOT, "$1")
    }
}'

#%UU%
#%MDESC%
#    Removes reading of all attributes.
def attr_mot_unsetup_all '{
    unglobal ATTR_MOT
}'

#%UU%
#%MDESC%
#    Displays informations about attributes macro motors.
def attr_mot_show '{
    local _mne

    print "--------==== macro motors ====-----------"
    print "mnemonic         device       attribute  "
    print "-----------------------------------------"

    for (i=0; i< list_n(ATTR_MOT); i++){
        _mne = ATTR_MOT[i+1]
        print ATTR_MOT[i+1]"      " ATTR_MOT[_mne]["dev_name"]"     "   \
            ATTR_MOT[_mne]["attr_name"]
    }
    print "-----------------------------------------"

}'

#%IU%
#%MDESC%
#    Called on reconfig.
def attr_mot_config(motnum, what, p1, p2) '{
    global ATTR_MOT
    local _ds_name

    if((motnum == "..") && (what == "ctrl")) {
        # Check that the Tango DS is running
        if(ds_is_tango(attr_mot_ADDR)) {
             # ok
        }
        else{
            attr_error_head
            printf("%s unresponsive -> disabling. \n", attr_mot_ADDR)
            return ".error."
        }
        return
    }

    if(what == "mot") {
        local _mne
        local _attr

        _mne = motor_mne(motnum)
        list_test ATTR_MOT
        list_add(ATTR_MOT, _mne)

        if((_attr=motor_par(motnum,"attr_name")) == 0) {
            #attr_error_head
            #printf("missing \"attr_name\" for motor \"%s\"\n", _mne)
            ##return ".error."
            #return
            # PC: take position as default
            _attr = "position"
            motor_par(motnum, "attr_name", _attr)
        }

        _ds_name = attr_mot_ADDR
        motor_par(motnum, "dev_name", _ds_name, "add")

        list_setpar(ATTR_MOT, _mne, "dev_name", attr_mot_ADDR)
        list_setpar(ATTR_MOT, _mne, "attr_name", _attr)

        printf("ATTR_MOT : Motor %10s linked to %s/%s\n",     \
               _mne, ATTR_MOT[_mne]["dev_name"], ATTR_MOT[_mne]["attr_name"])
    }
}'


#%IU%
#%MDESC%
#
def attr_mot_cmd(motnum, what, p1, p2) '{

    local _mot_mne  _dev  _attr

    _mot_mne = motor_mne(motnum)

    # Tests if _mot_mne is really an attr_ macro motor.
    if (list_check(ATTR_MOT, _mot_mne)){
        _dev    = list_getpar(ATTR_MOT, _mot_mne, "dev_name")
        _attr   = list_getpar(ATTR_MOT, _mot_mne, "attr_name")
        _target = _dev "/" _attr
    }
    else{
        #  print "Motor \"" _mot_mne "\" not set up in ATTR_MOT."
        return (-1)
    }

    if(motnum == ".."){
        # key applying to all motors
        # print "--"
    }

    #-------------------------------------
    # "position" must return dial position (in integer steps)
    # parameters: none
    if (what == "position") {
        local _pos

        if(motor_par(motnum, "type") == "wago"){
            local attr_key
            local out[]

            # Specific command to read real wago value and not cached one.
            attr_key = tango_io(_dev, "DevName2Key", _attr)
            tango_io(_dev, "DevReadNoCachePhys", attr_key, out)

            _pos = out[0]
        }
        else {
            if (motor_par(motnum, "type") == -1){
                cprint_red(sprintf("attr_mot_cmd / position : motor %d is not defined", motnum))
            }
            else{
                _pos = tango_get(_dev, _attr)
            }
        }

        if (TANGO_ERR != "0"){
            tty_cntl("md")
            print "\n" ATTR_MOT_ERROR " " _mot_mne  " " _dev  "/"  _attr
            printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
            print TANGO_ERR_STACK
            tty_cntl("me")
            return ".error."
        }
        return (_pos)
    }

    """
    #-------------------------------------
    # "get_status" called during the motion, must return a bit mask
    # bits meaning: 0 if motor idle
    #               2 if motor moving
    #               4 if negative limit on
    #               8 if positive limit on
    # parameters:   none
    """
    if (what == "get_status"){
        local _state
        local _ret

            _state = tango_io(_dev, "State")

            if (TANGO_ERR != "0"){
                tty_cntl("md")
                print "\n" ATTR_MOT_ERROR " " _mot_mne  " " _dev  "/"  _attr
                printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
                print TANGO_ERR_STACK
                tty_cntl("me")
                return ".error."
            }

        #
        # DEVMOVING
        #
        if (_state == 6) {
            _ret |= 2
            return _ret
        }
        #
        # DEVFAULT
        #

        # PC: problem with SyncAxis, acceleration is wrong; causes Fault State while moving
        # We do not care; this is anyway a provisional solution
        if (_state == 8) {
            _ret |= 2
            return _ret
        }

        return 0
    }

    #-------------------------------------
    # "start_one"
    # parameters: p2 is relative motion, p1 is absolute requested position
    if (what == "start_one") {

        tango_put(_dev, _attr, p1)

        if (TANGO_ERR != "0"){
            tty_cntl("md")
            print "\n" ATTR_MOT_ERROR " " _mot_mne  " " _dev  "/"  _attr
            printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
            print TANGO_ERR_STACK
            tty_cntl("me")
            return ".error."
        }
    }

    """
    #-------------------------------------
    # "set_position" called on "set_dial motor newpos"
    # parameters: p1 is the new position (in steps)
    #
    # PC: this does not change the dial value, it will move to position p1
    # We don not want set_dial to be used on Tango motors
    """
    if (what == "set_position") {
        #    tango_put(_dev, _attr, p1)
        #
        #    if (TANGO_ERR != "0"){
        #        tty_cntl("md")
        #        print "\n" ATTR_MOT_ERROR " " _mot_mne  " " _dev  "/"  _attr
        #        printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
        #        print TANGO_ERR_STACK
        #        tty_cntl("me")
        #        return ".error."
        #    }
        print "Changing dial value is not allowed"
    }

    #-------------------------------------
    # "abort_one" called on <Ctrl-C>
    #
    if (what == "abort_one") {
        tango_io(_dev, "Abort")

        if (TANGO_ERR != "0"){
            tty_cntl("md")
            print "\n" ATTR_MOT_ERROR " " _mot_mne  " " _dev  "/"  _attr
            printf ("TANGO_ERR_MSG = \"%s\" \n\n", TANGO_ERR)
            print TANGO_ERR_STACK
            tty_cntl("me")
            return ".error."
        }
    }
}'


#%MACROS%
#%IMACROS%
#%AUTHOR% JM CG MP PC AM(a little) HW (Jan 2017)