Skip to content

Commit

Permalink
Add data format C5 (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
ojousima authored Jan 31, 2024
1 parent 8b817f0 commit eb10bc2
Show file tree
Hide file tree
Showing 6 changed files with 518 additions and 11 deletions.
18 changes: 8 additions & 10 deletions .github/workflows/sonar-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,14 @@ jobs:
- name: Checkout submodules
run: git submodule update --init --recursive

- name: Set up Sonar Scanner 4.40
run: |
export SONAR_SCANNER_VERSION=4.4.0.2170
export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux
curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
echo "$SONAR_SCANNER_HOME/bin" >> $GITHUB_PATH
curl --create-dirs -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
# Setup java 17 to be default (sonar-scanner requirement as of 5.x)
- uses: actions/setup-java@v3
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'

- name: Install sonar-scanner and build-wrapper
uses: sonarsource/sonarcloud-github-c-cpp@v2

- name: Download Nordic SDK
run: |
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ if(IDF_VERSION_MAJOR GREATER_EQUAL 4)
SRCS "src/ruuvi_endpoint_5.h"
SRCS "src/ruuvi_endpoint_6.c"
SRCS "src/ruuvi_endpoint_6.h"
SRCS "src/ruuvi_endpoint_c5.c"
SRCS "src/ruuvi_endpoint_c5.h"
SRCS "src/ruuvi_endpoint_ca_uart.c"
SRCS "src/ruuvi_endpoint_ca_uart.h"
SRCS "src/ruuvi_endpoint_ibeacon.c"
Expand All @@ -20,6 +22,7 @@ elseif(CMAKE_PROJECT_NAME STREQUAL "ruuvi.node_nrf91.c")
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ruuvi_endpoint_3.c)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ruuvi_endpoint_5.c)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ruuvi_endpoint_6.c)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ruuvi_endpoint_c5.c)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ruuvi_endpoint_ca_uart.c)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ruuvi_endpoint_ibeacon.c)
else()
Expand Down
203 changes: 203 additions & 0 deletions src/ruuvi_endpoint_c5.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include "ruuvi_endpoint_c5.h"
#include "ruuvi_endpoints.h"
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>

#if RE_C5_ENABLED

#define RE_C5_ACC_RATIO (1000.0f)
#define RE_C5_HUMI_RATIO (400.0f)
#define RE_C5_TEMP_RATIO (200.0f)
#define RE_C5_PRES_RATIO (1.0f)
#define RE_C5_PRES_OFFSET (-50000.0f)
#define RE_C5_BATT_RATIO (1000.0f)
#define RE_C5_BATT_OFFSET (1600)
#define RE_C5_BATT_MIN (1.6f)

#define RE_C5_TXPWR_RATIO (2)
#define RE_C5_TXPWR_OFFSET (40)

#define RE_C5_MAC_MAX (281474976710655)
#define RE_C5_MAC_MIN (0)

#define RE_C5_BYTE_0_SHIFT (0U)
#define RE_C5_BYTE_1_SHIFT (8U)
#define RE_C5_BYTE_2_SHIFT (16U)
#define RE_C5_BYTE_3_SHIFT (24U)
#define RE_C5_BYTE_4_SHIFT (32U)
#define RE_C5_BYTE_5_SHIFT (40U)
#define RE_C5_BYTE_MASK (0xFFU)
#define RE_C5_BYTE_VOLTAGE_OFFSET (5U)
#define RE_C5_BYTE_VOLTAGE_MASK (0x7FFU)
#define RE_C5_BYTE_TX_POWER_OFFSET (0U)
#define RE_C5_BYTE_TX_POWER_MASK (0x1FU)

// Avoid mocking simple function
#ifdef TEST
void re_clip (re_float * const value, const re_float min, const re_float max)
{
if (*value > max)
{
*value = max;
}

if (*value < min)
{
*value = min;
}
}
#endif

static void re_c5_encode_set_address (uint8_t * const buffer, const re_c5_data_t * data)
{
// Address is 64 bits, skip 2 first bytes
uint8_t addr_offset = RE_C5_OFFSET_ADDR_MSB;
uint64_t mac = data->address;

if ( (RE_C5_MAC_MAX < data->address) || (RE_C5_MAC_MIN > data->address))
{
mac = RE_C5_INVALID_MAC;
}

buffer[addr_offset] = (mac >> RE_C5_BYTE_5_SHIFT) & RE_C5_BYTE_MASK;
addr_offset++;
buffer[addr_offset] = (mac >> RE_C5_BYTE_4_SHIFT) & RE_C5_BYTE_MASK;
addr_offset++;
buffer[addr_offset] = (mac >> RE_C5_BYTE_3_SHIFT) & RE_C5_BYTE_MASK;
addr_offset++;
buffer[addr_offset] = (mac >> RE_C5_BYTE_2_SHIFT) & RE_C5_BYTE_MASK;
addr_offset++;
buffer[addr_offset] = (mac >> RE_C5_BYTE_1_SHIFT) & RE_C5_BYTE_MASK;
addr_offset++;
buffer[addr_offset] = (mac >> 0) & RE_C5_BYTE_MASK;
}

static void re_c5_encode_humidity (uint8_t * const buffer, const re_c5_data_t * data)
{
uint16_t coded_humidity = RE_C5_INVALID_HUMIDITY;
re_float humidity = data->humidity_rh;

if (!isnan (humidity))
{
re_clip (&humidity, RE_C5_HUMI_MIN, RE_C5_HUMI_MAX);
coded_humidity = (uint16_t) roundf (humidity * RE_C5_HUMI_RATIO);
}

buffer[RE_C5_OFFSET_HUMI_MSB] = coded_humidity >> RE_C5_BYTE_1_SHIFT;
buffer[RE_C5_OFFSET_HUMI_LSB] = coded_humidity & RE_C5_BYTE_MASK;
}

static void re_c5_encode_temperature (uint8_t * const buffer, const re_c5_data_t * data)
{
uint16_t coded_temperature = RE_C5_INVALID_TEMPERATURE;
re_float temperature = data->temperature_c;

if (!isnan (temperature))
{
re_clip (&temperature, RE_C5_TEMP_MIN, RE_C5_TEMP_MAX);
int16_t rounded_temperature = (int16_t) roundf (temperature * RE_C5_TEMP_RATIO);
// Type cast adds 2^16 to a negative signed value, not changing bits.
coded_temperature = (uint16_t) rounded_temperature;
}

buffer[RE_C5_OFFSET_TEMP_MSB] = coded_temperature >> RE_C5_BYTE_1_SHIFT;
buffer[RE_C5_OFFSET_TEMP_LSB] = coded_temperature & RE_C5_BYTE_MASK;
}

static void re_c5_encode_pressure (uint8_t * const buffer, const re_c5_data_t * data)
{
uint16_t coded_pressure = RE_C5_INVALID_PRESSURE;
re_float pressure = data->pressure_pa;

if (!isnan (pressure))
{
re_clip (&pressure, RE_C5_PRES_MIN, RE_C5_PRES_MAX);
pressure += RE_C5_PRES_OFFSET;
coded_pressure = (uint16_t) roundf (pressure * RE_C5_PRES_RATIO);
}

buffer[RE_C5_OFFSET_PRES_MSB] = coded_pressure >> RE_C5_BYTE_1_SHIFT;
buffer[RE_C5_OFFSET_PRES_LSB] = coded_pressure & RE_C5_BYTE_MASK;
}


static void re_c5_encode_pwr (uint8_t * const buffer, const re_c5_data_t * data)
{
uint16_t coded_voltage = RE_C5_INVALID_VOLTAGE;
re_float voltage = data->battery_v;
uint16_t coded_tx_power = RE_C5_INVALID_POWER;
re_float tx_power = (re_float) data->tx_power;

if (!isnan (voltage))
{
re_clip (&voltage, RE_C5_VOLTAGE_MIN, RE_C5_VOLTAGE_MAX);
coded_voltage = (uint16_t) roundf ( (voltage * RE_C5_BATT_RATIO)
- RE_C5_BATT_OFFSET);
}

// Check against original int value
if (RE_C5_INVALID_POWER != data->tx_power)
{
re_clip (&tx_power, RE_C5_TXPWR_MIN, RE_C5_TXPWR_MAX);
coded_tx_power = (uint16_t) roundf ( (tx_power
+ RE_C5_TXPWR_OFFSET)
/ RE_C5_TXPWR_RATIO);
}

uint16_t power_info = ( (uint16_t) (coded_voltage << RE_C5_BYTE_VOLTAGE_OFFSET))
+ coded_tx_power;
buffer[RE_C5_OFFSET_POWER_MSB] = (power_info >> RE_C5_BYTE_1_SHIFT);
buffer[RE_C5_OFFSET_POWER_LSB] = (power_info & RE_C5_BYTE_MASK);
}

static void re_c5_encode_movement (uint8_t * const buffer, const re_c5_data_t * data)
{
uint8_t movement_count = RE_C5_INVALID_MOVEMENT;

if (RE_C5_MVTCTR_MAX >= data->movement_count)
{
movement_count = data->movement_count;
}

buffer[RE_C5_OFFSET_MVTCTR] = movement_count;
}

static void re_c5_encode_sequence (uint8_t * const buffer, const re_c5_data_t * data)
{
uint16_t measurement_seq = RE_C5_INVALID_SEQUENCE;

if (RE_C5_SEQCTR_MAX >= data->measurement_count)
{
measurement_seq = data->measurement_count;
}

buffer[RE_C5_OFFSET_SEQCTR_MSB] = (measurement_seq >> RE_C5_BYTE_1_SHIFT);
buffer[RE_C5_OFFSET_SEQCTR_LSB] = (measurement_seq & RE_C5_BYTE_MASK);
}

re_status_t re_c5_encode (uint8_t * const buffer, const re_c5_data_t * data)
{
re_status_t result = RE_SUCCESS;

if ( (NULL == buffer) || (NULL == data))
{
result |= RE_ERROR_NULL;
}
else
{
buffer[RE_C5_OFFSET_HEADER] = RE_C5_DESTINATION;
re_c5_encode_humidity (buffer, data);
re_c5_encode_temperature (buffer, data);
re_c5_encode_pressure (buffer, data);
re_c5_encode_movement (buffer, data);
re_c5_encode_sequence (buffer, data);
re_c5_encode_pwr (buffer, data);
re_c5_encode_set_address (buffer, data);
}

return result;
}

#endif
91 changes: 91 additions & 0 deletions src/ruuvi_endpoint_c5.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Ruuvi Endpoint C5 helper.
* Defines necessary data for creating a Ruuvi data format C5 broadcast.
* Drops acceleration values to leave space for service UUID field
*
* License: BSD-3
* Author: Otso Jousimaa <[email protected]>
*/

#ifndef RUUVI_ENDPOINT_C5_H
#define RUUVI_ENDPOINT_C5_H
#include "ruuvi_endpoints.h"
#include <stdbool.h>

#define RE_C5_DESTINATION (0xC5U)
#define RE_C5_INVALID_TEMPERATURE (0x8000U)
#define RE_C5_INVALID_HUMIDITY (0xFFFFU)
#define RE_C5_INVALID_PRESSURE (0xFFFFU)
#define RE_C5_INVALID_ACCELERATION (0x8000U)
#define RE_C5_INVALID_SEQUENCE (0xFFFFU)
#define RE_C5_INVALID_MOVEMENT (0xFFU)
#define RE_C5_INVALID_VOLTAGE (0x07FFU)
#define RE_C5_INVALID_POWER (0x1FU)
#define RE_C5_INVALID_MAC (0xFFFFFFFFFFFFU)
#define RE_C5_DATA_LENGTH (18U)

#define RE_C5_TEMP_MAX (163.835f)
#define RE_C5_TEMP_MIN (-163.835f)
#define RE_C5_HUMI_MAX (163.835f)
#define RE_C5_HUMI_MIN (0.0f)
#define RE_C5_PRES_MAX (115534.0f)
#define RE_C5_PRES_MIN (50000.0f)
#define RE_C5_VOLTAGE_MAX (3.646f)
#define RE_C5_VOLTAGE_MIN (1.6f)
#define RE_C5_TXPWR_MAX (20)
#define RE_C5_TXPWR_MIN (-40)
#define RE_C5_MVTCTR_MAX (254)
#define RE_C5_MVTCTR_MIN (0)
#define RE_C5_SEQCTR_MAX (65534)
#define RE_C5_SEQCTR_MIN (0)

#define RE_C5_OFFSET_PAYLOAD (7U)

#define RE_C5_OFFSET_HEADER (0U)
#define RE_C5_OFFSET_TEMP_MSB (1U)
#define RE_C5_OFFSET_TEMP_LSB (2U)
#define RE_C5_OFFSET_HUMI_MSB (3U)
#define RE_C5_OFFSET_HUMI_LSB (4U)
#define RE_C5_OFFSET_PRES_MSB (5U)
#define RE_C5_OFFSET_PRES_LSB (6U)
#define RE_C5_OFFSET_POWER_MSB (7U)
#define RE_C5_OFFSET_POWER_LSB (8U)
#define RE_C5_OFFSET_MVTCTR (9U)
#define RE_C5_OFFSET_SEQCTR_MSB (10U)
#define RE_C5_OFFSET_SEQCTR_LSB (11U)
#define RE_C5_OFFSET_ADDR_MSB (12U)

/** @brief All data required for Ruuvi dataformat 5 package. */
typedef struct
{
re_float humidity_rh;
//!< Humidity in relative humidity percentage.
re_float pressure_pa;
//!< Pressure in pascals.
re_float temperature_c;
//!< Temperature in celcius.
re_float battery_v;
//!< Battery voltage, preferably under load such as radio TX.
uint16_t measurement_count;
//!< Running counter of measurement.
uint8_t movement_count;
//!< Number of detected movements.
uint64_t address;
//!< BLE address of device, most significant byte first.
int8_t tx_power;
//!< Transmission power of radio, in dBm.
} re_c5_data_t;

/**
* @brief Encode given data to given buffer in Ruuvi DF5.
*
* NAN can be used as a placeholder for invalid / not available values.
*
* @param[in] buffer uint8_t array with length of 24 bytes.
* @param[in] data Struct containing all necessary information
* for encoding the data into buffer.
* @retval RE_SUCCESS if data was encoded successfully.
*/
re_status_t re_c5_encode (uint8_t * const buffer, const re_c5_data_t * data);

#endif
5 changes: 4 additions & 1 deletion src/ruuvi_endpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#if !defined(RE_8_ENABLED)
# define RE_8_ENABLED (1U)
#endif
#if !defined(RE_C5_ENABLED)
# define RE_C5_ENABLED (1U)
#endif
#if !defined(RE_CA_ENABLED)
# define RE_CA_ENABLED (1U)
#endif
Expand All @@ -29,7 +32,7 @@

#include <stdint.h>

#define RUUVI_ENDPOINTS_SEMVER "4.0.0" //!< SEMVER of endpoints.
#define RUUVI_ENDPOINTS_SEMVER "4.1.0" //!< SEMVER of endpoints.

#define RE_SUCCESS (0U) //!< Encoded successfully.
#define RE_ERROR_DATA_SIZE (1U << 3U) //!< Data size too large/small.
Expand Down
Loading

0 comments on commit eb10bc2

Please sign in to comment.