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.
- 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.
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
BroadcastSocketimplementation. - MQTT is safer because it implements encryption out of the box, however, JsonTalkie can have the same level of security via
BroadcastSocketinterface 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.
-
Using Arduino Library Manager:
- Open Arduino IDE
- Go to
Sketch > Include Library > Manage Libraries - Search for "JsonTalkie"
- Click "Install"
-
Manual Installation:
- Download the latest release from GitHub
- Extract to your Arduino libraries folder
- Restart Arduino IDE
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.
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[] = { ðernet_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*)
);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
}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.
- Talker in JsonTalkiePy
- Click the link above for more details concerning its usage
python talk.py>>> 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
>>>
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.
- Talker - The Talker
namecan't be in any circumstance more than 10 chars. - Action - The Action
namecan't be in any circumstance more than 10 chars.
- Talker - The Talker
descriptiontogether with itsnameshouldn't be more than 54 chars. - Action - The Action
descriptiontogether with itsnameshouldn't be more than 40 chars. - Socket - The
descriptionof theBroadcastSocketimplementation shouldn't be more than 35 chars. - Manifesto - The
descriptionof theTalkerManifestoimplementation shouldn't be more than 42 chars. - any - Any
descriptioncan't be in any circumstance more than 54 chars, however, innthtransmitted strings, being less than that doesn't guarantee transmission by itself , in other words, avoid big sized descriptions innthtransmitted strings.
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.
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 necessaryAvoid using the char ':' in name, description or nth fields because it is used by the JsonTalkie for recovering corrupt messages.
The center class is the MessageRepeater class, this class routes the JsonMessage between Uplinked
Talkers and Sockets and Downlinked Talkers and Sockets.
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 |
+--------+
|
+-----------+
| Manifesto |
+-----------+
The manifesto is what declares and defines the Talker actions.
The extensive list of all Values is in the structure TalkieCodes.
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
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.
This messages are exclusive to the system.
- manifesto - Show the Manifesto class name and the Talker linked mode,
1for Uplinked and2for 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
echomessages with0,1or2asnone,callsorallrespectively, relative to what is to be muted - delay - Gets or sets the maximum delay acceptable to a
callmessage before being dropped, guarantees sequence of arrival - errors - List per socket
indexthe amount of errors by this order,lost,recoveries,dropsandfails, whererecoveriesconcern messages that were initially lost and then recovered (notlost).- 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
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.
- All
remotemessages fromup_linkedandup_bridgedSockets are routed todown_linkednodes (2 to 2); - All
remotemessages fromdown_linkednodes are routed to theup_linkedandup_bridgedSockets (2 to 2); - All
localmessages are routed to all nodes except theup_linkedSockets (4 to 4); - All
selfmessages from a Talker are routed to that same Talker (1 to 1); - All
nonemessages are dropped and thus are NOT sent to any node (0 to 0).
In total there are the following 5 types of nodes:
up_linkedSockets (not bridged);up_bridgedSockets;down_linkedSockets;down_linkedTalkers;up_linkedTalkers.
The rules above result in the following exclusions of message transmission:
- A Socket being
up_linkedmeans that it can NOT handle or transmitlocalmessages. - A Talker being
up_linkedmeans that it can NOT handle or transmitremotemessages.
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.
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
talkcommand - 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,localor 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"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); }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.
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;This example is useful to illustrate how easy it is to include a Broadcast Socket library for a simple Serial socket.
#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 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.
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.
Type the following commands to start the Serial communication (change port if needed)
python talk.py --socket SERIAL --port COM5Then 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!
>>>
Besides the simple examples shown above, there are other interesting use cases that are important to consider.
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 tooWith 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.
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.
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).
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_HPPThis 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_HPPSo, 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.
