Showing posts with label Serial. Show all posts
Showing posts with label Serial. Show all posts

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);
}

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.

Monday, 3 April 2017

HOME AUTOMATION: ELK/NESS M1 Home Alarm Controller with OpenHAB over Serial

When I installed the NESS M1 home automation controller and home alarm I had plans of integrating it in to my overall setup. I purchased an M1XEP with plans of exposing the alarm control to the network but ran in to setup issues that I was never able to resolve.

When I installed my openHAB controller I looked for a binding for the NESS/ELK but was not able to find one. There are some active conversations on the forums but no available bindings, so I decided to try my hand at getting something going. I'm using a USB to Serial adaptor plugged from the serial port of my home alarm directly in to my Raspberry Pi running openHAB2.


OpenHAB Serial Binding

First thing to do is to install and configure the serial binding. I have already covered this in an older post.



Reading Zone States

The easiest thing to start with is to read the zone state from the NESS alarm controller. With serial configured this is simply a matter of reading the serial input data and interpreting the commands based on the well documented serial protocol for the controller.


Here are the files I have used to mirror the state of the motion sensors on my home alarm by reading the zone states:


alarm.items
 Switch AlarmUpdate  "Alarm Update"  { serial="/dev/ttyUSB0@115200" }  
 String AlarmMessage "Alarm Message" { serial="/dev/ttyUSB0@115200" }  
 Group gAlarm_PIR
 Contact Alarm_PIR_Zone1                                                (gAlarm_PIR)
 Contact Alarm_PIR_Zone2                                                (gAlarm_PIR)
 Contact Alarm_PIR_FF_MasterBedroom "Master Bedroom Motion Sensor [%s]" (gAlarm_PIR)
 Contact Alarm_PIR_Zone4                                                (gAlarm_PIR)
 Contact Alarm_PIR_Nursery "Nursery Motion Sensor [%s]"                 (gAlarm_PIR)
 Contact Alarm_PIR_FF_Stairs "Stairs Motion Sensor [%s]"                (gAlarm_PIR)
 Contact Alarm_PIR_GF_Lounge "Lounge Motion Sensor [%s]"                (gAlarm_PIR)
 Contact Alarm_PIR_GF_Stairs "Basement Stairs Motion Sensor [%s]"       (gAlarm_PIR)

init.rules
 rule "Init"  
 when  
     System started  
 then  
     // let openHAB settle and give other rules the chance to fire  
     createTimer(now.plusSeconds(180)) [|  
         gAlarm_PIR?.members.forEach[g |  
             g.postUpdate(CLOSED)  
         ]  
 end  


alarm.rules
 import java.lang.Integer.*  
 val AlarmMemoryMessage = "AM"  
 val ZoneChangedMessage = "ZC"  
 val NetworkAdapterPing = "XK"  
 val ZoneStatusTable_NormalUnconfigured = "0"  
 val ZoneStatusTable_NormalOpen = "1"  
 val ZoneStatusTable_NormalEOL = "2"  
 val ZoneStatusTable_NormalShort = "3"  
 val ZoneStatusTable_TroubleOpen = "5"  
 val ZoneStatusTable_TroubleEOL = "6"  
 val ZoneStatusTable_TroubleShort = "7"  
 val ZoneStatusTable_ViolatedOpen = "9"  
 val ZoneStatusTable_ViolatedEOL = "A"  
 val ZoneStatusTable_ViolatedShort = "B"  
 val ZoneStatusTable_BypassedOpen = "D"  
 val ZoneStatusTable_BypassedEOL = "E"  
 val ZoneStatusTable_BypassedShort = "F"  
 rule "Interpret Alarm Command"  
 when  
     Item AlarmMessage received update  
 then  
     var lines = AlarmMessage.state.toString.split('\n')  
     lines.forEach[line |  
         if(line.length > 6) {  
             var messageLength = line.substring(0,2)  
             var commandType = line.substring(2,4)  
             var checksum = line.substring(line.length-3, line.length-1)  
             //logInfo("alarm", "Message:" + line + " Len:" + messageLength + " Command:" + commandType)  
             if(commandType == AlarmMemoryMessage) {  
                 var alarmMemoryAreas = line.substring(4, 12)  
             }  
             else if(commandType == ZoneChangedMessage) {  
                 else if(commandType == ZoneChangedMessage) {  
                 var int zoneNumber = Integer::parseInt(line.substring(4,7))  
                 var zoneStatus = line.substring(7,8)  
                 //logInfo("alarm", "Zone:" + zoneNumber + " Status:" + zoneStatus)  
                 //logInfo("alarm", "gAlarm_PIR:" + gAlarm_PIR.members.filter(g | g.name == "Alarm_PIR_GF_Lounge").toString)  
                 var zone = Alarm_PIR_GF_Lounge  
                 var state = CLOSED  
                 // case statement setting the zone item from the message received  
                 switch zoneNumber {  
                     case zoneNumber == 1 : zone = Alarm_PIR_Zone1  
                     case zoneNumber == 2 : zone = Alarm_PIR_Zone2  
                     case zoneNumber == 3 : zone = Alarm_PIR_FF_MasterBedroom  
                     case zoneNumber == 4 : zone = Alarm_PIR_Zone4  
                     case zoneNumber == 5 : zone = Alarm_PIR_FF_Nursery  
                     case zoneNumber == 6 : zone = Alarm_PIR_FF_Stairs  
                     case zoneNumber == 7 : zone = Alarm_PIR_GF_Lounge  
                     case zoneNumber == 8 : zone = Alarm_PIR_GF_Stairs  
                 }  
                 // set the state of the zone from the message received  
                 if(zoneStatus == ZoneStatusTable_NormalUnconfigured) {  
                     state = CLOSED  
                 }  
                 else if(zoneStatus == ZoneStatusTable_NormalOpen) {  
                     state = CLOSED  
                 }  
                 else if(zoneStatus == ZoneStatusTable_NormalEOL) {  
                     state = CLOSED  
                 }  
                 else if(zoneStatus == ZoneStatusTable_TroubleOpen) {  
                     state = OPEN  
                 }  
                 else if(zoneStatus == ZoneStatusTable_TroubleEOL) {  
                     state = OPEN  
                 }  
                 else if(zoneStatus == ZoneStatusTable_TroubleShort) {  
                     state = OPEN  
                 }  
                 else if(zoneStatus == ZoneStatusTable_ViolatedOpen) {\  
                     state = OPEN  
                 }  
                 else if(zoneStatus == ZoneStatusTable_ViolatedEOL) {  
                     state = OPEN  
                 }  
                 else if(zoneStatus == ZoneStatusTable_ViolatedShort) {  
                     state = OPEN  
                 }  
                 else {  
                     state = CLOSED  
                 }  
                 postUpdate(zone, state)  
             }  
             else if(commandType == NetworkAdapterPing) {  
             }  
             else {  
                 //logInfo("alarm", "No code match")  
             }  
         }  
     ]  
 end  


Add the following lines to your sitemap:
 Frame label="Ground Floor Stairs" {  
   Text item=Alarm_PIR_GF_Stairs icon="motion"  
 }  


Testing

View the sitemap and we should see the motion sensor updates coming through



Test the sitemap is working as expected with the command:
 tail -f /var/log/openhab2/openhab.log -f /var/log/openhab2/events.log tail -f /var/log/openhab2/openhab.log -f /var/log/openhab2/events.log  

If everything is working as expected you should see updates coming through:
 ==> /var/log/openhab2/events.log <==  
 2017-04-03 13:44:24.818 [GroupItemStateChangedEvent] - gAlarm_PIR changed from CLOSED to UNDEF through Alarm_PIR_GF_Lounge  
 2017-04-03 13:44:28.191 [ItemStateChangedEvent   ] - AlarmMessage changed from 0AZC007900C2  
 1EAS000000000111111100000000000F  
 0CAM000000007F  
  to 0AZC007200C9  
 1EAS000000001111111100000000000E  
 0CAM000000007F  
 2017-04-03 13:46:26.579 [ItemStateChangedEvent   ] - Alarm_PIR_GF_Lounge changed from OPEN to CLOSED  


Update

There is a bug in openHAB2 that stops icon updates on the sitemap as the states are updated. It's documented here. The solution is to restart the OpenHAB server.

Monday, 27 March 2017

HOME AUTOMATION: OpenHAB2 Serial Binding on Raspberry Pi

I have always wanted to enable my home automation setup with per-room presence detection. With this in mind I purchased and installed an ELK/NESS home alarm with automation capability.

I haven't been able to find any decent OpenHAB binding for the alarm, and after recently giving up on getting my M1XEP serial to network box for my alarm working I've decided to try my hand at rolling my own serial solution. Here I'll discuss how I got the serial configuration up and running.

Installation

Install the binding from the configuration GUI


I'm using a USB to Serial adapter, but other serial devices should also show up as serial devices if they have been recognised by the kernel. You can check this with the command:
 dmesg  

Or to view messages relating to the serial port configuration use the command:
 dmesg | grep tty
This should tell you what port was assigned to your USB to Serial adapter, in my case ttyUSB0

Check which users have access to the serial port with the command:
 ll /dev/ttyUSB0  

Output should look something like:
 crw-rw---- 1 root dialout 188, 0 Mar 27 10:03 /dev/ttyUSB0  

You may need to add the default user to the dialout group:
 sudo adduser openhabian dialout  


It's a good time to check the serial connection to make sure it's working as expected. You can use the GNU screen command to view the serial port from the command line:
 screen /dev/ttyUSB0 115200  

The screen command won't exit with the typical commands like CTRL-X or CRTL-Z, to exit the screen command enter 'CTRL-a' then press '\', then hit 'Y' to exit the screen.

Now that we have the binding installed and confirmed the serial port is working as expected we can continue with the OpenHAB setup

OpenHAB2 Configuration 

I created and 'alarm.items' file to store alarm related configuration info:
 Switch AlarmUpdate "Alarm Update" { serial="/dev/ttyUSB0@115200" }  
 String AlarmMessage "Alarm Message" { serial="/dev/ttyUSB0@115200" }  

and a 'alarm.rules' file with the following:
 ule "Interpret Alarm Command"  
 when  
     Item AlarmMessage received update  
 then  
     logDebug("alarm", "Alarm message updated")  
 end  
   

Check everything is working with the command:
 tail -f /var/log/openhab2/openhab.log -f /var/log/openhab2/events.log  

And you should see log messages like the following:
 2017-03-27 12:08:27.946 [ItemStateChangedEvent   ] - AlarmMessage changed from 0AZC007200C9  
 1EAS000000001111111100000000000E  
 0CAM000000007F  
  to 16XK2708212270117011006C  

Now that the serial binding is working we can move on to interpreting the data!