.. _new_oracle:

Defining Your Own Oracle
========================

In this tutorial, we will discuss how you can easily add a new oracle
into our framework.

After DoppelTest executes a scenario, each ADS instance will produce its 
own record file. In the ADS we currently focused on
(Baidu Apollo), you can either use `CyberRT Developer Tools 
<https://github.com/ApolloAuto/apollo/blob/master/docs/04_CyberRT/CyberRT_Developer_Tools.md>`_
to replay the record file and visualize it in Dreamview, or use a Python library
`cyber_record <https://github.com/daohu527/cyber_record>`_ to parse
and analyze it.

.. code-block:: bash

    root@in-dev-docker:/apollo# cyber_recorder -h
    usage: cyber_recorder -h [options]
    Unknown command: -h
    usage: cyber_recorder <command> [<args>]
    The cyber_recorder commands are:
            info    Show information of an exist record.
            play    Play an exist record.
            record  Record same topic.
            split   Split an exist record.
            recover Recover an exist record.

The record file consists messages published by each of the ADS modules
at run time. For example, messages from topic ``/apollo/planning`` corresponds
to output of the Planning module, messages from topic
``/apollo/localization/pose`` corresponds to output of the Localization module,
etc.

Consider yourself trying to implement an oracle checking if the ADS is speeding at
any point, we will call it ``SpeedingOracle``.

Step 1: What does the oracle need?
----------------------------------

The first step is to think about what the oracle does and what information does the
oracle need. To know whether the ADS is speeding, we need 2 pieces of information:
1) the ADS's current speed, 2) the speed limit of the lane which the ADS is currently
traveling in. After understanding the need of the oracle, we need to figure out what
messages from a record file should this oracle be looking at. 

Luckily, **localization messages** alone are sufficient because each of them includes 
the position and velocity of the ADS.

Step 2: Laying out the structure
--------------------------------

We have formally defined an interface for implementing a new oracle,
named :ref:`internals/framework_oracles:framework.oracles.OracleInterface`. Each new oracle should
implement 3 abstract functions defined in this interface.

.. code-block:: Python

    class SpeedingOracle(OracleInterface):
        def get_interested_topics(self):
            pass
        
        def get_results(self):
            pass
        
        def on_new_message(self, topic, message, t):
            pass

Step 3: What Topics?
--------------------

As discussed earlier, the speeding oracle only needs to know
where the ADS is and what is its speed, therefore this oracle
is only interested in messages from the localization topic.

.. code-block:: Python

    def get_interested_topics(self):
        return ['/apollo/localization/pose']


Step 4: What to do?
------------------------------------

``on_new_message`` will get called each time a new message from
the interested topic is received. The oracle should compute
the ADS's speed based on the message and check if it violated
the speed limit.

.. code-block:: Python

    def on_new_message(self, topic, message, t):
        current_speed = calculate_speed(message)
        current_position = get_position(message)

        # check which lane the ADS is currently in
        current_lane = get_current_lane(current_position)

        speed_limit = current_lane.speed_limit

        if current_speed > speed_limit:
            # a violation occurred
            pass

.. note::  Here we used some fake functions such as ``calculate_speed``,
  ``get_position``, and ``get_current_lane`` to illustrate the 
  logic behind this oracle. The complete optimized implementation
  is provided at ``framework.oracles.impl.SpeedingOracle.py``.

Step 5: What to return?
-----------------------

Now we have the first 2 parts completed, all that is left is
to produce the result. In our current implementation, the oracle
should produce a tuple describing what is the violation. Therefore
we can modify ``on_new_message`` to store that violation, so we can
return it later.

.. code-block:: Python

    def on_new_message(self, topic, message, t):
        # ......
        if current_speed > speed_limit:
            self.violation = (
                'SpeedViolation',
                f'{current_speed} violated speed limit {speed_limit}'
            )

.. code-block:: Python

    def get_result(self):
        return self.violation

.. note:: ``self.violation`` may be undefined if a violation
    never occurred. Try to implement this oracle and think
    about what to do about this!

Recap: 3 functions to define an oracle
--------------------------------------

The interface design provides an abstracted view of an obstacle: which record messages
should the oracle look at? what should the oracle do with the messages? and at the end,
what are the violations detected?