This is the multi-page printable view of this section. Click here to print...
TWELITE Wings API / MWings for 32-bit Arduinos
1 - TWELITE Wings API / MWings for 32-bit Arduinos
1.1 - Using with TWELITE SPOT
1.2 - Using with Arduino UNO R4
Hardware Preparation
Preparing TWELITE
Write the Parent and Repeater App (App_Wings) to products such as TWELITE UART and TWELITE DIP.
Use the TWELITE STAGE APP for writing.
As of December 2023, the latest App_Wings (v1.3.0+) that supports setting Application ID via serial communication is not included in the TWELITE STAGE SDK (it will be added in the next release).
Please download the following files first, then write the firmware.
- For TWELITE BLUE: App_Wings_BLUE_L1305_V1-3-2.bin
- For TWELITE RED: App_Wings_RED_L1305_V1-3-2.bin
Connection with Arduino UNO R4
Use the following TWELITE pins:
- VCC (connect to 3.3V)
- GND (connect to GND)
- TXD (connect to D0/RX)
- RXD (connect to D1/TX)
- RST (connect to any port such as D11)
- PRG (connect to any port such as D12)
Below is an example wiring diagram based on the sample sketch included with the library.

Example connection with Arduino
Software Preparation
Installing the Library
You can install it via the Arduino Library Manager.
Refer to the TWELITE SPOT manual: Installing the MWings Library.
Verifying the Sample Sketch Operation
The library includes simple sample sketches for communicating with each TWELITE device.
For example, to receive data from the Extremely Simple! Standard App (App_Twelite), open the following sample sketch from the menu bar:
File > Examples > MWings > Arduino UNO R4 > Receive > monitor_uno_r4_app_twelite

Example display
monitor_uno_r4_app_twelite.ino
// Monitor example for TWELITE with Arduino UNO R4: Receive data from App_Twelite
#include <Arduino.h>
#include "MWings.h"
const int RST_PIN = D11;
const int PRG_PIN = D12;
const int LED_PIN = D13; // Use on-board LED as indicator
const uint8_t TWE_CHANNEL = 18;
const uint32_t TWE_APP_ID = 0x67720102;
void setup()
{
// Initialize serial ports
while (!Serial && millis() < 5000); // Wait for internal USB-UART
Serial.begin(115200);
Serial.println("Monitor example for TWELITE with Arduino UNO R4: App_Twelite");
Serial1.begin(115200);
// Initialize TWELITE
if (Twelite.begin(Serial1,
LED_PIN, RST_PIN, PRG_PIN,
TWE_CHANNEL, TWE_APP_ID)) {
Serial.println("Successfully initialized TWELITE");
} else {
Serial.println("Failed to initialize TWELITE");
}
// Attach an event handler to process packets from App_Twelite
Twelite.on([](const ParsedAppTwelitePacket& packet) {
Serial.println("");
Serial.print("Packet Timestamp: ");
Serial.print(packet.u16SequenceNumber / 64.0f, 1); Serial.println(" sec");
Serial.print("Source Logical ID: 0x");
Serial.println(packet.u8SourceLogicalId, HEX);
Serial.print("LQI: ");
Serial.println(packet.u8Lqi, DEC);
Serial.print("Supply Voltage: ");
Serial.print(packet.u16SupplyVoltage, DEC); Serial.println(" mV");
Serial.print("Digital Input: ");
Serial.print(packet.bDiState[0] ? " DI1:Lo" : " DI1:Hi");
Serial.print(packet.bDiState[1] ? " DI2:Lo" : " DI2:Hi");
Serial.print(packet.bDiState[2] ? " DI3:Lo" : " DI3:Hi");
Serial.println(packet.bDiState[3] ? " DI4:Lo" : " DI4:Hi");
Serial.print("Analog Input: ");
Serial.print(" AI1:"); Serial.print(packet.u16AiVoltage[0]); Serial.print(" mV");
Serial.print(" AI2:"); Serial.print(packet.u16AiVoltage[1]); Serial.print(" mV");
Serial.print(" AI3:"); Serial.print(packet.u16AiVoltage[2]); Serial.print(" mV");
Serial.print(" AI4:"); Serial.print(packet.u16AiVoltage[3]); Serial.println(" mV");
});
}
void loop()
{
// Update TWELITE
Twelite.update();
}
/*
* Copyright (C) 2023 Mono Wireless Inc. All Rights Reserved.
* Released under MW-OSSLA-1J,1E (MONO WIRELESS OPEN SOURCE SOFTWARE LICENSE AGREEMENT).
*/
For details of the sketch, please refer to the TWELITE SPOT sketch explanations. Most of the content is common.
The following are specific to Arduino UNO R4.
Port Settings
Lines 6-8 configure Arduino ports excluding UART-related pins.
const int RST_PIN = D11;
const int PRG_PIN = D12;
const int LED_PIN = D13; // Use on-board LED as indicator
Pin | Role | Notes |
---|---|---|
D11 | TWELITE RST control | See the connection example above |
D12 | TWELITE PRG control | See the connection example above |
D13 | Communication indicator LED control | Uses built-in LED |
Waiting for USB Serial Port Initialization
Line 16 waits for the USB serial port (Serial
) to initialize.
while (!Serial && millis() < 5000); // Wait for internal USB-UART
Arduino UNO R4 does not have a USB-serial converter IC like UNO R3; the ARM CPU itself handles USB serial conversion. Therefore, communication cannot occur before the serial port initialization completes.
Related Information
- MWings Library API Reference
- TWELITE SPOT Start Guide Sketch Explanation (most content is common)
- TWELITE SPOT Manual Partial Sketch Explanation (most content is common)
1.3 - Extending Packet Parsers
Overview
To add a parser, you need to edit the following four files:
- (New) Parser header file
parser/FooBarPacketParser.h
- Write the contents of the packet to be parsed and the criteria for a valid packet
- (New) Parser source file
parser/FooBarPacketParser.cpp
- Write the part that interprets the packet contents
- (Append) Main header file
MWings.h
- Add the parser
- (Append) Main source file
MWings.cpp
- Add the parser
You can obtain the source files from GitHub.
Typically, place them in the libraries/
directory inside your Arduino home directory.
You can view the actual commit diffs on GitHub.
Added App_Tag (ADC) packet parser ยท monowireless/mwings_arduino@aa5ecdb
Parser Header File
Duplicate an existing file under parser/
and rename it.
Here, we create parser/AppTagAdcPacketParser.h
.
#ifndef APPTAGADCPACKETPARSER_H
#define APPTAGADCPACKETPARSER_H
#include "MWings_Common.h"
/**
* @struct ParsedAppTagAdcPacket
* @brief Packet content for App_Tag (ADC)
*/
struct ParsedAppTagAdcPacket final : public mwings::ParsedPacketBase {
uint32_t u32RouterSerialId;
uint8_t u8SensorType;
uint16_t u16AiVoltage[2];
};
/**
* @class apptagadc::PacketParser
* @brief Packet parser for App_Tag (ADC)
*/
namespace apptagadc {
class PacketParser final : public mwings::PacketParserBase {
public:
// Check if the packet is from App_Tag (ADC)
inline bool isValid(const BarePacket& barePacket) const override {
if ((barePacket.u8At(12) == 0x10)
and (barePacket.u32At(18) == 0)
and (barePacket.u16PayloadSize == 22)) {
return true;
}
return false;
}
// Parse from bare packet
bool parse(const BarePacket& barePacket, mwings::ParsedPacketBase* const parsedPacket) const override;
};
}
extern apptagadc::PacketParser AppTagAdcPacketParser;
#endif // APPTAGADCPACKETPARSER_H
Describing the Packet Contents to be Parsed
First, please check the App_Tag (Analog Sensor) output format.
Example output data
:80000000B700628201015A0010DF08FD09A300000000E9
# | Data | Description | Value | |
---|---|---|---|---|
: | char | Header | : | |
80000000 | 0 | uint32 | Router serial ID | No router |
B7 | 4 | uint8 | LQI | 183/255 |
0062 | 5 | uint16 | Sequence number | 98 |
8201015A | 7 | uint32 | Source serial ID | 0x201015A |
00 | 11 | uint8 | Source logical ID | 0x00 |
10 | 12 | uint8 | Sensor type | Analog sensor |
DF | 13 | uint8 | Supply voltage (mV) | 3330 mV |
08FD | 14 | uint16 | ADC1 voltage | 2301 mV |
09A3 | 16 | uint16 | ADC2 voltage | 2467 mV |
00000000 | 18 | uint32 | Unused | |
E9 | 22 | uint8 | Checksum | 0xE9 |
char | Footer | \r | ||
char | Footer | \n |
After replacing the include guard, namespace
, and comments, inherit from mwings::ParsedPacketBase
and describe the packet contents.
Here, declare data unique to the target device. General data such as the destination logical device ID is already registered in mwings::ParsedPacketBase
.
Items in mwings::ParsedPacketBase
Type | Name | Description |
---|---|---|
uint32_t | u32SourceSerialId | Source serial ID |
uint8_t | u8SourceLogicalId | Source logical device ID |
uint16_t | u16SequenceNumber | Sequence number |
uint8_t | u8Lqi | LQI |
uint16_t | u16SupplyVoltage | Supply voltage (mV) |
It’s fine to have unused fields such as supply voltage.
In the following section, unique data for App_Tag (Analog Sensor) is declared.
struct ParsedAppTagAdcPacket final : public mwings::ParsedPacketBase {
uint32_t u32RouterSerialId;
uint8_t u8SensorType;
uint16_t u16AiVoltage[2];
};
Writing the Criteria for a Valid Packet
Create apptagadc::PacketParser
inheriting from mwings::PacketParserBase
and override the pure virtual function isValid()
to specify the criteria for a valid packet.
Packets matching this condition will be parsed.
In the following, it checks that the sensor type is 0x10
(analog sensor), the unused area is 0
, and the payload length is 22 bytes.
inline bool isValid(const BarePacket& barePacket) const override {
if ((barePacket.u8At(12) == 0x10)
and (barePacket.u32At(18) == 0)
and (barePacket.u16PayloadSize == 22)) {
return true;
}
return false;
}
Parser Source File
Duplicate an existing file under parser/
and rename it.
Here, we create parser/AppTagAdcPacketParser.cpp
.
#include "AppTagAdcPacketParser.h"
apptagadc::PacketParser AppTagAdcPacketParser;
bool apptagadc::PacketParser::parse(const BarePacket& barePacket, mwings::ParsedPacketBase* const parsedPacket) const
{
// WARNING: Note that there is NO RTTI
ParsedAppTagAdcPacket* const parsedAppTagAdcPacket = static_cast<ParsedAppTagAdcPacket*>(parsedPacket);
parsedAppTagAdcPacket->u32SourceSerialId = barePacket.u32At(7);
parsedAppTagAdcPacket->u8SourceLogicalId = barePacket.u8At(11);
parsedAppTagAdcPacket->u16SequenceNumber = barePacket.u16At(5);
parsedAppTagAdcPacket->u8Lqi = barePacket.u8At(4);
const uint16_t ecc = barePacket.u8At(13);
if (ecc <= 170) {
parsedAppTagAdcPacket->u16SupplyVoltage = 5 * ecc + 1950;
} else {
parsedAppTagAdcPacket->u16SupplyVoltage = 10 * (ecc - 170) + 2800;
}
parsedAppTagAdcPacket->u32RouterSerialId = barePacket.u32At(0);
parsedAppTagAdcPacket->u8SensorType = barePacket.u8At(12);
for (int i = 0; i < 2; i++) {
parsedAppTagAdcPacket->u16AiVoltage[i] = barePacket.u16At(2*i+14);
}
return true;
}
Writing the Parsing Logic
After replacing the header file name and namespace
, write the contents of parse()
.
The following stores the packet contents according to the data format.
Wireless Tag App (Analog Sensor) Output Format
# | Data | Description | Notes |
---|---|---|---|
char | Header | : only | |
0 | uint32 | Router serial ID | 80000000 if no router |
4 | uint8 | LQI | 0 -255 |
5 | uint16 | Sequence number | |
7 | uint32 | Source serial ID | |
11 | uint8 | Source logical ID | |
12 | uint8 | Sensor type | |
13 | uint8 | Supply voltage (mV) | See Supply Voltage Calculation |
14 | uint16 | ADC1 voltage | |
16 | uint16 | ADC2 voltage | |
18 | uint32 | Unused | |
22 | uint8 | Checksum |
Use methods like BarePacket.u8At()
to extract data from the raw payload.
// WARNING: Note that there is NO RTTI
ParsedAppTagAdcPacket* const parsedAppTagAdcPacket = static_cast<ParsedAppTagAdcPacket*>(parsedPacket);
parsedAppTagAdcPacket->u32SourceSerialId = barePacket.u32At(7);
parsedAppTagAdcPacket->u8SourceLogicalId = barePacket.u8At(11);
parsedAppTagAdcPacket->u16SequenceNumber = barePacket.u16At(5);
parsedAppTagAdcPacket->u8Lqi = barePacket.u8At(4);
const uint16_t ecc = barePacket.u8At(13);
if (ecc <= 170) {
parsedAppTagAdcPacket->u16SupplyVoltage = 5 * ecc + 1950;
} else {
parsedAppTagAdcPacket->u16SupplyVoltage = 10 * (ecc - 170) + 2800;
}
parsedAppTagAdcPacket->u32RouterSerialId = barePacket.u32At(0);
parsedAppTagAdcPacket->u8SensorType = barePacket.u8At(12);
for (int i = 0; i < 2; i++) {
parsedAppTagAdcPacket->u16AiVoltage[i] = barePacket.u16At(2*i+14);
}
About static_cast
mwings::ParsedPacketBase*
to apptagadc::ParsedPacket
is done using static_cast
instead of dynamic_cast
. This is due to hardware constraints that prevent using runtime type information.Main Header File
In addition to existing parsers, add the new parser.
Adding Include Directive
Include the packet parser header file.
//// AppTagAdcPacketParser for App_Tag (ADC)
#include "parser/AppTagAdcPacketParser.h"
Extending the Initializer List
Initialize the event handler to nullptr
.
//// AppTagAdcPacketParser for App_Tag (ADC)
_onAppTagAdcPacket(nullptr),
Adding Cleanup Code
Reset the event handler to nullptr
.
//// AppTagAdcPacketParser for App_Tag (ADC)
_onAppTagAdcPacket = nullptr;
Adding Event Handler Registration Method
Add an on()
method to register the event handler.
//// AppTagAdcPacketParser for App_Tag (ADC)
inline void on(void (*callback)(const ParsedAppTagAdcPacket& packet)) { _onAppTagAdcPacket = callback; }
Adding Event Handler Member
Add a pointer to store the event handler.
//// AppTagAdcPacketParser for App_Tag (ADC)
void (*_onAppTagAdcPacket)(const ParsedAppTagAdcPacket& packet);
Editing Main Source File
Add the new parsing process in addition to the existing parsing.
Initializing the Event Handler
Ensure the event handler is initialized when begin()
is called.
//// AppTagAdcPacketParser for App_Tag (ADC)
_onAppTagAdcPacket = nullptr;
Adding Parsing Process
Parse the target packet when received.
//// Start: AppTagAdcPacketParser for App_Tag (ADC)
if (AppTagAdcPacketParser.isValid(barePacket) and _onAppTagAdcPacket) {
ParsedAppTagAdcPacket parsedAppTagAdcPacket;
if (AppTagAdcPacketParser.parse(barePacket, &parsedAppTagAdcPacket)) {
_onAppTagAdcPacket(parsedAppTagAdcPacket);
}
}
//// End: AppTagAdcPacketParser for App_Tag (ADC)
Applying Changes
When you build the sketch, the library will also be rebuilt.