This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Multiple notifications on multiple characteristics

Using Blend Micro (Arduino Uno + nRF8001) to gather environmental sensor data and send to mobile via notifications. Here's my main loop:

void loop()
{
  aci_loop();
  
  if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_TEMPERATURE_MEASUREMENT_TX)
    && (false == radio_ack_pending)
    && (true == timing_change_done))
  {
    f = dht.readTemperature(true); // Read temperature as Fahrenheit from DHT
    if (lib_aci_send_data(PIPE_AIR_QUALITY_SENSOR_TEMPERATURE_MEASUREMENT_TX, (uint8_t *)&f, 4))
    {
      aci_state.data_credit_available--;
      Serial.print(F("Temperature Sent: "));
      Serial.print(f);
      Serial.println(F("* F"));
      radio_ack_pending = true;
    }
  }
            
  if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_RELATIVE_HUMIDITY_TX)
    && (false == radio_ack_pending)
    && (true == timing_change_done))
  {
    h = dht.readHumidity(); // Read humidity from DHT
    if (lib_aci_send_data(PIPE_AIR_QUALITY_SENSOR_RELATIVE_HUMIDITY_TX, (uint8_t *)&h, 4))
    {
      aci_state.data_credit_available--;
      Serial.print(F("Humidity Sent: "));
      Serial.print(h);
      Serial.println(F(" %"));
      radio_ack_pending = true;
    }
  }
  
  if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_CARBON_MONOXIDE_LEVEL_TX)
    && (false == radio_ack_pending)
    && (true == timing_change_done))
  {
    int VoutAn = analogRead(MQ7PIN); //Read Vout in analog from MQ7
    float Vout = (5.0/1023)*VoutAn;
    co = 100.468*(pow(((5/Vout)-1),-1.43));
    if (lib_aci_send_data(PIPE_AIR_QUALITY_SENSOR_CARBON_MONOXIDE_LEVEL_TX, (uint8_t *)&co, 4))
    {
      aci_state.data_credit_available--;
      Serial.print(F("CO Sent: "));
      Serial.print(co);
      Serial.println(F(" ppm"));
      radio_ack_pending = true;       
    } 
  }
}

I would like to cycle through and send data from each sensor so it is constantly updated on mobile device. But here it will only send notification for the first characteristic to which I subscribe. Is the problem in how I'm handling it in this loop, or in the ACI loop?

void aci_loop()
{
  static bool setup_required = false;

  // We enter the if statement only when there is a ACI event available to be processed
  if (lib_aci_event_get(&aci_state, &aci_data))
  {
    aci_evt_t * aci_evt;
    aci_evt = &aci_data.evt;
    
    switch(aci_evt->evt_opcode)
    {
      case ACI_EVT_DEVICE_STARTED: // As soon as you reset the nRF8001 you will get an ACI Device Started Event
      {
        aci_state.data_credit_available = aci_evt->params.device_started.credit_available;
        switch(aci_evt->params.device_started.device_mode)
        {
          case ACI_DEVICE_SETUP: //When the device is in the setup mode
            aci_state.device_state = ACI_DEVICE_SETUP;
            Serial.println(F("Evt Device Started: Setup"));
            setup_required = true;
            break;

          case ACI_DEVICE_STANDBY:
            aci_state.device_state = ACI_DEVICE_STANDBY;
            Serial.println(F("Evt Device Started: Standby"));
            if (aci_evt->params.device_started.hw_error)
            {
              delay(20); //Magic number used to make sure the HW error event is handled correctly.
            }
            else
            {
            lib_aci_connect(180/* in seconds */, 0x0100 /* advertising interval 100ms*/);
            Serial.println(F("Advertising started"));
            }
            break;
        }
      }
        break; //ACI Device Started Event

      case ACI_EVT_CMD_RSP:
        //If an ACI command response event comes with an error -> stop
        if (ACI_STATUS_SUCCESS != aci_evt->params.cmd_rsp.cmd_status)
        {
          //ACI ReadDynamicData and ACI WriteDynamicData will have status codes of
          //TRANSACTION_CONTINUE and TRANSACTION_COMPLETE
          //all other ACI commands will have status code of ACI_STATUS_SCUCCESS for a successful command
          Serial.print(F("ACI Status of ACI Evt Cmd Rsp"));
          Serial.println(aci_evt->params.cmd_rsp.cmd_status, HEX);
          Serial.print(F("ACI Command 0x"));
          Serial.println(aci_evt->params.cmd_rsp.cmd_opcode, HEX);
          Serial.println(F("Evt Cmd respone: Error. Arduino is in a while(1); loop"));
          while (1);
        }
        break;

      case ACI_EVT_PIPE_STATUS:
        Serial.println(F("Evt Pipe Status"));
        // Check if the peer has subscribed to any particleBox characterisitcs for notifications
        if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_TEMPERATURE_MEASUREMENT_TX)
            && (false == timing_change_done) )
        {
          // Request a change to the link timing as set in the GAP -> Preferred Peripheral Connection Parameters
          lib_aci_change_timing_GAP_PPCP();
          timing_change_done = true;
        }
         if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_RELATIVE_HUMIDITY_TX)
            && (false == timing_change_done) )
        {
          // Request a change to the link timing as set in the GAP -> Preferred Peripheral Connection Parameters
          lib_aci_change_timing_GAP_PPCP();
          timing_change_done = true;
        }
         if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_CARBON_MONOXIDE_LEVEL_TX)
            && (false == timing_change_done) )
        {
          // Request a change to the link timing as set in the GAP -> Preferred Peripheral Connection Parameters
          lib_aci_change_timing_GAP_PPCP();
          timing_change_done = true;
        }
         if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_PM10_CONCENTRATION_TX)
            && (false == timing_change_done) )
        {
          // Request a change to the link timing as set in the GAP -> Preferred Peripheral Connection Parameters
          lib_aci_change_timing_GAP_PPCP();
          timing_change_done = true;
        }
         if (lib_aci_is_pipe_available(&aci_state, PIPE_AIR_QUALITY_SENSOR_PM25_CONCENTRATION_TX)
            && (false == timing_change_done) )
        {
          // Request a change to the link timing as set in the GAP -> Preferred Peripheral Connection Parameters
          lib_aci_change_timing_GAP_PPCP();
          timing_change_done = true;
        }
        break;

      case ACI_EVT_TIMING: // Link timing has changed
        Serial.print(F("Timing changed: "));
        Serial.println(aci_evt->params.timing.conn_rf_interval, HEX);
        break;

      case ACI_EVT_CONNECTED:
        radio_ack_pending  = false;
        timing_change_done = false;
        aci_state.data_credit_available = aci_state.data_credit_total;
        Serial.println(F("Evt Connected"));
        break;


      case ACI_EVT_DATA_CREDIT:
        aci_state.data_credit_available = aci_state.data_credit_available + aci_evt->params.data_credit.credit;
        /**
        Bluetooth Radio ack received from the peer radio for the data packet sent.
        This also signals that the buffer used by the nRF8001 for the data packet is available again.
        */
        radio_ack_pending = false;
        break;

      case ACI_EVT_PIPE_ERROR:
        /**
        Send data failed. ACI_EVT_DATA_CREDIT will not come.
        This can happen if the pipe becomes unavailable by the peer unsubscribing to the Heart Rate
        Measurement characteristic.
        This can also happen when the link is disconnected after the data packet has been sent.
        */
        radio_ack_pending = false;

        //See the appendix in the nRF8001 Product Specication for details on the error codes
        Serial.print(F("ACI Evt Pipe Error: Pipe #:"));
        Serial.print(aci_evt->params.pipe_error.pipe_number, DEC);
        Serial.print(F("  Pipe Error Code: "));
        Serial.println(aci_evt->params.pipe_error.error_code, HEX);

        //Increment the credit available as the data packet was not sent.
        //The pipe error also represents the Attribute protocol Error Response sent from the peer and that should not be counted
        //for the credit.
        if (ACI_STATUS_ERROR_PEER_ATT_ERROR != aci_evt->params.pipe_error.error_code)
        {
          aci_state.data_credit_available++;
        }
        break;

       case ACI_EVT_DISCONNECTED:
        // Advertise again if the advertising timed out
        if(ACI_STATUS_ERROR_ADVT_TIMEOUT == aci_evt->params.disconnected.aci_status)
        {
          Serial.println(F("Evt Disconnected -> Advertising timed out"));
          {
            Serial.println(F("nRF8001 going to sleep"));
            lib_aci_sleep();
            aci_state.device_state = ACI_DEVICE_SLEEP;
          }
        }

        if(ACI_STATUS_EXTENDED == aci_evt->params.disconnected.aci_status)
        {
          Serial.print(F("Evt Disconnected -> Link lost. Bluetooth Error code = "));
          Serial.println(aci_evt->params.disconnected.btle_status, HEX);
          lib_aci_connect(180/* in seconds */, 0x0050 /* advertising interval 50ms*/);
          Serial.println(F("Advertising started"));
        }
        break;

      case ACI_EVT_HW_ERROR:
        Serial.print(F("HW error: "));
        Serial.println(aci_evt->params.hw_error.line_num, DEC);

        for(uint8_t counter = 0; counter <= (aci_evt->len - 3); counter++)
        {
          Serial.write(aci_evt->params.hw_error.file_name[counter]); //uint8_t file_name[20];
        }
        Serial.println();
        lib_aci_connect(30/* in seconds */, 0x0100 /* advertising interval 50ms*/);
        Serial.println(F("Advertising started"));
        break;

    }
  }
  else
  {
    // Serial.println(F("No ACI Events available"));
    // No event in the ACI Event queue and no event in ACI comman queue
    // Arduino can go to sleep now
  }

  /* setup_required is set to true when the device starts up and enters setup mode.
   * It indicates that do_aci_setup() should be called. The flag should be cleared if
   * do_aci_setup() returns ACI_STATUS_TRANSACTION_COMPLETE.
   */
  if(setup_required)
  {
    if (SETUP_SUCCESS == do_aci_setup(&aci_state))
    {
      setup_required = false;
    }
  }
}
Parents
  • If you look in deep into your source code, there is a parameter called "radio_ack_pending" that is setting to "true" after the sending of the first message through the PIPE_AIR_QUALITY_SENSOR_TEMPERATURE_MEASUREMENT_TX. The meaning of this parameter is related to the "aci_state.data_credit_available" parameter (check the doc). With this code, it's quite hard to send messages through all the pipes at the same time.

    I recommend to you the implementation of some kind of token ring /multiplex algorithm in order to scan the availability of the pipes in different times. You can use in your "loop()" method a simple integer variable or some flags to activate/deactivate the scanning process between the GATTServices; or you can look for more complex C algorithms to carry on this task.

  • I have tried a for loop to try to make it cycle through the pipes that are open, but it always defaults to the first one in the cycle that is open. I confess that I'm still trying to figure out this API and don't really understand how ack works in relation to notifications... I thought it only applied to read/write.

Reply Children
No Data
Related