A Workflow of Auto Executing Services on SUSI.AI Smart Speaker

As we plan to create a headless client on RaspberryPi, the requirement was that the SUSI.AI programs should run automatically. To do so, we had to figure out a way to boot up various scripts on startup.

We had the following options to execute the scripts on startup:

  1. Editing Rc.local file
  2. Systemd Rules
  3. Crontab

We decided to proceed with Systemd Rules because using Rc.local and Crontab requires modifying the default system files which in case of any error would make the os functionalities to crash very soon.

We then created the SystemD rules for the following services:

2. python-flask.service
3. susi-server.service
4. update-daemon.service
5. susi-linux.service

Now I’ll demonstrate the working and the functionality of each service being implemented.

1. Factory-Daemon Service

This service initiates the factory daemon with the raspberry Pi startup and then keeps it running continuously looking for any input from the GPiO port.

Description=SUSI Linux Factory Daemon

ExecStart=/usr/bin/python3 /home/pi/SUSI.AI/susi_linux/factory_reset/factory_reset.py


2. Python-Flask Service

This service starts a python Server to allow handshake between mobile apps and the Smart Speaker which will allow the user to configure SUSI Smart Speaker accordingly.

Description=Python Server for SUSI Linux

ExecStart=/usr/bin/python3  /home/pi/SUSI.AI/susi_linux/access_point/server/server.py


3.SUSI-Server Service

This service starts the Local SUSI Server as soon as the Raspberry Pi starts up which in turn allows the SUSI Linux programs to fetch responses of queries very quickly.

Description=Starting SUSI Server for SUSI Linux



4. Update-Daemon Service

This Service creates a Daemon which starts with the Raspberry Pi and fetches the latest updates from the repository from the upstream branch.

Description=Update Check- SUSI Linux



5. SUSI-Linux Service

This Service finally runs the main SUSI Linux software after everything has started.

Description=Starting SUSI Linux

ExecStart=/usr/bin/python3 -m main


This blog gives a brief workflow of auto-executing services on SUSI Smart Speaker.


Connecting the Smart Speaker with Mobile Clients

The beauty of SUSI Smart Speaker lies in it being customizable according to the user’s needs. And we allow the user to customize it by providing an interface through the mobile clients. To do so, we create a local server on the Raspberry Pi itself. The Raspberry Pi is started in an Access Point mode and the mobile clients hit the endpoints in a specific order and then the configuration is sent to the server and stored according to the user.


The following API’s are required to be executed by the mobile clients

1> /speaker_config

2> /wifi_credentials

3> /auth

4> /config


The following is the order of API execution

1. /speaker_config

This endpoint only takes the room name as a parameter. And then send send to the server to store the location of the device under the user’s account

def speaker_config():
   room_name = request.args.get(‘room_name’)
   config = json_config.connect(config_json_folder)
   config[‘room_name’] = rogom_name


2. /wifi_credentials

This endpoint takes the wifi ssid and wifi password as the parameters and then stores it in the raspberry Pi wifi config file.


def wifi_config():
   wifi_ssid = request.args.get(‘wifissid’)
   wifi_password = request.args.get(‘wifipassd’)
   subprocess.call([‘sudo’, ‘bash’, wifi_search_folder + ‘/wifi_search.sh’, wifi_ssid, wifi_password])
   display_message = {“wifi”:“configured”, “wifi_ssid”:wifi_ssid, “wifi_password”: wifi_password}
   resp = jsonify(display_message)
   resp.status_code = 200
   return resp


Now the script wifi_search is called which stores the wifi credentials in the wifi_config file using the following command


cat >> /etc/wpa_supplicant/wpa_supplicant.conf <<EOF


3. /auth

This endpoint takes the SUSI’s login credentials as parameters, i.e. the registered email id and the corresponding password.


def login():
   auth = request.args.get(‘auth’)
   email = request.args.get(’email’)
   password = request.args.get(‘password’)
   subprocess.call([‘sudo’, ‘bash’, access_point_folder + ‘/login.sh’, auth, email, password])
   display_message = {“authentication”:“successful”, “auth”: auth, “email”: email, “password”: password}
   resp = jsonify(display_message)
   resp.status_code = 200
   return resp


4. /config

Finally, this endpoint takes the stt, tts, hotword detection engine and wake button as the parameters and configures the speaker accordingly.


def config():
   stt = request.args.get(‘stt’)
   tts = request.args.get(‘tts’)
   hotword = request.args.get(‘hotword’)
   wake = request.args.get(‘wake’)
   subprocess.Popen([‘sudo’, ‘bash’, access_point_folder + ‘/config.sh ‘, stt, tts, hotword, wake])
   display_message = {“configuration”:“successful”, “stt”: stt, “tts”: tts, “hotword”: hotword, “wake”:wake}
   resp = jsonify(display_message)
   resp.status_code = 200
   return resp


Now, this function runs a script called config.sh which in turn runs a script called rwap.sh to convert the Raspberry Pi to normal mode and then finally start SUSI on startup.



if [ $EUID -ne 0 ]
then echo “Must be root”

cd /etc/hostapd/
sed -i ‘1,14d’ hostapd.conf

cd /etc/
sed -i ‘57,60d’ dhcpcd.conf

cd /etc/network/
sed -i ‘9,17d’ interfaces

echo “Please reboot”
sudo reboot


After successfully hitting all the endpoint from the client, your Smart Speaker would restart and would see the following screen on your client.



Additional Resources


fossasia, susi, susi.ai, gsoc, gsoc’18, handshake

Modifying Finite State Architecture On SUSI Linux to Process Multiple Queries

During the initial stages of SUSI Linux: As the code base grew, it was getting very difficult to manage code, so we opted to implement a Finite State Architecture in our repo. But, as there were new features implemented in the Repo, we realized that we couldn’t process more than one query at a time which restricted a lot of features. eg. The smart speaker was converted to a simple Bluetooth speaker since no response regarding playing/pausing were accepted.

To solve this issue, we made a slight modification in the architecture.

Brief About SUSI States

SUSI is working as a Finite State Machine and is present in 3 states namely IDLE state, Recognising state and Busy state. The State Machine executes in the following order.

  1. IDLE State:
    When the SUSI state Machine is in this State, SUSI is searching for the hotword “SUSI”, waiting to trigger the complete Machine.
  2. Recognizing State

In this State , the State Machine has started the STT client. After recognition, SUSI sends the query to the Server awaiting the response

  1. Busy State

After the response has been received, the TTS client is triggered and the answer is given out by SUSI

Adding a Second Hotword Recognition Class

Now, to allow SUSI to process the second query, The State machine must be triggered while SUSI is giving out the first response and to trigger the State Machine, we must have hotword recognition while SUSI is speaking the answer to the previous query. Hence, a hotword recognition engine is now initiated every time the State Machine enters the busy state.

We will be using Snowboy as Hotword Detection Engine.


import os
TOP_DIR = os.path.dirname(os.path.abspath(__file__))
RESOURCE_FILE = os.path.join(TOP_DIR, “susi.pmdl”)
class StopDetector():
   “””This implements the Stop Detection with Snowboy Hotword Detection Engine.”””
    def __init__(self, detection) -> None:
       self.detector = snowboydecoder.HotwordDetector(
           RESOURCE_FILE, sensitivity=0.6)
       self.detection = detection
    def run(self):
       “”” Implementation of run abstract method in HotwordDetector. This method is called when thread is
started for the first time. We start the Snowboy detection and declare detected callback as
       detection_callback method declared ina parent class.


Now, this class takes the Callback function as a parameter which is passed when the transition to busy state takes place from the recognition state.


Modifying the State Machine Architecture

After declaring a second hotword recognition engine , we must modify how the transitions take place between the States of the SUSI State Machine.

Hence the callback that will be triggered is passed from the busy state.


def detection(self):
       “””This callback is fired when a Hotword Detector detects a hotword.
       :return: None
       if hasattr(self, ‘video_process’):
           subprocess.Popen([‘play’, str(self.components.config[‘detection_bell_sound’])])
       if hasattr(self, ‘audio_process’):
           subprocess.Popen([‘play’, str(self.components.config[‘detection_bell_sound’])])


As soon as the hotword is detected ,the state machine makes transitions to the Recognition State while pausing the current Music and resumes the Music after the second query has been completed.


This is how SUSI processes multiple queries simultaneously while still maintaining finite state archi.


Additional Resources


gsoc, gsoc’18, finite_state_machine, susi_linux, multiple_query, susi.ai, susi


Creating a Factory Reset Daemon for SUSI.AI Smart Speaker

In our constantly evolving SUSI.AI Smart Speaker project, we require regular updates for our devices. And imagine a scenario that during a crucial update, there is a crash or an internet disconnection which stops the SUSI.AI Linux program from booting up. We’ll require a reset method for that. So, we have added a button in SUSI smart speaker that works as a factory reset switch. This daemon was accomplished by using python scripting, bash scripting, and Raspbian’s systemd rules.

Approach followed

We have created a python script that detects the button presses on GPIO port 17. The script is run as soon as the Raspberry Pi is booted using the systemd rules and checks for the device inputs. And if the button press is for more than 7 seconds, the factory_reset.sh script is run which deletes all the contents of the repo and clones it again.


#! /bin/bash
# To be executed using a physical button

SCRIPT_PATH=$(realpath $0)

cd $DIR_PATH/../..
mv susi_linux/ susi_temp
git clone https://github.com/fossasia/susi_linux #while testing change to personal repo
cd susi_linux

rm -rf ../susi_temp



Detecting the Button Press

We have Used the library RPi.GPIO to detect button click on raspberry Pi.

while True:
       if GPIO.input(17) == 1:
       elif GPIO.input(17) == 0 :
           start = time.time()
          while GPIO.input(17) == 0 :
          end = time.time()
           total = end – start
           if total >= 7 :
          else :
               mixer = alsaaudio.Mixer()
               value = mixer.getvolume()[0]
              if value != 0:


If the button press is greater than 7 seconds, factory reset process will start and if the press is less than 7 seconds, the button will function as mute button


Auto Booting The program


For the script to autorun everytime the raspberry pi started. We create systemd file which will allow the program to start as soon as the device has started


Description=SUSI Linux Factory Daemon

ExecStart=/usr/bin/python3 /home/pi/SUSI.AI/susi_linux/factory_reset/factory_reset.py



This runs the factory reset script to boot up as soon as the Raspberry Pi starts



susi, factory_daemon, factory_reset, gsoc, gsoc’18,susi_linux , fossasia

Using a Flask Server to Connect to SUSI smart speaker

A smart speaker becomes significantly smarter when it is connected to a Smart-Phone.

So, we added a way to connect the Smart-Phone to the Smart Speaker and initiate the first way towards a Smart Home.

Use a simple HTTP connection protocol and deploy a light-weight server on the Raspberry Pi to allow connection from a mobile phone.

Step 1: Setting Up the server

Use flask to deploy a light-weight server on the raspberry pi. We’ll install flask using raspbian repos.


1>Install Flask by using the following command

sudo apt-get install python3-flask


2> Setting up the boilerplate code.

Open the terminal and type the following commands


mkdir server_app
cd server_app

touch app.py



Add the following code to your app.py file. This create a server at localhost:5000


from flask import Flask

app = Flask(__name__)

def index():
   return ‘Hello world’

if __name__ == ‘__main__’:
   app.run(debug=False, host=‘’)  #This will allow the server to be accessible on all devices


Step 2: Adding Endpoints

Now , add endpoints which will trigger the scripts during initialisation of the raspberry Pi. This will trigger the respective endpoints

def login(auth, email, passwd):
os.system(‘sudo ./login.sh {} {} {}’.format(auth, email,passwd)) #nosec #pylint-disable type: ignore
return ‘Authenticated’ # [email protected](‘/wifi_credentials/<wifissid>/<wifipassd>’)
def wifi_config(wifissid,wifipassd):
wifi_ssid = wifissid
wifi_password = wifipassd
os.system(‘sudo ./home/pi/SUSI.AI/susi_linux/access_point/wifi_search.sh {} {}’.format(wifi_ssid,wifi_password))  #nosec #pylint-disable type: ignore
return ‘Wifi Configured’ # pylint-enable


Step 3: Connecting to the endpoints

Now, try and hit the API endpoints to get the response.

eg.As shown in the above example, you will be getting a single line response and will execute a bash script behind the scenes

Now you can access the other endpoints and configure the clients with the SUSI Smart Speaker




fossasia,GSoC,Python, Flask , raspberryPi, SUSI,smart-speaker,FOSSASIA

Create a Wireless Access Point Using a Raspberry Pi to Connect with SUSI Smart Speaker

To use the pi as a wifi bridge, a local network or just as a wifi range extender.We at FOSSASIA are using it as a network to connect between our SUSI.AI smart speaker and the Android and IOS devices. Or maybe because you can !! :’)


  1. Raspberry Pi Model 3(since we will be using an internal wifi)
  2. Power supply for the Pi.
  3. Monitor (optional)
  4. Keyboard (optional)
  5. Mouse (optional)


1.Install and upgrade raspbian


Sudo apt-get update && sudo apt-get install


2. Install hostapd and dnsmasq .
This will allow us to use our raspberry pi as a wireless access point


apt-get remove –purge hostapd -yqq
apt-get update -yqq
apt-get upgrade -yqq
apt-get install hostapd dnsmasq -yqq


3. Now we will add broadcasting IP and DNS address in the dnsmasq configuration file

To access the configuration file use:

sudo nano /etc/dnsmasq.co


And to the bottom of the file, add the following commands




  1. Now to select the SSID and the PASSWORD for the access point, we’ll need to change the configurations of hostapd package
sudo nano /etc/hostapd/hostapd.conf


Then, use the following commands :




  1. To finally sum up the configuration, we’ll have to create a  custom network interface that combines all the settings that we have made.
sudo nano /etc/network/interfaces


And add the following lines it the EOF

allow-hotplug wlan0
iface wlan0 inet static


Now, we just have to have to disable default interfaces so that they do not interfere with the custom interfaces that we have made.

To do so


sudo nano /etc/dhcpcd.conf


Add the following line at the end of the file

denyinterfaces wlan0


  1. Now just restart the services


systemctl enable hostapd && systemctl enable dnsmasq

sudo service hostapd start && sudo service dnsmasq start

sudo reboot


Now, you will be able to enjoy a self-made access point which is used as a basic mode of connection in SUSI Smart Speaker and can also be used in various other access point methods.






Adding Audio Streaming from Youtube in SUSI Linux

In this blog post we will describe how the youtube streaming works in the

SUSI smart speaker and how audio is streamed directly from youtube videos.

To achieve this process, we have used an amazing Open-Source project called MPV music Player along with python libraries like Subprocess.

1.Processing a Query to the server

Firstly , the user asks the smart speaker to play the youtube audio by simply adding a ‘play’ word before his/her favorite song. eg. I’ll say ‘play despacito’ and then the command is recognized and a query is sent to the server which sends the following response as a JSON object.

“actions”: [
       “type”: “answer”,
       “expression”: “Playing Luis Fonsi – Despacito ft. Daddy Yankee”
       “identifier”: “kJQP7kiw5Fk”,
       “identifier_type”: “youtube”,
       “type”: “video_play”

2.Parsing the response

Then the speaker parses the response in the following way.

The Speaker traverses through all the actions returned in the response and checks for all the “identifier” by assigning a custom class to it.

class VideoAction(BaseAction):
   def __init__(self, identifier , identifier_type):
       self.identifier = identifier
       self.identifier_type = identifier_type

Now we check whether the query is the type of a custom class VideoAction and then the client processes the query as the response.

      elif isinstance(action, VideoAction):
          result[‘identifier’] = action.identifier
           audio_url = result[‘identifier’]  

3.Implementing the Actions

Now that we have identified that the response contains a Video Action, we can finally implement a way to play the audio from the URL.
We use a music player called MPV Music Player and the library Subprocess to make it run asynchronously.

if ‘identifier’ in reply.keys():
   classifier = reply[‘identifier’]
   if classifier[:3] == ‘ytd’:
       video_url = reply[‘identifier’]
       video_pid = subprocess.Popen(‘mpv –no-video https://www.youtube.com/watch?v={} –really-quiet &’.format(video_url[4:]), shell=True)  # nosec #pylint-disable type: ignore
       self.video_pid = video_pid.pid

This is how audio is streamed from youtube videos in SUSI Smart Speaker.


  1. https://github.com/mpv-player/mpv
  2. https://docs.python.org/2/library/subprocess.html
  3. https://github.com/fossasia/susi_linux
  4. https://github.com/fossasia/susi_api_wrapper


fossasia, gsoc’18, susi, susi.ai, youtube, music, mp3 , mpv, audio stream


Adding Offline support To SUSI Linux

Till now, SUSI smart speaker was working only as an online model like the other speakers in the market. For the first time, we have introduced a feature which allows the speaker to work offline. We deployed the server on the hardware itself and also provide the option of an online server as a fallback.


The Offline Support was implemented in the following steps


Step 1: Deploying SUSI Server Locally


Firstly , configure a bash script to allow automatic deployment of the server along with the initialization of the susi_linux script.


echo “Deploying local server”
if  [ ! -e “susi-server” ]
   git clone https://github.com/fossasia/susi_server.git

if [ -e “susi_server” ]
   cd susi_server
   git submodule update –recursive –remote
   git submodule update –init –recursive
   ./gradlew build


The above builds the server and deploys it on ‘localhost:4000’.


Then, add the following test on SUSI Linux wrapper to check if the local server is up and running. Using the local server not adds an offline support but also increases the efficiency by around 30%.

def check_local_server():
   test_params = {
       ‘q’: ‘Hello’,
       ‘timezoneOffset’: int(time.timezone / 60)
       chat_url = ‘http://localhost:4000/susi/chat.json’
       if (requests.get(chat_url, test_params)):
           print(‘connected to local server’)
           global api_endpoint
           api_endpoint = ‘http://localhost:4000’
   except requests.exceptions.ConnectionError:
       print(‘local server is down’)



As shown above, this is a test checking for the local server. If the local server is down, the online server is chosen as a fallback


Step 2: Adding an Offline STT Service

Now, that we are able to process a query offline. We must have a way in which, we can recognize the user’s voice commands without using the internet. For that, we use the service of PocketSphinx. But first, we check if the internet is available or not


def internet_on():
           urllib2.urlopen(‘’, timeout=1)  # nosec #pylint-disable type: ignore
           return True  # pylint-enable
       except urllib2.URLError as err:
           return False


If the internet connection is available, we use the online STT service which is Google STT ( default) and switch over to PocketSphinx in case the internet connection is not available.


Step 3: Adding the Offline TTS service

Finally, we’ll need an offline TTS service which will help us turn SUSI’s response to voice commands. We’ll be using a service called flite TTS as our offline TTS.


elif payload == ‘ConnectionError’:
            self.notify_renderer(‘error’, ‘connection’)                                  self.notify_renderer(‘error’, ‘connection’)
            config[‘default_tts’] = ‘flite’
            os.system(‘play extras/connect-error.wav’)              


We check if there is a ConnectionError, and then we switch to flite TTS after play an error query


Final Output:

We now get a Smart Speaker which is functional without any internet connection.





Fossasia, susi, gsoc, gsoc’18, offline_tts , offline_stt ,flite , pocketsphinx

Displaying Skills Feedback on SUSI.AI Android App

SUSI.AI has a feedback system where the user can post feedback for a skill using Android, iOS, and web clients. In skill details screen, the feedback posted by different users is displayed. This blog shows how the feedback from different users can be displayed in the skill details screen under feedback section.

Three of the items from the feedback list are displayed in the skill details screen. To see the entire list of feedback, the user can tap the ‘See All Reviews’ option at the bottom of the list.

The API endpoint that has been used to get skill feedback from the server is https://api.susi.ai/cms/getSkillFeedback.json

The following query params are attached to the above URL to get the specific feedback list :

  • Model
  • Group
  • Language
  • Skill Name

The list received is an array of `Feedback` objects, which hold three values :

  • Feedback String (feedback) – Feedback string posted by a user
  • Email (email) – Email address of the user who posted the feedback
  • Time Stamp – Time of posting feedback

To display feedback, use the RecyclerView. There can be three possible cases:

  • Case – 1: Size of the feedback list is greater than three
    In this case, set the size of the list to three explicitly in the FeedbackAdapter so that only three view holders are inflated. Inflate the fourth view holder with “See All Reviews” text view and make it clickable if the size of the received feedback list is greater than three.
    Also, when the user taps “See All Reviews”, launch an explicit intent to open the Feedback Activity. Set the AllReviewsAdapter for this activity. The size of the list will not be altered here because this activity must show all feedback.
  • Case – 2: Size of the feedback list is less than or equal to three
    In this case simply display the feedback list in the SkillDetailsFragment and there is no need to launch any intent here. Also, “See All Reviews” will not be displayed here.

    Case – 3: Size of the feedback list is zero
    In this case simply display a message that says no feedback has been submitted yet.Here is an example of how a “See All Reviews” screen looks like :


First of all, define an XML layout for a feedback item and then create a data class for storing the query params.

data class FetchFeedbackQuery(
       val model: String,
       val group: String,
       val language: String,
       val skill: String

Now, make the GET request using Retrofit from the model (M in MVP).

override fun fetchFeedback(query: FetchFeedbackQuery, listener: ISkillDetailsModel.OnFetchFeedbackFinishedListener) {

   fetchFeedbackResponseCall = ClientBuilder.fetchFeedbackCall(query)

   fetchFeedbackResponseCall.enqueue(object : Callback<GetSkillFeedbackResponse> {
       override fun onResponse(call: Call<GetSkillFeedbackResponse>, response: Response<GetSkillFeedbackResponse>) {

       override fun onFailure(call: Call<GetSkillFeedbackResponse>, t: Throwable) {

override fun cancelFetchFeedback() {

The feedback list received in the JSON response can now be used to display the user reviews with the help of custom adapters, keeping in mind the three cases already discussed above.


Display skills sorted in different orders in SUSI.AI Android App

Skills in SUSI.AI were displayed in a random order earlier as per the response received from the server. To provide more flexibility to the users, the skills can be sorted by various orders like top-rated, lexicographical, recently updated and so on. This blog shows how to get sorted skills from the server using the getSkillList.json API.

API Information

For requesting a list of SUSI skills, the endpoint used is /cms/getSkillList.json.
This will give you the sorted skills as per the applied filter. Some of the filters include top rated skills, recently updated skills, newly created skills, etc.

Base URL : https://api.susi.ai/cms/getSkillList.json

Parameters to be passed :

  • group – This is the group to which a skill belongs to.
  • language – The language in which the skill is needed.
  • applyFilter – This parameter tells if the filtering needs to be enabled.
  • filter_type – This is the order in which the skills need to be sorted and is applicable if applyFilter is true.
  • filter_name – This tells whether the order of sorting needs to be ascending or descending and is applicable if applyFilter is true.

Currently, there are following filters available :

  • Top Rated : The skills will be sorted based on the skills ratings by users.

filter_type :  rating
filter_name : ascending or descending (based on the requirement).


  • Lexicographical : The skills will be sorted in alphabetical order.

filter_type :  lexicographical
filter_name : ascending (to show skills in the order A-Z) or (descending to show skills in the order Z-A).


  • Newly Created : The skills will be displayed based on the date of creation.

filter_type :  creation_date
filter_name : ascending to show newly created skills first and descending to show the oldest created skills first.


  • Recently Updated : The skills will be sorted based on the date when they were last updated.

filter_type :  modified_date
filter_name : ascending or descending as per requirement.


  • Feedback Count : The skills will be sorted as per the feedback count.

filter_type :  feedback
filter_name : ascending to show skills with the most number of feedbacks first and descending to show skills with the least number of feedbacks first.


  • This Week Usage : The skills will be sorted as per the usage analytics of the week.

filter_type :  usage
duration : 7
filter_name : descending to show the most used skill first and vice-versa.


  • This Week Usage : The skills will be sorted as per the usage analytics for the last 30 days.

filter_type :  usage
duration : 30
filter_name : descending to show the most used skill first and vice-versa.


Note: In all the above cases, the ‘applyFilter’ param will be passed with the value ‘true’ otherwise the skills will not be sorted.

Here is an example of a URL for displaying the top rated skills:



To make a request to the getSkillList.json API, make a GET request as follows :

Call<ListSkillsResponse> fetchListSkills(@QueryMap Map<String, String> query);


Here the query map contains all the aforementioned params.

Now, make the GET request using Retrofit from the model :

private lateinit var authResponseCallSkills: Call<ListSkillsResponse>

override fun fetchSkills(group: String, language: String, listener: IGroupWiseSkillsModel.OnFetchSkillsFinishedListener) {
   val queryObject = SkillsListQuery(group, language, "true", "descending", "rating")
   authResponseCallSkills = ClientBuilder.fetchListSkillsCall(queryObject)

   authResponseCallSkills.enqueue(object : Callback<ListSkillsResponse> {
       override fun onResponse(call: Call<ListSkillsResponse>, response: Response<ListSkillsResponse>) {
           listener.onSkillFetchSuccess(response, group)

       override fun onFailure(call: Call<ListSkillsResponse>, t: Throwable) {

override fun cancelFetch() {
   try {
   } catch (e: Exception) {


The skills in the filteredData array, received in the JSON response, shall be sorted in the order based on the filter_type and filter_name params that you passed. Now, this array can be used to display skills on the skills listing page.