Control system monitor

There are two ways to get the antenna parameters: by using the HTTP REST API, as explained in chapter Application Programming Interface, and by using a Redis client, as discussed in this chapter. The Redis client allows you to get the parameters as quickly as possible, while the HTTP API has a lower time resolution but allows you to look at the history of the attribute, asking for the last N values, for all values after datetime x or between datetime x and datetime y.

Note

Suricate is composed by a monitor, a database filler and a server. The monitor collects all attributes information from the control system and stores them on a Redis database. The database filler reads the attributes from Redis and stores them on a persistent database. When the user performs a request by means of the HTTP REST API, the server executes a query to the persistent database and returns the response. The user, as explained in this chapter, can also read the attributes information by reading directly from the Redis in memory database.

If you want to get the antenna parameters from the Redis database, you need a Redis client installed on your machine.

Install a Redis client

In this section we will briefly see how to install one of them for Python and C, but if you do not use these languages, visit the official Redis webpage to get the right client for your operating system and programming language.

Python

The most used Python client is called redis-py. To install it by pip, simply:

$ pip install redis

If you do not have pip, then download the source files from the redis-py webpage, extract them, move to the source file directory and execute:

$ python setup.py install

C Programming Language

The official C client is available on the Hiredis GitHub page. Clone it on your machine:

$ git clone https://github.com/redis/hiredis.git

The installation steps depend of your operating system and compiler. For instance, on Linux CentOS:

$ cd hiredis/
$ make
$ sudo make install

Use your client to get the antenna parameters

To get the antenna parameters you have to connect your client to the Redis server. Server IP and port are 192.168.200.203 and 6379. That is not enough, because you need to understand how your client works. These instructions should be provided by your Redis client documentation. Let’s see two examples, using Python and C. Please read the How to use the Python client section also if you want to use the C programming language, because the Python examples show you all information (the How to use the C client section is just a short summary).

How to use the Python client

Let’s see how to get the rawAzimuth from a Python shell:

>>> from redis import StrictRedis # Import the redis client
>>> r = StrictRedis(host='192.168.200.203', port=6379)  # Connect to server
>>> r.hgetall('ANTENNA/Boss/rawAzimuth')  # Ask for the rawAzimuth parameter
{
  'units': 'radians', 'timestamp': '2019-12-18 12:52:04.206445',
  'description': 'raw azimuth (encoder value), without any correction',
  'value': '0.602314332058', 'error': '', 'timer': '2.0'
}

The format is the same as you see in previous chapter: SYSTEM/Component/name. The result of the request contains the attribute value, its units, description, timer and timestamp. In case of error there is an error message, and the value is an empty string:

>>> r.hgetall('ANTENNA/Boss/rawAzimuth')
{
  'units': 'radians', 'timestamp': '2019-12-18 12:55:13.197819',
  'description': 'raw azimuth (encoder value), without any correction',
  'value': '', 'error': 'component ANTENNA/Boss not available',
  'timer': '2.0'
}

Here is how to get a particular field:

>>> result = r.hgetall('ANTENNA/Boss/rawAzimuth')
>>> result['units']
'radians'
>>> result['value']
'0.599527371772'
>>> result['description']
'raw azimuth (encoder value), without any correction'

Another way is to use hget(), giving the field name as a second argument:

>>> r.hget('ANTENNA/Boss/rawAzimuth', 'value')
'0.598923921358'

Note

The Python hgetall() and hget() methods execute the Redis calls HGETALL and HGET.

To know the status of the components (available or unavailable) use the key components:

>>> r.hgetall('components')
{
  'MANAGEMENT/Gavino': 'available', 'WEATHERSTATION/WeatherStation': 'available',
  'RECEIVERS/SRTKBandMFReceiver': 'available', 'RECEIVERS/SRT7GHzReceiver': 'available',
  'ANTENNA/Boss': 'available', 'RECEIVERS/Boss': 'available'
}

Some attributes are sequences, but Suricate saves them as strings. For instance, have a look at the current LO value:

>>> lo = r.hget('RECEIVERS/Boss/LO', 'value')
>>> lo
'(5850.0, 5850.0)'

To get a tuple you do not need to parse the string. Just use literal_eval from the standard library ast module:

>>> from ast import literal_eval
>>> literal_eval(lo)
(5850.0, 5850.0)

How to use the C client

To understand how to get the rawAzimuth parameter look at the most important lines of example.c source file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    reply = redisCommand(c,"HGETALL ANTENNA/Boss/rawAzimuth");
    int i;
    printf("\nHGETALL ANTENNA/Boss/rawAzimuth\n");
    for(i=0; i<reply->elements; i++) {
        if(i%2 == 0) {
            printf("* %s: ", reply->element[i]->str);
        }
        else {
            printf("%s\n", reply->element[i]->str);
        }
    }   
    freeReplyObject(reply);

    reply = redisCommand(c,"HGET ANTENNA/Boss/rawAzimuth value");
    printf("\nHGET ANTENNA/Boss/rawAzimuth value: %s\n", reply->str);
    freeReplyObject(reply);

If you compile example.c and execute the program, then you get the following output:

$ gcc example.c -I /usr/local/include/hiredis/ -lhiredis  # Linux CentOS
$ ./a.out

HGETALL ANTENNA/Boss/rawAzimuth
* description: raw azimuth (encoder value), without any correction
* error:
* units: radians
* timer: 2.0
* timestamp: 2019-12-18 16:19:50.042722
* value: 0.469729642021

HGET ANTENNA/Boss/rawAzimuth value: 0.469729642021

For more information about the Redis C client please scroll down the C client website page.

Public and subscribe

We saw how to ask for antenna parameters in a request-response manner. Using that pattern you have to take care of the result, because you can get the same value for different requests. For intance, if you look at the SRT configuration file you see that rawAzimuth has a sampling time of 2 seconds. That is why if you ask for the parameter to fast, for instance, once per second, you get the same result for different requests:

>>> import time
>>> import redis
>>> r = redis.StrictRedis(host='192.168.200.203', port=6379)
...     print(r.hgetall('ANTENNA/Boss/rawAzimuth')['timestamp'])
...     time.sleep(0.9)  # 900ms
...
2019-12-20 12:11:48.443357
2019-12-20 12:11:49.842924
2019-12-20 12:11:49.842924
2019-12-20 12:11:50.446206
2019-12-20 12:11:50.446206

As you can see by looking at these timestamps, the second result is the same as the third, and the forth is the same as the fifth. It is not a big issue, because you can discard the result in case its timestamp is the same as the previous one. But there is another way: the public-subscribe pattern. In that case you subscribe to a channel and wait for new data. Let’see how to do it by examples, using the Python client.

As a first step we create a pubsub object and subscribe it to the ANTENNA/Boss/rawAzimuth channel:

>>> import redis
>>> r = redis.StrictRedis(host='192.168.200.203', port=6379)
>>> pubsub = r.pubsub()
>>> pubsub.subscribe('ANTENNA/Boss/rawAzimuth')

Now we are ready to get the messages. The first one is a kind of header that tell us which channel we are listening from. Its type is subscribe:

>>> pubsub.get_message()
{
  u'pattern': None, u'type': 'subscribe',
  u'channel': 'ANTENNA/Boss/rawAzimuth', u'data': 1L
}

From now on the type of the messages is message, and that means they contain the antenna parameter:

>>> pubsub.get_message()
{
  u'pattern': None, u'type': 'message', u'channel': 'ANTENNA/Boss/rawAzimuth',
  u'data': '{
    "description": "raw azimuth (encoder value), without any correction",
    "error": "", "units": "radians", "timestamp": "2019-12-20 14:43:16.262544",
    "value": "3.97375753112", "timer": "2.0"
  }'
}

The pubsub has a method listen() that waits for new messages. In fact, as you can see in the following example, we do not get the same message on different responses, as appened in the request-response pattern:

... for item in pubsub.listen():
...     if item['type'] == 'message':
...         data = json.loads(item['data'])
...         timestamp = data.get('timestamp')
...         value = data.get('value')
...         print(timestamp, value)
...
(u'2019-12-20 15:04:22.148343', u'4.07524413623')
(u'2019-12-20 15:04:24.043550', u'4.07527853477')
(u'2019-12-20 15:04:26.465494', u'4.07530617372')
(u'2019-12-20 15:04:28.843325', u'4.07533925004')
(u'2019-12-20 15:04:30.956359', u'4.07536964412')

Note

The antenna parameter is stored as a json string in the data field of the item. I used json.loads() in order to convert the json string to a Python dictionary.

You can subscribe to more than one channel at the same time:

... pubsub = r.pubsub()
... pubsub.subscribe(
...       'ANTENNA/Boss/rawAzimuth',
...       'ANTENNA/Boss/rawElevation'
... )
... for item in pubsub.listen():
...     if item['type'] == 'message':
...         channel = item['channel']
...         data = json.loads(item['data'])
...         value = data.get('value')
...         print(channel, value)
...
('ANTENNA/Boss/rawAzimuth', u'0.758804232371')
('ANTENNA/Boss/rawElevation', u'0.659474554184')
('ANTENNA/Boss/rawAzimuth', u'0.758810651617')
('ANTENNA/Boss/rawElevation', u'0.659490653912')
('ANTENNA/Boss/rawElevation', u'0.659506753743')
                      ...

You can also use the glob syntax. It means you can use a * to listen from more than one channel. For insance, in the following case we are listening to all channels starting with ANTENNA/Boss:

... pubsub = r.pubsub()
... pubsub.psubscribe('ANTENNA/Boss/*')
... for item in pubsub.listen():
...     if item['type'] == 'pmessage':
...         channel = item['channel']
...         data = json.loads(item['data'])
...         value = data.get('value')
...         print(channel, value)
...
('ANTENNA/Boss/observedDeclination', u'0.952583014116')
('ANTENNA/Boss/observedAzimuth', u'0.800302875968')
('ANTENNA/Boss/rawElevation', u'0.668070294866')
('ANTENNA/Boss/observedElevation', u'0.666206037338')
('ANTENNA/Boss/rawAzimuth', u'0.762179742186')
('ANTENNA/Boss/observedRightAscension', u'0.929348150783')
('ANTENNA/Boss/observedGalLongitude', u'2.53064537516')
('ANTENNA/Boss/observedGalLatitude', u'-0.0212982297497')
('ANTENNA/Boss/status', u'MNG_OK')
('ANTENNA/Boss/observedAzimuth', u'0.800308445826')
                        ...

Note

In the last example (glob syntax) we subcribed to the channels using pubsub.psubscribe() and not pubsub.subscribe(), and we waited for the type pmessage, not message.