Skip to content
Snippets Groups Projects
Commit a0b9016c authored by Thomas Juerges's avatar Thomas Juerges
Browse files

Merge branch 'master' into 2021-03-22T14.42.30-branched_from_master-Makefile_refactoring

parents 15d430b0 363bba72
No related branches found
No related tags found
1 merge request!21Small refactoring of the Makefile
This commit is part of merge request !21. Comments created here will be created in the context of that merge request.
Showing
with 1803 additions and 0 deletions
{
"servers": {
"PCC": {
"1": {
"PCC": {
"LTS/PCC/1": {
"properties": {
"OPC_Server_Name": [
"ltspi.astron.nl"
]
}
}
}
}
},
"SDP": {
"1": {
"SDP": {
"LTS/SDP/1": {
"properties": {
"OPC_Server_Name": [
"DESPi2.astron.nl"
]
}
}
}
}
},
"example_device": {
"1": {
"example_device": {
"LTS/example_device/1": {
"attribute_properties": {
"Ant_mask_RW": {
"archive_period": [
"600000"
]
}
},
"properties": {
"OPC_Server_Name": [
"host.docker.internal"
],
"OPC_Server_Port": [
"4842"
],
"OPC_Time_Out": [
"5.0"
]
}
}
}
}
}
}
}
{
"servers": {
"PCC": {
"1": {
"PCC": {
"LTS/PCC/1": {
"properties": {
"OPC_Server_Name": [
"host.docker.internal"
]
}
}
}
}
},
"SDP": {
"1": {
"SDP": {
"LTS/SDP/1": {
"properties": {
"OPC_Server_Name": [
"host.docker.internal"
]
}
}
}
}
},
"example_device": {
"1": {
"example_device": {
"LTS/example_device/1": {
"attribute_properties": {
"Ant_mask_RW": {
"archive_period": [
"600000"
]
}
},
"properties": {
"OPC_Server_Name": [
"host.docker.internal"
],
"OPC_Server_Port": [
"4842"
],
"OPC_Time_Out": [
"5.0"
]
}
}
}
}
}
}
}
...@@ -87,6 +87,10 @@ class PCC(Device): ...@@ -87,6 +87,10 @@ class PCC(Device):
# Attributes # Attributes
# ---------- # ----------
RCU_state_R = attribute(
dtype = (numpy.str),
)
RCU_mask_RW = attribute( RCU_mask_RW = attribute(
dtype=(numpy.bool_,), dtype=(numpy.bool_,),
max_dim_x=32, max_dim_x=32,
...@@ -204,6 +208,11 @@ class PCC(Device): ...@@ -204,6 +208,11 @@ class PCC(Device):
access=AttrWriteType.READ_WRITE, access=AttrWriteType.READ_WRITE,
) )
uC_ID_R = attribute(
dtype=(numpy.int64,),
max_dim_x=32,
)
RCU_monitor_rate_RW = attribute( RCU_monitor_rate_RW = attribute(
dtype=numpy.float_, dtype=numpy.float_,
access=AttrWriteType.READ_WRITE, access=AttrWriteType.READ_WRITE,
...@@ -233,6 +242,8 @@ class PCC(Device): ...@@ -233,6 +242,8 @@ class PCC(Device):
self.debug_stream("Mapping OPC-UA MP/CP to attributes...") self.debug_stream("Mapping OPC-UA MP/CP to attributes...")
self.attribute_mapping["RCU_state_R"] = self.get_pcc_node("RCU_state_R")
self.attribute_mapping["RCU_mask_RW"] = self.get_pcc_node("RCU_mask_RW") self.attribute_mapping["RCU_mask_RW"] = self.get_pcc_node("RCU_mask_RW")
self.attribute_mapping["Ant_mask_RW"] = self.get_pcc_node("Ant_mask_RW") self.attribute_mapping["Ant_mask_RW"] = self.get_pcc_node("Ant_mask_RW")
...@@ -277,6 +288,8 @@ class PCC(Device): ...@@ -277,6 +288,8 @@ class PCC(Device):
self.attribute_mapping["HBA_element_pwr_RW"] = self.get_pcc_node("HBA_element_pwr_RW") self.attribute_mapping["HBA_element_pwr_RW"] = self.get_pcc_node("HBA_element_pwr_RW")
self.attribute_mapping["uC_ID_R"] = self.get_pcc_node("uC_ID_R")
self.attribute_mapping["RCU_monitor_rate_RW"] = self.get_pcc_node("RCU_monitor_rate_RW") self.attribute_mapping["RCU_monitor_rate_RW"] = self.get_pcc_node("RCU_monitor_rate_RW")
self.function_mapping["RCU_off"] = self.get_pcc_node("RCU_off") self.function_mapping["RCU_off"] = self.get_pcc_node("RCU_off")
...@@ -314,6 +327,9 @@ class PCC(Device): ...@@ -314,6 +327,9 @@ class PCC(Device):
# Set default values in the RW/R attributes and add them to # Set default values in the RW/R attributes and add them to
# the mapping. # the mapping.
self._RCU_state_R = ""
self.attribute_mapping["RCU_state_R"] = {}
self._RCU_mask_RW = numpy.full(32, False) self._RCU_mask_RW = numpy.full(32, False)
self.attribute_mapping["RCU_mask_RW"] = {} self.attribute_mapping["RCU_mask_RW"] = {}
...@@ -380,6 +396,9 @@ class PCC(Device): ...@@ -380,6 +396,9 @@ class PCC(Device):
self._HBA_element_pwr_RW = numpy.full((96, 32), 0) self._HBA_element_pwr_RW = numpy.full((96, 32), 0)
self.attribute_mapping["HBA_element_pwr_RW"] = {} self.attribute_mapping["HBA_element_pwr_RW"] = {}
self._uC_ID_R = numpy.full(32, 0)
self.attribute_mapping["uC_ID_R"] = {}
self._RCU_monitor_rate_RW = 30.0 self._RCU_monitor_rate_RW = 30.0
self.attribute_mapping["RCU_monitor_rate_RW"] = {} self.attribute_mapping["RCU_monitor_rate_RW"] = {}
...@@ -450,6 +469,13 @@ class PCC(Device): ...@@ -450,6 +469,13 @@ class PCC(Device):
# ------------------ # ------------------
# Attributes methods # Attributes methods
# ------------------ # ------------------
@only_when_on
@fault_on_error
def read_RCU_state_R(self):
"""Return the RCU_state_R attribute."""
self._RCU_state_R = self.attribute_mapping["RCU_state_R"].get_value()
return self._RCU_state_R
@only_when_on @only_when_on
@fault_on_error @fault_on_error
def read_RCU_mask_R(self): def read_RCU_mask_R(self):
...@@ -675,6 +701,13 @@ class PCC(Device): ...@@ -675,6 +701,13 @@ class PCC(Device):
self.attribute_mapping["HBA_element_pwr_RW"].set_value(value.flatten().tolist()) self.attribute_mapping["HBA_element_pwr_RW"].set_value(value.flatten().tolist())
self._HBA_element_pwr_RW = value self._HBA_element_pwr_RW = value
@only_when_on
@fault_on_error
def read_uC_ID_R(self):
"""Return the uC_ID_R attribute."""
self._uC_ID_R = numpy.array(self.attribute_mapping["uC_ID_R"].get_value())
return self._uC_ID_R
@only_when_on @only_when_on
@fault_on_error @fault_on_error
def read_RCU_monitor_rate_RW(self): def read_RCU_monitor_rate_RW(self):
......
PCC/test/test-PCC.py 100755 → 100644
File mode changed from 100755 to 100644
# -*- coding: utf-8 -*-
#
# This file is part of the PCC project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" Hardware Device Server for LOFAR2.0
"""
# PyTango imports
from tango.server import run
# Additional import
from src.hardware_device import *
__all__ = ["HW_dev"]
class HW_dev(hardware_device):
"""
This class is the minimal (read empty) implementation of a class using 'hardware_device'
"""
# ----------
# Attributes
# ----------
"""
attribute wrapper objects can be declared here. All attribute wrapper objects will get automatically put in a ist (attr_list) for easy access
example = attribute_wrapper(comms_annotation="this is an example", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE)
...
"""
def always_executed_hook(self):
"""Method always executed before any TANGO command is executed."""
pass
def delete_device(self):
"""Hook to delete resources allocated in init_device.
This method allows for any memory or other resources allocated in the
init_device method to be released. This method is called by the device
destructor and by the device Init command (a Tango built-in).
"""
self.debug_stream("Shutting down...")
self.Off()
self.debug_stream("Shut down. Good bye.")
# --------
# overloaded functions
# --------
def fault(self):
""" user code here. is called when the state is set to FAULT """
pass
def off(self):
""" user code here. is called when the state is set to OFF """
pass
def on(self):
""" user code here. is called when the state is set to ON """
pass
def standby(self):
""" user code here. is called when the state is set to STANDBY """
pass
def initialise(self):
""" user code here. is called when the sate is set to INIT """
pass
# ----------
# Run server
# ----------
def main(args=None, **kwargs):
"""Main function of the hardware device module."""
return run((HW_dev,), args=args, **kwargs)
if __name__ == '__main__':
main()
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 ASTRON Netherlands Institute for Radio Astronomy
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
devices/PCC.py 0 → 100644
+ 231
0
View file @ a0b9016c
# -*- coding: utf-8 -*-
#
# This file is part of the PCC project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" PCC Device Server for LOFAR2.0
"""
# PyTango imports
from tango import DebugIt
from tango.server import run, command
from tango.server import device_property
# Additional import
from clients.opcua_connection import OPCUAConnection
from src.attribute_wrapper import *
from src.hardware_device import *
__all__ = ["PCC", "main"]
class PCC(hardware_device):
"""
**Properties:**
- Device Property
OPC_Server_Name
- Type:'DevString'
OPC_Server_Port
- Type:'DevULong'
OPC_Time_Out
- Type:'DevDouble'
"""
# -----------------
# Device Properties
# -----------------
OPC_Server_Name = device_property(
dtype='DevString',
mandatory=True
)
OPC_Server_Port = device_property(
dtype='DevULong',
mandatory=True
)
OPC_Time_Out = device_property(
dtype='DevDouble',
mandatory=True
)
OPC_namespace = device_property(
dtype='DevString',
mandatory=False
)
# ----------
# Attributes
# ----------
RCU_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_mask_RW"], datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE)
Ant_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:Ant_mask_RW"], datatype=numpy.bool_, dims=(3, 32), access=AttrWriteType.READ_WRITE)
RCU_attenuator_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_R"], datatype=numpy.int64, dims=(3, 32))
RCU_attenuator_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE)
RCU_band_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_R"], datatype=numpy.int64, dims=(3, 32))
RCU_band_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE)
RCU_temperature_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_temperature_R"], datatype=numpy.float64, dims=(32,))
RCU_Pwr_dig_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_Pwr_dig_R"], datatype=numpy.int64, dims=(32,))
RCU_LED0_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_R"], datatype=numpy.int64, dims=(32,))
RCU_LED0_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_RW"], datatype=numpy.int64, dims=(32,), access=AttrWriteType.READ_WRITE)
RCU_ADC_lock_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_lock_R"], datatype=numpy.int64, dims=(3, 32))
RCU_ADC_SYNC_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_SYNC_R"], datatype=numpy.int64, dims=(3, 32))
RCU_ADC_JESD_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_JESD_R"], datatype=numpy.int64, dims=(3, 32))
RCU_ADC_CML_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_CML_R"], datatype=numpy.int64, dims=(3, 32))
RCU_OUT1_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_OUT1_R"], datatype=numpy.int64, dims=(3, 32))
RCU_OUT2_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_OUT2_R"], datatype=numpy.int64, dims=(3, 32))
RCU_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ID_R"], datatype=numpy.int64, dims=(32,))
RCU_version_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_version_R"], datatype=numpy.str_, dims=(32,))
HBA_element_beamformer_delays_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_R"], datatype=numpy.int64, dims=(32,96))
HBA_element_beamformer_delays_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_RW"], datatype=numpy.int64, dims=(32,96), access=AttrWriteType.READ_WRITE)
HBA_element_pwr_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_R"], datatype=numpy.int64, dims=(32,96))
HBA_element_pwr_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_RW"], datatype=numpy.int64, dims=(32,96), access=AttrWriteType.READ_WRITE)
RCU_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_monitor_rate_RW"], datatype=numpy.float64, access=AttrWriteType.READ_WRITE)
def delete_device(self):
"""Hook to delete resources allocated in init_device.
This method allows for any memory or other resources allocated in the
init_device method to be released. This method is called by the device
destructor and by the device Init command (a Tango built-in).
"""
self.debug_stream("Shutting down...")
self.Off()
self.debug_stream("Shut down. Good bye.")
# --------
# overloaded functions
# --------
def off(self):
""" user code here. is called when the state is set to OFF """
# Stop keep-alive
self.opcua_connection.stop()
def initialise(self):
""" user code here. is called when the state is set to INIT """
# Init the dict that contains function to OPC-UA function mappings.
self.function_mapping = {}
self.function_mapping["RCU_on"] = {}
self.function_mapping["RCU_off"] = {}
self.function_mapping["ADC_on"] = {}
self.function_mapping["RCU_update"] = {}
self.function_mapping["CLK_on"] = {}
self.function_mapping["CLK_off"] = {}
self.function_mapping["CLK_PLL_setup"] = {}
#set up the OPC ua client
self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Standby, self.Fault, self)
# map the attributes to the OPC ua comm client
for i in self.attr_list():
try:
i.set_comm_client(self.OPCua_client)
except:
pass
self.OPCua_client.start()
# --------
# Commands
# --------
@command()
@DebugIt()
@only_when_on
@fault_on_error
def RCU_off(self):
"""
:return:None
"""
self.function_mapping["RCU_off"]()
@command()
@DebugIt()
@only_when_on
@fault_on_error
def RCU_on(self):
"""
:return:None
"""
self.function_mapping["RCU_on"]()
@command()
@DebugIt()
@only_when_on
@fault_on_error
def ADC_on(self):
"""
:return:None
"""
self.function_mapping["ADC_on"]()
@command()
@DebugIt()
@only_when_on
@fault_on_error
def RCU_update(self):
"""
:return:None
"""
self.function_mapping["RCU_update"]()
@command()
@DebugIt()
@only_when_on
@fault_on_error
def CLK_off(self):
"""
:return:None
"""
self.function_mapping["CLK_off"]()
@command()
@DebugIt()
@only_when_on
@fault_on_error
def CLK_on(self):
"""
:return:None
"""
self.function_mapping["CLK_on"]()
@command()
@DebugIt()
@only_when_on
@fault_on_error
def CLK_PLL_setup(self):
"""
:return:None
"""
self.function_mapping["CLK_PLL_setup"]()
# ----------
# Run server
# ----------
def main(args=None, **kwargs):
"""Main function of the PCC module."""
return run((PCC,), args=args, **kwargs)
if __name__ == '__main__':
main()
# Device wrapper
This code provides an attribute_wrapper class in place of attributes for tango devices. the attribute wrappers contain additional code
that moves a lot of the complexity and redundant code to the background.
The tango Device class is also abstracted further to a "hardware_device" class. This class wraps
The only things required on the users part are to declare the attributes using the attribute_wrapper (see `example/example_device`),
declare what client the attribute has to use in the initialisation and provide support for the used clients.
To see how to add support for new clients, see `clients/README.md`
In addition it also provides an abstraction to the tango device, specifically for hardware devices. Examples of hardware devices
can be found in TODO and an empty template can be found in `HW_device_template.py`
Requires numpy
```pip install numpy```
Requires opcua
```pip install opcua```
Requires pytango
```pip install pytango```
### usage
You can start the device by calling it in any console with:
<Device_name>.py instance_name
# -*- coding: utf-8 -*-
#
# This file is part of the SDP project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" SDP Device Server for LOFAR2.0
"""
# PyTango imports
from tango.server import run
from tango.server import device_property
# Additional import
from clients.opcua_connection import OPCUAConnection
from src.attribute_wrapper import *
from src.hardware_device import *
__all__ = ["SDP", "main"]
class SDP(hardware_device):
"""
**Properties:**
- Device Property
OPC_Server_Name
- Type:'DevString'
OPC_Server_Port
- Type:'DevULong'
OPC_Time_Out
- Type:'DevDouble'
"""
# -----------------
# Device Properties
# -----------------
OPC_Server_Name = device_property(
dtype='DevString',
mandatory=True
)
OPC_Server_Port = device_property(
dtype='DevULong',
mandatory=True
)
OPC_Time_Out = device_property(
dtype='DevDouble',
mandatory=True
)
# ----------
# Attributes
# ----------
fpga_mask_RW = attribute_wrapper(comms_annotation=["1:fpga_mask_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE)
fpga_scrap_R = attribute_wrapper(comms_annotation=["1:fpga_scrap_R"], datatype=numpy.int32, dims=(2048,))
fpga_scrap_RW = attribute_wrapper(comms_annotation=["1:fpga_scrap_RW"], datatype=numpy.int32, dims=(2048,), access=AttrWriteType.READ_WRITE)
fpga_status_R = attribute_wrapper(comms_annotation=["1:fpga_status_R"], datatype=numpy.bool_, dims=(16,))
fpga_temp_R = attribute_wrapper(comms_annotation=["1:fpga_temp_R"], datatype=numpy.float_, dims=(16,))
fpga_version_R = attribute_wrapper(comms_annotation=["1:fpga_version_R"], datatype=numpy.str_, dims=(16,))
fpga_weights_R = attribute_wrapper(comms_annotation=["1:fpga_weights_R"], datatype=numpy.int16, dims=(16, 12 * 488 * 2))
fpga_weights_RW = attribute_wrapper(comms_annotation=["1:fpga_weights_RW"], datatype=numpy.int16, dims=(16, 12 * 488 * 2), access=AttrWriteType.READ_WRITE)
tr_busy_R = attribute_wrapper(comms_annotation=["1:tr_busy_R"], datatype=numpy.bool_)
# NOTE: typo in node name is 'tr_reload_W' should be 'tr_reload_RW'
tr_reload_RW = attribute_wrapper(comms_annotation=["1:tr_reload_W"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
tr_tod_R = attribute_wrapper(comms_annotation=["1:tr_tod_R"], datatype=numpy.uint64)
tr_uptime_R = attribute_wrapper(comms_annotation=["1:tr_uptime_R"], datatype=numpy.uint64)
def always_executed_hook(self):
"""Method always executed before any TANGO command is executed."""
pass
def delete_device(self):
"""Hook to delete resources allocated in init_device.
This method allows for any memory or other resources allocated in the
init_device method to be released. This method is called by the device
destructor and by the device Init command (a Tango built-in).
"""
self.debug_stream("Shutting down...")
self.Off()
self.debug_stream("Shut down. Good bye.")
# --------
# overloaded functions
# --------
def off(self):
""" user code here. is called when the state is set to OFF """
# Stop keep-alive
self.opcua_connection.stop()
def initialise(self):
""" user code here. is called when the sate is set to INIT """
"""Initialises the attributes and properties of the PCC."""
# set up the OPC ua client
self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Standby, self.Fault, self)
# will contain all the values for this object
self.setup_value_dict()
# map an access helper class
for i in self.attr_list():
i.set_comm_client(self.OPCua_client)
self.OPCua_client.start()
# --------
# Commands
# --------
# ----------
# Run server
# ----------
def main(args=None, **kwargs):
"""Main function of the SDP module."""
return run((SDP,), args=args, **kwargs)
if __name__ == '__main__':
main()
this folder contains all the comms_client implementations for organisation
### How to add a new client
soon™
\ No newline at end of file
from src.comms_client import *
__all__ = ["OPCUAConnection"]
numpy_to_OPCua_dict = {
numpy.bool_: opcua.ua.VariantType.Boolean,
numpy.int8: opcua.ua.VariantType.SByte,
numpy.uint8: opcua.ua.VariantType.Byte,
numpy.int16: opcua.ua.VariantType.Int16,
numpy.uint16: opcua.ua.VariantType.UInt16,
numpy.int32: opcua.ua.VariantType.Int32,
numpy.uint32: opcua.ua.VariantType.UInt32,
numpy.int64: opcua.ua.VariantType.Int64,
numpy.uint64: opcua.ua.VariantType.UInt64,
numpy.datetime_data: opcua.ua.VariantType.DateTime, # is this the right type, does it even matter?
numpy.float32: opcua.ua.VariantType.Float,
numpy.double: opcua.ua.VariantType.Double,
numpy.float64: opcua.ua.VariantType.Double,
numpy.str_: opcua.ua.VariantType.String,
numpy.str: opcua.ua.VariantType.String,
str: opcua.ua.VariantType.String
}
# <class 'numpy.bool_'>
class OPCUAConnection(CommClient):
"""
Connects to OPC-UA in the foreground or background, and sends HELLO
messages to keep a check on the connection. On connection failure, reconnects once.
"""
def start(self):
super().start()
def __init__(self, address, namespace, timeout, on_func, fault_func, streams, try_interval=2):
"""
Create the OPC ua client and connect() to it and get the object node
"""
super().__init__(on_func, fault_func, streams, try_interval)
self.client = Client(address, timeout)
# Explicitly connect
if not self.connect():
# hardware or infra is down -- needs fixing first
fault_func()
return
# determine namespace used
try:
if type(namespace) is str:
self.name_space_index = self.client.get_namespace_index(namespace)
elif type(namespace) is int:
self.name_space_index = namespace
except Exception as e:
#TODO remove once SDP is fixed
self.streams.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = 2.")
self.name_space_index = 2
self.obj = self.client.get_objects_node()
def _servername(self):
return self.client.server_url.geturl()
def connect(self):
"""
Try to connect to the client
"""
try:
self.streams.debug_stream("Connecting to server %s", self._servername())
self.client.connect()
self.connected = True
self.streams.debug_stream("Connected to %s. Initialising.", self._servername())
return True
except socket.error as e:
#TODO
self.streams.error_stream("Could not connect to server %s: %s", self._servername())
raise Exception("") from e
def disconnect(self):
"""
disconnect from the client
"""
self.connected = False # always force a reconnect, regardless of a successful disconnect
try:
self.client.disconnect()
except Exception as e:
self.streams.error_stream("Disconnect from OPC-UA server %s failed: %s", self._servername(), e)
def ping(self):
"""
ping the client to make sure the connection with the client is still functional.
"""
try:
self.client.send_hello()
except Exception as e:
raise Exception("Lost connection to server %s: %s", self._servername(), e)
def _setup_annotation(self, annotation):
"""
This class's Implementation of the get_mapping function. returns the read and write functions
"""
if isinstance(annotation, dict):
# check if required path inarg is present
if annotation.get('path') is None:
raise Exception("OPC-ua mapping requires a path argument in the annotation, was given: %s", annotation)
path = annotation.get("path") # required
elif isinstance(annotation, list):
path = annotation
else:
raise Exception("OPC-ua mapping requires either a list of the path or dict with the path. Was given %s type containing: %s", type(annotation), annotation)
try:
node = self.obj.get_child(path)
except Exception as e:
self.streams.error_stream("Could not get node: %s on server %s: %s", path, self._servername(), e)
raise Exception("Could not get node: %s on server %s", path, self._servername()) from e
return node
def setup_value_conversion(self, attribute):
"""
gives the client access to the attribute_wrapper object in order to access all data it could potentially need.
the OPC ua read/write functions require the dimensionality and the type to be known
"""
dim_x = attribute.dim_x
dim_y = attribute.dim_y
ua_type = numpy_to_OPCua_dict[attribute.numpy_type] # convert the numpy type to a corresponding UA type
return dim_x, dim_y, ua_type
def setup_attribute(self, annotation, attribute):
"""
MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions
"""
# process the annotation
node = self._setup_annotation(annotation)
# get all the necessary data to set up the read/write functions from the attribute_wrapper
dim_x, dim_y, ua_type = self.setup_value_conversion(attribute)
# configure and return the read/write functions
prot_attr = ProtocolAttribute(node, dim_x, dim_y, ua_type)
try:
# NOTE: debug statement tries to get the qualified name, this may not always work. in that case forgo the name and just print the path
node_name = str(node.get_browse_name())[len("QualifiedName(2:"):]
self.streams.debug_stream("connected OPC ua node {} of type {} to attribute with dimensions: {} x {} ".format(str(node_name)[:len(node_name)-1], str(ua_type)[len("VariantType."):], dim_x, dim_y))
except:
pass
# return the read/write functions
return prot_attr.read_function, prot_attr.write_function
class ProtocolAttribute:
"""
This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code
"""
def __init__(self, node, dim_x, dim_y, ua_type):
self.node = node
self.dim_y = dim_y
self.dim_x = dim_x
self.ua_type = ua_type
def read_function(self):
"""
Read_R function
"""
value = numpy.array(self.node.get_value())
if self.dim_y != 0:
value = numpy.array(numpy.split(value, indices_or_sections=self.dim_y))
else:
value = numpy.array(value)
return value
def write_function(self, value):
"""
write_RW function
"""
# set_data_value(opcua.ua.uatypes.Variant(value = value.tolist(), varianttype=opcua.ua.VariantType.Int32))
if self.dim_y != 0:
v = numpy.concatenate(value)
self.node.set_data_value(opcua.ua.uatypes.Variant(value=v.tolist(), varianttype=self.ua_type))
elif self.dim_x != 1:
self.node.set_data_value(opcua.ua.uatypes.Variant(value=value.tolist(), varianttype=self.ua_type))
else:
self.node.set_data_value(opcua.ua.uatypes.Variant(value=value, varianttype=self.ua_type))
from src.comms_client import *
# <class 'numpy.bool_'>
class example_client(CommClient):
"""
this class provides an example implementation of a comms_client.
Durirng initialisation it creates a correctly shaped zero filled value. on read that value is returned and on write its modified.
"""
def start(self):
super().start()
def __init__(self, standby_func, fault_func, streams, try_interval=2):
"""
initialises the class and tries to connect to the client.
"""
super().__init__(standby_func, fault_func, streams, try_interval)
# Explicitly connect
if not self.connect():
# hardware or infra is down -- needs fixing first
fault_func()
return
def connect(self):
"""
this function provides a location for the code neccecary to connect to the client
"""
self.streams.debug_stream("the example client doesn't actually connect to anything silly")
self.connected = True # set connected to true
return True # if succesfull, return true. otherwise return false
def disconnect(self):
self.connected = False # always force a reconnect, regardless of a successful disconnect
self.streams.debug_stream("disconnected from the 'client' ")
def _setup_annotation(self, annotation):
"""
this function gives the client access to the comm client annotation data given to the attribute wrapper.
The annotation data can be used to provide whatever extra data is necessary in order to find/access the monitor/control point.
the annotation can be in whatever format may be required. it is up to the user to handle its content
example annotation may include:
- a file path and file line/location
- COM object path
"""
# as this is an example, just print the annotation
self.streams.debug_stream("annotation: {}".format(annotation))
def _setup_value_conversion(self, attribute):
"""
gives the client access to the attribute_wrapper object in order to access all
necessary data such as dimensionality and data type
"""
if attribute.dim_y > 1:
dims = (attribute.dim_y, attribute.dim_x)
else:
dims = (attribute.dim_x,)
dtype = attribute.numpy_type
return dims, dtype
def _setup_mapping(self, dims, dtype):
"""
takes all gathered data to configure and return the correct read and write functions
"""
value = numpy.zeros(dims, dtype)
def read_function():
self.streams.debug_stream("from read_function, reading {} array of type {}".format(dims, dtype))
return value
def write_function(write_value):
self.streams.debug_stream("from write_function, writing {} array of type {}".format(dims, dtype))
value = write_value
self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object")
return read_function, write_function
def setup_attribute(self, annotation=None, attribute=None):
"""
MANDATORY function: is used by the attribute wrapper to get read/write functions.
must return the read and write functions
"""
# process the comms_annotation
self._setup_annotation(annotation)
# get all the necessary data to set up the read/write functions from the attribute_wrapper
dims, dtype = self._setup_value_conversion(attribute)
# configure and return the read/write functions
read_function, write_function = self._setup_mapping(dims, dtype)
# return the read/write functions
return read_function, write_function
from tango.server import attribute
from tango import AttrWriteType
import numpy
from src.wrappers import only_when_on, fault_on_error
import logging
logger = logging.getLogger()
class attribute_wrapper(attribute):
"""
Wraps all the attributes in a wrapper class to manage most of the redundant code behind the scenes
"""
def __init__(self, comms_annotation=None, datatype=None, dims=(1,), access=AttrWriteType.READ, init_value=None, **kwargs):
"""
wraps around the tango Attribute class. Provides an easier interface for 1d or 2d arrays. Also provides a way to abstract
managing the communications interface.
"""
# ensure the type is a numpy array
if "numpy" not in str(datatype) and type(datatype) != str:
raise TypeError("Attribute needs to be a Tango-supported numpy or str type, but has type \"%s\"" % (datatype,))
self.comms_annotation = comms_annotation # store data that can be used by the comms interface. not used by the wrapper itself
self.numpy_type = datatype # tango changes our attribute to their representation (E.g numpy.int64 becomes "DevLong64")
self.init_value = init_value
max_dim_y = 0
# tango doesn't recognise numpy.str_, for consistencies sake we convert it here and hide this from the top level
# NOTE: discuss, idk if this is an important detail somewhere else
if datatype is numpy.str_:
datatype = str
# check if not scalar
if isinstance(dims, tuple):
# get first dimension
max_dim_x = dims[0]
# single dimension/spectrum requires the datatype to be wrapped in a tuple
datatype = (datatype,)
if len(dims) == 2:
# get second dimension
max_dim_y = dims[1]
# wrap the datatype tuple in another tuple for 2d arrays/images
datatype = (datatype,)
else:
# scalar, just set the single dimension
max_dim_x = 1
if access == AttrWriteType.READ_WRITE:
""" if the attribute is of READ_WRITE type, assign the RW and write function to it"""
@only_when_on
@fault_on_error
def read_RW(device):
# print("read_RW {}, {}x{}, {}, {}".format(me.name, me.dim_x, me.dim_y, me.attr_type, me.value))
"""
read_RW returns the value that was last written to the attribute
"""
try:
return device.value_dict[self]
except Exception as e:
raise Exception("Attribute read_RW function error, attempted to read value_dict with key: `%s`, are you sure this exists?", self) from e
@only_when_on
@fault_on_error
def write_RW(device, value):
"""
_write_RW writes a value to this attribute
"""
self.write_function(value)
device.value_dict[self] = value
self.fget = read_RW
self.fset = write_RW
else:
""" if the attribute is of READ type, assign the read function to it"""
@only_when_on
@fault_on_error
def read_R(device):
"""
_read_R reads the attribute value, stores it and returns it"
"""
device.value_dict[self] = self.read_function()
return device.value_dict[self]
self.fget = read_R
super().__init__(dtype=datatype, max_dim_y=max_dim_y, max_dim_x=max_dim_x, access=access, **kwargs)
return
def initial_value(self):
"""
returns a numpy array filled with zeroes fit to the size of the attribute. Or if init_value is not the default None, return that value
"""
if self.init_value is not None:
return self.init_value
if self.dim_y > 1:
dims = (self.dim_x, self.dim_y)
else:
dims = (self.dim_x,)
# x and y are swapped for numpy and Tango. to maintain tango conventions, x and y are swapped for numpy
if len(dims) == 2:
numpy_dims = tuple((dims[1], dims[0]))
else:
numpy_dims = dims
value = numpy.zeros(numpy_dims, dtype=self.numpy_type)
return value
def set_comm_client(self, client):
"""
takes a communications client as input arguments This client should be of a class containing a "get_mapping" function
and return a read and write function that the wrapper will use to get/set data.
"""
try:
self.read_function, self.write_function = client.setup_attribute(self.comms_annotation, self)
except Exception as e:
def pass_func(value=None):
pass
logger.error("setting comm_client failed. using pass function instead")
self.read_function = pass_func
self.write_function = pass_func
raise Exception("Exception while setting comm_client read/write functions. using pass function instead. %s") from e
from threading import Thread
import socket
import time
import numpy
import opcua
from opcua import Client
from tango import DevState
class CommClient(Thread):
"""
The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world
"""
def __init__(self, standby_func, fault_func, streams, try_interval=2):
"""
"""
self.standby_func = standby_func
self.fault_func = fault_func
self.try_interval = try_interval
self.streams = streams
self.stopping = False
self.connected = False
super().__init__(daemon=True)
def connect(self):
"""
Function used to connect to the client.
"""
self.connected = True
return True
def disconnect(self):
"""
Function used to connect to the client.
"""
self.connected = False
def run(self):
# Explicitly connect
if not self.connect():
# hardware or infra is down -- needs fixing first
self.fault_func()
return
self.standby_func()
self.stopping = False
while not self.stopping:
# keep trying to connect
if not self.connected:
if self.connect():
self.standby_func()
else:
# we retry only once, to catch exotic network issues. if the infra or hardware is down,
# our device cannot help, and must be reinitialised after the infra or hardware is fixed.
self.fault_func()
return
# keep checking if the connection is still alive
try:
while not self.stopping:
self.ping()
time.sleep(self.try_interval)
except Exception as e:
self.streams.error_stream("Fault condition in communication detected.", e)
# technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect.
self.disconnect()
# signal that we're disconnected
self.fault_func()
def ping(self):
pass
def stop(self):
"""
Stop connecting & disconnect. Can take a few seconds for the timeouts to hit.
"""
if not self.ident:
# have not yet been started, so nothing to do
return
self.stopping = True
self.join()
self.disconnect()
def setup_attribute(self, annotation, attribute):
"""
This function is responsible for providing the attribute_wrapper with a read/write function
How this is done is implementation specific.
The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client
as well as a reference to the attribute itself.
It should do this by first calling: _setup_annotation and setup_value_conversion to get all data necceacry to configure the read/write functions.
It should then return the read and write functions to the attribute.
MANDATORY:
annotation_outputs = _setup_annotation(annotation)
attribute_outputs = _setup_annotation(attribute)
(note: outputs are up to the user)
REQUIRED: provide read and write functions to return, there are no restrictions on how these should be provided,
except that the read function takes a single input value and the write function returns a single value
MANDATORY:
return read_function, write_function
Examples:
- File system: get_mapping returns functions that read/write a fixed
number of bytes at a fixed location in a file. (SEEK)
- OPC-UA: traverse the OPC-UA tree until the node is found.
Then return the read/write functions for that node which automatically
convert values between Python and OPC-UA.
"""
raise NotImplementedError("the setup_attribute must be implemented and provide return a valid read/write function for the attribute")
def _setup_annotation(self, annotation):
"""
This function is responsible for handling the annotation data provided by the attribute to configure the read/write function the client must provide.
This function should be called by setup_attribute
"""
raise NotImplementedError("the _setup_annotation must be implemented, content and outputs are up to the user")
def setup_value_conversion(self, attribute):
"""
this function is responsible for setting up the value conversion between the client and the attribute.
This function should be called by setup_attribute
"""
raise NotImplementedError("the setup_value_conversion must be implemented, content and outputs are up to the user")
# -*- coding: utf-8 -*-
#
# This file is part of the PCC project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" PCC Device Server for LOFAR2.0
"""
# PyTango imports
from tango.server import Device, command
from tango import DevState, DebugIt
# Additional import
from src.attribute_wrapper import *
__all__ = ["hardware_device"]
from src.wrappers import only_in_states
class hardware_device(Device):
"""
**Properties:**
States are as follows:
INIT = Device is initialising.
STANDBY = Device is initialised, but pends external configuration and an explicit turning on,
ON = Device is fully configured, functional, controls the hardware, and is possibly actively running,
FAULT = Device detected an unrecoverable error, and is thus malfunctional,
OFF = Device is turned off, drops connection to the hardware,
The following state transitions are implemented:
boot -> OFF: Triggered by tango. Device will be instantiated,
OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices),
INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user,
STANDBY -> ON: Triggered by user. Device reports to be functional,
* -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost,
* -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes,
* -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command,
FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error,
The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()).
"""
@classmethod
def attr_list(cls):
""" Return a list of all the attribute_wrapper members of this class. """
return [v for k, v in cls.__dict__.items() if type(v) == attribute_wrapper]
def setup_value_dict(self):
""" set the initial value for all the attribute wrapper objects"""
self.value_dict = {i: i.initial_value() for i in self.attr_list()}
def init_device(self):
""" Instantiates the device in the OFF state. """
# NOTE: Will delete_device first, if necessary
Device.init_device(self)
self.set_state(DevState.OFF)
# --------
# Commands
# --------
@command()
@only_in_states([DevState.FAULT, DevState.OFF])
@DebugIt()
def Initialise(self):
"""
Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
:return:None
"""
self.set_state(DevState.INIT)
self.setup_value_dict()
self.initialise()
@only_in_states([DevState.INIT])
def Standby(self):
"""
Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
:return:None
"""
self.standby()
self.set_state(DevState.STANDBY)
@command()
@only_in_states([DevState.STANDBY])
@DebugIt()
def On(self):
"""
Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
:return:None
"""
self.on()
self.set_state(DevState.ON)
@command()
@DebugIt()
def Off(self):
"""
Command to ask for shutdown of this device.
:return:None
"""
if self.get_state() == DevState.OFF:
# Already off. Don't complain.
return
# Turn off
self.set_state(DevState.OFF)
self.off()
# Turn off again, in case of race conditions through reconnecting
self.set_state(DevState.OFF)
@command()
@only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY])
@DebugIt()
def Fault(self):
"""
FAULT state is used to indicate our connection with the OPC-UA server is down.
This device will try to reconnect once, and transition to the ON state on success.
If reconnecting fails, the user needs to call Initialise() to retry to restart this device.
:return:None
"""
self.fault()
self.set_state(DevState.FAULT)
# functions that can be overloaded
def fault(self):
pass
def off(self):
pass
def on(self):
pass
def standby(self):
pass
def initialise(self):
pass
def always_executed_hook(self):
"""Method always executed before any TANGO command is executed."""
pass
def delete_device(self):
"""Hook to delete resources allocated in init_device.
This method allows for any memory or other resources allocated in the
init_device method to be released. This method is called by the device
destructor and by the device Init command (a Tango built-in).
"""
self.debug_stream("Shutting down...")
self.Off()
self.debug_stream("Shut down. Good bye.")
from tango import DevState, Except
from functools import wraps
import traceback
__all__ = ["only_in_states", "only_when_on", "fault_on_error"]
def only_in_states(allowed_states):
"""
Wrapper to call and return the wrapped function if the device is
in one of the given states. Otherwise a PyTango exception is thrown.
"""
def wrapper(func):
@wraps(func)
def state_check_wrapper(self, *args, **kwargs):
if self.get_state() in allowed_states:
return func(self, *args, **kwargs)
self.warn_stream("Illegal command: Function %s can only be called in states %s. Current state: %s" % (func.__name__, allowed_states, self.get_state()))
Except.throw_exception("IllegalCommand", "Function can only be called in states %s. Current state: %s" % (allowed_states, self.get_state()), func.__name__)
return state_check_wrapper
return wrapper
def only_when_on(func):
"""
Wrapper to call and return the wrapped function if the device is
in the ON state. Otherwise None is returned and nothing
will be called.
"""
@wraps(func)
def when_on_wrapper(self, *args, **kwargs):
if self.get_state() == DevState.ON:
return func(self, *args, **kwargs)
return None
return when_on_wrapper
def fault_on_error(func):
"""
Wrapper to catch exceptions. Sets the device in a FAULT state if any occurs.
"""
@wraps(func)
def error_wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
self.error_stream("Function failed. Trace: %s", traceback.format_exc())
self.Fault()
return None
return error_wrapper
# -*- coding: utf-8 -*-
#
# This file is part of the PCC project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" PCC Device Server for LOFAR2.0
"""
# PyTango imports
from tango.server import run
from tango.server import device_property
from tango import DevState
# Additional import
from clients.test_client import example_client
from src.attribute_wrapper import *
from src.hardware_device import *
__all__ = ["test_device", "main"]
class test_device(hardware_device):
# -----------------
# Device Properties
# -----------------
OPC_Server_Name = device_property(
dtype='DevString',
)
OPC_Server_Port = device_property(
dtype='DevULong',
)
OPC_Time_Out = device_property(
dtype='DevDouble',
)
# ----------
# Attributes
# ----------
bool_scalar_R = attribute_wrapper(comms_annotation="numpy.bool_ type read scalar", datatype=numpy.bool_)
bool_scalar_RW = attribute_wrapper(comms_annotation="numpy.bool_ type read/write scalar", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
int64_spectrum_R = attribute_wrapper(comms_annotation="numpy.int64 type read spectrum (len = 8)", datatype=numpy.int64, dims=(8,))
str_spectrum_RW = attribute_wrapper(comms_annotation="numpy.str type read/write spectrum (len = 8)", datatype=numpy.str_, dims=(8,), access=AttrWriteType.READ_WRITE)
double_image_R = attribute_wrapper(comms_annotation="numpy.double type read image (dims = 2x8)", datatype=numpy.double, dims=(2, 8))
double_image_RW = attribute_wrapper(comms_annotation="numpy.double type read/write image (dims = 8x2)", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE)
int32_scalar_R = attribute_wrapper(comms_annotation="numpy.int32 type read scalar", datatype=numpy.int32)
uint16_spectrum_RW = attribute_wrapper(comms_annotation="numpy.uint16 type read/write spectrum (len = 8)", datatype=numpy.uint16, dims=(8,), access=AttrWriteType.READ_WRITE)
float32_image_R = attribute_wrapper(comms_annotation="numpy.float32 type read image (dims = 8x2)", datatype=numpy.float32, dims=(8, 2))
uint8_image_RW = attribute_wrapper(comms_annotation="numpy.uint8 type read/write image (dims = 2x8)", datatype=numpy.uint8, dims=(2, 8), access=AttrWriteType.READ_WRITE)
# --------
# overloaded functions
# --------
def initialise(self):
""" user code here. is called when the sate is set to INIT """
"""Initialises the attributes and properties of the PCC."""
self.set_state(DevState.INIT)
#set up the OPC ua client
self.example_client = example_client(self.Standby, self.Fault, self)
# map an access helper class
for i in self.attr_list():
i.set_comm_client(self.example_client)
self.example_client.start()
# ----------
# Run server
# ----------
def main(args=None, **kwargs):
"""Main function of the example module."""
return run((test_device,), args=args, **kwargs)
if __name__ == '__main__':
main()
...@@ -114,6 +114,8 @@ pull: ## pull the images from the Docker hub ...@@ -114,6 +114,8 @@ pull: ## pull the images from the Docker hub
$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) pull $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) pull
build: ## rebuild images build: ## rebuild images
# docker-compose does not support build dependencies, so manage those here
$(DOCKER_COMPOSE_ARGS) docker-compose -f lofar-device-base.yml build
$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build
up: minimal ## start the base TANGO system and prepare all services up: minimal ## start the base TANGO system and prepare all services
...@@ -144,6 +146,9 @@ attach: ## attach a service to an existing Tango network ...@@ -144,6 +146,9 @@ attach: ## attach a service to an existing Tango network
status: ## show the container status status: ## show the container status
$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) ps $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) ps
images: ## show the container images
$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) images
clean: down ## clear all TANGO database entries clean: down ## clear all TANGO database entries
docker volume rm $(BASEDIR)_tangodb docker volume rm $(BASEDIR)_tangodb
......
#
# Docker compose file that launches an interactive iTango session.
#
# Connect to the interactive session with 'docker attach itango'.
# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q>
#
# Defines:
# - itango: iTango interactive session
#
# Requires:
# - lofar-device-base.yml
#
version: '2'
services:
device-pcc:
image: lofar-device-base
container_name: ${CONTAINER_NAME_PREFIX}device-pcc
network_mode: ${NETWORK_MODE}
volumes:
- ${TANGO_LOFAR_CONTAINER_MOUNT}
environment:
- TANGO_HOST=${TANGO_HOST}
entrypoint:
- /usr/local/bin/wait-for-it.sh
- ${TANGO_HOST}
- --timeout=30
- --strict
- --
- python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/PCC/PCC LTS -v
restart: on-failure
#
# Docker compose file that launches an interactive iTango session.
#
# Connect to the interactive session with 'docker attach itango'.
# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q>
#
# Defines:
# - itango: iTango interactive session
#
# Requires:
# - lofar-device-base.yml
#
version: '2'
services:
device-sdp:
image: lofar-device-base
container_name: ${CONTAINER_NAME_PREFIX}device-sdp
network_mode: ${NETWORK_MODE}
volumes:
- ${TANGO_LOFAR_CONTAINER_MOUNT}
environment:
- TANGO_HOST=${TANGO_HOST}
entrypoint:
- /usr/local/bin/wait-for-it.sh
- ${TANGO_HOST}
- --timeout=30
- --strict
- --
- python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/SDP/SDP LTS -v
restart: on-failure
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment