Jump to content

Unraveling Ninebot One E+ BLE Protocol - Success?


Paco Gorina

Recommended Posts

A possible compatible protocol that allows better performance when supported in the application and maintains compatibility could be :

- Let the request of a value be a "subscription" to the value. It would be in effect till it is cancelled in some way. That way the application doesn't need to flood the wheel with requests, just one and would continue to receive updates automatically.

- A Legacy application would send many requests but most would have no effect.

- As the Wheel knows when values change it would only notify a value when it changes or if it receives the request so new applications would be simpler.

- Also I woud try not to split answers between many packets. Probably it is the same to the Nindroid application as far as we don't send packets too long (I believe is 28 bytes). It would be better to send more answers. That mades the client application near stateless which is always a plus.

 

Link to comment
Share on other sites

I'm now able to remotely control and drive the motion of the Ninebot mini, it's quite similar to what Paco described in the OP. Here are the additional details:

The Ninebot mini uses a UART-style interface, similar to what Paco described, with some slight differences. It actually uses the same UUIDs as the "Noridc UART Service" 6e400001-b5a3-f393-e0a9-e50e24dcca9e

WRITE COMMANDs (Write without response) are sent from the app to the "RX Characteristic" 6e400002-b5a3-f393-e0a9-e50e24dcca9e ("RX" is from the point-of-view of the Ninebot mini)

NOTIFICATIONS of "TX Characteristic" value changes occur on 6e400003-b5a3-f393-e0a9-e50e24dcca9e

Again, all messages are sent as packets structured like

 +---+---+---+---+---+---+---+---+---+
 |x55|xAA| l | b1| b2| c |dat|ck0|ck1|
 +---+---+---+---+---+---+---+---+---+


x55 and xAA are indeed a fixed header; you can see that if you take a look through the decompiled Android app.

b1 and b2 can vary, they are usually x09 and x01, respectively, as Paco observed, but for a lot of the "command" types that change something on the Ninebot mini, b2 is x03. I loosely suspect that b2=x01 means "get status variable / info" and b2=x03 often means "command"

I haven't looked at all the messages yet, so I don't know if messages are still spread across multiple packets in some cases.

 

Remote control of the Ninebot mini

 

To enter remote control mode for the Ninebot mini, b1 = 0x0A, b2 = 0x03, c = 0x7A, and data = 0x0100

To send a drive command, b1 = 0x0A, b2 = 0x03, c = 0x7B, and data is 4 bytes, organized as such:

 +---+---+---+---+---+---+---+---+---+---+---+---+
 |x55|xAA|x06|x0A|x03|x7B|s_l|s_h|r_l|r_h|ck0|ck1|
 +---+---+---+---+---+---+---+---+---+---+---+---+

where

[s_l:s_h] is a 16-bit value (s_l is low bits, s_h is high bits) to set the target speed forward / backward. (The Ninebot mini figures out how much to tilt and when to reduce tilt angle / tilt in the opposite direction when it goes over the target speed, it has some sort of simple onboard feedback loop to try to reach the target speed).

[s_h:s_l] from 0x7FFF to 0x0000 ranges from maximum forward speed to 0 forward speed

[s_h:s_l] from 0x8000 to 0xFFFF ranges from maximum backward speed to 0 backward speed

 

[t_l:t_h] is a 16-bit value (t_l is low bits, t_h is high bits) to set the turning speed left or right.

[t_h:t_l] from 0x7FFF to 0x0000 ranges from maximum turning speed left to 0 turning speed left

[t_h:t_l] from 0x8000 to 0xFFFF ranges from maximum turning speed right to 0 turning speed right

 

Probably for safety reasons, you must continuously send these drive commands to keep the Ninebot mini in motion. If you stop sending drive commands, after a second or so, the Ninebot mini stops moving.

Edited by bdonkey
  • Upvote 3
Link to comment
Share on other sites

An interesting finding :

Variable 116 is the Speed Limit (you set it in m/h)(meters/hour). The Ninebot application only allows to fix it till 10 km/h. but the protocol allows other values and they are stored correctly and survive between stop/start of the wheel. Not checked if it really affects behavior but would be very interesting.

Also variable 115 seems to be the maximum speed. Well, probably the speed to begin tilt back. In my Ninebot One E+ is set to 20 km/h. it would be interesting to know what is the value for the P.

The application doesn't allow to change it but the protocol allows to change it to more or less than 20km/h and also survives between stop/start of the wheel.

Not tested if it affects behavior. Will try after work.

 

  • Upvote 1
Link to comment
Share on other sites

Tested in the field :

Update of speed limit is instantaneous but seems a firmware limit over 10 km/h but much less than 15  although you may set the speed limit at any value the maximum appears. 

Also updating the max speed does not allow you to run faster without tilt back. There must be some limit that is not up datable. 

 

Link to comment
Share on other sites

Well, Wheelemetrics is still the one and only Android-app I've ever written, plus I have no experience with Bluetooth LE. Maybe one day, if I get a wheel with BT LE, and have the time & energy... ;)

  • Upvote 1
Link to comment
Share on other sites

  • 2 weeks later...
10 hours ago, Paco Gorina said:

Finals 9BMetrics 1.0 is ready in the App Store !!! Versión 1.1 near finished

 

Thank you! Made my first testride yesterday evening!

To take our conversation from the "Anatomy of an Overlean" to were it belongs:

On 18.3.2016 at 9:34 AM, Paco Gorina said:

Made some tests and got sampling periods of aprox 0.05 s (varying) by changing to another thread and just don't waiting. Just made preliminary tests so I don't know if it has any impact in the wheel performance

FYI the timing of my 9bots data comming in (all in sec) - I assume by now the version witht he event queue is now in the app store (and not the "fast" one with thread switching)

  Alt Current Speed Temp Voltage
min 0,13070 0,00010 0,00020 0,01740 0,01870
max 0,26340 1,47750 0,25110 0,56080 1,82250
average 0,13956 0,01410 0,01191 0,12412 0,20685

Anyway - by this my 9bot just behaved very normal.

The graphical representation is by now not implemented in the app?

Link to comment
Share on other sites

Yes, the version in the AppStore is the 1.0 without the fast reading. Unfortunately fast reading generates Big Files (easy 10Mb or more) so I am trying to reduce them.

 Yes graphical representation IS working in the v 1.0, just open the log and put your iPhone in Landscape position. It changes automatically.

The data you present comes from? Data is from the screen of from the file?. 

Alt is change in altitude, not absolute altitude. It always begins with 0 and changes as you run.

My "not moving data" for example is :

Speed 0.00 Km/h

Voltage 60.30V

Current 0.04A

Temp 23.9ºC

in the screen

 

 

 

 

Sorry for last answer. I misunderstood the values. Are seconds between samples.

To explain better the log information, the file logs repeated values as 2 values.

For example if you are looking into voltage and recieve

6100 (10 time) 6059(1 time) 6058 (1 time)

you get 2 samples gor 6100 one with first time and the other with last time the application received 6100

The a sample for 6059 and another one for 6058

So the variation in Voltage timings is logical. Max is when the application receives many same values and doesn't stores them.

Alt is totally different as it is loaded from the Altimeter. It also uses the same algorithm of not storing repeated values but frquency of updates is form another queue that is userd just for the altimeter manager.

 

  • Upvote 1
Link to comment
Share on other sites

I used your app now again for a bigger tour through the city - works really great!

I played now a little with awk and gnuplot to get the graphs ... need just a little bit more of formatting ;)

On two logs out of six (i always stopped recording for each longer stop - i assume i could have "restarted" by reconnecting in the same log file?) the app did not log the Current (80) - all other values where fine!

Link to comment
Share on other sites

The problem with current is known and solved in next version. Probably will upload one on Monday with some improvements.

Problem is when it stops with a current = -0.01A It tinterprets it as No Value and doesn't save the data.

In principle It should reconnect and probably will ask you for the device. In next version I doesn't ask.

When reconnecting when you stop and start the wheel most values continue ok except things as distance that go to 0.

Also in next version there is an easy export option which exports a selection of the track in TAB separated format, which is possible to import directly to Excel an most analysis programs.

  • Upvote 3
Link to comment
Share on other sites

  • 1 month later...

Hi, Well I have picked up a sporting injury (unrelated to riding my EUC) so may have some time to attempt to create something for Android. I haven't built an Android app in several years and I'm unfamiliar with BLE but have managed to rework a BLE tutorial to connect to my NB1 and see some of the services and associated characteristics. 

@Paco Gorina - Can you confirm that I need to focus on service 0000ffe0-0000-1000-8000-00805f9b34fb and characteristic 0000ffe1-0000-1000-8000-00805f9b34fb

Link to comment
Share on other sites

You may get MY code, it is IN Swift and for iOS but changing to Java/Android should not be difficult.

Connection code and service/variables are in BLEConnection.swift file

Protocol is in BLEMessage and BLESimulatedClient.

in http://www.gorina.es/9BMetrics/  there are pages for protocol and variable meaning.

The wheel starts a server which offers 

  • Service FFE0

who has

  • Characteristic FFE1

with attributes

  • Read
  • Write without response
  • Notify

Communication is always started by the application writing to the characteristic and answers are "notified" by the wheel varying the value of the characteristic.

Hope that works for you. 

Get better for the injury. It is near 4 months I Amber fighting a Plantar Fascitis and it mines your moral.

Link to comment
Share on other sites

  • 3 weeks later...
On 2 May 2016 at 8:52 PM, Paco Gorina said:

It is near 4 months I Amber fighting a Plantar Fascitis and it mines your moral.

Beside topic, but can't help commenting: good luck with the Plantar Fascitis! Mine was diagnosed a year ago, a standing desk being the probable cause. It is much better now, but I still can't walk more than a kilometer per day, which is miserable. However, the EUC is a great walking assistance device and the 9B1 greatly extended my territory.

Link to comment
Share on other sites

  • 9 months later...

HELLO,

Tthanks for the grate post @Paco Gorina

i'm trying to communicate with my ninebot mini

i need java code for the checksum method of w byte[] .. like you did in swift :

func check(bArr : [UInt8], len : Int) -> (UInt8, UInt8) {   //Comença a i2 = 2 per c bytes
var i : UInt16 = 0;
 
for i2 in 2 ..< len + 2 {
i = (i + UInt16(bArr[i2]))
}
let v : UInt16 = (i ^ 0xFFFF) & self.CUSTOMER_ACTION_MASK
 
return( UInt8(v & UInt16(255)), UInt8(v>>8))
 

}

what i find so far is this code :

 long checksum(byte[] buf, int length) {
     int i = 0;
     long sum = 0;
     while (length > 2) {
         sum += (buf[i++] & 0xff) << 8;
         if ((--length) == 0) break;
         sum += (buf[i++] & 0xff);
         --length;
     }

     return (~((sum & 0xFFFF) + (sum >> 16))) & 0xFFFF;
}

 

CAN YOU HELP ME !!

 

 

Link to comment
Share on other sites

  • 6 months later...

Great work and thanks for all the info!

I'd like to write an app that records the forward/backward and left/right commands of an actual Ninebot rider while they are riding. I've got the basics working in an iOS app, I can get attributes from the Ninebot but it seems that the tilt angles attribute is actually the tilt angle of the Ninebot itself, not the control stick you push with your knees.

Does anybody know which attribute might represent information related to the control stick angle that the rider is using? Also the same for the speed.

Thanks!

Link to comment
Share on other sites

  • 8 months later...
  • 5 weeks later...
  • 3 weeks later...
On 3/10/2016 at 4:04 AM, bdonkey said:

I'm now able to remotely control and drive the motion of the Ninebot mini, it's quite similar to what Paco described in the OP. Here are the additional details:

The Ninebot mini uses a UART-style interface, similar to what Paco described, with some slight differences. It actually uses the same UUIDs as the "Noridc UART Service" 6e400001-b5a3-f393-e0a9-e50e24dcca9e

WRITE COMMANDs (Write without response) are sent from the app to the "RX Characteristic" 6e400002-b5a3-f393-e0a9-e50e24dcca9e ("RX" is from the point-of-view of the Ninebot mini)

NOTIFICATIONS of "TX Characteristic" value changes occur on 6e400003-b5a3-f393-e0a9-e50e24dcca9e

Again, all messages are sent as packets structured like

 +---+---+---+---+---+---+---+---+---+
 |x55|xAA| l | b1| b2| c |dat|ck0|ck1|
 +---+---+---+---+---+---+---+---+---+


x55 and xAA are indeed a fixed header; you can see that if you take a look through the decompiled Android app.

b1 and b2 can vary, they are usually x09 and x01, respectively, as Paco observed, but for a lot of the "command" types that change something on the Ninebot mini, b2 is x03. I loosely suspect that b2=x01 means "get status variable / info" and b2=x03 often means "command"

I haven't looked at all the messages yet, so I don't know if messages are still spread across multiple packets in some cases.

 

Remote control of the Ninebot mini

 

To enter remote control mode for the Ninebot mini, b1 = 0x0A, b2 = 0x03, c = 0x7A, and data = 0x0100

To send a drive command, b1 = 0x0A, b2 = 0x03, c = 0x7B, and data is 4 bytes, organized as such:

 +---+---+---+---+---+---+---+---+---+---+---+---+
 |x55|xAA|x06|x0A|x03|x7B|s_l|s_h|r_l|r_h|ck0|ck1|
 +---+---+---+---+---+---+---+---+---+---+---+---+

where

[s_l:s_h] is a 16-bit value (s_l is low bits, s_h is high bits) to set the target speed forward / backward. (The Ninebot mini figures out how much to tilt and when to reduce tilt angle / tilt in the opposite direction when it goes over the target speed, it has some sort of simple onboard feedback loop to try to reach the target speed).

[s_h:s_l] from 0x7FFF to 0x0000 ranges from maximum forward speed to 0 forward speed

[s_h:s_l] from 0x8000 to 0xFFFF ranges from maximum backward speed to 0 backward speed

 

[t_l:t_h] is a 16-bit value (t_l is low bits, t_h is high bits) to set the turning speed left or right.

[t_h:t_l] from 0x7FFF to 0x0000 ranges from maximum turning speed left to 0 turning speed left

[t_h:t_l] from 0x8000 to 0xFFFF ranges from maximum turning speed right to 0 turning speed right

 

Probably for safety reasons, you must continuously send these drive commands to keep the Ninebot mini in motion. If you stop sending drive commands, after a second or so, the Ninebot mini stops moving.

I was trying to send such packets my ninebot mini plus using 6e400002-b5a3-f393-e0a9-e50e24dcca9e Characteristic but nothing didn't work, I also tried to send write command packets I copied from Wireshark sniffing app, but also didn't help

Also I see that official Ninebot bot app sends some Write Request (only once) after connecting to device

Screenshot_15.png

mb we also need this? (command packets won't work without it?)

https://stackoverflow.com/questions/51041629/android-ble-how-to-send-write-request-instead-of-write-command

Link to comment
Share on other sites

  • 1 month later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...