Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

Monday, August 19, 2019

Tello Drone, Swift and State Machines (Part 2)

Stop Rolling Your Own State Machine Code



FIGURE 1. The Flight Plan iOS App


This sub heading is aimed at me to serve as a reminder to stop reinventing the wheel! A lot of the apps that I write benefit from having a Finite State Machine computational model. In an earlier article I wrote about controlling the Tello drone remotely using Swift. If you have a look at this code you will see that I have implemented a simple state machine to track the state of the drone. You need this because you can't send the drone a command unless WiFi is connected and the drone is in command mode (activated by sending it the "command" string via UDP). Thus our app responds differently depending on what state the drone is in.

I felt justified in writing my own state machine code because the initial application was relatively simple. If I was writing a game then I would always use GKStateMachine, the state machine class provided by Apple as part of game kit, but because this is a utility and I was in the UIKit headspace as opposed to the SceneKit/GameplayKit space I didn't think about it. But there is no reason you can't use GameplayKit classes in your UIKit app and in retrospect that is what I should have done (and just spent a day refactoring my code to do). Insert face palm emoji here!

I have continued to add functionality to my drone control app and as the complexity increased my home grown state machine started to become part of the problem and not the solution. Due to the organic development process (i.e. unstructured), I ended up with two state machines which were not scaling well. More importantly the app was acting weird and ending up in undefined states. Of course I could have fixed this in time, but I realised that Apple have already spent a lot of time putting together a robust state machine class and I should be using that!

FIGURE 2. Drone State Machine Diagram

The collateral benefit of having to refactor my code using GKStateMachine was that it made me sit down and plan out what states I needed and what would cause a transition. In other words I needed to develop a state transition table or diagram (Figure 2). After doing this exercise it became apparent that I didn't need two state machines, I just needed to add two states to the original machine. In addition, being forced to come up with the table made me think about some states and/or transitions that I wasn't handling.

TL;DR - Use GKStateMachine even for simple applications!

To demonstrate how easy it is, I will include the boiler plate code for my drone app.

STEP 1 - Create the state classes


For every state in your FSM you need a class to handle transitions, etc. Typically you will need to override the functions shown. I have included the outline for the disconnected state class below. The other state classes have exactly the same format but with different names.

//
//  DisconnectedState.swift
//  FlightPlan
//
//  Created by David Such on 18/8/19.
//  Copyright © 2019 Kintarla Pty Ltd. All rights reserved.
//

import Foundation
import GameplayKit

class DisconnectedState: GKState {
    unowned let viewController: ViewController
    
    init(viewController: ViewController) {
        self.viewController = viewController
        super.init()
    }
    
    override func didEnter(from previousState: GKState?) {
        viewController.statusLabel.text = "DISC"
        viewController.WiFiImageView.image = UIImage(named: "WiFiDisconnected")
        
        if !UserDefaults.standard.warningShown {
            viewController.showAlert(title: "Not Connected to Tello WiFi", msg: "In order to control the Tello you must be connected to its WiFi network. Turn on the Tello and then go to Settings -> WiFi to connect.")
            UserDefaults.standard.warningShown = true
        }
    }
    
    override func willExit(to nextState: GKState) {
        
    }
    
    override func isValidNextState(_ stateClass: AnyClass) -> Bool {
        return (stateClass == WiFiUpState.self) || (stateClass == PlanningState.self)
    }
    
    override func update(deltaTime seconds: TimeInterval) {
        
    }

}

A couple of points. Firstly, make sure that you import GameplayKit. Second, note the constant definition:

unowned let viewController: ViewController

In my app this is the main view controller which contains the UI and will never be NIL. To prevent a retain cycle we use unowned (and not weak since that view controller can never be NIL).

This constant is used to update the UI based on state changes (alternatively you could use a delegate).

STEP 2 - Define the State Machine


Next, within the view controller referred to in step 1, you need to define your state machine.

//
//  ViewController.swift
//  FlightPlan
//
//  Created by David Such on 3/6/19.
//  Copyright © 2019 Kintarla Pty Ltd. All rights reserved.
//

import UIKit
import GameplayKit

class ViewController: UIViewController {
    
    lazy var stateMachine: GKStateMachine = GKStateMachine(states: [
        DisconnectedState(viewController: self),
        WiFiUpState(viewController: self),
        CommandState(viewController: self),
        PlanningState(viewController: self),
        ManualState(viewController: self),
        AutoPilotState(viewController: self)
        ])

As shown above, this is very straight forward. A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration. We need this so that we can assign a pointer to the class containing our state machine (i.e. viewController which is an instance of ViewController) after it has been initialised.

STEP 3 - Use the State Machine


Now we can use our new state machine to keep track of the drone state and handle transitions between states. The first thing you will want to do is to set the initial state. For our drone this is the disconnected state.

stateMachine.enter(DisconnectedState.self)

You will probably do this in the viewDidLoad method of viewController. Then you can change states when the appropriate event is triggered. For example, the following method is called when the take off button is tapped.

@IBAction func takeOffTapped(_ sender: UIButton) {
        switch stateMachine.currentState {
        case is DisconnectedState:
            showAlert(title: "Not Connected to Tello WiFi", msg: "In order to control the Tello you must be connected to its WiFi network. Turn on the Tello and then go to Settings -> WiFi to connect.")
        case is WiFiUpState:
            showAlert(title: "Awaiting CMD Response", msg: "We haven't received a valid response to our initialisation command. Try sending again from Setup.")
        case is CommandState:
            tello.takeOff()
            stateMachine.enter(ManualState.self)
        case is PlanningState:
            if tello.flightPlan.count == 0 {
                let zoom = scrollView.zoomScale - 0.25
                let pitch = dronePointer.frame.size.height
                
                tello.flightPlan.append(CMD.takeOff)
                dronePointerCenter.y -= pitch
                UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {self.dronePointer.center = self.dronePointerCenter}, completion: nil)
                scrollView.setZoomScale(zoom, animated: true)
            }
        default:
            break
        }

    }

Depending on the current drone state (stateMachine.currentState) we want to perform different actions. To take off manually, we need to be in the command state. In the planning state, we add the take off command to our flight plan, and animate the action on our viewController.

One last tip. In the example above we are using switch for program control to handle the various states. If you want to check the current state against only one state don't use "==". It wont compile. You need to use "is" instead. For example, to check if the current state is Auto Pilot, you would use:

if stateMachine.currentState is AutoPilotState {
            tello.stopAutoPilot()
}

That's it. Next time you need a state machine, don't write your own! Hop on over to GameplayKit and grab GKStateMachine.

Tuesday, June 25, 2019

Mesh Security System (Argon Hub, OLED and MP3 Shields) - Part 2

OLED Display


Figure 5. Argon mounted on Tripler with OLED.


Having demonstrated that we can blink a LED on the Argon, we now want to move onto something a bit more useful. The Argon will form the hub of the Mesh Security System and will connect to an OLED and MP3 shield to indicate system status. In Part 2 we will get the OLED and MP3 shields working.

As shown in Figure 5, connection is simple using the Featherwing Tripler. By mounting the shields horizontally rather than stacking them you can still easily see all the indication LEDs. You will have to solder the headers on the tripler and shields. Do the tripler first. I solder one pin and then check that the header is correctly positioned before soldering the rest. It is a lot easier to rectify an issue with only one pin soldered in place. Once you have completed soldering the headers on the tripler you can use this as a jig to hold the pins in place when soldering them to the shields. This will ensure that the shield pins line up with the headers on the tripler.

Figure 6. OLED Operational


The display board is 128x32 monochrome OLED which has 3 user buttons plus reset. This screen is made of 128x32 individual white OLED pixels and because the display makes its own light, no backlight is required. This reduces the power required to run the OLED and is why the display has such high contrast. The board uses a SSD1306 and connects via I2C (pins D0 and D1), so it is very pin frugal. As I2C is a shared bus you can have other shields which utilise I2C connected at the same time (as long as they have different I2C addresses). The three buttons use:
ButtonPinNotes
AD4No pull-up. Can't be used with Ethernet.
BD3100K pull-up. Can't be used with Ethernet.
CD2No pull-up.
So all up this shield uses 5 pins (D0 - D4).

The library is available in the Web IDE as oled-wing-adafruit and using the display from the Argon is easy. The library takes care of setting the appropriate input modes and debouncing the buttons for you.

I've reproduced my test code stub below. I always like to get each element of a project working before adding the next. This makes debugging much easier.



MP3 Shield


The MP3 Shield is shown in Figure 5 above. This is before the through hole headers have been soldered onto the shield. The shield version that we are using is the Adafruit Music Maker FeatherWing. This shield uses the the VS1053, an encoding/decoding (codec) chip that can decode a wide variety of audio formats such as MP3, AAC, Ogg Vorbis, WMA, MIDI, FLAC, WAV (PCM and ADPCM). This chip also allows you to adjust bass, treble, and volume digitally.

Figure 7. Argon Block Diagram (showing I/O).


Communication is via a SPI interface which allows audio to be played from an SD card. There's also a special MIDI mode that you can boot the chip into that will read 'classic' 31250 Kbaud MIDI data from the UART TX pin. The hardware SPI pins are needed whenever you are transmitting data from the SD card to the decoder chip. If you are using the wing in the special MIDI mode, they're not used.

D11: SPI MISO - connected to MISO - used by both the SD card and VS1053
D12: SPI MOSI - connected to MOSI - used by both the SD card and VS1053
D13: SPI SCK - connected to SCK - used by both the SD card and VS1053

The Adafruit VS1053 Library does include a constructor to define the SPI pins you want to use, but this doesn't help us because:

  1. The hardware SPI pins are already connected by the tripler; and
  2. The alternative SPI pins on the Argon are D2, D3 and D4 - which seem to be very popular with shield designers!


Figure 8. Adafruit Music Maker FeatherWing Shield.


Next are the control pins required to play music. From left to right, in Figure 9 below, they are:

MP3_DCS - this is the VS1053 data select pin
DREQ        - this is the VS1053 data request interrupt pin
MP3_CS    - this is the VS1053 chip select pin
SD_CS       - this is the SD Card chip select pin

Figure 9. MP3 Control Pins.


Unfortunately the MP3 control pins connected (via the tripler) to the Argon conflict with the A, B and C buttons connected to D2, D3 and D4 from the OLED shield. Thankfully there is no conflict on pins D0 or D1, so we can still control the OLED with the MP3 shield in place. Obviously the designers of the two shields at Adafruit didn't talk to each other!

Figure 10. MP3 Shield Installed.


To summarise, the Argon pins used to control the MP3 shield are:

SD_CS                = D2;                 // SD Card chip select pin
MP3_CS             = D3;                 // VS1053 chip select pin (output)
DREQ                 = D4;                 // VS1053 Data request, ideally an Interrupt pin
MP3_DCS          = D5;                 // VS1053 Data/command select pin (output)
SPI MISO           = D11;               // used by both the SD card and VS1053
SPI MOSI           = D12;              // used by both the SD card and VS1053
SPI SCK             = D13;               // used by both the SD card and VS1053

Figure 10 shows the MP3 shield in place on the tripler adjacent to the OLED shield. To give myself a bit more room, I removed the OLED shield while soldering the header pins to the MP3 shield. I again inserted the header pins into the tripler before soldering to make sure that everything lined up.

There are two versions of the Adafruit Music Maker, one includes an amplifier and the other just has a 3.5mm connection for headphones or powered speakers. In retrospect I should have got the one with the amplifier built in. Nevertheless I happen to have a Duinotech 2 x 3W amplifier, so I might as well use that. This is the red PCB shown in Figure 10. Before dealing with this, you will want to make sure that the MP3 shield is working.

Thankfully ScruffR has done the hard work of porting the Adafruit VS1053 Arduino library to work with Particle mesh boards. You will need to import this library and the SDFat library in order to get the shield working. This is easy, just search for the libraries in the Web IDE and then add them. Plug in some headphones (assuming you have the same version shield as I do) and you can use the code below to test the operation of your shield. You will obviously need to copy some mp3 files to SD card before you can play them. Make sure that the names of the files are in the 8.3 format or they wont be able to be played.



Duinotech 2 x 3W Amplifier


Rather than use the 3.5mm jack on the MP3 shield, we will connect directly to the Ground, Right and Left pins next to the headphone jack (Figure 11). They are line level, AC coupled outputs which are suitable for connection to an amplifier.

Figure 11. MP3 Shield Audio Out Pins.


The Duinotech 2 x 3W Class D Amplifier (Figure 12) has greater than 90% efficiency and typically delivers 3W into 4 ohm speakers (or 1.5W into 8 ohms). Its operating voltage range is 2.5 to 5.5 VDC.

The amplifier board uses the PAM8403 chip and power output will be determined by a combination of the input voltage supplied and output impedance. As we are using the regulated 3.3V from the Argon and 8 ohm speakers our expected power output from the amplifier is around 0.5W.

Figure 12. Duinotech 2 x 3W Amplifier.

The amplifier pin out description is provided in the table below.

Amplifier Pinout
Module
Function
R+/R-
Right Speaker
L-/L+
Left Speaker
GND
Ground Connection
+5V
Power Supply
5W
Shutdown Control
GND
Ground Connection
LIN
Left Audio In
GND
Ground for Audio
RIN
Right Audio In

Connection between the MP3 shield and amplifier is straight forward.
  1. MP3 Shield L and G connect to LIN and Audio GND on the amplifier.
  2. MP3 Shield R and G connect to RIN and Audio GND on the amplifier.
  3. R+/R- on the amplifier connect to the right speaker.
  4. L+/L- on the amplifier connect to the left speaker.
  5. +5V and GND on the amplifier connect to the 3.3V and GND pins on the Argon.
In Part 3 we will complete construction of the Argon Hub and 3D print an enclosure for it. We will then move onto configuring the Xenon's.

Sunday, July 22, 2018

Espressif ESP32 Tutorial - Programming (ESP-IDF)

Overview


The native software development framework for the ESP-32 is called the Espressif IoT Development Framework (ESP-IDF). If you want maximum functionality and compatibility this is what you should use. It is a bit more "hard core" than some of the other programming options but it does help you better understand what is happening under the hood.

The ESP-IDF functionality includes menu based configuration, compiling and firmware download to ESP32 boards.




What you Need


To develop applications for ESP32 you will need:
  • A PC loaded with either Windows, Linux or the Mac operating system
  • Toolchain to build the Application for ESP32
  • ESP-IDF that essentially contains the API for ESP32 and scripts to operate the Toolchain
  • A text editor to write programs (Projects) in C, e.g. Eclipse
  • The ESP32 board itself and a USB cable to connect it to the PC
The quickest way to start development with the ESP32 is by installing a prebuilt toolchain. Head over to the Espressif site and follow the provided instructions for your OS. I will outline the process for Mac.

Install Prerequisites


  • install pip:
    sudo easy_install pip
    
  • install pyserial:
    sudo pip install pyserial

Tool Chain Setup


The ESP32 toolchain for macOS is available for download from the Espressif website:
Download this file, then extract it into the ~/esp directory:
mkdir -p ~/esp
cd ~/esp
tar -xzf ~/Downloads/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz
The tool chain will be extracted into the ~/esp/xtensa-esp32-elf/directory.

To use it, you will need to update your PATH environment variable in the ~/.profile file. To make xtensa-esp32-elf available for all terminal sessions, add the following line to your ~/.profile file:
export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin
You can edit the .profile file using nano (e.g. nano ~/.profile). While you are here add the IDF_PATH environment variable to make the ESP-IDF available as well. To include the IDF_PATH  add the following line to the ~/.profile file:
export IDF_PATH=~/esp/esp-idf
Quitting Terminal will make this change effective.
Note
If you have /bin/bash set as login shell, and both the .bash_profile and .profile files exist, then update .bash_profile instead.
Run the following command to check if IDF_PATH is set:
printenv IDF_PATH
The path previously entered into the ~/.profile file (or set manually) should be printed out.
Alternatively, you may create an alias for the above command. This way you can get access the tool chain only when you need it. 

To do this, add this alternate line to your ~/.profile file:
alias get_esp32="export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin" 
Then when you need the tool chain you can just type get_esp32 on the command line and the tool chain will be added to your PATH. You can do something similar for IDF_PATH.



Get the ESP-IDF


Besides the toolchain (that contains programs to compile and build the application), you also need ESP32 specific API / libraries. They are provided by Espressif in ESP-IDF repository. To get it, open terminal, navigate to the directory you want to put ESP-IDF, and clone it using git clonecommand:
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
ESP-IDF will be downloaded into ~/esp/esp-idf.


Establish a Serial Connection


Connect the ESP32 board to the PC using the USB cable. Once again, the following are for the Mac, if you have another OS then follow the instructions on the Espressif web site.


To check the device name for the serial port of your ESP32 board, run this command:
ls /dev/cu.*

On my MacBook Pro (early 2015) running High Sierra, the port on the Duinotech ESP32 Dev Board was not recognized. If this happens, you may need to install the drivers for the USB-serial converter for this board. It uses a CP2102 IC, and the drivers are found on the Silicon Labs CP2102 website:

https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers.

After you download the correct driver version for your OS. You will need to install it. On the Mac this involves, unzipping the archive, mounting the DMG disk image and then running the Silicon Labs VCP Driver.pkg. You will need to give permission to run this driver in the Security and Privacy preference (this will pop up). You should now be able to see the port.


On my Mac, the port name was: /dev/cu.SLAB_USBtoUART



Hello World Project



We will use the getstarted/hello_world project from the
examples directory in IDF.
Copy get-started/hello_world to the ~/esp directory:
cd ~/esp
cp -r $IDF_PATH/examples/get-started/hello_world .
You can find a range of other example projects in the examples directory in ESP-IDF. There are examples for:

  • Ethernet
  • Mesh
  • Protocols
  • System
  • Bluetooth
  • Getting Started
  • Peripherals
  • Storage
  • WiFi
These example project directories can be copied in the same way as shown above.
Important
The esp-idf build system does not support spaces in paths to esp-idf or to the projects.
In Terminal, change to the hello_world directory by typing cd ~/esp/hello_world. Then start the project configuration utility using menuconfig:
cd ~/esp/hello_world
make menuconfig
All going well, the following menu will be displayed:

In the menu, navigate to:

 Serial flasher config > Default serial port 

to configure the serial port that your ESP32 is connected to. 

Enter the name that you had above (e.g. /dev/cu.SLAB_USBtoUART). Confirm selection by pressing enter, save the configuration by selecting < Save > and then exit the config application by selecting < Exit >.
Here are couple of tips on the navigation and use of menuconfig:
  • Use up & down arrow keys to navigate the menu.
  • Use Enter key to go into a submenu, Escape key to go out or to exit.
  • Type ? to see a help screen. Enter key exits the help screen.
  • Use Space key, or Y and N keys to enable (Yes) and disable (No) configuration items with checkboxes “[*]
  • Pressing ? while highlighting a configuration item displays help about that item.
  • Type / to search the configuration items.

Build and Flash


Now you can build and flash the application. Run:
make flash
This will compile the application and all the ESP-IDF components, generate bootloader, partition table, and application binaries, and flash these binaries to your ESP32 board.
esptool.py v2.0-beta2
Flashing binaries to serial port /dev/ttyUSB0 (app at offset 0x10000)...
esptool.py v2.0-beta2
Connecting........___
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Attaching SPI flash...
Configuring flash size...
Auto-detected Flash size: 4MB
Flash params set to 0x0220
Compressed 11616 bytes to 6695...
Wrote 11616 bytes (6695 compressed) at 0x00001000 in 0.1 seconds (effective 920.5 kbit/s)...
Hash of data verified.
Compressed 408096 bytes to 171625...
Wrote 408096 bytes (171625 compressed) at 0x00010000 in 3.9 seconds (effective 847.3 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 82...
Wrote 3072 bytes (82 compressed) at 0x00008000 in 0.0 seconds (effective 8297.4 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting...
If there are no issues, at the end of build process, you should see messages describing progress of loading process. Finally, the end module will be reset and “hello_world” application will start.

Monitor


To check if the “hello_world” application is indeed running, type make monitor. This command will launch the IDF Monitor application:
$ make monitor
MONITOR
--- idf_monitor on /dev/ttyUSB0 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
ets Jun  8 2016 00:22:57
...
Several lines on, after start up and the diagnostic log, you should see “Hello world!” printed out by the application.

To exit the monitor use shortcut Ctrl+].

Blink Project


For direct comparison with the Arduino IDE in the previous post, we will also give the Blink project a crack. The process is very similar to hello_world.

Copy get-started/blink to the ~/esp directory:
cd ~/esp
cp -r $IDF_PATH/examples/get-started/blink .
In Terminal, change to the blink directory by typing cd ~/esp/blink. Then start the project configuration utility using menuconfig:
cd ~/esp/blink
make menuconfig
As for the hello_world example above, set the default serial port to the correct value, then save and exit.

Before building and flashing the ESP32, we just need to check which LED is going to blink. If you open up blink.c in your favourite text editor, you will see the line:

#define BLINK_GPIO CONFIG_BLINK_GPIO
So you can either run "make menuconfig" to select the GPIO to blink, or just change the number here. Since I already had the file open, I changed the number and saved the file.

It is interesting to compare the code required to blink a LED in native ESP-32 C versus Arduino C. The ESP-32 version is shown below.

#define BLINK_GPIO 2

void blink_task(void *pvParameter)
{
    /* Configure the IOMUX register for pad BLINK_GPIO (some pads are
       muxed to GPIO on reset already, but some default to other
       functions and need to be switched to GPIO. Consult the
       Technical Reference for a list of pads and their default
       functions.)
    */
    gpio_pad_select_gpio(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    while(1) {
        /* Blink off (output low) */
        gpio_set_level(BLINK_GPIO, 0);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        /* Blink on (output high) */
        gpio_set_level(BLINK_GPIO, 1);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main()
{
    xTaskCreate(&blink_task, "blink_task", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
}
While not being a lot harder than doing it with Arduino C++, it is certainly less readable. The standard Arduino library hides a lot of the boiler plate code. I've reproduced the standard Arduino blink code below for comparison.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Now you can build and flash the blink application. Run:
make flash
After a lot of scrolling on Terminal the blink binary will be flashed to your ESP-32 and the Dev Board reset. assuming you have a LED connected to pin 2, it should now be blinking.




Conclusion

So the obvious conclusion from all this is that if you want to do simple applications and you are already familiar with the Arduino IDE and language, then the Arduino IDE plus the ESP-32 extension is probably your best route.

If you want maximum functionality and compatibility, and are not afraid of a command line interface (CLI), then I would use the ESP-IDF.

Next up we look at trying to get the best of both worlds. Combining native compatibility with a GUI which includes features like code completion. This option still requires the ESP-IDF behind the scenes so installing this will get you ahead of the curve in the next tutorial.