I was recently (April 2021) selected to road test the Silicon Labs BGM220 Explorer Kit.
This blog is about one of my discoveries I made when testing out the BGM220 Explorer Kit.
Contents:
Introduction
As with many new products you start with assumptions on how you should use the product and then you discover during the learning process other ways of using which you had never considered before.
In this case, I started with assumption that the only way you can develop a custom application for the BGM220 Bluetooth module is to develop your application using the prescribed SDK and then you flash this onto the SoC (System on Chip) itself. This way your application and the Bluetooth wireless network stack both reside on the chip. What I’ve now subsequently learnt is that with the Silicon Labs v3.x Bluetooth Stack there is another operating mode available, which is called the Network Co-Processor (NCP) mode.
In NCP Mode, the Bluetooth stack still runs on the BGM220 SoC but your application will now reside and run on a separate host MCU or PC and the communication between the SoC and external MCU/PC is managed over a serial interface such UART.
Those familiar with cellular machine-to-machine (M2M) modems and modules would of course be thinking that this is nothing new, as most of these type of devices would use something similar with the AT (or Hayes) command set where these commands were sent via text phrases through UART. Adafruit also offer something similar with an AT command set for their nRF51822 Bluefruit SPI / UART Friend breakout boards.
With the BGM220, the application programming interface (API) between your application and the v3.x Bluetooth Stack is handled by a Silicon Labs Proprietary Protocol called BGAPI, which is a custom binary protocol used to control the BGM220 SoC.
What is also quite handy is that all the core BGAPI functionalities, including Direct Test Mode (DTM), Persistent Key Storage (PS) and Device Firmware Update (DFU) mode are also available in NCP mode.
To facilitate developers, Silicon Labs also provide an ANSI C reference implementation of the BGAPI binary protocol called BGLib. You have the option to use this library with your host MCU (or PC) to develop your own NCP mode application. I found that their Knowledge Base article (KBA_BT_1602: NCP Host Implementation and Example) is a useful reference to learn about some of the detail.
In my case, the BGLib option was not useful as I personally find developing desktop applications, which include a decent User Interface, not that quick and simple using C (or C++). Instead, I planned to use the Processing 3.0 IDE to develop my application, which is underpinned by the JAVA programming language, as I am now more familiar with this IDE having recently completed other projects using this IDE like my "Cheer Us Up" Project14 submission, for example.
So this blog covers what I've learnt to date when starting from scratch with NCP Mode and how I went about developing this desktop computer application using Processing without having to rely on the BGLib option. Note that if you search online, you can also find others who have done something similar using the Python programming language.
Getting started with NCP Mode
One way to get started with NCP Mode is to flash your BGM220 Explorer Kit with the Bluetooth - NCP Empty demo, which can found in Simplicity Studio 5 Example Projects and Demos for that board.
Once you have flashed your board you can then use the Bluetooth NCP Commander tool, which is an optional tool available within Simplicity Studio 5 Tools catalogue – note that you may have to download this tool separately within Simplicity Studio.
When you launch the Bluetooth NCP Commander tool you’ll be presented with this screen if you have already connected to your board on the Simplicity Studio launch screen, as in, you are able to communicate with the board via USB port. If your board is not connected it will ask you to connect to it.
This tool now gives you full control of your board via the BGAPI.
For example, you can configure your BGM220 board as a BLE “peripheral” device or alternatively you can use the board as a BLE “central” device and scan for other peripheral devices.
There is also a “Smart Console” which shows which command was sent and then what response was received via the USB/serial interface. It also provides a live feed of events as they occur. Through this console you can also manually enter in any command to choice... we’ll get into this a bit more later.
Here is a short video demonstrating how quickly you can configure your board to use NCP mode by flashing your board with the NCP_empty firmware which will then allow you to scan for other BLE devices using the NCP Commander tool. From start to finish takes less than 2 minutes.
So while NCP Commander is a great Simplicity Studio tool (there is plenty to detail provided and well worth exploring), which allows you to learn and test different commands, it is not really suitable for use as a customisable or automated stand-alone desktop application outside of the IDE.
Now, where does one go from here?
Building a custom NCP host tool (using Processing)
Well, you have to start by reading through the documentation such as the dedicated Silicon Labs application note (AN1259). This application note provides great detail on developing either a NCP target or a NCP host application. It also provides good detail on the NCP Commander Tool, which I had presented above. The app-note then goes on to describe how to build your own windows NCP host application, and provides good detail on the structure of the example NCP Host and Target projects and highlights the parts that can be important if you want to create your own project.
The problem with starting with this application note, is that you may think (as I initially did) that the only option available to you is to use this ANSI C library (BGlib), which as described in the app-note is made up of various files within different folders (in case you are hoping for just one stand-alone folder named “BGlib”). This means quite a steep learning curve to get familiar with the architecture and formats before attempting to use.
There is also the standard Silabs documentation portal which has a section on BGAPI as found in docs.silabs.com (BGAPI Section). This is the reference guide and is provides all the detail you need on commands available and the messaging and response data structures. It does not give you the big picture.
Thankfully, I stumbled across one of Silicon Labs Wireless Connectivity Tech Talks videos, titled “Implementing a Bluetooth Network Co-Processor (Aug 2020)”. This “Tech Talk” provided a great overview and highlighted what others have done previously using the Python programming language. Unfortunately this was not much use to me as I tend to look at Python as my option of last resort. Nevertheless it provided me with an approach and enough detail to get me started with my new favourite desktop dev-tool Processing.
And, here is the result.
Now before I jump into the detail, let me just start by saying that this was one of the few occasions where I really missed having a proper debug tool as it was rather tricky getting this code just to work using the Processing IDE (it's rather dated). On the positive side I discovered how to resolve some very interesting performance issues when combining the use of the Serial port interface to receive large volumes of data and handle a graphics UI at the same time.
Getting to grips with the BGAPI command set
First, we need to know the binary data structures used to transmit the commands and then we'll need to know how the data is formatted when we receive command responses and data events.
Thankfully, this information can be found on page 28 (Section 5: Custom API Support) of the Application Note (AN1259), which contains three tables detailing the Command Byte Sequence (table 5.1), the Response Byte Sequence (table 5.2) and the Event Byte Sequence (table 5.3). Here they are for reference:
Next, we need to know what commands can be sent and what the respective parameters and payloads should be.
Well, we can find all these details on the docs.silabs.com BGAPI page under the section BGAPI Headers. This page provides everything you need to know from data endianness (BGAPI protocol stores data in little endian) through to decoding specific bytes (like the 1st byte of the message header) within a BGAPI Message to the exact parameter values and expected message response for each command.
For example, the command “cmd_system_hello” uses the 0x01 as the “Class ID” parameter and 0x00 as “Message ID” and it does not require a payload (i.e. payload size is 0 bytes).
So in this case our binary command byte sequence (or byte array) to send this command would be: {0x20,0x00,0x01,0x00} where the 2nd parameter is the payload length size, followed by “ClassID” and “MessageID”. The response to this message would then be an echo of what was sent together with and a SL_STATUS_OK (or 0x00) if successful or an error code if otherwise (in uint16_t format).
Now, you may have noticed that when you have the Bluetooth NCP Command Tool loaded and your device is connected via the USB port, the first command this tool automatically sends is:
To replicate this in our code, we would send a byte sequence {0x20,0x00,0x01,0x15} for the command “sl_bt_system_get_identity_address()” and based on the docs.silabs.com reference, we would expect to get a response which includes two parameters “address” (which is formatted as data struct bd_addr or uint8_t addr[6]) and “type” (uint8_t) together with a SL_STATUS_OK if successful or Error Code if otherwise (in uint16_t format).
But if you tried this yourself with some code you will first receive a raw 4-byte response as follows: 0x20, 0x09, 0x01, 0x15.
This looks fine at face value, but how do you confirm this response is linked to the command that was just sent and how do you confirm the payload size?
Well, the docs.silabs.com reference provides us with the answer.
There is a formula given within the subsection Payload Length Calculation:
SL_BGAPI_MSG_LEN = ((((HDR) & 0x7) << 8) | (((HDR) & 0xff00) >> 8)), where HDR is the raw 4-byte header as a uint32_t integer.
Now it’s worth noting (as it caught me out, initially) that with the Processing (JAVA) language there are no unsigned data types, which means we need to use the “long” data type for uint32_t and “int” (or “char” is also possible) for uint8_t. A “byte” in Processing/JAVA is a signed data type with a range -127 to 128 while in this blog I still refer to a "byte" as being a uint8_t data type (C language). Hope that's not too confusing.
So in this example our long HDR value is 352389408 and using that formula we get SL_BGAPI_MSG_LEN = 9 as our answer for payload size.
Then to decode the message received so as to validate which command this response is linked to we convert our HDR (long) value to a “Message ID” (long) value using this formula:
msg_ID = HDR & 0xffff00f8 = 352387104;
Now we can split this long value back to a 4-int array as follows:
Hdr[0] = int((msg_ID >> 0) & 0xFF); Hdr[1] = int((msg_ID >> 8) & 0xFF); Hdr[2] = int((msg_ID >> 16) & 0xFF); Hdr[3] = int((msg_ID >> 24) & 0xFF);
Now if we check these 4 values we get: 0x20, 0x00, 0x01, 0x15 as our linked command confirmation.
So that is how to parse just the message header.
To parse the payload, we can refer to the documentation reference, which informs us that data structure type “bd_addr” is a 6-byte array which contains the MAC address in reverse order format and the 2nd parameter is “type”.
This gives us 7 bytes of the 9 byte payload. So, what are the other two bytes. This is response code telling us whether there is an error, in which case here is a uint16_t error code, or all is fine. The error code is always for 1st two bytes of the payload.
Decoding Scan Response packets
We know from the Event Byte Sequence table (table 5.3) that when an event is triggered the first byte in message header will be 0xA0. This will be followed by the payload length and then Message Class and Message ID.
Then when the BGM220 is in scanning mode we will start to receive event scan reports as soon as a Bluetooth LE device is detected. The data structure used in an event report (sl_bt_evt_scanner_scan_report_s) will then contain 12 defined data fields, which can be parsed and handled by our code:
Data field [1] is defined as packet type (size: 1 byte / type uint8_t). This is best viewed in binary (bit) format:
Data field [2] is the Bluetooth address of the remote BLE device (size: 6 bytes / type uint8_t).
Data field [3] is the advertiser address type (size: 1 byte / type uint8_t). This has 3 possible values:
- 0: Public address
- 1: Random address
- 255: No address provided (anonymous advertising)
Data field [4] is the Bonding_handle if the remote advertising device has previously bonded with the local (central) device (size: 1 byte / type uint8_t). This has 2 possible values: 255 / 0xFF to indicate No bonding or a bonding handle index is provided.
Data fields [5] and [6] is the PHY on which advertising packets are transmitted on the primary [5] and secondary [6] advertising channels (size: 1 byte / type uint8_t). This has 3 possible values:
- 0x01 – 1M PHY
- 0x02 – 2M PHY
- 0x04 – Coded PHY
Data field [7] is the Advertising Set Identifier (size: 1 byte / type uint8_t).
Data field [8] is the TX power value in the received packet header. Units: dBm (size: 1 byte / type int8_t). If the value is 127 then this information is unavailable.
Data field [9] is the Signal strength indicator (RSSI) in the last received packet. Units: dBm (size: 1 byte / type int8_t).
Data field [10] is the channel number on which the last packet was received (size: 1 byte / type uint8_t).
Data field [11] is the periodic advertising interval (size: 2 bytes / type uint16_t). Value 0 indicates no periodic advertising value provided. Otherwise the value (Range: 0x06 to 0xFFFF) provided is multiplied by 1.25 ms to give periodic advertising interval (ms).
And, Data field [12] is the advertising or scan response data (size is variable / type uint8array). The uint8array is a data structure made up of 1 byte representing the data array size and then n bytes which is the actual advertising/scan response data.
Processing 3.x Demo Code
Note that the code presented here is simply a first build to prove the concept. The code uses the polling method to check if there is any data available from the serial port rather than using the SerialEvent callback method. I've also included a "data" folder as an attachment. This folder should be inserted into your sketch folder. It contains the button svg files, a banner png file and a true type font file.
The code also uses the noLoop() and loop() functions inside the main draw() routine to control when to update the screen. I found that using auto frame rate or if you defined a frame rate using frameRate(fps) that this had a very significant detrimental impact on the UART data flow rate. It took me a good while to figure this out as I found that the BGM220 was not responding to my scan_stop requests.
Besides that hurdle to resolve, the rest was fairly straightforward putting the code together. For the scan response packets I used JSON arrays and objects to store the data and this seems to work pretty well for me.
At the moment there is no recovery methods inserted into the code should data get corrupted or other runtime error codes are received. For now it simply terminates.
I am still pleased with the result. Time will tell if this code base is a good place to start when developing other NCP mode host applications.
/** * Silicon Labs BGAPI NCP Host Demo * by BigG * Copyright 2021 * * Note: This sketch assumes that the device on the other end of the serial * port is using the Silicon Labs BGAPI Demo Firmware */ import processing.serial.*; final boolean DEBUG = false; final color orange = color(95, 45, 0); final color e14blue = color(0, 102, 204); final int btnWidth = 210; final int btnHeight = 80; float btnX = 0; float btnY = 0; final int sl_bt_system_hello[] = {0x20,0x00,0x01,0x00}; final int sl_bt_system_reset[] = {0x20,0x01,0x01,0x01,0x00}; // We can set this up as an ArrayList (TODO) // For now we assume this is the order we will work from. This is controlled by index variable Commandsent final int sl_bt_system_get_version[] = {0x20,0x00,0x01,0x1b}; final int sl_bt_system_get_identity_address[] = {0x20,0x00,0x01,0x15}; final int sl_bt_scanner_set_timing[] = {0x20,0x05,0x05,0x01,0x01,0x01,0x01,0x99,0x00}; final int sl_bt_scanner_start[] = {0x20,0x02,0x05,0x03,0x01,0x01}; //settings: 1M PHY, discover_generic final int sl_bt_scanner_stop[] = {0x20,0x00,0x05,0x05}; int SCANSTOPINTERVAL = 15000; enum Level { system_get_version, system_get_identity_address, scanner_set_timing, scanner_start, scanner_stop, terminate } // UI Globals boolean overBox = false; boolean locked = false; PImage img; // Declare variable "img" of type PImage for our banner PShape btn; PShape btnHover; PShape btnClick; PFont f; String Header = "BGAPI NCP Demo"; String SubHeader = ""; // Serial UART interface Serial NCPserial; // The silabs NCP serial port int PortNum = -1; // the serial port matching index for USB port String NCPString = null; int[] NCPcheck = new int[4]; // Where we'll put what we receive IntList get_version = new IntList(); // App Global Variables Level CommandLevel = Level.system_get_version; boolean headerRecd = false; boolean headerValid = false; long headerMessage = 0; IntList Payload = new IntList(); JSONArray ScanReport; int deviceCnt = 0; int respCount = 0; // A count of how many bytes we receive int CommandType = 0x00; // This tracks whether we have a 0x20 or a 0xA0 header response from BGM220 int PayloadSize = 0; int Scan_t = 0; boolean firstContact = false; boolean ScanBtn_Show = false; boolean Scan_Click = false; boolean refreshScreen = false; // We don't need to continually refresh the screen - as this slows down serial port void setup() { size(1080, 640); rectMode(RADIUS); shapeMode(CENTER); f = createFont("SourceCodePro-Regular.ttf", 24); textFont(f); textAlign(CENTER, TOP); //Define the position of our button btnX = width*0.50; btnY = height*0.75; img = loadImage("BGM220P-e14Banner.png"); // Load the image into the program btn = loadShape("ScanButton.svg"); btnHover = loadShape("ScanButtonHover.svg"); btnClick = loadShape("ScanButtonClick.svg"); btn.setVisible(false); btnHover.setVisible(false); btnClick.setVisible(false); // Print a list of the serial ports, for debugging purposes: //printArray(Serial.list()); // We will use the last serial port found that matches our search criteria (this works with Linux or Windows) for (int i = 0; i < Serial.list().length; i++) { if ((Serial.list()[i].indexOf("ACM",0) >= 0) || (Serial.list()[i].indexOf("COM",0) >= 0)) { PortNum = i; } } if (PortNum >=0) { String portName = Serial.list()[PortNum]; if (DEBUG) println(portName); SubHeader = "Serial Port: " + portName; NCPserial = new Serial(this, portName, 115200); ScanReport = new JSONArray(); delay(1000); NCPserial.clear(); //Let's write our first command to get the BGM220P version if (DEBUG) println("CMD: sl_bt_system_get_version"); Scan_t = millis(); // We use this timer to determine if we can communicate with our device for (byte x = 0; x < 4; x++) NCPcheck[x] = sl_bt_system_get_version[x]; headerRecd = false; // set this to false // create a temporary byte array byte[] tempArray = new byte[sl_bt_system_get_version.length]; for (int xx = 0; xx < sl_bt_system_get_version.length; xx++) { tempArray[xx] = byte(sl_bt_system_get_version[xx]); } NCPserial.write(tempArray); } if (NCPserial == null) SubHeader = "BGM220P Serial Port is not detected"; } void draw() { background(255); image(img, (width*0.5)-(img.width*0.15), 10, img.width*0.3, img.height*0.3); fill(e14blue); textSize(28); if (!Scan_Click) text(Header, width*0.5, height*0.5-100); // Text wraps within text box // Test if the cursor is over the scan me button else text(Header, 10, height*0.5-100); // Text wraps within text box // Test if the cursor is over the scan me button fill(orange); textSize(12); if (!Scan_Click) text(SubHeader, width*0.5, height*0.5-50); // Text wraps within text box // Test if the cursor is over the scan me button else text(SubHeader, 10, height*0.5-50); // Text wraps within text box // Test if the cursor is over the scan me button if (NCPserial != null) { if (CommandLevel != Level.terminate) { refreshScreen = false; noLoop(); } while (!refreshScreen) { if (ScanBtn_Show) { if (!Scan_Click) ScanButton_handler(); else { } } if (!headerRecd) { if (NCPserial.available()>=4) { CommandType = 0x00; int[] Hdr = new int[4]; Hdr[0] = int(NCPserial.read()); Hdr[1] = int(NCPserial.read()); Hdr[2] = int(NCPserial.read()); Hdr[3] = int(NCPserial.read()); headerMessage = Hdr[0] | (Hdr[1] << 8) | (Hdr[2] << 16) | (Hdr[3] << 24); PayloadSize = int((((headerMessage) & 0x7) << 8) | (((headerMessage) & 0xff00) >> 8)); long msg_ID = headerMessage & 0xffff00f8; Hdr[0] = int((msg_ID >> 0) & 0xFF); Hdr[1] = int((msg_ID >> 8) & 0xFF); Hdr[2] = int((msg_ID >> 16) & 0xFF); Hdr[3] = int((msg_ID >> 24) & 0xFF); // If this header is a response to a command, we validate otherwise if event we ignore this check if (Hdr[0] == 0x20) { CommandType = Hdr[0]; if (DEBUG) println("Payload len: " + PayloadSize + " | Header: 0x" + hex(Hdr[0],2) + " 0x" + hex(Hdr[1],2) + " 0x" + hex(Hdr[2],2) + " 0x" + hex(Hdr[3],2)); // Validate the header against command sent headerValid = true; if (NCPcheck[0] != Hdr[0]) headerValid = false; // we ignore index 1 if (NCPcheck[2] != Hdr[2]) headerValid = false; if (NCPcheck[3] != Hdr[3]) headerValid = false; if (DEBUG) println("Header Validated: " + headerValid); headerRecd = true; respCount = 0; if (!firstContact) firstContact = true; } else if(Hdr[0] == 0xA0) { CommandType = Hdr[0]; //println("Scan report"); //println("Payload len: " + PayloadSize + " | Header: 0x" + hex(Hdr[0],2) + " 0x" + hex(Hdr[1],2) + " 0x" + hex(Hdr[2],2) + " 0x" + hex(Hdr[3],2)); headerRecd = true; respCount = 0; if (!firstContact) firstContact = true; } else { if (DEBUG) println("Unknown header format: 0x" + hex(Hdr[0],2)); } } else { if (!firstContact) { if (Scan_t > 0 && (millis() - Scan_t) > 3000) { if (DEBUG) println("UART Timeout. Cannot communicate with BGM220P"); SubHeader += "\r\nUART Timeout. Cannot communicate with BGM220P"; Scan_t = 0; refreshScreen = true; } } } } else { if (PayloadSize > 0) { if (NCPserial.available()>0) { Payload.append(NCPserial.read()); respCount++; if (respCount == PayloadSize) { headerRecd = false; // set this to false to wait for next header if (CommandType == 0x20) { switch(CommandLevel) { case system_get_version: if (system_get_version_handler()) { // Now let's get the sl_bt_system_get_identity_address if (DEBUG) println("CMD: system_get_identity_address"); for (byte x = 0; x < 4; x++) NCPcheck[x] = sl_bt_system_get_identity_address[x]; // create a temporary byte array byte[] tempArray = new byte[sl_bt_system_get_identity_address.length]; for (int xx = 0; xx < sl_bt_system_get_identity_address.length; xx++) { tempArray[xx] = byte(sl_bt_system_get_identity_address[xx]); } // Clear our Payload list Payload.clear(); // Define next CommandLevel to process CommandLevel = Level.system_get_identity_address; //Write to the BGM220P module NCPserial.write(tempArray); } else { println("Terminating Program"); Payload.clear(); NCPserial.clear(); while(true) {;;} } break; case system_get_identity_address: // We only process this section once (i.e. when ScanBtn_Show = false) if (system_get_identity_address_handler()) { // Clear our Payload list Payload.clear(); ScanBtn_Show = true; } else { println("Terminating Program"); Payload.clear(); NCPserial.clear(); while(true) {;;} } break; case scanner_set_timing: if (system_scanner_set_timing_handler()) { // Now we can start our scan if (DEBUG) println("CMD: sl_bt_scanner_start"); for (byte x = 0; x < 4; x++) NCPcheck[x] = sl_bt_scanner_start[x]; // create a temporary byte array byte[] tempArray = new byte[sl_bt_scanner_start.length]; for (int xx = 0; xx < sl_bt_scanner_start.length; xx++) { tempArray[xx] = byte(sl_bt_scanner_start[xx]); } // Clear our Payload list Payload.clear(); // Define next CommandLevel to process CommandLevel = Level.scanner_start; // We set a scan_timer to handle a scanning timeout Scan_t = millis(); NCPserial.write(tempArray); } else { println("Terminating Program"); Payload.clear(); NCPserial.clear(); while(true) {;;} } break; case scanner_start: if (system_scanner_start_handler()) { Payload.clear(); } else { println("Terminating Program"); Payload.clear(); NCPserial.clear(); while(true) {;;} } break; case scanner_stop: if (system_scanner_stop_handler()) { Payload.clear(); NCPserial.clear(); println("RESPONSE OK: sl_bt_scanner_stop"); Header += " Auto Stopped"; CommandLevel = Level.terminate; refreshScreen = true; } else { println("Terminating Program"); Payload.clear(); NCPserial.clear(); while(true) {;;} } break; case terminate: break; } } else if (CommandType == 0xA0) { if (scan_report_handler()) { } Payload.clear(); if ((millis() - Scan_t) > SCANSTOPINTERVAL) { for (byte x = 0; x < 4; x++) NCPcheck[x] = sl_bt_scanner_stop[x]; // create a temporary byte array byte[] tempArray = new byte[sl_bt_scanner_stop.length]; for (int xx = 0; xx < sl_bt_scanner_stop.length; xx++) { tempArray[xx] = byte(sl_bt_scanner_stop[xx]); } // Define next CommandLevel to process CommandLevel = Level.scanner_stop; Scan_t = millis(); NCPserial.setRTS(true); while (!NCPserial.getCTS()) print("."); NCPserial.write(tempArray); NCPserial.setRTS(false); println("CMD: sl_bt_scanner_stop"); SCANSTOPINTERVAL = 1000; } } } } } } } loop(); } } void mousePressed() { if(overBox) locked = true; else locked = false; } void mouseReleased() { if (locked && CommandLevel == Level.system_get_identity_address) { Scan_Click = true; btn.setVisible(false); btnClick.setVisible(false); btnHover.setVisible(false); // Add in the headers for the scan report textAlign(LEFT, TOP); Header = "Scanning Report:"; SubHeader = "PckType | MAC | AddrTyp | Bond | PHY | RSSI | Chl | Data"; refreshScreen = true; // Now let's get the sl_bt_scanner_set_timing if (DEBUG) println("CMD: scanner_set_timing"); for (byte x = 0; x < 4; x++) NCPcheck[x] = sl_bt_scanner_set_timing[x]; // create a temporary byte array byte[] tempArray = new byte[sl_bt_scanner_set_timing.length]; for (int xx = 0; xx < sl_bt_scanner_set_timing.length; xx++) { tempArray[xx] = byte(sl_bt_scanner_set_timing[xx]); } // Define next CommandLevel to process CommandLevel = Level.scanner_set_timing; NCPserial.write(tempArray); } locked = false; } void ScanButton_handler() { refreshScreen = true; // Draw the button if (mouseX > btnX-btnWidth*0.5 && mouseX < btnX+btnWidth*0.5 && mouseY > btnY-btnHeight*0.5 && mouseY < btnY+btnHeight*0.5) { overBox = true; if(!locked) { btn.setVisible(false); btnClick.setVisible(false); btnHover.setVisible(true); shape(btnHover, btnX, btnY); // If command sent, we need to evaluate the header message contained within the first 4 bytes } else { btnHover.setVisible(false); btn.setVisible(false); btnClick.setVisible(true); shape(btnClick, btnX, btnY); // If command sent, we need to evaluate the header message contained within the first 4 bytes } } else { // Draw the button //btn = btnDefault; btnHover.setVisible(false); btnClick.setVisible(false); btn.setVisible(true); shape(btn, btnX, btnY); // If command sent, we need to evaluate the header message contained within the first 4 bytes overBox = false; } } boolean system_get_version_handler() { refreshScreen = true; if (error_check((Payload.get(0) | Payload.get(1)<<8))) { int major = Payload.get(2) | (Payload.get(3) << 8); //major int minor = Payload.get(4) | (Payload.get(5) << 8); //minor int patch = Payload.get(6) | (Payload.get(7) << 8); //patch int build = Payload.get(8) | (Payload.get(9) << 8); //build long bootloader = Payload.get(10) | (Payload.get(11) << 8) | (Payload.get(12) << 16) | (Payload.get(13) << 24); int hasha = Payload.get(14) | (Payload.get(15) << 8); long hashb = Payload.get(16) | (Payload.get(17) << 8); // This needs to be long to prevent integer overflow long hash = hasha | hashb << 16; String msg = "Sys_Ver: Major:" + major + ", Minor:" + minor + ", Patch:" + patch + ", Build:" + build + ", Bootloader:" + bootloader + ", Hash:" + hash; if (DEBUG) println(msg + "\r\n"); SubHeader += "\r\n" + msg; } else return false; return true; } boolean system_get_identity_address_handler() { refreshScreen = true; if (error_check((Payload.get(0) | Payload.get(1)<<8))) { String msg = "BGM220P MAC: " + hex(Payload.get(7),2) + ":" + hex(Payload.get(6),2) + ":" + hex(Payload.get(5),2) + ":" + hex(Payload.get(4),2) + ":" + hex(Payload.get(3),2) + ":" + hex(Payload.get(2),2) + ", type: " + Payload.get(8); if (DEBUG) println(msg + "\r\n"); SubHeader += "\r\n" + msg; } else return false; return true; } boolean system_scanner_set_timing_handler() { refreshScreen = true; if (error_check((Payload.get(0) | Payload.get(1)<<8))) { if (DEBUG) println(Payload + "\r\n"); } else return false; return true; } boolean system_scanner_start_handler() { refreshScreen = true; if (error_check((Payload.get(0) | Payload.get(1)<<8))) { if (DEBUG) println(Payload + "\r\n"); } else return false; return true; } boolean system_scanner_stop_handler() { refreshScreen = true; if (error_check((Payload.get(0) | Payload.get(1)<<8))) { if (DEBUG) println(Payload + "\r\n"); } else return false; return true; } boolean scan_report_handler() { refreshScreen = true; //Start with Subheader Titles SubHeader = "PckType | MAC | AddrTyp | Bond | PHY | RSSI | Chl | Data"; boolean CreateNewDevice = true; if (ScanReport.size() > 0) { // Check if device already exists, and if so update records for (int x = 0; x < ScanReport.size(); x++) { JSONObject device = ScanReport.getJSONObject(x); JSONArray MACData = device.getJSONArray("MAC"); JSONArray AdvData = device.getJSONArray("AdvData"); if (Payload.get(0) == device.getInt("PckType")) { if ((MACData.getInt(0)==Payload.get(1)) && (MACData.getInt(1)==Payload.get(2)) && (MACData.getInt(2)==Payload.get(3)) && (MACData.getInt(3)==Payload.get(4)) && (MACData.getInt(4)==Payload.get(5)) && (MACData.getInt(5)==Payload.get(6))) { CreateNewDevice = false; // Update the values device.setInt("RSSI", byte(Payload.get(13))); for (int j = 17; j < Payload.size(); j++) { AdvData.setInt(j-17, Payload.get(j)); } device.setJSONArray("AdvData", AdvData); if (DEBUG) { print("MAC: "+ hex(Payload.get(6),2) + ":" + hex(Payload.get(5),2) + ":" + hex(Payload.get(4),2) + ":" + hex(Payload.get(3),2) + ":" + hex(Payload.get(2),2) + ":" + hex(Payload.get(1),2)); print(" | RSSI: " + byte(Payload.get(13)) + " | channel: " + Payload.get(14)); print(" | Data: 0x"); for (int y = 17; y < Payload.size(); y++) print(hex(Payload.get(y),2)); println(""); println("Device Matched!"); } } } // Update the data on the screen int[] MAC = MACData.getIntArray(); int[] Dat = AdvData.getIntArray(); SubHeader += "\r\n" + binary(device.getInt("PckType"),8)+" | " + hex(MAC[5],2)+":"+hex(MAC[4],2)+":"+hex(MAC[3],2)+":"+hex(MAC[2],2)+":"+hex(MAC[1],2)+":"+hex(MAC[0],2); SubHeader += " | " + device.getString("AddrType") + " | " + device.getString("Bonding") + " | " + device.getString("P-PHY") + " | " + device.getInt("RSSI") + " | " + device.getInt("Chnl"); SubHeader += " | 0x"; for (int y = 0; y < Dat.length; y++) SubHeader +=hex(Dat[y],2); } } if (CreateNewDevice) { // Create our first record JSONObject device = new JSONObject(); JSONArray MACData = new JSONArray(); JSONArray AdvData = new JSONArray(); device.setInt("PckType", Payload.get(0)); for (int i = 1; i < 7; i++) MACData.setInt(i-1, Payload.get(i)); device.setJSONArray("MAC", MACData); if (Payload.get(7) == 0) device.setString("AddrType", "Public"); else if (Payload.get(7) == 1) device.setString("AddrType", "Random"); else if (Payload.get(7) == 255) device.setString("AddrType", "Anon"); if (Payload.get(8) == 255) device.setString("Bonding", "None"); else device.setString("Bonding", "Handle:0x" + hex(Payload.get(8),2)); if (Payload.get(9) == 0x01) device.setString("P-PHY", "1M"); else if (Payload.get(9) == 0x02) device.setString("P-PHY", "2M"); else if (Payload.get(9) == 0x04) device.setString("P-PHY", "Coded"); else device.setString("P-PHY", "0x"+hex(Payload.get(9),2)); device.setInt("RSSI", byte(Payload.get(13))); device.setInt("Chnl", byte(Payload.get(14))); for (int i = 17; i < Payload.size(); i++) { AdvData.setInt(i-17, Payload.get(i)); } device.setJSONArray("AdvData", AdvData); ScanReport.setJSONObject(deviceCnt, device); deviceCnt++; int[] MAC = MACData.getIntArray(); int[] Dat = AdvData.getIntArray(); // Update the data on the screen SubHeader += "\r\n" + binary(device.getInt("PckType"),8)+" | " + hex(MAC[5],2)+":"+hex(MAC[4],2)+":"+hex(MAC[3],2)+":"+hex(MAC[2],2)+":"+hex(MAC[1],2)+":"+hex(MAC[0],2); SubHeader += " | " + device.getString("AddrType") + " | " + device.getString("Bonding") + " | " + device.getString("P-PHY") + " | " + device.getInt("RSSI") + " | " + device.getInt("Chnl"); SubHeader += " | 0x"; for (int y = 0; y < Dat.length; y++) SubHeader +=hex(Dat[y],2); if (DEBUG) { print(binary(Payload.get(0),8)); print(" | MAC: "+ hex(Payload.get(6),2) + ":" + hex(Payload.get(5),2) + ":" + hex(Payload.get(4),2) + ":" + hex(Payload.get(3),2) + ":" + hex(Payload.get(2),2) + ":" + hex(Payload.get(1),2)); print(" | Addr Type: 0x" + hex(Payload.get(7),2) + " | Bonding: 0x" + hex(Payload.get(8),2) + " | P-PHY: 0x" + hex(Payload.get(9),2) + " | S-PHY: 0x" + hex(Payload.get(10),2)); println(" | adv_sid: " + Payload.get(11) + " | TX: " + byte(Payload.get(12)) + " | RSSI: " + byte(Payload.get(13)) + " | channel: " + Payload.get(14)); print("Data: 0x"); for (int x = 17; x < Payload.size(); x++) print(hex(Payload.get(x),2)); println(""); } } return true; } boolean error_check(int error) { if (error == 0) return true; else { switch(error) { case 0x0001: println("ERROR: SL_STATUS_FAIL"); break; case 0x0002: println("ERROR: SL_STATUS_INVALID_STATE"); break; case 0x0003: println("ERROR: SL_STATUS_NOT_READY"); break; case 0x0004: println("ERROR: SL_STATUS_BUSY"); break; case 0x0005: println("ERROR: SL_STATUS_IN_PROGRESS"); break; case 0x0006: println("ERROR: SL_STATUS_ABORT"); break; case 0x0007: println("ERROR: SL_STATUS_TIMEOUT"); break; case 0x0008: println("ERROR: SL_STATUS_PERMISSION"); break; case 0x0009: println("ERROR: SL_STATUS_WOULD_BLOCK"); break; case 0x000A: println("ERROR: SL_STATUS_IDLE"); break; case 0x000B: println("ERROR: SL_STATUS_IS_WAITING"); break; case 0x000C: println("ERROR: SL_STATUS_NONE_WAITING"); break; case 0x000D: println("ERROR: SL_STATUS_SUSPENDED"); break; case 0x000E: println("ERROR: SL_STATUS_NOT_AVAILABLE"); break; case 0x000F: println("ERROR: SL_STATUS_NOT_SUPPORTED"); break; case 0x0021: println("ERROR: SL_STATUS_INVALID_PARAMETER"); break; default: println("ERROR: 0x" + hex(error,4) + " / " + error); break; } } return false; }
Top Comments