Showing posts with label Bluetooth. Show all posts
Showing posts with label Bluetooth. Show all posts

Tuesday, 12 November 2019

ESP32 with PS3 Controller - M5StickC

Arduino PS3 Library for the ESP32

Library available originally written for use with Espressif library, but it does have beta support for Arduino at the time of writing.

https://github.com/jvpernis/esp32-ps3

In my experience the device works well in both loop and callback configurations, but I did see issues when attempting to read all the available data from PS3 controller and then write to the screen. It was causing a device reboot that I wasn't able to easily solve.

How to install library:

Tested with Arduino 1.8.10 on Windows 10.

  1. From the command line, navigate to Arduino/libraries/ folder
  2. git clone https://github.com/jvpernis/esp32-ps3.git
  3. git checkout develop
  4. Restart the Arduino IDE and the library should be available in the "examples" section


How to connect to controller:

There are two ways to configure the controller with your device

  1. Retrieve the Bluetooth MAC address of the device the controller is currently connected to
  2. Set the Bluetooth MAC address of the controller
I've opted for option 1 as it allows me to keep the device connected to my PS3 for normal use. To get the PS3 controller Bluetooth MAC address follow the instructions in the readme and retrieve using the SixaxisPairTool.


M5StickC Testing

Example program showing how to retrieve all the available information from the controller and write to the M5StickC screen:

#include <M5StickC.h>
#include <Ps3Controller.h>

// M5StickC screen parameters
#define TEXT_HEIGHT 16                // Height of text to be printed and scrolled
#define TOP_FIXED_AREA 14             // Number of lines in top fixed area (lines counted from top of screen)
#define BOTTOM_FIXED_AREA 0           // Number of lines in bottom fixed area (lines counted from bottom of screen)
#define YMAX 80                       // Bottom of screen area
#define XMAX 160                      // Bottom of screen area

// M5StickC tracking drawing to screen positions
int deviceName = 0;
int controllerInfoX = 20;
int joystickInfoX = 40;
int gyroInfoX = 60;


void setup() {
  // Setup the TFT display
  M5.begin();
  M5.Lcd.setRotation(3);                               // Must be setRotation(0) for this sketch to work correctly
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLUE);
  M5.Lcd.fillRect(0, 0, XMAX, TEXT_HEIGHT, TFT_BLUE);
  M5.Lcd.drawCentreString("AliPhoenix Hexapod", XMAX/2, 0, 2);
  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);    // Change colour for scrolling zone text
  
  // Setup the PS3 controller
  Ps3.begin("2c:81:58:a9:8d:76");

  // Setup the serial port for debugging
  Serial.begin(115200);
  Serial.println("Ready.");
}

void loop()
{
  if (Ps3.isConnected()){
    // Clear the area we will be writing to
    M5.Lcd.fillRect(0, controllerInfoX, XMAX, TEXT_HEIGHT, TFT_BLACK);
    
    if( Ps3.data.button.cross ){ 
      M5.Lcd.drawString("Button: CROSS", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.square ){
      M5.Lcd.drawString("Button: SQUARE", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.triangle ){
      M5.Lcd.drawString("Button: TRIANGLE", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.circle ){
      M5.Lcd.drawString("Button: CIRCLE", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.left ){
      M5.Lcd.drawString("Button: LEFT", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.right ){
      M5.Lcd.drawString("Button: RIGHT", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.up ){
      M5.Lcd.drawString("Button: UP", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.down ){
      M5.Lcd.drawString("Button: DOWN", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.l1 ){
      M5.Lcd.drawString("Button: L1", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.l2 ){
      M5.Lcd.drawString("Button: L2", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.l3 ){
      M5.Lcd.drawString("Button: L3", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.r1 ){
      M5.Lcd.drawString("Button: R1", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.r2 ){
      M5.Lcd.drawString("Button: R2", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.r3 ){
      M5.Lcd.drawString("Button: R3", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.start ){
      M5.Lcd.drawString("Button: START", 0, controllerInfoX, 2);
    }
    if( Ps3.data.button.select ){
      M5.Lcd.drawString("Button: SELECT", 0, controllerInfoX, 2);
    }
  //-----------------------------------------------------------------------
    // Clear the area we will be writing to
    M5.Lcd.fillRect(0, joystickInfoX, XMAX, TEXT_HEIGHT, TFT_BLACK);
    
    String messageJoystick;
    
    messageJoystick += "lx:";
    messageJoystick += Ps3.data.analog.stick.lx;
    messageJoystick += ", ly:";
    messageJoystick += Ps3.data.analog.stick.ly;
    messageJoystick += ", rx:";
    messageJoystick += Ps3.data.analog.stick.rx;
    messageJoystick += ", ry:";
    messageJoystick += Ps3.data.analog.stick.ry;
    
    // Write the message to the screen
    M5.Lcd.drawString(messageJoystick, 0, joystickInfoX, 2);
  //-----------------------------------------------------------------------
    String message = "PS3: Connected";
  
    // Clear the area we will be writing to
    M5.Lcd.fillRect(0, deviceName, XMAX, TEXT_HEIGHT, TFT_BLACK);
    
    if (Ps3.data.status.battery == ps3_status_battery_charging) message += ", Charging";
    else if (Ps3.data.status.battery == ps3_status_battery_high) message += ", High";
    else if (Ps3.data.status.battery == ps3_status_battery_full) message += ", Full";
    else if (Ps3.data.status.battery == ps3_status_battery_low) message += ", Low";
    else if (Ps3.data.status.battery == ps3_status_battery_dying) message += ", Dying";
    else if (Ps3.data.status.battery == ps3_status_battery_shutdown) message += ", Shutdown";
    else message += ", Undefined";
  
    // Write the message to the screen
    M5.Lcd.drawString(message, 0, deviceName, 2);
  //-----------------------------------------------------------------------
    String messageAccel;
    
    messageAccel += "a.x:";
    messageAccel += Ps3.data.sensor.accelerometer.x;
    messageAccel += " y:";
    messageAccel += Ps3.data.sensor.accelerometer.y;
    messageAccel += " z:";
    messageAccel += Ps3.data.sensor.accelerometer.z;
  
    String messageGyro;
    messageGyro += " g.z:";
    messageGyro += Ps3.data.sensor.gyroscope.z;
  
    M5.Lcd.drawString(messageAccel+messageGyro, 0, gyroInfoX, 2);
  }
  
  delay(100);
}

Monday, 9 September 2019

ARDUINO: ESP32 Bluetooth Serial Pass-through And Debugger - M5StickC

This is a simple Arduino project showing how to use the M5StickC device as a bluetooth/serial port pass through with the LCD screen as a built in debugger.

The device shows up on your phone as a bluetooth device called "M5StickC", and once you've connected from your device you can use an app like Serial Bluetooth Terminal communicate with your device.

Messages coming from USB serial port are printed as white text and messages coming from bluetooth are printed as blue text.



#include "bluetoothserial.h"
#include "m5stickc.h"

// M5StickC screen parameters
#define TEXT_HEIGHT 16                    // Height of text to be printed and scrolled
#define TOP_FIXED_AREA 14             // Number of lines in top fixed area (lines counted from top of screen)
#define BOTTOM_FIXED_AREA 0       // Number of lines in bottom fixed area (lines counted from bottom of screen)
#define YMAX 80                                  // Bottom of screen area
#define XMAX 160                                // Bottom of screen area

uint16_t  yStart = 0;                               // The initial y coordinate of the top of the scrolling area
uint16_t  yArea = YMAX-TOP_FIXED_AREA-BOTTOM_FIXED_AREA; // yArea must be a integral multiple of TEXT_HEIGHT
uint16_t  yDraw = 0;                              // The initial y coordinate of the top of the bottom text line
uint16_t  xPos = 0;                                // Keep track of the drawing x coordinate
byte      data = 0;
int       blank[19];                                    // We keep all the strings pixel lengths to optimise the speed of the top line blanking


BluetoothSerial SerialBT;


void setup() {
  // Setup the TFT display
  M5.begin();
  M5.Lcd.setRotation(3);                               // Must be setRotation(0) for this sketch to work correctly
  M5.Lcd.fillScreen(TFT_BLACK);

  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLUE);
  M5.Lcd.fillRect(0, 0, XMAX, TEXT_HEIGHT, TFT_BLUE);
  M5.Lcd.drawCentreString("Serial Term - 115200", XMAX/2, 0, 2);
  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);    // Change colour for scrolling zone text

  setupScrollArea(TOP_FIXED_AREA, BOTTOM_FIXED_AREA); // Setup scroll area
  for (byte i = 0; i<18; i++) blank[i]=0;            // Zero the array

  Serial.begin(115200);                                 // Initialise the serial ports
  SerialBT.begin("M5StickC");                       // Bluetooth device name
  Serial.println("\n# Bluetooth started #\n");  // Let the user know via usb serial port the device is ready
}

void loop() {
  // Check the Serial buffer for a new message
  while(Serial.available()) {
    data = Serial.read();                                 // Read the character from the serial port
    SerialBT.write(data);                                 // Write the message to the Bluetooth serial port
    M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK); 
    writeToScreen(data);                                // Also write to the M5StickC screen
  }

  // Check the bluetooth buffer for a new message
  while(SerialBT.available()) {
    data = SerialBT.read();                             // Read the character from the bluetooth port
    Serial.write(data);                                     // Write the message to the serial port
    M5.Lcd.setTextColor(TFT_BLUE, TFT_BLACK); 
    writeToScreen(data);                                // Also write to the M5StickC screen
  }
  
  delay(20);
}

void writeToScreen(byte data) {
  // If it is a CR or we are near end of line then scroll one line
  if (data == '\r' || xPos > XMAX) {
    xPos = 0;
    yDraw = scroll_line();                                // It can take 13ms to scroll and blank 16 pixel lines
  }
  
  if (data > 31 && data < 128) {
    xPos += M5.Lcd.drawChar(data,xPos,yDraw,2);
  }
}

/**
 * Scroll the display one text line
 */
int scroll_line() {
  // Store the old yStart, this is where we draw the next line
  int yTemp = yStart;
  
  // Use the record of line lengths to optimise the rectangle size we need to erase the top line
  M5.Lcd.fillRect(0,yStart,XMAX,TEXT_HEIGHT,TFT_BLACK);

  // Change the top of the scroll area
  yStart += TEXT_HEIGHT;

  // The value must wrap around as the screen memory is a circular buffer
  if (yStart >= YMAX)
    yStart = 0;

  // Now we can scroll the display
  scrollAddress(yStart);

  return  yTemp;
}

/**
 * Setup the vertical scrolling start address pointer
 */
void scrollAddress(uint16_t vsp) {
  M5.Lcd.writecommand(ST7735_VSCRDEF);  // Vertical scrolling pointer
  M5.Lcd.writedata(vsp>>8);
  M5.Lcd.writedata(vsp);
}

/**
 * Setup a portion of the screen for vertical scrolling
 * We are using a hardware feature of the display, so we can only scroll in portrait orientation
 */
void setupScrollArea(uint16_t tfa, uint16_t bfa) {
  M5.Lcd.writecommand(ST7735_VSCRDEF);  // Vertical scroll definition
  M5.Lcd.writedata(tfa >> 8);                               // Top Fixed Area line count
  M5.Lcd.writedata(tfa);
  M5.Lcd.writedata((YMAX-tfa-bfa)>>8);             // Vertical Scrolling Area line count
  M5.Lcd.writedata(YMAX-tfa-bfa);
  M5.Lcd.writedata(bfa >> 8);                              // Bottom Fixed Area line count
  M5.Lcd.writedata(bfa);
}

Sunday, 16 April 2017

HOME AUTOMATION: Teptron Move control from OpenHAB

I've previously explored how to control the bluetooth version of Teptron Move from a linux PC (or Raspberry Pi) with the built in Bluetooth adapter. While it's interesting to be able to do this from the Linux commands line it's not particularly practical for use in a home automation system. In this post

I'll explain how to integrate those controls with openHAB for more practical use.


OpenHAB Binding Configuration

In order to run a linux script from within openHAB we need to first install the "Exec Binding".

Exec Binding Installation


There are a few examples on how to configure the exec binding but none of the examples worked for me. I ended up following this post to get the right combination of Things/Items/Sitemaps working to give the following result:


I'll go in to some more detail below on how I configured my Things/Items/Sitemap to get the units working.


There are a few peculiarities in working with the Bluetooth version of the Teptron Move with the command line functions. I've noticed the following inconsistencies:

  • Sometimes the device does not respond and the command reports an error
  • Other times the device responds and accepts the commands but does not move
  • The time between receiving the accepted response and the device moving varies from immediate to ~5 seconds

I've tried a number of workarounds for this but so far I've had the most success with simply just retrying the command multiple times, but this has the issue of pausing the unit motion when the second/third command is resent. I'm not ready to give up on this one just yet, but I'm fast running out of ideas.


Move Control Script

Create a new script in the in the openHAB scripts folder:
 sudo nano /etc/openhab/scripts/teptron_move_control.sh  

Copy the following lines in to the file and save the file:
 #!/bin/sh  
 RESPONSE=""  
   
 # execute the command multiple times  
 #for i in 1 2 3  
 for i in 1  
 do  
  RESPONSE="$(/home/openhabian/development/csrmesh/bin/csrmesh-cli move --pin 8888 --dest 43:c5:5b:04:00:06 --objid $1 --position $2)"  
  echo "Response: ${RESPONSE}"  
  sleep 1  
 done  
   

Now make the script executable with the command:
 chmod +x /etc/openhab/scripts/teptron_move_command.sh  

This script accepts two input arguments:
  1. The move unit we want to move
  2. The position to be moved to (0 to 255)
Test the command is working as expected, as an example use the command:
 /etc/openhab/scripts/teptron_move_command.sh 2 255  

This will command unit 2 to move to the fully opened position.

Things Configuration

Create a new .things file for the blinds:
 sudo nano /etc/openhab/things/blinds.things  

Add the following lines:
 Thing exec:command:Blinds_GF_Lounge_North "Blinds" [ command="bash /etc/openhab2/scripts/teptron_move_control.sh 1 %2$s", interval=0, autorun=true ]  
 Thing exec:command:Blinds_GF_Lounge_South "Blinds" [ command="bash /etc/openhab2/scripts/teptron_move_control.sh 2 %2$s", interval=0, autorun=true ]  

The important part of the command here is the  name of the Thing, in this case "Blinds_GF_Lounge_North" and "Blinds_GF_Lounge_South". These will need to match the name of the items we will create next.


Items Configuration

Next we must define items that will take the input from the sitemap and send it as a parameter to the exec binding and execute the script:

Create a new .items file:
 sudo nano /etc/openhab/items/blinds.items  

Add the following items:
 String Blinds_GF_Lounge_North_Command "Blinds" { channel="exec:command:Blinds_GF_Lounge_North:input", autoupdate="false" }  
 String Blinds_GF_Lounge_South_Command "Blinds" { channel="exec:command:Blinds_GF_Lounge_South:input", autoupdate="false" }  

Ensure the names of the defined items match the names of the things we defined previously.

Sitemap Configuration

We need to define a couple of switches with custom mapping to command the move units to the "Open" and "Close" positions:
 sudo nano /etc/openhab/sitemaps/default.sitemap  

Add the following lines where applicable:
 Switch item=Blinds_GF_Lounge_North_Command label="North Blinds" mappings=["255"="Open", "0"="Close"]  
 Switch item=Blinds_GF_Lounge_South_Command label="South Blinds" mappings=["255"="Open", "0"="Close"]  

If everything was working properly, pressing the buttons on the sitemap should command the move units to the open and closed positions.



Technically it should be possible to use a slider here to set the blind position, but I'm yet to work out how to do it.

Remember the following command is great to be able to debug any openHAB issues:
 tail -f /var/log/openhab2/openhab.log -f /var/log/openhab2/events.log   

Saturday, 8 April 2017

ROBOTICS: Android App for Bluetooth Remote Control Robots

A while back I was tinkering with the MIT App Inventor project and wrote a little android app that would connect to a bluetooth device and send ASCII commands via a bluetooth serial connection. It turned out to be a really handy way to control my robots over bluetooth so I shared it and made a short youtube video showing how to use it. It's my most successful youtube video to date and I still get people asking for the codes.

Since then I taught myself to code for Android and rewrote the app with plans of releasing it on the app store, but never found the motivation to take it all the way. It's been a couple of years since I put any time in to active development of the app, so in the spirit of sharing the codes I've decided to upload my project to Github for anyone who is interested. To make it easier for use I've also included a compiled apk.


The App

Basically, the app connects to a bluetooth device and creates a serial connection and streams ASCII characters to and from the connected device. Typically the way I use this by assigning specific ASCII characters to buttons, then when coding my android project I interpret the ASCII characters and react based on the command.
Vertical Layout

Landscape Layout

Pair with Bluetooth device

Preferences and Help menu


The app also allows you to change the button labels as well as the characters being sent for each button.

The app requires a minimum of Android 4.0 and has been written to support a number of screen resolutions and configurations.

Features

Inverted button layout switches the position of the direction keys and action keys:





Receive commands from your robot with the Feedback Group scale scale bars. Commands need to be sent over serial in the format "SB1=XX" where XX is a number from 0 to 100.






Known Bugs

Pairing a new bluetooth device is clunky. I've had the most success in performing the pairing from within the settings menu first, then going back in to the app and connecting to the bluetooth device.