Implementing Country Preference in Orga App

In the Open Event Orga App, there was no option earlier to save the country preference in the shared preference. So every time the user had to select the country while creating events. Hence an option to select a country was added to the Event Settings. So any value which gets selected here acts as a default country while creation of events.

Steps

  • Add the constant key to the Constants.java class.
public static final String PREF_PAYMENT_COUNTRY = “key”;
  • Create a class CountryPreference.java which extends DialogPreference. It is in this class that all the code related to the dialog which appears in selecting the Country preference will appear. First we create a layout for the dialog box. Following is the XML file for the same.
<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:layout_margin=“@dimen/spacing_small”
  android:padding=“@dimen/spacing_small”
  android:orientation=“vertical”>

  <android.support.v7.widget.AppCompatSpinner
      android:id=“@+id/country_spinner”
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:layout_marginTop=“@dimen/spacing_small” />

</LinearLayout>
  • Now we create the CountryPreference constructor where we specify the UI Of the dialog box. It would include the positive and negative button.
private int layoutResourceId = R.layout.dialog_payment_country;
private int savedIndex;

public CountryPreference(Context context, AttributeSet attrs) {
  super(context, attrs, R.attr.preferenceStyle);
  setDialogLayoutResource(R.layout.dialog_payment_country);
  setPositiveButtonText(android.R.string.ok);
  setNegativeButtonText(android.R.string.cancel);
  setDialogIcon(null);
}
  • We override the method onSetInitialValue where we set the preference of the country in the shared preference. We call the method setCountry and pass the persisted value.
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
  setCountry(restorePersistedValue ? getPersistedInt(savedIndex) : (int) defaultValue);
  super.onSetInitialValue(restorePersistedValue, defaultValue);
}

 

public void setCountry(int index) {
  savedIndex = index;
  persistInt(index);
}
  • We create a class CountryPreferenceCompat which extends PreferenceDialogFragmentCompat. It is here that we initialize the spinner and set the adapter. It is here that we override the method onDialogClosed which should happen when the dialog box is closed. Following is the code for the same.
@Override
public void onDialogClosed(boolean positiveResult) {
  if (positiveResult) {
      DialogPreference preference = getPreference();
      if (preference instanceof CountryPreference) {
          CountryPreference countryPreference = ((CountryPreference) preference);
          countryPreference.setCountry(index);
      }
  }
}
  • In the PaymentPrefsFragment the code for initialization of the dialog is added. We override the onDisplayPreferenceDialog.
@Override
public void onDisplayPreferenceDialog(Preference preference) {
  CountryPreferenceFragmentCompat dialogFragment = null;
  if (preference instanceof CountryPreference)
      dialogFragment = CountryPreferenceFragmentCompat.newInstance(Constants.PREF_PAYMENT_COUNTRY);

  if (dialogFragment != null) {
      dialogFragment.setTargetFragment(this, 1);
      dialogFragment.show(this.getFragmentManager(),
          “android.support.v7.preference” +
              “.PreferenceFragment.DIALOG”);
  } else {
      super.onDisplayPreferenceDialog(preference);
  }
}
  • Now the PaymentCountry spinner can be seen on testing.

References

  1. Building Custom Preference https://www.hidroh.com/2015/11/30/building-custom-preferences-v7/
  2. StackOverflow solution https://stackoverflow.com/questions/16577173/spinnerpreference-how-to-embed-a-spinner-in-a-preferences-screen  

 

Continue Reading

Implementing the Order Receipt End Point in Orga App

In the  Open Event Orga App, I have implemented the Order Receipt endpoint with the help of which the organizer will be able to send the receipt of the ‘completed’ orders to the attendee via email. Initially the API was made in the server and then it was implemented in the the app.

Following steps were followed:

  • Firstly a method named sendReceipt was made in the OrderDetailFragment as follows. We pass in the orderIdentifier string as a parameter.
private void sendReceipt() {
  orderDetailViewModel.sendReceipt(orderIdentifier);
}
  • Now we implement 2 classes for OrderReceiptRequest and OrderReceiptResponse. The implementation is as follows. In the OrderReceiptRequest class we add just the orderIdentifier instance variable as the request involves just the order identifier parameter.
@Data
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class OrderReceiptRequest {

   public String orderIdentifier;
}
  • Now we implement the OrderReceiptResponse class which will consist of 2 parameters message and error.
public class OrderReceiptResponse {

  public String message;
  public String error;
}
  • In the OrderDetailsViewModel we add the following method. We create an object OrderReceipt where we pass the orderIdentifier. In the following statements we call the sendReceipts method of OrderRepositorry which takes in this OrderReceiptRequest as parameter.
public void sendReceipt(String orderIdentifier) {
  OrderReceiptRequest orderReceipt = new OrderReceiptRequest();
  orderReceipt.setOrderIdentifier(orderIdentifier);
  compositeDisposable.add(orderRepository.sendReceipt(orderReceipt)
      .doOnSubscribe(disposable -> progress.setValue(true))
      .doFinally(() -> progress.setValue(false))
      .subscribe(() -> success.setValue(“Email Sent!”),
          throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));
}
  • We then add the method sendReceipt in Order Repository which returns a Completable.
  • Now we implement the sendReceipt methid in OrderRepositoryImpl as follows. First we check whether the repository is connected or not. If not then a network error message is sent.Then the sendReceiptEmail method present in the Orderapi class is called where we pass the orderReceiptRequest object. The next step will show the adding of the API for this particular end point.
@Override
public Completable sendReceipt(OrderReceiptRequest orderReceiptRequest) {
  if (!repository.isConnected())
      return Completable.error(new Throwable(Constants.NO_NETWORK));

  return orderApi
          .sendReceiptEmail(orderReceiptRequest)
          .flatMapCompletable(
              var -> Completable.complete())
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread());
}
  • Now in the OrdersAPI interface the following API call is written. We pass the OrderReceiptRequest in the body and the respinse is collected in the OrderReceiptRequest class and diplayed as the outcome.
@POST(“attendees/send-receipt”)
Observable<OrderReceiptResponse> sendReceiptEmail(@Body OrderReceiptRequest orderReceiptRequest);
  • Certain UI changes also had to be done which are shown below.
<LinearLayout
  android:id=“@+id/emailReceiptLl”
  android:layout_width=“0dp”
  android:layout_height=“wrap_content”
  android:layout_weight=“1”
  android:layout_gravity=“center”
  android:gravity=“center”
  android:orientation=“vertical”>

  <Button
      android:id=“@+id/emailReceipt”
      android:layout_width=“30dp”
      android:layout_height=“30dp”
      android:background=“@drawable/ic_email”
      app:backgroundTint=“@color/materialcolorpicker__white” />

  <TextView
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:text=“@string/receipt”
      android:textAllCaps=“true”
      android:textSize=“10sp”
      android:textColor=“@color/materialcolorpicker__white”/>
</LinearLayout> 

Resources

  1. Using Observables and Completables https://github.com/ReactiveX/RxJava/wiki/What’s-different-in-2.0
  2. Medium article on RxJava https://blog.aritraroy.in/the-missing-rxjava-2-guide-to-supercharge-your-android-development-part-1-624ef326bff4
Continue Reading

Add Support for Online Events in Orga App

The Open Event Orga App  didn’t have support for online events. After the support for online events was added in the server, it got implemented in the orga app as well. The main difference between offline and online events is that offline events need location details while online events don’t need any location details.

Following steps were followed in its implementation:

  • Firstly the parameter isEventOnline was added to the Event.java model class. As the JSON tag name for online event was is_event_online the parameter was named as isEventOnline and was made of the type boolean.
public boolean isEventOnline;
  • Now a checkbox had to be added to the UI and so the following XML code was added to the event_details_step_one.xml. With the help of DataBinding it was checked whether the checkBox is checked or not. If it was checked then the Layout consisting of the Location Details was hidden and if not then it is an offline event and it can be shown.
<CheckBox
  android:id=“@+id/online_event”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:layout_marginTop=“@dimen/spacing_normal”
  android:onCheckedChanged=“@{ (switch, checked) -> event.setEventOnline(checked) }”
  android:padding=“@dimen/spacing_extra_small”
  android:text=“@string/event_online” />

<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:orientation=“vertical”
  android:visibility=“@{ onlineEvent.checked ? View.GONE : View.VISIBLE }”>

Now the same changes had to be done to UpdateEventsScreen as well. Hence the above XML code was added to it as well.

  • Now one thing that needed to be taken care of is that online events do not require location details and without which events cant get published. So in the EventDashboardPresenter it was made sure that if the event is online then it can get published even though no location details have been provided. The extra condition was added to the else if block of confirmToggle( ).
public void confirmToggle() {
  if (Event.STATE_PUBLISHED.equals(event.state)) {
      getView().switchEventState();
      getView().showEventUnpublishDialog();
  } else if (Utils.isEmpty(event.getLocationName()) && !event.isEventOnline) {
      getView().switchEventState();
      getView().showEventLocationDialog();
  } else {
      toggleState();
  }
}

Resources

  1. Medium article on using 2 way data binding https://medium.com/google-developers/android-data-binding-lets-flip-this-thing-dc17792d6c24
  2. Medium article on using Lombok in andorid https://medium.com/@wkrzywiec/project-lombok-how-to-make-your-model-class-simple-ad71319c35d5
Continue Reading

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:

1.factory-daemon.service
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.

[Unit]
Description=SUSI Linux Factory Daemon
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/pi/SUSI.AI/susi_linux/factory_reset/factory_reset.py

[Install]
WantedBy=multi-user.target

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.

[Unit]
Description=Python Server for SUSI Linux
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python3  /home/pi/SUSI.AI/susi_linux/access_point/server/server.py

[Install]
WantedBy=multi-user.target

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.

[Unit]
Description=Starting SUSI Server for SUSI Linux
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/home/pi/SUSI.AI/susi_linux/susi_server/susi_server/bin/restart.sh

[Install]
WantedBy=multi-user.target

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.

[Unit]
Description=Update Check- SUSI Linux
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/home/pi/SUSI.AI/susi_linux/update_daemon/update_check.sh

[Install]
WantedBy=multi-user.target

5. SUSI-Linux Service

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

[Unit]
Description=Starting SUSI Linux
Wants=network-online.target
After=network-online.target

[Service]
Type=idle
WorkingDirectory=/home/pi/SUSI.AI/susi_linux/
ExecStart=/usr/bin/python3 -m main

[Install]
WantedBy=multi-user.target

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

Resources

Continue Reading

Configuring LED Lights with SUSI Smart Speaker

To make the SUSI Smart Speaker more interactive and to improve the visual aesthetics, we configured SUSI Smart Speaker’s response with 3 RGB led lights. We have used a new piHat as an external hardware to configure the LEDs.

Now the new hardware specs of the SUSI Smart Speaker are:

  1. Raspberry Pi
  2. ReSpeaker PiHat 2 Mic Array
  3. External Speakers

Using an external PiHat not only added the RGB light functionality but also eliminated the need to use a USB microphone and configured a factory reset button

Configuring the PiHat as the default Audio driver

To Use the PiHat as the default input driver, we use the package called PulseAudio.

And we use the following command in the installation script.

pacmd set-sink-port alsa_output.platform-soc_sound.analog-stereo analog-output-headphones

Configuring PiHat’s GPIO Button with Factory Reset

There is an onboard User Button, which is connected to GPIO17. We use the python library RPi.GPIO to detect the user button. The python script is used in the following way

GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.IN)
i = 1
while True:
if GPIO.input(17) == 1:
       time.sleep(0.1)
       pass
   elif GPIO.input(17) == 0 :
       start = time.time()
       while GPIO.input(17) == 0 :
           time.sleep(0.1)
       end = time.time()
       total = end – start
       if total >= 7 :
           subprocess.call([‘bash’,‘factory_reset.sh’])  # nosec #pylint-disable type: ignore
       else :
           mixer = alsaaudio.Mixer()
           value = mixer.getvolume()[0]
           if value != 0:
               mixer.setvolume(0)
           else:
               mixer.setvolume(50)
       print(total)
       time.sleep(0.1)

 

This script checks on the button which is configured on GPIO port 17 on the PiHat. If the button is pressed for than 7 secs, the factory reset process takes place, else the device is muted.

Configuring PiHat’s LED with Speaker’s Response

We use a python library called SPIDEV to sync the LED lights with SUSI’s response. SPIDEV is usually used to send a response to the bus devices on the Raspberry Pi.

The first step was installing spidev

sudo pip install spidev

Now we create a class where we store all the methods where we send the signal to the bus port. We treat the LED lights as a circular array and then have a rotation of RGB lights

class LED_COLOR:
    # Constants
   MAX_BRIGHTNESS = 0b11111    LED_START = 0b11100000
    def __init__(self, num_led, global_brightness=MAX_BRIGHTNESS,
                order=‘rgb’, bus=0, device=1, max_speed_hz=8000000):
       self.num_led = num_led
       order = order.lower()
       self.rgb = RGB_MAP.get(order, RGB_MAP[‘rgb’])
       if global_brightness > self.MAX_BRIGHTNESS:
           self.global_brightness = self.MAX_BRIGHTNESS
       else:
           self.global_brightness = global_brightness
        self.leds = [self.LED_START, 0, 0, 0] * self.num_led
       self.spi = spidev.SpiDev()
       self.spi.open(bus, device)
       if max_speed_hz:
           self.spi.max_speed_hz = max_speed_hz
    def clear_strip(self):
        for led in range(self.num_led):
           self.set_pixel(led, 0, 0, 0)
       self.show()
    def set_pixel(self, led_num, red, green, blue, bright_percent=100):
       if led_num < 0:
           return          if led_num >= self.num_led:
           return
       brightness = int(ceil(bright_percent * self.global_brightness / 100.0))
       ledstart = (brightness & 0b00011111) | self.LED_START
        start_index = 4 * led_num
       self.leds[start_index] = ledstart
       self.leds[start_index + self.rgb[0]] = red
       self.leds[start_index + self.rgb[1]] = green
       self.leds[start_index + self.rgb[2]] = blue
    def set_pixel_rgb(self, led_num, rgb_color, bright_percent=100):
       self.set_pixel(led_num, (rgb_color & 0xFF0000) >> 16,
                      (rgb_color & 0x00FF00) >> 8, rgb_color & 0x0000FF, bright_percent)
    def rotate(self, positions=1):
       cutoff = 4 * (positions % self.num_led)
       self.leds = self.leds[cutoff:] + self.leds[:cutoff]
    def show(self):
       data = list(self.leds)
       while data:
           self.spi.xfer2(data[:32])
           data = data[32:]
       self.clock_end_frame()
    def cleanup(self):
       self.spi.close()  # Close SPI port
    def wheel(self, wheel_pos):
       “””Get a color from a color wheel; Green -> Red -> Blue -> Green”””
        if wheel_pos > 255:
           wheel_pos = 255  # Safeguard
       if wheel_pos < 85:  # Green -> Red
           return self.combine_color(wheel_pos * 3, 255 – wheel_pos * 3, 0)
       if wheel_pos < 170:  # Red -> Blue
           wheel_pos -= 85
           return self.combine_color(255 – wheel_pos * 3, 0, wheel_pos * 3)
       wheel_pos -= 170
       return self.combine_color(0, wheel_pos * 3, 255 – wheel_pos * 3)

 

Now we use the threading to create non-blocking code which will allow SUSI to send response as well as change the LED’s simultaneously.

class Lights:
   LIGHTS_N = 3
    def __init__(self):
        self.next = threading.Event()
       self.queue = Queue.Queue()
       self.thread = threading.Thread(target=self._run)
       self.thread.daemon = True
       self.thread.start()
    def wakeup(self, direction=0):
       def f():
           self._wakeup(direction)
        self.next.set()
       self.queue.put(f)
    def listen(self):
       self.next.set()
       self.queue.put(self._listen)
    def think(self):
       self.next.set()
       self.queue.put(self._think)
    def speak(self):
       self.next.set()
       self.queue.put(self._speak)
    def off(self):
       self.next.set()
       self.queue.put(self._off)

This is how LED lights are configured with SUSI’s response

Resources

Additional Resources

Continue Reading

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
network={
   ssid=“$SSID”
   psk=“$PSK”
}
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.

 

#!/bin/bash

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

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.

 

References

Additional Resources

Tags

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

Continue Reading

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:
       super().__init__()
       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.
       “””
       self.detector.start(detected_callback=self.detection)

 

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’):
           self.video_process.send_signal(signal.SIGSTOP)
           lights.wakeup()
           subprocess.Popen([‘play’, str(self.components.config[‘detection_bell_sound’])])
           lights.off()
           self.transition(self.allowedStateTransitions.get(‘recognizing’))
           self.video_process.send_signal(signal.SIGCONT)
       if hasattr(self, ‘audio_process’):
           self.audio_process.send_signal(signal.SIGSTOP)  
           lights.wakeup()
           subprocess.Popen([‘play’, str(self.components.config[‘detection_bell_sound’])])
           lights.wakeup()
           self.transition(self.allowedStateTransitions.get(‘recognizing’))
           self.audio_process.send_signal(signal.SIGCONT)

 

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

Tags

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

 

Continue Reading

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)
DIR_PATH=$(dirname $SCRIPT_PATH)

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

rm -rf ../susi_temp

./install.sh

 

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:
           pass
       elif GPIO.input(17) == 0 :
           start = time.time()
          while GPIO.input(17) == 0 :
               print(“on”)
               time.sleep(0.1)
          end = time.time()
           total = end – start
           if total >= 7 :
              subprocess.call([‘bash’,‘factory_reset.sh’])
          else :
               mixer = alsaaudio.Mixer()
               value = mixer.getvolume()[0]
              if value != 0:
                  mixer.setvolume(0)
               else:
                   mixer.setvolume(50)
           print(total)
           time.sleep(0.1)

 

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

 

[Unit]
Description=SUSI Linux Factory Daemon
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/pi/SUSI.AI/susi_linux/factory_reset/factory_reset.py

[Install]
WantedBy=multi-user.target

 

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

References

Tags

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

Continue Reading

Creating a Custom Raspbian Image containing SUSI.AI Linux Libraries

Installing Raspbian and SUSI Linux on your Raspberry Pi can be a long process and if your raspberry Pi crashes due to some bug, you have to repeat the process again and again.

It wastes a lot of valuable time. So, we will discuss a method in which we will have to install the SUSI Linux repo only once and can use it again in case of any issues.

First, we’ll go through the requirements for f installing SUSI Linux on our hardware

Hardware Requirements

1> Raspberry Pi

2> Micro SD card (16GB or greater)

3> USB Mic

4> USB Mouse and USB Keyboard

5> HDMI Monitor

6> ReSpeaker Pi Hat 2 Mic Array(optional)

7> 3.5 mm Jack Headphones / Speaker

 

Step 1: Preparing SD for Installation

1> To format your SD card

  • You can use softwares like SDCardformater to do so.

2> To install Raspbian

  • Download raspbian official build from here
  • Now mount the Image using software like Etcher or win32diskimager

 

Step 2: Installing SUSI Linux on your Pi

1> Navigate to the folder `/home/pi` and make a folder called SUSI.AI

 

cd /home/pi
mkdir SUSI.AI
cd SUSI.AI

 

2> Clone the SUSI Linux repo from here and navigate in the repo

 

git clone http://github.com/fossasia/susi_linux

cd susi_linux/

 

3> Run the installation script by using the command `./install.sh`

 

./install.sh

 

4> Run the configuration script by using the following command

`python3 config_generator.py <stt> <tts> <hotword> <wake>`

 

5> Run SUSI linux with the following command

`python3 -m main`

‘log’

If it plays a bell after you say ‘SUSI’ , it means that your software has been successfully installed

Step 3: Creating the image

1> Now that you have successfully installed SUSI Linux on your raspberry Pi , we will make a backup of the current stage of the system and use it for future references

 

2>Turn off the raspberry Pi , and remove the SD card from the Pi and insert it in your system.

 

3> To create the custom Image , use something like win32 imager and follow the steps below

  • In the text box , create a custom where you want your image to exist
  • Click on read button
  • And voila

 

References

https://www.raspberrypi.org/downloads/raspbian/

Tags

Fossasia, gsoc’18, SUSI.AI , susi_linux , gsoc, SUSI HW, installation

Continue Reading

Implementing Volume Action in SUSI Smart Speaker

We all know that a Smart Speaker to excel above its competitors has to excel in first being a good “Speaker” and a speaker has a basic and essential feature which is “volume control”. But things get better if you can control your volume with your voice.

So, we have implemented a feature that allows the user to control the volume of the audio with his/her voice.

Below are the steps we had to follow to implement this feature

 

Step 1: Creating the Skills

The skills required to implement the ‘volume-action’ is implemented in the SUSI Server repo itself.

The skill is located in

susi_server/conf/system_skills/general/en/en_0001_foundation.txt

 

And below are the skills required

 

set audio volume to *|set audio volume to * percent|set audio volume to * points|set volume to *|set volume to * percent|set volume to * points
!console:Audio volume is now $1$ percent.
{“actions”:[
{“type”:“audio_volume”, “volume”:$1$}
]}
eol

 

We get the following response from the server

 

“actions”: [
     {
       “volume”: “80”,
       “type”: “audio_volume”
     },
     {
       “type”: “answer”,
       “expression”: “Audio volume is now 80 percent.”
     }

 

Step 2: Finding Volume Action in the server response

Now that our Server responds to our queries regarding the voice change action , we must implement it in our Smart Speaker Client.


We first create a custom class in our in the SUSI API Wrapper repo which has only one member

 

class VolumeAction(BaseAction):
   def __init__(self , volume):
       super().__init__()
       self.volume = volume

 

We check through the actions in the server’s response

 

elif isinstance(action, VolumeAction):
           result[‘volume’] = action.volume

 

Step 3: Implementing it in the client

Now to implement the action in our client we use a library called ‘alsaaudio’ to control the master volume of our RaspberryPi

 

              m = alsaaudio.Mixer()
               m.setvolume(int(reply[‘volume’]))
               os.system(‘play {0} &’.format(self.components.config[‘detection_bell_sound’]))  # nosec #pylint-disable type: ignore                m = alsaaudio.Mixer()
               m.setvolume(int(reply[‘volume’]))
               os.system(‘play {0} &’.format(self.components.config[‘detection_bell_sound’]))  # nosec #pylint-disable type: ignore

 

Now the user can easily change the speaker using the voice commands

References

 

Tags

GSoC, GSoC’18, SUSI.AI, SUSI Linux, Smart Speaker , SUSI API Wrapper, SUSI Server, FOSSASIA, Volume Action

Continue Reading
Close Menu