Device state not changing to ALARM

Hi all,

I have designed a device which has an attribute "temperature", which reads temperature from a board. When reading the attribute, I am outputting some information to the debug stream like so:


    def read_temperature(self, attr):
        self.debug_stream("In read_temperature()")
        #—– PROTECTED REGION ID(TPM_DS.temperature_read) ENABLED START —–#
        try:
            self.attr_temperature_read = self.fpga_instance.temperature()
            attr.set_value(self.attr_temperature_read)
            self.debug_stream("Temperature: %f" % self.attr_temperature_read)
            self.debug_stream("Current device state: %s" % str(self.dev_state()))
        except Exception as e:
            self.debug_stream(str(e))
        #—– PROTECTED REGION END —–#	//	TPM_DS.temperature_read

From some client code, I am configuring the attribute with the following:

# Get attribute info for temperature
temp_attr_config = tpm.get_attribute_config("temperature")
temp_attr_config.alarms.max_alarm = '60'
temp_attr_config.alarms.min_alarm = '40'
temp_attr_config.min_value = '0'
temp_attr_config.max_value = '75'
tpm.set_attribute_config(temp_attr_config)
tpm.poll_attribute(attr_name="temperature", period=1000)

This code sets alarm values of 40 (min) and 60 (max), and starts polling the attribute every second. This works, and I can see the debug_stream output every second, report temperature correctly.

I then use Jive (or other client code) to reset the max_alarm value to 50 (from 60), to cause an alarm. The debug_stream shows the following:


1457451939 [140264765122304] DEBUG test/tpm_board/1 In read_temperature()
1457451939 [140264765122304] DEBUG test/tpm_board/1 Temperature: 59.062500
1457451939 [140264765122304] DEBUG test/tpm_board/1 Current device state: ON
1457451939 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451939 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451940 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451940 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451940 [140264765122304] DEBUG test/tpm_board/1 In read_attr_hardware()
1457451940 [140264765122304] DEBUG test/tpm_board/1 In read_attr_hardware()
1457451940 [140264765122304] DEBUG test/tpm_board/1 In read_temperature()
1457451940 [140264765122304] DEBUG test/tpm_board/1 Temperature: 59.062500
1457451940 [140264765122304] DEBUG test/tpm_board/1 Current device state: ON
1457451940 [140264765122304] ERROR test/tpm_board/1 MAX ALARM for attribute temperature
1457451940 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451940 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451941 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451941 [140264765122304] DEBUG test/tpm_board/1 In always_excuted_hook()
1457451941 [140264765122304] DEBUG test/tpm_board/1 In read_attr_hardware()
1457451941 [140264765122304] DEBUG test/tpm_board/1 In read_attr_hardware()
1457451941 [140264765122304] DEBUG test/tpm_board/1 In read_temperature()
1457451941 [140264765122304] DEBUG test/tpm_board/1 Temperature: 59.000000
1457451941 [140264765122304] DEBUG test/tpm_board/1 Current device state: ON

As can be seen, the ERROR shows when MAX ALARM is caught, but I expect the device state to go into ALARM and not remain in ON state. Why could this be happening?

Thanks,
Andrea

Dr Andrea DeMarco, BSc (Hons) (Melita), MSc (Melita), DPhil (UEA)
Lecturer | Researcher
Department of Physics
Institute of Space Sciences and Astronomy

Room 220, Maths and Physics Building
University of Malta, Msida MSD2080, MALTA
Hi Andrea,

I guess you overloaded the dev_state method.
Did you use Pogo to generate the code?

In Python or PythonHL?

It might be that there is something missing in the generated code?
I did a quick test and I get the following generated code in python:

    def dev_state(self):
        """ This command gets the device state (stored in its device_state data member) and returns it to the caller.
        :return: Device state
        :rtype: PyTango.CmdArgType.DevState
        """
        self.debug_stream("In dev_state()")
        argout = PyTango.DevState.UNKNOWN
        #—– PROTECTED REGION ID(TestPy.State) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	TestPy.State
        if argout != PyTango.DevState.ALARM:
            PyTango.Device_4Impl.dev_state(self)
        return self.get_state()

Pogo generates the following for PythonHL:

    @command(
    dtype_out='DevState', 
    doc_out="Device state"
    )
    @DebugIt()
    def dev_state(self):
        #—– PROTECTED REGION ID(TestPy.State) ENABLED START —–#
        argout = PyTango.DevState.ON
        #—– PROTECTED REGION END —–#	//	TestPy.State

And the following in C++:

Tango::DevState TestPy::dev_state()
{
	DEBUG_STREAM << "TestPy::State()  - " << device_name << endl;
	/*—– PROTECTED REGION ID(TestPy::dev_state) ENABLED START —–*/
	
	Tango::DevState	argout = Tango::UNKNOWN; // replace by your own algorithm
	//	Add your own code
	
	/*—– PROTECTED REGION END —–*/	//	TestPy::dev_state
	set_state(argout);    // Give the state to Tango.
	if (argout!=Tango::ALARM)
		DeviceImpl::dev_state();
	return get_state();  // Return it after Tango management.
}

The Python and C++ code generation look very similar but a line seem to be missing in the Python version, the line equivalent to the following C++ line just after the protected region:

    set_state(argout);    // Give the state to Tango.

I am not a pyTango expert so this might be normal. I will let the pyTango experts confirm.

As I am more used to work in C++, I can tell you that the code just after the protected region in C++ is the important part of the code which will check for attributes in alarm and change your state to ALARM if you've set the state to ON before.

If you still have problems and if you overloaded the dev_state method, could you please send us your code for this method?

Hoping this helps.
Reynald
Rosenberg's Law: Software is easy to make, except when you want it to do something new.
Corollary: The only software that's worth making is software that does something new.
Edited 8 years ago
Hi Reynald,

Thanks for your detailed reply. Let me try to answer as best I can.

1) I did not overload the dev_state method.
2) I am using PyTango8 8.1.1, with standard Python POGO code generation (not PythonHL)

I am recreating the problem very easily with a minimal working example here. The following is the device code. It is a dumb device, with 2 states ON/ALARM, and a single attribute "temperature".

#!/usr/bin/env python
# -*- coding:utf-8 -*- 


##############################################################################
## license :
##============================================================================
##
## File :        Tester.py
## 
## Project :     AAVS
##
## This file is part of Tango device class.
## 
## Tango is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## Tango is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with Tango.  If not, see <http://www.gnu.org/licenses/>.
## 
##
## $Author :      andrea.demarco$
##
## $Revision :    $
##
## $Date :        $
##
## $HeadUrl :     $
##============================================================================
##            This file is generated by POGO
##    (Program Obviously used to Generate tango Object)
##
##        © - Software Engineering Group - ESRF
##############################################################################

""""""

__all__ = ["Tester", "TesterClass", "main"]

__docformat__ = 'restructuredtext'

import PyTango
import sys
# Add additional import
#—– PROTECTED REGION ID(Tester.additionnal_import) ENABLED START —–#

#—– PROTECTED REGION END —–#	//	Tester.additionnal_import

## Device States Description
## ON : 
## ALARM : 

class Tester (PyTango.Device_4Impl):

    #——— Add you global variables here ————————–
    #—– PROTECTED REGION ID(Tester.global_variables) ENABLED START —–#
    
    #—– PROTECTED REGION END —–#	//	Tester.global_variables

    def __init__(self,cl, name):
        PyTango.Device_4Impl.__init__(self,cl,name)
        self.debug_stream("In __init__()")
        Tester.init_device(self)
        #—– PROTECTED REGION ID(Tester.__init__) ENABLED START —–#
        self.set_state(PyTango.DevState.ON)
        #—– PROTECTED REGION END —–#	//	Tester.__init__
        
    def delete_device(self):
        self.debug_stream("In delete_device()")
        #—– PROTECTED REGION ID(Tester.delete_device) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.delete_device

    def init_device(self):
        self.debug_stream("In init_device()")
        self.get_device_properties(self.get_device_class())
        self.attr_temperature_read = 0.0
        #—– PROTECTED REGION ID(Tester.init_device) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.init_device

    def always_executed_hook(self):
        self.debug_stream("In always_excuted_hook()")
        #—– PROTECTED REGION ID(Tester.always_executed_hook) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.always_executed_hook

    #—————————————————————————–
    #    Tester read/write attribute methods
    #—————————————————————————–
    
    def read_temperature(self, attr):
        self.debug_stream("In read_temperature()")
        #—– PROTECTED REGION ID(Tester.temperature_read) ENABLED START —–#
        attr.set_value(self.attr_temperature_read)
        self.debug_stream("Temperature: %f" % self.attr_temperature_read)
        #—– PROTECTED REGION END —–#	//	Tester.temperature_read
        
    def write_temperature(self, attr):
        self.debug_stream("In write_temperature()")
        data=attr.get_write_value()
        #—– PROTECTED REGION ID(Tester.temperature_write) ENABLED START —–#
        self.attr_temperature_read = data
        #—– PROTECTED REGION END —–#	//	Tester.temperature_write
        
    
    
        #—– PROTECTED REGION ID(Tester.initialize_dynamic_attributes) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.initialize_dynamic_attributes
            
    def read_attr_hardware(self, data):
        self.debug_stream("In read_attr_hardware()")
        #—– PROTECTED REGION ID(Tester.read_attr_hardware) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.read_attr_hardware


    #—————————————————————————–
    #    Tester command methods
    #—————————————————————————–
    

    #—– PROTECTED REGION ID(Tester.programmer_methods) ENABLED START —–#
    
    #—– PROTECTED REGION END —–#	//	Tester.programmer_methods

class TesterClass(PyTango.DeviceClass):
    #——— Add you global class variables here ————————–
    #—– PROTECTED REGION ID(Tester.global_class_variables) ENABLED START —–#
    
    #—– PROTECTED REGION END —–#	//	Tester.global_class_variables

    def dyn_attr(self, dev_list):
        """Invoked to create dynamic attributes for the given devices.
        Default implementation calls
        :meth:`Tester.initialize_dynamic_attributes` for each device
    
        :param dev_list: list of devices
        :type dev_list: :class:`PyTango.DeviceImpl`"""
    
        for dev in dev_list:
            try:
                dev.initialize_dynamic_attributes()
            except:
                import traceback
                dev.warn_stream("Failed to initialize dynamic attributes")
                dev.debug_stream("Details: " + traceback.format_exc())
        #—– PROTECTED REGION ID(Tester.dyn_attr) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.dyn_attr

    #    Class Properties
    class_property_list = {
        }


    #    Device Properties
    device_property_list = {
        }


    #    Command definitions
    cmd_list = {
        }


    #    Attribute definitions
    attr_list = {
        'temperature':
            [[PyTango.DevDouble,
            PyTango.SCALAR,
            PyTango.READ_WRITE]],
        }


def main():
    try:
        py = PyTango.Util(sys.argv)
        py.add_class(TesterClass,Tester,'Tester')
        #—– PROTECTED REGION ID(Tester.add_classes) ENABLED START —–#
        
        #—– PROTECTED REGION END —–#	//	Tester.add_classes

        U = PyTango.Util.instance()
        U.server_init()
        U.server_run()

    except PyTango.DevFailed,e:
        print '——-> Received a DevFailed exception:',e
    except Exception,e:
        print '——-> An unforeseen exception occured….',e

if __name__ == '__main__':
    main()

In order to test the code, I have the following client side code that I am debugging with. Essentially:
(1) Create a device proxy.
(2) Read current temperature attribute (starts at 0.0)
(3) Set a fake temperature of 45 and read it again (correctly 45.0)
(4) Configure the attribute a FIRST time with alarm settings of 40 (min) and 100 (max), and start polling every second.
(5) Heat up the device a bit by putting it to 65 and read it again (correctly 65.0)
(6) Reconfigure the alarm a SECOND time, with alarm settings of 40 (min) and 60 (max), whilst retaining the previous polling on.


#INIT device
tpm = PyTango.DeviceProxy("test/tester/1")
print tpm
# Get temperature
print "Temperature: %f" % (tpm.temperature)

# Get new temperature
tpm.temperature = 45
print "Temperature: %f" % (tpm.temperature)

# Get attribute info for temperature
temp_attr_config = tpm.get_attribute_config("temperature")
temp_attr_config.alarms.max_alarm = '100'
temp_attr_config.alarms.min_alarm = '40'
temp_attr_config.min_value = '0'
temp_attr_config.max_value = '75'
tpm.set_attribute_config(temp_attr_config)
tpm.poll_attribute(attr_name="temperature", period=1000)

# Start heating up
tpm.temperature = 65
print "Temperature: %f" % (tpm.temperature)


temp_attr_config = tpm.get_attribute_config("temperature")
temp_attr_config.alarms.max_alarm = '60'
temp_attr_config.alarms.min_alarm = '40'
temp_attr_config.min_value = '0'
temp_attr_config.max_value = '75'
tpm.set_attribute_config(temp_attr_config)

From the debug stream:

1457457266 [140141034260224] DEBUG test/tester/1 In always_excuted_hook()
1457457266 [140141034260224] DEBUG test/tester/1 In read_attr_hardware()
1457457266 [140141034260224] DEBUG test/tester/1 In read_temperature()
1457457266 [140141034260224] DEBUG test/tester/1 Temperature: 0.000000
1457457266 [140141034260224] DEBUG test/tester/1 In always_excuted_hook()
1457457266 [140141034260224] DEBUG test/tester/1 In write_temperature()
1457457267 [140141034260224] DEBUG test/tester/1 In always_excuted_hook()
1457457267 [140141034260224] DEBUG test/tester/1 In read_attr_hardware()
1457457267 [140141034260224] DEBUG test/tester/1 In read_temperature()
1457457267 [140141034260224] DEBUG test/tester/1 Temperature: 45.000000
1457457271 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457271 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457271 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457271 [140140555007744] DEBUG test/tester/1 Temperature: 45.000000
1457457272 [140141034260224] DEBUG test/tester/1 In always_excuted_hook()
1457457272 [140141034260224] DEBUG test/tester/1 In write_temperature()
1457457272 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457272 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457272 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457272 [140140555007744] DEBUG test/tester/1 Temperature: 65.000000
1457457273 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457273 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457273 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457273 [140140555007744] DEBUG test/tester/1 Temperature: 65.000000
1457457274 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457274 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457274 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457274 [140140555007744] DEBUG test/tester/1 Temperature: 65.000000
1457457275 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457275 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457275 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457275 [140140555007744] DEBUG test/tester/1 Temperature: 65.000000
1457457275 [140140555007744] ERROR test/tester/1 MAX ALARM for attribute temperature
1457457276 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457276 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457276 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457276 [140140555007744] DEBUG test/tester/1 Temperature: 65.000000
1457457277 [140140555007744] DEBUG test/tester/1 In always_excuted_hook()
1457457277 [140140555007744] DEBUG test/tester/1 In read_attr_hardware()
1457457277 [140140555007744] DEBUG test/tester/1 In read_temperature()
1457457277 [140140555007744] DEBUG test/tester/1 Temperature: 65.000000

If I open the device in AtkPanel, I can see the temperature attribute in orange colour (ALARM), but the device state is still green (ON).

Something seems really wrong :(

EDIT: If I simply start the device, and modify the temperature via AtkPanel, and set up alarm max/min via Jive, then the device works as expected - the device goes to ALARM state! So something is wrong with how the attribute/alarm is being configured via my code, or via PyTango….

Andrea
Dr Andrea DeMarco, BSc (Hons) (Melita), MSc (Melita), DPhil (UEA)
Lecturer | Researcher
Department of Physics
Institute of Space Sciences and Astronomy

Room 220, Maths and Physics Building
University of Malta, Msida MSD2080, MALTA
Edited 8 years ago
I have created a workaround for the problem. In the device code itself, I have added the following command:

    def config_attribute_alarm(self, argin):
        """ This method sets up an alarm for a particular attribute in this device.
        
        :param argin: 
            Pickled dictionary with:
            1) name of attribute
            2) min alarm level
            3) max alarm level
        :type: PyTango.DevString
        :return: 
        :rtype: PyTango.DevVoid """
        self.debug_stream("In config_attribute_alarm()")
        #—– PROTECTED REGION ID(Tester.config_attribute_alarm) ENABLED START —–#
        self.debug_stream("Unpacking arguments…")
        arguments = pickle.loads(argin)
        attr_name = arguments.get('name')
        min_alarm = arguments.get('min_alarm')
        max_alarm = arguments.get('max_alarm')

        multi_attr = self.get_device_attr()
        attribute = multi_attr.get_attr_by_name(attr_name)
        multi_prop = attribute.get_properties()
        multi_prop.min_alarm = min_alarm
        multi_prop.max_alarm = max_alarm
        try:
            self.info_stream("Setting alarm…")
            attribute.set_properties(multi_prop)
            self.info_stream("Done.")
        except PyTango.DevFailed as df:
            self.debug_stream("Failed to set attribute levels: %s" % df)
        except:
            self.debug_stream("Unexpected error. Operation ignored. Maybe inputs are incorrect?")
        #—– PROTECTED REGION END —–#	//	Tester.config_attribute_alarm

From the device proxy (client), the code to set an alarm for an attribute would be like:

tpm = PyTango.DeviceProxy("test/tester/1")
arguments = {}
arguments['name'] = 'temperature'
arguments['min_alarm'] = '40'
arguments['max_alarm'] = '60'
argin = pickle.dumps(arguments)
tpm.command_inout("config_attribute_alarm", argin)

Doing things this way, the ALARM state is triggered as expected! So essentially, something is possibly wrong with set_attribute_config() in PyTango?

Cheers,
Andrea
Dr Andrea DeMarco, BSc (Hons) (Melita), MSc (Melita), DPhil (UEA)
Lecturer | Researcher
Department of Physics
Institute of Space Sciences and Astronomy

Room 220, Maths and Physics Building
University of Malta, Msida MSD2080, MALTA
Dear Andrea,
you should not need the workaround but there is obviously a bug which is preventing you from using the correct approach. I wonder if you are suffering from this bug:

https://sourceforge.net/p/tango-cs/bugs/645/

To be sure I ran your code in the Tango 9.2 VM which I am currently preparing with PyTango9 and Tango 9.2.0. The result is as you expect i.e. the bug has disappeared and your client and server display the alarm correctly as shown in this screenshot:



Andy
Hi Andy,

Thanks for the reply. It looks like it really is that bug! We're still using Tango8 until PyTango9 is released of course - so for now the workaround will do. Looking forward to having a full release of PyTango9 with all the goodies in there :)

Thanks,
Andrea
Dr Andrea DeMarco, BSc (Hons) (Melita), MSc (Melita), DPhil (UEA)
Lecturer | Researcher
Department of Physics
Institute of Space Sciences and Astronomy

Room 220, Maths and Physics Building
University of Malta, Msida MSD2080, MALTA
Not long to wait now - PyTango9 + Tango 9.2.1 will be released this Friday (so I am told) …
Excellent - thanks for the heads up! :)
Dr Andrea DeMarco, BSc (Hons) (Melita), MSc (Melita), DPhil (UEA)
Lecturer | Researcher
Department of Physics
Institute of Space Sciences and Astronomy

Room 220, Maths and Physics Building
University of Malta, Msida MSD2080, MALTA
Hello guys,

Never believe what a boss is saying when talking about future package availability

Cheers

Manu

PS : I couldn't resist
Haha,

Thanks :) For the time being Tango8 is good enough anyway :)

Andrea
Dr Andrea DeMarco, BSc (Hons) (Melita), MSc (Melita), DPhil (UEA)
Lecturer | Researcher
Department of Physics
Institute of Space Sciences and Astronomy

Room 220, Maths and Physics Building
University of Malta, Msida MSD2080, MALTA
 
Register or login to create to post a reply.