This is the multi-page printable view of this section. Click here to print...

Return to the regular view of this page

As of 2025-07-24

TWELITE Wings API / MWings for 32-bit Arduinos

TWELITE Wings API / MWings for 32-bit Arduino boards

1 - TWELITE Wings API / MWings for 32-bit Arduinos

Latest Edition

1.1 - Using with TWELITE SPOT

How to use with the ESP32 on TWELITE SPOT
TWELITE SPOT Start Guide: See Receiving Data from TWELITE Child Devices.

1.2 - Using with Arduino UNO R4

How to use with the Arduino UNO R4 series
This guide explains how to connect TWELITE parent devices such as TWELITE UART or TWELITE DIP to the Arduino UNO R4 series and communicate with child devices using the MWings library.

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.

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

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

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).
 */

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
PinRoleNotes
D11TWELITE RST controlSee the connection example above
D12TWELITE PRG controlSee the connection example above
D13Communication indicator LED controlUses 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.

1.3 - Extending Packet Parsers

How to add packet parsers
In the MWings library, you can easily add 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

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.

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.

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.

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);
}

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.