Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOpin service documentation #13

Open
LoFiRobot opened this issue May 18, 2020 · 10 comments
Open

IOpin service documentation #13

LoFiRobot opened this issue May 18, 2020 · 10 comments

Comments

@LoFiRobot
Copy link

LoFiRobot commented May 18, 2020

Hey Rob,

Thanks for your great library! It really simplifies talking to micro:bit over web-bluetooth.

I cannot find any documentation about accessing IOpin service.

After diggin into console I managed to work out PWM control to operate a servo motor.

      let pwm = {
        pin: pin, // 0,1,2
        value: val, // 50-250
        period: 10000 // 10000 microsecconds = 10 millisecconds
      }
      services.ioPinService.setPwmControl(pwm);

....

Still I am confused how to take analog readings from IO pins. Setting AD and IO configuration with bitmasks is a riddle to me. Could you please provide any code sample how to set:
setIoConfiguration and getAdConfiguration to configure P0 , P1, and P2 for analog readings??

Thank you in advance.

Maciej

@thegecko
Copy link
Owner

I haven't had the chance to set this up, so haven't documented it. I was working in the dark a little, so any help from the community here would be awesome.

I'm happy to write up some basic docs if someone has this working.

@benmoran Anything you could share?

@benmoran
Copy link
Contributor

Hi! @LoFiRobot
I have personally only controlled servos and not used the analog readings. But I think:

  • you will definitely need to be using the latest version with the patch that was recently merged with the iopin service fixes

  • you should get back an array of 24 zeros and ones from getAdConfiguration corresponding to the analog /digital configuration bits for each of the 24 pins

  • you should provide a similar array to setAdConfiguration, so I think you want an array of length 24 that starts with at least three 1s to enable analog reading if I have that the right way around.

Before the little endian patch, these functions didn't work - you'd get a list of 24 garbage ints instead.

(You don't need to do any bit twiddling from javascript, unlike the raw C++ api which expects a single 24bit unsigned int - @thegecko has thoughtfully done that for us)

@LoFiRobot
Copy link
Author

LoFiRobot commented May 19, 2020

Hello @thegecko and @benmoran

I think I am using latest version of the library - I cloned the repo few days ago.

When it comes to pin readings.

  1. getIoCconfiguration method returns an array of 24 int
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    But when I try to pass similar array to setIoConfiguration with first element changed to 1 (to set pin 0 as input) as this:

let io_conf = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
services.ioPinService.setIoConfiguration(io_conf);

nothing changes - using getIoConfiguration afterwards still returns array of zeroes.

  1. I am also using this guide to better understand how MB services work - https://ukbaz.github.io/howto/ubit_ble_profile.html
    When I pass bits manually to the microbit as in the guide I am able to set the pins for inputs and get readings.

So i tried it that way:
I connect MB to NRFConnect app and passed 0x01000000 to Pin IO Configuration characteristic (set P0 as input). Then disconnected from the NRF app and connected to your library. I used getIoConfiguration method and received this values in return:

[65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0]

However when I reset the microbit, reconnect to your library and try to pass the same array to setIoConfiguration - no effect, getIoConfiguration returns zeroes.

When I am setting P0 to analog input IO and AD in NRF app ( passing 0x01000000 to both characteristics) then reconnecting to your library and using services.ioPinService.readPinData() I recieve proper readings from the pin - {pin: 0, value: 83}

So part I am missing is how to use setIoConfiguration and setAdConfiguration methods properly.

@LoFiRobot
Copy link
Author

LoFiRobot commented May 20, 2020

Update

I still have not managed to figure out how to use setIoConfiguration and seAdConfiguration methods.

But I managed to set pins configuration by writing to characteristics directly:

const Ad_char = "e95d5899-251d-470a-a062-fa1922dfa9a8";
const Io_char = "e95db9fe-251d-470a-a062-fa1922dfa9a8";
let cmd = new Uint32Array([0x01]);  
services.ioPinService.helper.setCharacteristicValue(Io_char, cmd);
services.ioPinService.helper.setCharacteristicValue(Ad_char, cmd);

Important part is the cmd variable - each pin has its address
P0 - 0x01
P1 - 0x02
P2 - 0x04

To set P0 to analog read - pass 0x01 to both IO and AD characteristics.
To set P0 to digital read - pass 0x01 to IO and 0x00 to AD characteristic.

To set P0 and P1 to analog read - sum the bytes - pass 0x03 to IO and AD
To set P0, P1 and P2 to analog read - sum the bytes - pass 0x07 to IO and AD

To read the values simply call:
services.ioPinService.readPinData();

Performance suggestion:
When I read the data with event handler
services.ioPinService.addEventListener("pindatachanged", eventHandler);
at times it gets updated so fast that it tends to overload web-bluetooth communication speed
I test on Macbook pro (2012), Android (OnePlus5) and Chromebook (Acer R11)
Even on the Macbook the app tends too freeze as the web-bluetooth queue gets overloaded.
Android and ChromeOS performs about two times slower so it will get even worse there.

It would be nice to have an eqivalent of
accelerometerService.setAccelerometerPeriod(50)
for reading pin data. Setting te period to about 50-100ms would keep the performance stable on slower devices and still keep the refresh rate smooth enough.

@thegecko
Copy link
Owner

It would be nice to have an eqivalent of
accelerometerService.setAccelerometerPeriod(50)
for reading pin data.

Nice, feel free to create a separate issue in case I forget ;)

@dakanadaka
Copy link

Do you have any example on reading analog from pin 1?
if(services.ioPinService){ console.log('ioPinServices'); const Ad_char = "e95d5899-251d-470a-a062-fa1922dfa9a8"; const Io_char = "e95db9fe-251d-470a-a062-fa1922dfa9a8"; let cmd = new Uint32Array([0x02]); services.ioPinService.helper.setCharacteristicValue(Io_char, cmd); services.ioPinService.helper.setCharacteristicValue(Ad_char, cmd); services.ioPinService.readPinData(); }

I'm getting crash on browser and also on microbit....I thing it has to do with giving the period of 50ms before read.

Any javascript examples code would be nice.

@LoFiRobot
Copy link
Author

LoFiRobot commented Jun 21, 2021

Hey dakandaka,

I am using timer to ping the service for data (the interval is 100ms, no crashes):

componentDidMount() {
this.intervalId = setInterval(this.timer.bind(this), 100);
}
componentWillUnmount(){
clearInterval(this.intervalId);
}

async timer() {

let  pin_data = []
let _p0 = 0;
let _p1 = 0;
let _p2 = 0;

if (window.services != null && this.state.connected === true) {
  pin_data = await window.services.ioPinService.readPinData();
}


for (let i = 0; i < pin_data.length; i++) {

  if (pin_data[i].pin === 0 ) {
    _p0 = Math.round(pin_data[i].value/2.55);
    window.p0 = _p0;
  }
    if (pin_data[i].pin === 1 ) {
    _p1 = Math.round(pin_data[i].value/2.55);
    window.p1 = _p1;
  }
  if (pin_data[i].pin === 2 ) {
    _p2 = Math.round(pin_data[i].value/2.55);
    window.p2 = _p2;
  }

}

@dakanadaka
Copy link

thank you for quick response :)

I see componentDidMount() you are doing it in react? and state...I think it is react.
Can you post complete react class/function or code?

@LoFiRobot
Copy link
Author

Yes it is React.
Below you have full component. It is a messy solution because I am keeping the microbit object in scope of main window object and accessing it in react from there (not a proper solution ;-)
Anyway feel free to ask anything, but I was doing this project over a year ago so forgot a bit how and why the things were made.

The app live is here:
https://lofi-apps.web.app/
Microbit firmware:
https://www.lofirobot.com/files/lofi_firmware.hex

BLE component
.........

import React, {Component} from 'react';
import InputsMonitor from './inputs_monitor.js'
import {FullscreenIcon, RobotIcon} from './icons.js'
//import {microbit} from 'microbit-web-bluetooth';

let check_connection_interval = null;

let x = 0;
let y = 0;
let temp = 0;
let compass = 0;
let p0 = 0;
let p1 = 0;
let p2 = 0;

let MBdevice = null;
let MBservices = null;

class BLE_connect extends Component {

constructor(props) {
super(props);
this.state = {
disconnection_feedback: 0,
connected: false,
RX: null,
sensors: {A:0,B:0,x:0,y:0,temp:0,comp:0,p0:0,p1:0,p2:0,connected:false}
};
window.send = this.send_ble.bind(this);

}

componentDidMount() {
this.intervalId = setInterval(this.timer.bind(this), 100);
}
componentWillUnmount(){
clearInterval(this.intervalId);
}

async timer() {

let  pin_data = []
let _p0 = 0;
let _p1 = 0;
let _p2 = 0;

if (window.services != null && this.state.connected === true) {
  pin_data = await window.services.ioPinService.readPinData();
}


for (let i = 0; i < pin_data.length; i++) {

  if (pin_data[i].pin === 0 ) {
    _p0 = Math.round(pin_data[i].value/2.55);
    window.p0 = _p0;
  }
    if (pin_data[i].pin === 1 ) {
    _p1 = Math.round(pin_data[i].value/2.55);
    window.p1 = _p1;
  }
  if (pin_data[i].pin === 2 ) {
    _p2 = Math.round(pin_data[i].value/2.55);
    window.p2 = _p2;
  }

}

//console.log(_p0);
this.setState(prevState => ({
  sensors: {
    ...prevState.sensors,
    p0: _p0,
    p1: _p1,
    p2: _p2,

  }
}));

}

async connect_ble() {

const onDisconnected = event => {
this.setState({connected:false});
window.connected = false;
this.setState(prevState => ({
sensors: {
...prevState.sensors,
connected: false,
}
}));

}

const eventHandler = event => {

this.props.sensors(this.state.sensors);


if (event.type === "buttonastatechanged") {
  const a_button = event.detail*100;
  window.buttonA = a_button;
  this.setState(prevState => ({
    sensors: {
      ...prevState.sensors,
      A: a_button,
    }
  }));
}

if (event.type === "buttonbstatechanged") {
  const b_button = event.detail*100;
  window.buttonB = b_button;
  this.setState(prevState => ({
    sensors: {
      ...prevState.sensors,
      B: b_button,
    }
  }));
}

if (event.type === "temperaturechanged") {
  window.temp = event.detail;
  this.setState(prevState => ({
    sensors: {
      ...prevState.sensors,
      temp: event.detail,
    }
  }));
}

if (event.type === "accelerometerdatachanged") {
  const x = Math.max(Math.min(Math.round((event.detail.x+1)*50),100),0);
  const y = Math.max(Math.min(Math.round((event.detail.y+1)*50),100),0);
  window.x = x;
  window.y = y;


  this.setState(prevState => ({
    sensors: {
      ...prevState.sensors,
      x: x,
      y: y,
    }
  }));
}


if (event.type === "magnetometerbearingchanged") {
  //console.log(event.detail);
  window.compass = event.detail/3.6;
  this.setState(prevState => ({
    sensors: {
      ...prevState.sensors,
      comp: Math.round(event.detail/3.6),
    }
  }));
}

if (event.type === "pindatachanged") {

  const  pin_data = event.detail;
  let _p0 = 0;
  let _p1 = 0;
  let _p2 = 0;

  for (let i = 0; i < pin_data.length; i++) {

    if (pin_data[i].pin === 0 )
      _p0 = Math.round(pin_data[i].value/2.55);
      if (pin_data[i].pin === 1 )
      _p1 = Math.round(pin_data[i].value/2.55);
    if (pin_data[i].pin === 2 )
      _p2 = Math.round(pin_data[i].value/2.55);

}


  //console.log(_p0);
  this.setState(prevState => ({
    sensors: {
      ...prevState.sensors,
      p0: _p0,
      p1: _p1,
      p2: _p2,

    }
  }));

}

}

const device = await window.microbit.requestMicrobit(window.navigator.bluetooth);
if (device) {
let bluetoothDevice = device;
MBdevice = device;
window.device = device;
bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected);

              this.setState({connected:true});
              window.connected = true;
              this.setState(prevState => ({
              sensors: {
                ...prevState.sensors,
                connected: true,
              }
            }));

                 const services = await window.microbit.getServices(device);
                 MBservices = services;
                 window.services = services;

                 if (services.deviceInformationService) {
                     console.log(await services.deviceInformationService.readDeviceInformation());
                 }

                 if (services.uartService) {
                     services.uartService.addEventListener("receiveText", eventHandler);
                     await services.uartService.send(new Uint8Array([104, 101, 108, 108, 111, 58])); // hello:
                 }

                 if (services.ledService) {


                     await services.ledService.writeMatrixState([
                         [1, 0, 0, 1, 1],
                         [1, 1, 0, 1, 1],
                         [0, 0, 0, 0, 0],
                         [1, 1, 0, 1, 0],
                         [1, 0, 0, 1, 0]
                     ]);


                     //console.log(await services.ledService.readMatrixState());

                     await services.ledService.setScrollingDelay(70);
                     //console.log(await services.ledService.getScrollingDelay());

                     //await services.ledService.writeText("LOFI");
                 }

                 if (services.buttonService) {
                     services.buttonService.addEventListener("buttonastatechanged", eventHandler);
                     services.buttonService.addEventListener("buttonbstatechanged", eventHandler);
                 }

                 if (services.temperatureService) {
                     await services.temperatureService.setTemperaturePeriod(2000);
                     console.log(await services.temperatureService.getTemperaturePeriod());
                    services.temperatureService.addEventListener("temperaturechanged", eventHandler);
                 }

                 if (services.accelerometerService) {
                     await services.accelerometerService.setAccelerometerPeriod(50);
                     console.log(await services.accelerometerService.getAccelerometerPeriod());
                    services.accelerometerService.addEventListener("accelerometerdatachanged", eventHandler);
                 }

                 if (services.magnetometerService) {

                         await services.magnetometerService.setMagnetometerPeriod(640);
                         console.log(await services.magnetometerService.getMagnetometerPeriod());

                         //services.magnetometerService.addEventListener("magnetometerdatachanged", eventHandler);
                         services.magnetometerService.addEventListener("magnetometerbearingchanged", eventHandler);


                 }
                 if (services.ioPinService) {
                   const Ad_char = "e95d5899-251d-470a-a062-fa1922dfa9a8";
                   const Io_char = "e95db9fe-251d-470a-a062-fa1922dfa9a8";
                   let cmd = new Uint32Array([0x07]);
                   //await services.ioPinService.helper.setCharacteristicValue(Io_char, cmd);
                   //await services.ioPinService.helper.setCharacteristicValue(Ad_char, cmd);

                   //services.ioPinService.addEventListener("pindatachanged", eventHandler);

                 }
             }

}

send_ble(key,value) {

let _key = key;

if (key === 208) {
_key = 0;
}
if (key === 209) {
_key = 1;
}
if (key === 210) {
_key = 2;
}

let pwm = {
pin: _key,
value: (value*2)+50,
period: 10000
}
MBservices.ioPinService.setPwmControl(pwm);

if (key === 211) {
console.log(value);
MBservices.ledService.writeText(value.toString());
}

}

render () {

return (

<div id="right_toolbox">

<button id="connect_button" className=

{
this.state.connected === true
? ("outline")
: ("")
}

onClick={() => this.connect_ble()}>

{//
}

)

}
}

export default BLE_connect

@larsvanherwijnen
Copy link

hey,

I have the issue that ioPinService comes back as undifined. How do i fix this issue? bare in mind this is my first time using a mircobit so it is probably something stupid! I'm using the ''ble-open-microbit-universal.hex'' hex file.

Thanks in advance.

image

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants