Skip to content

ruiseixasm/JsonTalkie

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,132 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JsonTalkie - Walkie-Talkie based Communication for Arduino (JeT: Json et Talkie)

A lightweight library for Arduino (IoT devices) communication and control, it uses JSON messages over Broadcast sockets that by working as Broadcast ensure the fastest possible common reception of the targeted devices. This is a peer-to-peer protocol, so, no central brokers are needed. A good alternative to the heavy MQTT protocol.

Features

  • Bi-directional Walkie-Talkie based communication between Arduino devices and/or Python JsonTalkiePy.
  • Simple command/response pattern with "Walkie-talkie" style interaction.
  • Broadcasted able communications for immediate common reception from multiple devices at once and in sync.
  • Talker configuration with a Manifesto for self-describing actions.
  • Automatic talkers discovery and description with the command talk.
  • Support for unlimited devices on the same network.

Comparison with MQTT

JsonTalkie is a lightweight messaging framework designed for direct peer-to-peer communication in resource-constrained embedded systems. It was created to address the practical limitations of MQTT on small microcontrollers, where TCP/IP stacks, brokers, and dynamic memory usage make traditional MQTT implementations unsuitable or unreliable.

Instead of relying on a centralized broker and TCP connections, JsonTalkie uses a decentralized, broadcast-oriented model that can operate over multiple transport layers, including UDP, SPI, Serial, and other protocols. This makes it an excellent alternative to MQTT (MQTT alternative) and particularly well suited for Arduino Uno, Arduino Nano, ATmega-based boards, and other similar devices operating on common networks like a LAN.

The following points highlight the key architectural and practical differences between JsonTalkie and MQTT:

  • The immediate difference is that JsonTalkie is lighter, you can easily use JsonTalkie in an Arduino Nano or Uno with low memory.
  • Contrary to the MQTT, JsonTalkie doesn't require any broker or server, devices truly communicate directly.
  • MQTT is limited to the TCP/IP protocol while JsonTalkie can be used with any communication protocol, like SPI and Serial.
  • MQTT "cloud" logic is complex in its setup while JsonTalkie is just a matter of including this library and associate it with the needed Manifesto.
  • MQTT is aimed to long distance communication, like the Internet, JsonTalkie is aimed to short distance communication, like a LAN.
  • Also, JsonTalkie is aimed to speed and low latency, so, the transmission is limitedly guaranteed and very dependent on the robustness of the BroadcastSocket implementation.
  • MQTT is safer because it implements encryption out of the box, however, JsonTalkie can have the same level of security via BroadcastSocket interface implementation or a VPN.
  • JsonTalkie requires that the Socket being used is able to send in broadcast to all same network devices, resulting in simultaneous receptions and thus minimal latency, in contrast, the MQTT centers the communication in a Broker resulting in communications with higher latency.

Installation

Arduino Library

  1. Using Arduino Library Manager:

    • Open Arduino IDE
    • Go to Sketch > Include Library > Manage Libraries
    • Search for "JsonTalkie"
    • Click "Install"
  2. Manual Installation:

    • Download the latest release from GitHub
    • Extract to your Arduino libraries folder
    • Restart Arduino IDE

Usage

Include the needed Manifesto and Socket files

To use it you only have to create a Manifesto and include it in a Sketch. You also need a Socket, you can pick one in the folder sockets, and place the respective socket file side-by-side with your sketch, like so:

#include <JsonTalkie.hpp>
#include "S_Broadcast_SPI_Arduino_Slave.h"
#include "M_BuzzerManifesto.hpp"

You may include more than one Manifesto or Socket, like so:

#include <JsonTalkie.hpp>
#include "S_BroadcastESP_WiFi.hpp"
#include "S_Broadcast_SPI_2xArduino_Master.hpp"
#include "M_Esp66Manifesto.hpp"
#include "M_LedManifesto.hpp"
#include "M_MessageTester.hpp"
#include "M_Spy.hpp"

You may find Manifesto examples in the manifestos folder too.

Create the Talkers and initiate the Repeater

After the includes above you have to create the needed list of instantiations and pass them as pointer to the Repeater, like so:

// TALKERS 
// M_Spy Talker
const char t_spy_name[] = "spy";
const char t_spy_desc[] = "I'm a Spy and I spy the talkers' pings";
M_Spy spy_manifesto;
JsonTalker t_spy = JsonTalker(t_spy_name, t_spy_desc, &spy_manifesto);

// Talker (led)
const char l_led_name[] = "blue";
const char l_led_desc[] = "I turn led Blue on and off";
M_LedManifesto led_manifesto(LED_BUILTIN);
JsonTalker l_led = JsonTalker(l_led_name, l_led_desc, &led_manifesto);

// Talker (JsonMessage tester)
const char t_tester_name[] = "test";
const char t_tester_desc[] = "I test the JsonMessage class";
M_MessageTester message_tester;
JsonTalker t_tester = JsonTalker(t_tester_name, t_tester_desc, &message_tester);


// SOCKETS
// Singleton requires the & (to get a reference variable)
auto& ethernet_socket = S_EthernetENC_Broadcast::instance();
/**
 * CAUTION:
 * - For more than one single board as SPI Slave, make sure you connect a resistor of **around 500 Ohms** to
 *   each SPI Slave MISO pin, in the case of the Arduino Uno and Nano is the pin 12!
 * 
 *   [1st Slave Arduino MISO] ----[500Ω]----┐
 *   [2nd Slave Arduino MISO] ----[500Ω]----┼---- [Master ESP32 MISO]
 *   [3rd Slave Arduino MISO] ----[500Ω]----┘
 *
 */
const int spi_pins[] = {4, 16};	// To which each Arduino CS (D10) pin is connected on the ESP32
auto& spi_socket = S_Broadcast_SPI_ESP_Arduino_Master::instance(spi_pins, sizeof(spi_pins)/sizeof(int));


// SETTING THE REPEATER
BroadcastSocket* uplinked_sockets[] = { &ethernet_socket };
JsonTalker* downlinked_talkers[] = { &t_spy, &t_tester, &l_led };
BroadcastSocket* downlinked_sockets[] = { &spi_socket };
const MessageRepeater message_repeater(
		uplinked_sockets, sizeof(uplinked_sockets)/sizeof(BroadcastSocket*),
		downlinked_talkers, sizeof(downlinked_talkers)/sizeof(JsonTalker*),
		downlinked_sockets, sizeof(downlinked_sockets)/sizeof(BroadcastSocket*)
	);

Keep the Repeater looping

Finally keep calling the Message Repeater loop method, like so:

void loop() {
    Ethernet.maintain();		// Maintain DHCP lease (important for long-running applications)
	message_repeater.loop();	// Keep calling the Message Repeater
}

Examples

You can select a JsonTalkie example by going to File > Examples > JsonTalkie and pick any of them for more details. All these examples can be found in the folder Examples.

Python Command Line

JsonTalkiePy repository with command line as Talker

  • Talker in JsonTalkiePy
  • Click the link above for more details concerning its usage

Start command line

python talk.py

Typical usage

>>> talk
    [talk spy]                 I'm a Spy and I spy the talkers' pings
    [talk test]                I test the JsonMessage class
    [talk blue]                I turn led Blue on and off
    [talk nano]                Arduino Nano
    [talk mega]                I'm a Mega talker
    [talk uno]                 Arduino Uno
    [talk green]               I'm a green talker
    [talk buzzer]              I'm a buzzer that buzzes
>>> list nano
    [call nano 0|buzz]         Buzz for a while
    [call nano 1|ms]           Gets and sets the buzzing duration
>>> call nano 0
    [call nano 0]              roger
>>> ping nano
    [ping nano]                3
>>> system nano board
    [system nano board]        Arduino Uno/Nano (ATmega328P)
>>> system nano sockets
    [system nano sockets]      0       BroadcastSocket_EtherCard       10
>>> system nano manifesto
    [system nano manifesto]    BlackManifesto          2
>>> list test
    [call test 0|all]          Tests all methods
    [call test 1|parse_json]   Test deserialize (fill up)
    [call test 2|compare]      Test if it's the same
    [call test 3|has]          Test if it finds the given char
    [call test 4|has_not]      Try to find a given char
    [call test 5|length]       Test it has the right length
    [call test 6|type]         Test the type of value
    [call test 7|validate]     Validate message fields
    [call test 8|identity]     Extract the message identity
    [call test 9|value]        Checks if it has a value 0
    [call test 10|message]     Gets the message number
    [call test 11|from]        Gets the from name string
    [call test 12|remove]      Removes a given field
    [call test 13|set]         Sets a given field
    [call test 14|edge]        Tests edge cases
    [call test 15|copy]        Tests the copy constructor
    [call test 16|string]      Has a value 0 as string
>>> call test 0
    [call test 0]              roger
>>>

Maximum sizes

The json message has a maximum size of 128 bytes, so, definitions like names and descriptions must be limited in size too, otherwise failure of transmissions may occur. This way, the following maximum sizes must be respected to avoid any failed transmission due to it.

Names

  • Talker - The Talker name can't be in any circumstance more than 10 chars.
  • Action - The Action name can't be in any circumstance more than 10 chars.

Descriptions

  • Talker - The Talker description together with its name shouldn't be more than 54 chars.
  • Action - The Action description together with its name shouldn't be more than 40 chars.
  • Socket - The description of the BroadcastSocket implementation shouldn't be more than 35 chars.
  • Manifesto - The description of the TalkerManifesto implementation shouldn't be more than 42 chars.
  • any - Any description can't be in any circumstance more than 54 chars, however, in nth transmitted strings, being less than that doesn't guarantee transmission by itself , in other words, avoid big sized descriptions in nth transmitted strings.

The math

The maximum length is calculated over the minimum required Json keys, like the ones concerning the MessageValue and the BroadcastValue, like so:

{"m":1,"b":1,"i":12345,"f":"1234567890","t":"1234567890","c":12345}

The message length above is 67 chars, this means that all extra fields have available a maximum of 61 chars. Given this limit, each field adds up the following chars just for the keys:

  • 5 chars just for numerical fields ,"n":,
  • 7 chars just for string fields ,"s":"".

Then, on top of the length above for each type of field, you start adding the length for the value itself. This means that for two 16bits numbers, it may be needed to transmit it in a single 32bits numerical field and manage the bitwise split on both ends, because this way you will be using just a single 5 chars key.

Payload size

The Payload size is by default 128 Bytes as explained above. However, for specific scenarios, you can set higher sizes, but given the specifics of certain SPI Sockets, the new size should be a multiple of 4 and not greater than 256 Bytes, so, typical ideal values above 128 Bytes are 160 and 192 Bytes.

In order to change the Payload size, go to the file src/TalkieCodes.hpp and edit the value in the following line:

#define TALKIE_BUFFER_SIZE 128	    ///< Default buffer size for JSON message, you can use 160 or 192 if necessary

The char ':'

Avoid using the char ':' in name, description or nth fields because it is used by the JsonTalkie for recovering corrupt messages.

JsonTalkie architecture

The center class is the MessageRepeater class, this class routes the JsonMessage between Uplinked Talkers and Sockets and Downlinked Talkers and Sockets.

Repeater diagram

The Repeater

The Repeater works in similar fashion as an HAM radio repeater on the top of a mountain, with a clear distinction of Uplinked and Downlinked communications, where the Uplinked sockets are considered remote sockets and the downlinked nodes are considered local nodes.

+-------------------------+      +-------------------------+
| Uplinked Sockets (node) |      | Uplinked Talkers (node) |
+-------------------------+      +-------------------------+
                        |          |
                    +------------------+
                    | Message Repeater |
                    +------------------+
                        |          |
+---------------------------+  +---------------------------+
| Downlinked Sockets (node) |  | Downlinked Talkers (node) |
+---------------------------+  +---------------------------+

Talker diagram

+--------+
| Talker |
+--------+
       |
     +-----------+
     | Manifesto |
     +-----------+

The manifesto is what declares and defines the Talker actions.

Message protocol

The extensive list of all Values is in the structure TalkieCodes.

Message Value

These are the Message Values (commands):

  • noise - Data not targeted to Talkers or without a MessageValue
  • talk - Lists existing talkers in the network
  • channel - Channel listing or setting
  • ping - Network presence check and latency
  • call - Action Talker invocation
  • list - Lists Talker actions
  • system - System information or configuration
  • echo - Messages Echo returns
  • error - Error notification

Broadcast Value

Local messages aren't send to uplinked Sockets, except if they are up bridged.

  • none - No broadcast, the message is dropped
  • remote - Broadcast to remote talkers
  • local - Broadcast within local network talkers
  • self - Broadcast to self only (loopback) The 'self' broadcast exists because otherwise no message is accepted by any Talker if 'from' that same Talker.

System Value

This messages are exclusive to the system.

  • manifesto - Show the Manifesto class name and the Talker linked mode, 1 for Uplinked and 2 for Downlinked.
  • board - Board/system information request
  • sockets - List all Sockets, their index, respective description and a decimal number representing link mode and bridge configuration (Ex. 11 means uplinked and bridged)
  • mute - Gets or sets the mute mode to the echo messages with 0, 1 or 2 as none, calls or all respectively, relative to what is to be muted
  • delay - Gets or sets the maximum delay acceptable to a call message before being dropped, guarantees sequence of arrival
  • errors - List per socket index the amount of errors by this order, lost, recoveries, drops and fails, where recoveries concern messages that were initially lost and then recovered (not lost).
    • lost - Received corrupt messages that weren't recovered
    • recoveries - Received corrupt messages that were recovered
    • drops - Received messages that arrived out of order with a delay equal or greater than the maximum set one
    • fails - Messages that failed to be sent

Repeater Rules

The MessageRepeater routes the messages accordingly to its source and message value, the source comes from the call method _transmitToRepeater depending if it's a Socket or a Talker, both here called nodes.

  1. All remote messages from up_linked and up_bridged Sockets are routed to down_linked nodes (2 to 2);
  2. All remote messages from down_linked nodes are routed to the up_linked and up_bridged Sockets (2 to 2);
  3. All local messages are routed to all nodes except the up_linked Sockets (4 to 4);
  4. All self messages from a Talker are routed to that same Talker (1 to 1);
  5. All none messages are dropped and thus are NOT sent to any node (0 to 0).

In total there are the following 5 types of nodes:

  1. up_linked Sockets (not bridged);
  2. up_bridged Sockets;
  3. down_linked Sockets;
  4. down_linked Talkers;
  5. up_linked Talkers.

The rules above result in the following exclusions of message transmission:

  • A Socket being up_linked means that it can NOT handle or transmit local messages.
  • A Talker being up_linked means that it can NOT handle or transmit remote messages.

The setting of an up_linked Socket into an up_bridged one is useful for the situation where the transmissions are done in the same platform considered as local and thus it shall bridge all local messages too.

The placement of a Talker as up_linked is intended to keep all its transmissions as local, thus invisible to all remote Talkers.

Note: A message can be either remote or local, so, there is no broadcast value where a message can be sent to remote and local Talkers in one go.

The Talker and its Manifesto

Talker

These are the attributes of a Talker:

  • name - The name by which a Talker is identified in the network and targeted with
  • description - A brief description of the Talker returned in response to the talk command
  • channel - A channel in order to multiple Talkers be simultaneously targeted among others (broadcasted commands)
  • manifesto - The Talker manifesto that sets all its Actions in detail The 'name' should be unique mainly in the type of network it is isolated in, remote, local or both.
>>> channel
    [channel spy]              255
    [channel test]             255
    [channel blue]             255
    [channel green]            255
>>> channel blue 1
    [channel blue]             1
>>> channel green 1
    [channel green]            1
>>> channel
    [channel spy]              255
    [channel test]             255
    [channel blue]             1
    [channel green]            1
>>> list 1
    [call blue 0|on]           Turns led ON
    [call blue 1|off]          Turns led OFF
    [call blue 2|actions]      Total of triggered Actions
    [call green 0|on]          Turns led ON
    [call green 1|off]         Turns led OFF
    [call green 2|bpm_10]      Sets the Tempo in BPM x 10
    [call green 3|bpm_10]      Gets the Tempo in BPM x 10
    [call green 4|toggle]      Toggles 'blue' led on and off
>>> call 1 on
    [call blue on]             roger
    [call green on]            roger
>>>

The default start up channel 255 is a deaf channel, meaning, no Talker listens on it, but you can always set a different start up channel for any talker, you just need to add this line in the setup function of the Arduino sketch.

	caller.set_channel(5);	// Sets the channel 5 for the Talker "caller"

Manifesto interface

In the folders manifestos you can find further description and some manifesto examples for multiple types of actions, with descriptions and respective methods, together with implementations of the loop, echo and error methods.

A Manifesto implementation has the following attributes:

  • actions - An array of Action pairs (name and description)

An example of a actions array:

const Action actions[3] = {
	{"on", "Turns led ON"},
	{"off", "Turns led OFF"},
	{"actions", "Total of triggered Actions"}
};

Besides the actions, a Manifesto implementation should also have these mandatory methods:

const char* class_description() const override { return "BlueManifesto"; }
const Action* _getActionsArray() const override { return actions; }
uint8_t _actionsCount() const override { return sizeof(actions)/sizeof(Action); }

The Broadcast Socket (interface)

A Broadcast Socket implementation in principle shall be able to send in broadcast mode, meaning, its outputted messages shall be received simultaneously by all Talkers. This doesn't mean it must be so, it can be a pseudo-broadcasted, meaning it sends to all Talkers but not simultaneously, this is mainly true for protocols like th I2C where each device can only be target individually and never commonly as a truly broadcasted transmission.

Implementation

In the folders sockets you can find further description and many socket examples for multiple types of protocols and even a library like the EthernetENC_Broadcast one configured with the Broadcast enabled.

These are the member variables of the BroadcastSocket class:

	MessageRepeater* _message_repeater = nullptr;
	LinkType _link_type = LinkType::TALKIE_LT_NONE;
	bool _bridged = false;	///< Bridged: Can send and receive LOCAL broadcast messages too
    uint8_t _max_delay_ms = 5;
    bool _control_timing = false;
    unsigned long _last_local_time = 0;	// millis() compatible
    uint16_t _last_message_timestamp = 0;
    uint16_t _lost_count = 0;
    uint16_t _recoveries_count = 0;
    uint16_t _drops_count = 0;
    uint16_t _fails_count = 0;
	uint8_t _consecutive_errors = 0;	// Avoids a runaway flux of errors
	

    enum CorruptionType : uint8_t {
		TALKIE_CT_CLEAN,
		TALKIE_CT_DATA,
		TALKIE_CT_NAME,
		TALKIE_CT_CHECKSUM,
		TALKIE_CT_IDENTITY,
		TALKIE_CT_UNRECOVERABLE
    };

	struct CorruptedMessage {
		CorruptionType corruption_type;
		BroadcastValue broadcast;
		
		size_t length;
		uint16_t checksum;
		uint16_t identity;
		char from_name[TALKIE_NAME_LEN] = {'\0'};
		uint16_t received_time;
		bool active = false;
	};
	CorruptedMessage _corrupted_message;

And these are the methods which definition in the socket implementation are mandatory:

virtual const char* class_description() const = 0;
virtual void _receive() = 0;
virtual bool _send(const JsonMessage& json_message) = 0;

A bare minimum sketch with a Serial socket

This example is useful to illustrate how easy it is to include a Broadcast Socket library for a simple Serial socket.

The .ino sketch for a Serial socket (115200)

#include <JsonTalkie.hpp>
#include "M_SerialManifesto.hpp"
#include "S_SocketSerial.hpp"


const char talker_name[] = "serial";
const char talker_desc[] = "I'm a serial talker";
M_SerialManifesto serial_manifesto;
JsonTalker talker = JsonTalker(talker_name, talker_desc, &serial_manifesto);

// Singleton requires the & (to get a reference variable)
auto& serial_socket = S_SocketSerial::instance();

// SETTING THE REPEATER
BroadcastSocket* uplinked_sockets[] = { &serial_socket };
JsonTalker* downlinked_talkers[] = { &talker };
const MessageRepeater message_repeater(
		uplinked_sockets, sizeof(uplinked_sockets)/sizeof(BroadcastSocket*),
		downlinked_talkers, sizeof(downlinked_talkers)/sizeof(JsonTalker*)
	);


void setup() {
    // Initialize pins FIRST before anything else
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, LOW); // Start with LED off

    // Then start Serial
    Serial.begin(115200);
    delay(250); // Important: Give time for serial to initialize
    Serial.println("\n\n=== Arduino with SERIAL ===");

    // Final startup indication
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);

    Serial.println("Setup completed - Ready for JSON communication!");
}


void loop() {
    message_repeater.loop();
}

The included manifesto and socket

The included manifesto and socket are in the folders manifestos and sockets respectively, depending on the socket implemented being used, you may have more methods available specific for that socket.

Interacting with the Talker

This example uses a Serial socket, so, the interaction is always one-to-one, and here you have two options, using another Talker with a different Manifesto just to send commands to this one, or instead, do it in an easier way with the JsonTalkerPy command line in the computer. To do so just follow the instructions here.

Command line usage

Type the following commands to start the Serial communication (change port if needed)

python talk.py --socket SERIAL --port COM5

Then you can just type commands

>>> talk
    [talk serial]              I'm a serial talker
>>> list serial
    [call serial 0|on]         Turns led ON
    [call serial 1|off]        Turns led OFF
>>> ping serial
    [ping serial]              15
>>> call serial on
    [call serial on]           roger
>>> call serial off
    [call serial off]          roger
>>> call serial off
    [call serial off]          negative        Already Off!
>>>

Use Cases

Besides the simple examples shown above, there are other interesting use cases that are important to consider.

One platform, multiple boards

The JsonTalkie allows both remote and local broadcast communications depending on the type of linking. By local communication one doesn't necessarily mean in the same board, it is possible to have a local communication among multiple boards as long as they are in the same platform, so, you may have a main board where different arduino boards communicating with each other via protocols like the well known SPI protocol.

+-----------------------------+                                     +-----------------------------+
| Ethernet socket (up linked) |                              +------| SPI socket (**up bridged**) |
+-----------------------------+                              |      +-----------------------------+
                          |                                  |                    |
                 +------------------+                        |           +------------------+
                 | Message Repeater |                        |           | Message Repeater |
                 +------------------+                        |           +------------------+
                    |           |                            |                    |
+-----------------------+  +--------------------------+      |         +-----------------------+
| Talkers (down linked) |  | SPI Socket (down linked) |------+         | Talkers (down linked) |
+-----------------------+  +--------------------------+                +-----------------------+

+-----------------------------------------------------+             +-----------------------------+
|                   ESP32 Board                       |             |        Arduino nano         |
+-----------------------------------------------------+             +-----------------------------+
+-------------------------------------------------------------------------------------------------+
|                                    Local platform with two boards                               |
+-------------------------------------------------------------------------------------------------+

In the scheme above, the Arduino nano board has its up linked SPI Broadcast Socket configured as bridged, thus up bridged, this means that not only remote messages are sent trough it, buy also, local messages.

On the other hand, the SPI Socket in the ESP32 board is a down linked one, this means it behaves such as a down linked Talker, transmitting both remote and local messages. This way, the SPI link between interconnected SPI Sockets carries both, remote and local messages, like a bridge, meaning, setting it as a bridge on a down linked Socket makes no difference.

By default, the Repeater automatically sets the up linked sockets just as up linked, so, in order to turn an up linked socket into a bridged one, you need to set it in the Arduino setup function like so:

    spi_socket.bridgeSocket();  // Now accepts LOCAL messages too

With the command system it's possible to get the the sockets and their respective link type, for instance, 20 for a down linked socket.

>>> talk
	[talk spy]           	   I'm a Spy and I spy the talkers' pings
	[talk test]          	   I test the JsonMessage class
	[talk blue]          	   I turn led Blue on and off
	[talk green]         	   I'm a green talker
	[talk buzzer]        	   I'm a buzzer that buzzes
>>> system blue board
	[system blue board]  	   ESP32 (Rev 100) (ID 00002E334DFD714D)
>>> system green board
	[system green board] 	   Arduino Uno/Nano (ATmega328P)
>>> system blue sockets
	[system blue sockets]      0       EthernetENC_Broadcast           10
	[system blue sockets]      1       Basic_SPI_ESP_Arduino_Master          20
>>> system green sockets
    [system green sockets]     0       SPI_Arduino_Slave       11
>>>

Note1: You can have more than two boards on the same platform, given that the SPI protocol allows more than a single SPI Slave.

Note2: The number after the Socket description has two algorisms, the first one is the link type, 1 for up_linked, and the seconds one is for the bridged condition, where 1 means bridged.

Unit testing

One difficulty in dealing with embedded development, is the ability to test and debug single methods, however, this can be easily accomplished with the JsonTalkie. You can create a Manifesto intended to do just that.

Bellow is the interaction with the M_MessageTester manifesto that does unit tests to the class JsonMessage.

>>> talk test
	[talk test]          	   I test the JsonMessage class
>>> list test
	[call test 0|all]    	   Tests all methods
	[call test 1|parse_json]   Test deserialize (fill up)
	[call test 2|compare]	   Test if it's the same
	[call test 3|has]    	   Test if it finds the given char
	[call test 4|has_not]	   Try to find a given char
	[call test 5|length] 	   Test it has the right length
	[call test 6|type]   	   Test the type of value
	[call test 7|identity]	   Extract the message identity
	[call test 8|value]  	   Checks if it has a value 0
	[call test 9|message]	   Gets the message number
	[call test 10|from]  	   Gets the from name string
	[call test 11|remove]	   Removes a given field
	[call test 12|set]   	   Sets a given field
	[call test 13|edge]  	   Tests edge cases
	[call test 14|copy]  	   Tests the copy constructor
	[call test 15|string]	   Has a value 0 as string
>>> call test edge
	[call test edge]     	   roger
>>> call test 0
	[call test 0]        	   roger
>>>

In the example above, specific edge cases are also tested, the roger return means that the test passed, otherwise the return would be negative. It is also possible to run all tests at once, with roger meaning all have passed. You can find this M_MessageTester manifesto in the manifestos folder.

Inside calls

So far we have been doing remote calls from a computer via Python, but there are cases when it is useful to do a call from inside the board's Talker itself. This is the case of the spy manifesto.

>>> talk spy
	[talk spy]           	   I'm a Spy and I spy the talkers' pings
>>> list spy
	[call spy 0|ping]    	   Ping talkers by name or channel
	[call spy 1|ping_self]	   I can even ping myself
	[call spy 2|call]    	   Able to do [<talker> <action>]
>>> call spy 0 blue
	[call spy 0]         	   roger	   1	   blue
>>> call spy 0 green
	[call spy 0]         	   roger	   4	   green
>>> ping blue
	[ping blue]          	   3
>>> ping green
	[ping green]         	   8
>>>

In the interaction above, we can clearly see different ping results, from the board we have 1 and 4 milliseconds, while from the computer we have 3 and 8 milliseconds respectively.

From the WiFi connected computer those results are greater because they also reflect the wi-fi latency. Note that the latency values above include the return time, so, in reality, it takes less than half those values to reach the Talker.

Nevertheless, thanks to the spy Talker, we can see that the SPI connections represents an increase of 3 milliseconds in latency (4 - 1).

Talker as Caller

So far the calls were made via Python command line with JsonTalkiePy, but a device can be a caller too. Most of the time the JsonTalkiePy shall be used to configure Arduino talkers and not to act on themselves directly, like in this example, where the talker caller is activated and then set with the current time minutes. Then is up to the caller to call the nano talker's action buzz. This is done once as it follows.

>>> talk caller
    [talk caller]              I'm a 60 minutes buzzer caller
>>> list caller
    [call caller 0|active]     Gets or sets the active status
    [call caller 1|minutes]    Gets or sets the actual minutes
>>> call caller 0
    [call caller 0]            roger           0
>>> call caller 0 1
    [call caller 0]            roger           1
>>> call caller 1
    [call caller 1]            roger           58
>>> call caller 1 3
    [call caller 1]            roger           3
>>>

After this, the caller has its minutes synced with the actual minutes and will call the buzz of the nano every hour. The example named Mega_Ethernet, that you can find in the examples folder, uses the manifesto M_CallerManifesto, that you can find in the manifestos folder, this manifesto is as follows.

#ifndef CALLER_MANIFESTO_HPP
#define CALLER_MANIFESTO_HPP

#include <TalkerManifesto.hpp>


class M_CallerManifesto : public TalkerManifesto {
public:

	// The Manifesto class description shouldn't be greater than 42 chars
	// {"m":7,"s":1,"b":1,"f":"","t":"","i":58485,"0":"","1":1,"c":11266} <-- 128 - (66 + 2*10) = 42
    const char* class_description() const override { return "CallerManifesto"; }

    M_CallerManifesto() : TalkerManifesto() {
		pinMode(LED_BUILTIN, OUTPUT);
		digitalWrite(LED_BUILTIN, LOW); // Start with LED off
	}	// Constructor


protected:

	// ALWAYS MAKE SURE THE DIMENSIONS OF THE ARRAYS BELOW ARE CORRECT!

	// The Action pair name and description shouldn't be greater than 40 chars
	// {"m":7,"b":1,"i":6442,"f":"","t":"","0":255,"1":"","2":"","c":25870} <-- 128 - (68 + 2*10) = 40

	// ------------- MAXIMUM SIZE RULER --------------|
	//	 "name", "123456789012345678901234567890123456"
    const Action actions[3] = {
		{"active", "Gets or sets the active status"},
		{"minutes", "Gets or sets the actual minutes"},
		{"state", "The actual state of the led"}
    };
    
	bool _active_caller = false;
	uint32_t _time_to_call = 0;
	uint32_t _time_to_live = 0;
    bool _is_led_on = false;	// keep track of the led state, by default it's off

public:
    
    const Action* _getActionsArray() const override { return actions; }

    // Size methods
    uint8_t _actionsCount() const override { return sizeof(actions)/sizeof(Action); }


    // Index-based operations (simplified examples)
    bool _actionByIndex(uint8_t index, JsonTalker& talker, JsonMessage& json_message, TalkerMatch talker_match) override {
        (void)talker;		// Silence unused parameter warning
        (void)talker_match;	// Silence unused parameter warning
		
		// Actual implementation would do something based on index
		switch(index) {

			case 0:
			{
				if (json_message.has_nth_value_number(0)) {
					if (json_message.get_nth_value_number(0)) {
						if (_active_caller) {
							json_message.set_nth_value_string(0, "Already active!");
						} else {
							_active_caller = true;
							return true;
						}
					} else {
						if (!_active_caller) {
							json_message.set_nth_value_string(0, "Already inactive!");
						} else {
							_active_caller = false;
							return true;
						}
					}
				} else {
					return json_message.set_nth_value_number(0, (uint32_t)_active_caller);
				}
			}
			break;

			case 1:
			{
				const uint32_t present_time = millis();
				if (json_message.has_nth_value_number(0)) {
					uint32_t milliseconds_to_call = json_message.get_nth_value_number(0) % 60;
					milliseconds_to_call = (60UL - milliseconds_to_call) * 60 * 1000;
					_time_to_call = present_time + milliseconds_to_call;
					return true;
				} else {
					uint32_t minutes = (_time_to_call - present_time) / 1000 / 60;
					minutes = 59UL - minutes % 60;	// 0 based (0 to 59 minutes)
					return json_message.set_nth_value_number(0, minutes);
				}
			}
			break;
			
            case 2:
				json_message.set_nth_value_number(0, (uint32_t)_is_led_on);
                return true;
            break;
				
            default: break;
		}
		return false;
	}


	void _loop(JsonTalker& talker) override {
		const uint32_t present_time = millis();
		// 32 bits is 0xFFFFFFFF (4 bytes, 8 nibbles) (excludes 'negatives')
		if (present_time - _time_to_call < 0xFFFFFFFF / 2) {
			if (_active_caller) {
				JsonMessage call_buzzer;
				call_buzzer.set_message_value(MessageValue::TALKIE_MSG_CALL);
				call_buzzer.set_broadcast_value(BroadcastValue::TALKIE_BC_REMOTE);
				call_buzzer.set_to_name("nano");
				call_buzzer.set_action_name("buzz");
				talker.transmitToRepeater(call_buzzer);
			}
			// The time needs to be updated regardless of the transmission above
			_time_to_call += 60UL * 60 * 1000;			// Add 60 minutes
		}
		// 32 bits is 0xFFFFFFFF (4 bytes, 8 nibbles) (excludes 'negatives')
		if (present_time - _time_to_live < 0xFFFFFFFF / 2) {
			digitalWrite(LED_BUILTIN, LOW);
			_is_led_on = false;
			_time_to_live = _time_to_call + 1UL * 60 * 1000;	// Add 1 minute extra
		}
	}


	void _echo(JsonTalker& talker, JsonMessage& json_message, MessageValue message_value, TalkerMatch talker_match) override {
        (void)talker;		// Silence unused parameter warning
        (void)json_message;	// Silence unused parameter warning
        (void)message_value;	// Silence unused parameter warning
        (void)talker_match;	// Silence unused parameter warning

		if (json_message.is_from_name("nano")) {
			_time_to_live = _time_to_call + 1UL * 60 * 1000;		// Add 1 minute extra
			digitalWrite(LED_BUILTIN, HIGH);
			_is_led_on = true;
		}
    }
    
};


#endif // CALLER_MANIFESTO_HPP

This manifesto calls the nano Talker to trigger the action buzz, this command is directly done between two Arduino boards without any computer evolvement. The calling is done inside the _loop method as such:

			if (_active_caller) {
				JsonMessage call_buzzer;
				call_buzzer.set_message_value(MessageValue::TALKIE_MSG_CALL);
				call_buzzer.set_broadcast_value(BroadcastValue::TALKIE_BC_REMOTE);
				call_buzzer.set_to_name("nano");
				call_buzzer.set_action_name("buzz");
				talker.transmitToRepeater(call_buzzer);
			}

The nano talker is also in the examples folder in the example TalkieEtherCard that contains the manifesto M_BlackManifesto that can also be found in the manifestos folder and is as follows.

#ifndef BLACK_MANIFESTO_HPP
#define BLACK_MANIFESTO_HPP

#include <TalkerManifesto.hpp>

#define BUZZ_PIN 3	// External BLACK BOX pin


class M_BlackManifesto : public TalkerManifesto {
public:

	// The Manifesto class description shouldn't be greater than 42 chars
	// {"m":7,"f":"","s":1,"b":1,"t":"","i":58485,"0":"","1":1,"c":11266} <-- 128 - (66 + 2*10) = 42
    const char* class_description() const override { return "BlackManifesto"; }

    M_BlackManifesto() : TalkerManifesto() {}	// Constructor


protected:

    uint16_t _buzz_duration_ms = 100;
	uint16_t _buzz_start = 0;

	// ALWAYS MAKE SURE THE DIMENSIONS OF THE ARRAYS BELOW ARE CORRECT!

	// The Action pair name and description shouldn't be greater than 40 chars
	// {"m":7,"b":1,"i":6442,"f":"","t":"","0":255,"1":"","2":"","c":25870} <-- 128 - (68 + 2*10) = 40

	// ------------- MAXIMUM SIZE RULER --------------|
	//	 "name", "123456789012345678901234567890123456"
    const Action actions[2] = {
		{"buzz", "Buzz for a while"},
		{"ms", "Gets and sets the buzzing duration"}
    };
    
public:

    const Action* _getActionsArray() const override { return actions; }

    // Size methods
    uint8_t _actionsCount() const override { return sizeof(actions)/sizeof(Action); }


	void _loop(JsonTalker& talker) override {
        (void)talker;		// Silence unused parameter warning
		if ((uint16_t)millis() - _buzz_start > _buzz_duration_ms) {
			#ifdef BUZZ_PIN
			digitalWrite(BUZZ_PIN, LOW);
			#endif
		}
	}

    
    // Index-based operations (simplified examples)
    bool _actionByIndex(uint8_t index, JsonTalker& talker, JsonMessage& json_message, TalkerMatch talker_match) override {
        (void)talker;		// Silence unused parameter warning
    	(void)talker_match;	// Silence unused parameter warning

		// Actual implementation would do something based on index
		switch(index) {

			case 0:
			{
				digitalWrite(BUZZ_PIN, HIGH);
				_buzz_start = (uint16_t)millis();

				return true;
			}
			break;

			case 1:
				if (json_message.has_nth_value_number(0)) {
					_buzz_duration_ms = (uint16_t)json_message.get_nth_value_number(0);
				} else {
					json_message.set_nth_value_number(0, _buzz_duration_ms);
				}
				return true;
			break;

			default: break;
		}
		return false;
	}
    

    void _echo(JsonTalker& talker, JsonMessage& json_message, MessageValue message_value, TalkerMatch talker_match) override {
		(void)talker;		// Silence unused parameter warning
        (void)message_value;	// Silence unused parameter warning
		(void)talker_match;	// Silence unused parameter warning

		char temp_string[TALKIE_MAX_LEN];
		json_message.get_from_name(temp_string);
		Serial.print( temp_string );
        Serial.print(" - ");
		
		ValueType value_type = json_message.get_nth_value_type(0);
		switch (value_type) {

			case ValueType::TALKIE_VT_STRING:
				json_message.get_nth_value_string(0, temp_string);
				Serial.println(temp_string);
			break;
			
			case ValueType::TALKIE_VT_INTEGER:
				Serial.println(json_message.get_nth_value_number(0));
			break;
			
			default:
            	Serial.println(F("Empty echo received!"));
			break;
		}
    }


    void _error(JsonTalker& talker, JsonMessage& json_message, ErrorValue error_value, TalkerMatch talker_match) override {
		(void)talker;		// Silence unused parameter warning
		(void)error_value;	// Silence unused parameter warning
		(void)talker_match;	// Silence unused parameter warning

		char temp_string[TALKIE_MAX_LEN];
		json_message.get_from_name(temp_string);
		Serial.print( temp_string );
        Serial.print(" - ");
		
		ValueType value_type = json_message.get_nth_value_type(0);
		switch (value_type) {

			case ValueType::TALKIE_VT_STRING:
				json_message.get_nth_value_string(0, temp_string);
				Serial.println(temp_string);
			break;
			
			case ValueType::TALKIE_VT_INTEGER:
				Serial.println(json_message.get_nth_value_number(0));
			break;
			
			default:
            	Serial.println(F("Empty echo received!"));
			break;
		}
    }

};


#endif // BLACK_MANIFESTO_HPP

So, as expected, by being Walkie-Talkie alike, JsonTalkie communicates as peer-to-peer and not necessarily in a centralized fashion like a commander device or computer.

About

A library that allows the communication between devices in a broadcasting fashion, like a Walkie-talkie!

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors