Examples: Sensor Personality Card¶
Overview¶
The Sensor Personality Card provides an example of a Moto Mod with a custom sensor that provides data to a custom Android application running on a Moto Z smartphone. The Moto Mod and application communicate over the Raw Protocol using custom protocol defined for this Personality Card.
This sensor used in the example card is a thermistor (NTC Thermistor NCP03XV103E05RL) that can measure ambient temperature. When the thermistor circuitry is enabled by asserting the Moto Mod Micro Controller (MuC) general purpose input/output pin (GPIO), it outputs voltage that varies depending on the temperature. The voltage output is connected to the MuC ADC channel for taking measurements. The measurements are reported to the Moto Z upon requests.
Before you begin...¶
Required Hardware¶
This app and example requires a Moto Z, Reference Moto Mod, and a Sensor Personality Card. To get started, you'll need to buy the required hardware.
Software and Tools¶
See the Build > Tools section to set up your development environment and learn about building from source, flashing firmware, and how to debug & log.
Download Example Source Code¶
- Open source application code for the MDK Sensor App is available at https://github.com/jksim/mdksensor.
- Open source firmware code for this example is located at https://github.com/jksim/nuttx - specifically:
- Build Target: https://github.com/jksim/nuttx/tree/master/nuttx/configs/hdk/muc/temperature
- Source Directory: https://github.com/jksim/nuttx/tree/master/nuttx/configs/hdk/muc/src
Download Schematics & More¶
We also provide schematics, layouts, BOMs, and CAD files for this example. See the Downloads section at the end of this page.
‘MDK Sensor’ App¶
MDK Sensor is a simple app that provides an end-to-end example demonstrating how to obtain readings from the Sensor Personality card attached to your Reference Moto Mod, which it then displays in the app as graph.
The MDK Sensor App will only work on Moto Z devices.
- MDK Sensor app (rebuilt from source; was on Google Play Store)
- MDK Sensor app (rebuilt from source; was on Lenovo App Store)
For more details, see the Moto Mods Android Application section further down this page.
Community and Support¶
Ask questions, engage with the developer community, and get support for this example in the Moto Mods group at element14.com. Also check out our Support page.
Electrical Details¶
Block Diagram¶
Sensor card connected to Reference Moto Mod.
(Diagram coming soon...)
Pin Connection Table¶
(Table coming soon...)
Schematic & Layout¶
See the Downloads section at the end of this page for the schematic, layout and more.
Hardware Setup¶
To use the Sensor Personality Card, first detach the Reference Moto Mod from the Moto Z. Locate dip switch A1 on the Mod and turn it on and dip switch A2 off to enable using Sensor from the example card, then insert the card into the Mod following these instructions. The Reference Moto Mod can be attached back to the Moto Z after that.
Firmware Development¶
Follow these instructions to setup your firmware development environment and download source code if it has not been done already.
The Moto Mods base firmware has a target for the Sensor Personality Card. The information below details the steps to create firmware for the Sensor Personality Card as if this support did not exist and needed to be added.
New Build Target¶
Create a new build target for the Sensor Personality Card using hdk/muc/base_powered target as the base.
$ cd $BUILD_TOP/nuttx/nuttx/configs/hdk/muc
$ mkdir example-temperature
$ cp base_powered/* example-temperature/
Switch to using the new build target.
The new target does not support required functionality yet. To read the analog signal from the card and communicate with Moto Z over the Raw protocol, the following features need to be enabled:
- Raw protocol
- ADC support
- Raw Protocol device driver
Raw Protocol¶
Files¶
nuttx/drivers/greybus/raw.c- Raw protocol
Configuration Changes¶
Set the configuration options to enable Raw Protocol.
ADC Support¶
Set the configuration options to enable ADC support.
Device Drivers --->
[*] Analog Device(ADC/DAC) Support --->
--- Analog Device(ADC/DAC) Support
[*] Analog-to-Digital Conversion
(8) ADC buffer size
[ ] TI ADS1255/ADS1256 support
[ ] TI PGA112/3/6/7 support
[ ] Digital-to-Analog Conversion
Raw Protocol Device Driver¶
The driver implements Temperature Personality Card Raw Protocol for a data exchange between the Moto Z and the Reference Moto Mod with the example card and measure thermistor circuit output voltage.
Files¶
nuttx/configs/hdk/muc/src/stm32_modsraw_temperature.c- raw protocol device driver for the Personality Card
Temperature Personality Card Raw Protocol¶
Message Format
(Table coming soon...)
Message Commands Sent to and from the Reference Moto Mod
#define TEMP_RAW_COMMAND_INVALID 0x00
#define TEMP_RAW_COMMAND_INFO 0x01
#define TEMP_RAW_COMMAND_ON 0x02
#define TEMP_RAW_COMMAND_OFF 0x03
#define TEMP_RAW_COMMAND_DATA 0x04
#define TEMP_RAW_COMMAND_CHALLENGE 0x05
#define TEMP_RAW_COMMAND_CHLGE_RESP 0x06
Responses from Reference Moto Mod to apk will have the command ids with high bit mask.
INFO Command The command is sent to to the Reference Moto Mod to request device info.
(Table coming soon...)
Response from the Reference Moto Mod.
(Table coming soon...)
/* device attributes */
struct temp_raw_attr {
uint8_t version; /* device driver version */
uint8_t reserved; /* reserved */
uint16_t max_interval; /* max report interval device supports */
uint8_t name[64]; /* name of the device */
} __packed;
ON Command The command is sent to to the Reference Moto Mod to turn on temperature reporting. Payload has the interval in milliseconds apk expects the temperature data.
(Table coming soon...)
OFF Command The command is sent to to the Reference Moto Mod to turn off temperature reporting.
(Table coming soon...)
DATA Command The message is sent from the Reference Moto Mod to report temperature measurements.
(Table coming soon...)
CHALLENGE Command The message is sent from the Reference Moto Mod.
(Table coming soon...)
CHALLENGE_RESPONSE Command The message is sent to the Reference Moto Mod.
(Table coming soon...)
Driver Details¶
This driver is based on nuttx/configs/hdk/muc/src/stm32_modsraw.c driver.
static struct device_raw_type_ops temp_raw_type_ops = {
.recv = temp_raw_recv,
.register_callback = temp_raw_register_callback,
.unregister_callback = temp_raw_unregister_callback,
};
static struct device_driver_ops temperature_driver_ops = {
.probe = temp_raw_probe,
.remove = temp_raw_remove,
.type_ops = &temp_raw_type_ops,
};
struct device_driver mods_raw_temperature_driver = {
.type = DEVICE_TYPE_RAW_HW,
.name = "mods_raw_temperature",
.desc = "Temperature sensor Raw Interface",
.ops = &temperature_driver_ops,
};
The pinmap file at nuttx/arch/arm/src/stm32/chip/stm32l4x6xx_pinmap.h contains the following definition:
Initialize and register ADC1 with channel GPIO_ADC1_IN4.
/* Configure the pins as analog inputs for the selected channels */
for (i = 0; i < ARRAY_SIZE(g_pinlist); i++)
{
stm32_configgpio(g_pinlist[i]);
}
-----
/* Call stm32_adcinitialize() to get an instance of the ADC interface */
adc = stm32_adcinitialize(1, g_chanlist, ARRAY_SIZE(g_chanlist));
------
/* Register the ADC driver at "/dev/adc0" */
ret = adc_register(TEMP_RAW_ADC_DEVPATH, adc);
Implement raw message handling in mods_raw_recv.
Challenge the client after processing INFO command. Use the current time in nanoseconds as data to be encrypted with AES 128-bit ECB with PKCS5 padding .
case TEMP_RAW_COMMAND_INFO:
----
up_rtc_gettime(&ts);
time_ns = timespec_to_nsec(&ts);
----
/* Encrypt padded time_ns */
AES128_ECB_encrypt(&info->tr_aes.input[2],
(const uint8_t *)&info->tr_aes.key[2],
&info->tr_aes.encrypted_data[2]);
/* send the challenge */
temp_raw_build_send_mesg(dev, TEMP_RAW_COMMAND_CHALLENGE,
sizeof(info->tr_aes.encrypted_data),
(uint8_t *)&info->tr_aes.encrypted_data[2], 0);
Process the challenge response and verify the previously sent data had the expected modification.
case TEMP_RAW_COMMAND_CHLGE_RESP:
----
/* Decrypt the adjusted time data */
AES128_ECB_decrypt(&smsg->payload[2],
(const uint8_t *)&info->tr_aes.key[2],
&info->tr_aes.decrypted_data[2]);
/*
* compare the sent data with received adjusted data.
* allow ON operation upon match
*/
memcpy(&time_ns, &info->tr_aes.input[2], sizeof(time_ns));
time_ns += TEMP_RAW_AES_CLIENT_ADDS;
retval = memcmp(&info->tr_aes.decrypted_data[2],
&time_ns, sizeof(time_ns));
if (!retval) {
info->client_verified = 1;
/* Initialize ADC */
adc_devinit();
} else {
info->client_verified = 0;
dbg("Client verification failed: %d\n", retval);
}
Setup low priority work queue to handle ON command (only if the client was verified).
case TEMP_RAW_COMMAND_ON:
----
if (info->client_verified == 0)
break;
----
gpio_set_value(GPIO_MODS_DEMO_ENABLE, 1);
----
/* cancel any work and reset ourselves */
if (!work_available(&data_report_work))
work_cancel(LPWORK, &data_report_work);
/* schedule work */
work_queue(LPWORK, &data_report_work,
temp_raw_worker, info, 0);
Read adc device for temperature data in the worker function and queue next worker due at interval requested.
/**
* worker function for the scheduled work queue.
* open ADC device and trigger a sample reading.
*/
static void temp_raw_worker(void *arg)
{
----
read(adc_fd, &sample, sizeof(sample));
----
temp_raw_build_send_mesg(info->gDevice, TEMP_RAW_COMMAND_DATA,
sizeof(sample.am_data), (uint8_t *)&sample.am_data, 0);
-----
/* cancel any work and reset ourselves */
if (!work_available(&data_report_work))
work_cancel(LPWORK, &data_report_work);
/* schedule work */
work_queue(LPWORK, &data_report_work,
temp_raw_worker, info, MSEC2TICK(info->interval));
Cancel the work queue on OFF command.
case TEMP_RAW_COMMAND_OFF:
----
/* cancel any work and reset ourselves */
if (!work_available(&data_report_work))
work_cancel(LPWORK, &data_report_work);
gpio_set_value(GPIO_MODS_DEMO_ENABLE, 0);
Configuration Changes¶
Set the configuration options to enable the raw protocol device driver for the Temperature Personality Card.
Board Selection --->
Select target board (Motorola HDK MuC) --->
[*] Temperature Personality Mods Raw support
Driver and Device Initialization¶
The GPIO to enable thermistor circuitry on the Personality Card is defined in nuttx/configs/hdk/muc/include/mods.h
The device driver is initialized in nuttx/configs/hdk/muc/src/stm32_boot.c. Entry in the devices[] array.
#ifdef CONFIG_MODS_RAW_TEMPERATURE
{
.type = DEVICE_TYPE_RAW_HW,
.name = "mods_raw_temperature",
.desc = "Temperature sensor Raw Interface",
.id = 0,
},
#endif
Part of the board_initialize() function.
#ifdef CONFIG_MODS_RAW_TEMPERATURE
extern struct device_driver mods_raw_temperature_driver;
device_register_driver(&mods_raw_temperature_driver);
#endif
Hardware Manifest¶
New Manifest¶
Create hardware manifest for the example using apps/greybus-utils/manifests/hdk-powered.mnfs as the base.
Edit the temperature-example.mnfs file to change the interface string and add Raw Interfaces and Bundle.
; RAW interface on CPort 4
[cport-descriptor 4]
bundle = 3
protocol = 0xfe
; RAW Bundle 3
[bundle-descriptor 3]
class = 0xfe
Setting the Manifest¶
Set the configuration options to use the newly created manifest.
Application Configuration --->
-*- Greybus utility --->
Select a predefined Manifest (Custom manifest)
(temperature-example) manifest name
Building and Flashing Firmware¶
The new target supports all required functionality now. The firmware can be built and flashed into the Reference Moto Mod. Make sure that the Moto Mod is running in development mode as per these instructions or build and flash the development bootloader yourself as documented here.
Moto Mods Android Application¶
Temperature card sample APK¶
The Moto Temperature card sample APK is an open source project, works as an downloadable Android app, to show the capability of Temperature sensor personality card.
Install APK¶
Download and install the Temperature card sample app - see MDK Sensor app on Google Play Store
Source code¶
The source code of the MDK Sensor APK is published at https://github.com/jksim/mdksensor.
To build the sample APK from source code, please download and install Android Studio from Android Developer Site, then import the mdksensor project into Android Studio. See Developer Tools: Setup Your Development Environment for more info.
Sequence Diagram¶
(Diagram coming soon...)
Reference for ModManager Interface:¶
Refer to the Hello, World example for details on common Moto Mod Management. That source code is reused throughout the Examples.
Write RAW command for Sensor Card:¶
/**
* Command is [cmd ID(1 byte)] [size of payload(1 byte)] [payload]
*/
public static final int TEMP_RAW_COMMAND_RESP_MASK = 0x80;
public static final int TEMP_RAW_COMMAND_INVALID = 0x00;
public static final int TEMP_RAW_COMMAND_INFO = 0x01;
public static final int TEMP_RAW_COMMAND_ON = 0x02;
public static final int TEMP_RAW_COMMAND_OFF = 0x03;
public static final int TEMP_RAW_COMMAND_DATA = 0x04;
public static final int TEMP_RAW_COMMAND_CHALLENGE = 0x05;
public static final int TEMP_RAW_COMMAND_CHLGE_RESP = 0x06;
/** Challenge code for MDK Temperature Sensor protocol */
public static final int CHALLENGE_ADDATION = 777;
/** Samples for RAW command format */
public static final int SENSOR_COMMAND_SIZE = 0x02;
public static byte[] RAW_CMD_INFO = {TEMP_RAW_COMMAND_INFO, 0x00};
public static byte[] RAW_CMD_CHALLENGE = {TEMP_RAW_COMMAND_CHALLENGE,
0x00, /* should use actually size of encrypted AES */
0x00, /* should be payload of encrypted AES */};
public static byte[] RAW_CMD_START = {TEMP_RAW_COMMAND_ON, SENSOR_COMMAND_SIZE,
0x00, /* interval(low) */
0x20, /* interval(high) */};
public static byte[] RAW_CMD_STOP = {TEMP_RAW_COMMAND_OFF, 0x00}; /* stop cmd not need payload */
int interval = Integer.valueOf(values[spinner.getSelectedItemPosition()]);
byte intervalLow = (byte) (interval & 0x00FF);
byte intervalHigh = (byte) (interval >> 8);
byte[] cmd = { Constants.SENSOR_COMMAND_ON,
Constants.SENSOR_COMMAND_SIZE,
intervalLow,
intervalHigh };
outputStream.write(cmd);
Read RAW data from Sensor Card:¶
/** Read the RAW I/O input pipe, the data is written by attached mod device */
byte[] buffer = new byte[MAX_BYTES];
FileDescriptor fd = parcelFD.getFileDescriptor();
FileInputStream inputStream = new FileInputStream(fd);
int ret = 0;
synchronized (syncPipes) {
while (ret >= 0) {
try {
/** Poll on the exit pipe and the raw channel */
int polltype = blockRead();
if (polltype == POLL_TYPE_READ_DATA) {
ret = inputStream.read(buffer, 0, MAX_BYTES);
if (ret > 0) {
/** Got raw data */
onRawData(buffer, ret);
}
} else if (polltype == POLL_TYPE_EXIT) {
break;
}
} catch (IOException e) {
Log.e(Constants.TAG, "IOException while reading from raw file" + e);
onIOException();
} catch (Exception e) {
Log.e(Constants.TAG, "Exception while reading from raw file" + e);
e.printStackTrace();
}
}
Parse RAW data:¶
/**
* Response is [cmd ID(1 byte)] [size of payload(1 byte)] [payload]
*/
public static final int CMD_OFFSET = 0;
public static final int CMD_LENGTH = 1;
public static final int SIZE_OFFSET = CMD_OFFSET + CMD_LENGTH;
public static final int SIZE_LENGTH = 1;
public static final int PAYLOAD_OFFSET = SIZE_OFFSET + SIZE_LENGTH;
public static int CMD_INFO_VERSION_OFFSET = 0;
public static int CMD_INFO_VERSION_SIZE = 1;
public static int CMD_INFO_RESERVED_OFFSET = CMD_INFO_VERSION_OFFSET + CMD_INFO_VERSION_SIZE;
public static int CMD_INFO_RESERVED_SIZE = 1;
public static int CMD_INFO_LATENCYLOW_OFFSET = CMD_INFO_RESERVED_OFFSET + CMD_INFO_RESERVED_SIZE;
public static int CMD_INFO_LATENCYLOW_SIZE = 1;
public static int CMD_INFO_LATENCYHIGH_OFFSET = CMD_INFO_LATENCYLOW_OFFSET + CMD_INFO_LATENCYLOW_SIZE;
public static int CMD_INFO_LATENCYHIGH_SIZE = 1;
public static int CMD_INFO_NAME_OFFSET = CMD_INFO_LATENCYHIGH_OFFSET + CMD_INFO_LATENCYHIGH_SIZE;
public static int CMD_INFO_HEAD_SIZE = CMD_INFO_VERSION_SIZE + CMD_INFO_RESERVED_SIZE
+ CMD_INFO_LATENCYLOW_SIZE + CMD_INFO_LATENCYHIGH_SIZE;
public static int CMD_DATA_LOWDATA_OFFSET = 0;
public static int CMD_DATA_LOWDATA_SIZE = 1;
public static int CMD_DATA_HIGHDATA_OFFSET = CMD_DATA_LOWDATA_OFFSET + CMD_DATA_LOWDATA_SIZE;
public static int CMD_DATA_HIGHDATA_SIZE = 1;
public static int CMD_DATA_RESERVED_SIZE = 2;
public static int CMD_DATA_SIZE = CMD_DATA_LOWDATA_SIZE + CMD_DATA_HIGHDATA_SIZE + CMD_DATA_RESERVED_SIZE;
public static int CMD_CHALLENGE_SIZE = 16;
public static int CMD_CHLGE_RESP_OFFSET = 0;
public static int CMD_CHLGE_RESP_SIZE = 4;
/** Read the RAW I/O input pipe, the data is written by attached mod device */
private int blockRead() {
/** Poll on the pipe to see whether signal to exit, or any data on raw fd to read */
StructPollfd[] pollfds = new StructPollfd[2];
/** readRawFd will watch whether data is available on the raw channel */
StructPollfd readRawFd = new StructPollfd();
pollfds[0] = readRawFd;
readRawFd.fd = parcelFD.getFileDescriptor();
readRawFd.events = (short) (OsConstants.POLLIN | OsConstants.POLLHUP);
/** syncFd will watch whether any exit signal */
StructPollfd syncFd = new StructPollfd();
pollfds[1] = syncFd;
syncFd.fd = syncPipes[0];
syncFd.events = (short) OsConstants.POLLIN;
try {
/** Waits for file descriptors pollfds to become ready to perform I/O */
int ret = Os.poll(pollfds, -1);
if (ret > 0) {
if (syncFd.revents == OsConstants.POLLIN) {
/** POLLIN on the syncFd as signal to exit */
byte[] buffer = new byte[1];
new FileInputStream(syncPipes[0]).read(buffer, 0, 1);
return POLL_TYPE_EXIT;
} else if ((readRawFd.revents & OsConstants.POLLHUP) != 0) {
/** RAW driver existing */
return POLL_TYPE_EXIT;
} else if ((readRawFd.revents & OsConstants.POLLIN) != 0) {
/** Finally data ready to read */
return POLL_TYPE_READ_DATA;
} else {
/** Unexcpected error */
Log.e(Constants.TAG, "unexpected events in blockRead rawEvents:"
+ readRawFd.revents + " syncEvents:" + syncFd.revents);
return POLL_TYPE_EXIT;
}
} else {
/** Error */
Log.e(Constants.TAG, "Error in blockRead: " + ret);
}
} catch (ErrnoException e) {
Log.e(Constants.TAG, "ErrnoException in blockRead: " + e);
e.printStackTrace();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException in blockRead: " + e);
e.printStackTrace();
}
return POLL_TYPE_EXIT;
}
/** Parse raw data to header and payload */
int cmd = buffer[Constants.CMD_OFFSET] & ~Constants.TEMP_RAW_COMMAND_RESP_MASK & 0xFF;
int payloadLength = buffer[Constants.SIZE_OFFSET];
/** Checking the size of buffer we got to ensure sufficient bytes */
if (payloadLength + Constants.CMD_LENGTH + Constants.SIZE_LENGTH != length) {
return;
}
/** Parser payload data */
byte[] payload = new byte[payloadLength];
System.arraycopy(buffer, Constants.PAYLOAD_OFFSET, payload, 0, payloadLength);
parseResponse(cmd, payloadLength, payload);
Verify¶
Attach Temperature card to phone, then install and launch Temperature card sample apk, check the Mod / Personality card status and information, then turn on Temperature switcher UI to read Temperature card data
(Screenshot coming soon...)
If Temperature card is ready, the sampling value read from HDK Temperature card for every interval will show up.
Downloads¶
This section contains the downloadable files associated with this example.
Before downloading, please read through the Moto Mods Development Kit Terms & Conditions: