Using Particle primitives like Particle.publish is how you communicate between your Particle device, the Internet, and external services.
If you haven't programmed in C++ or Arduino recently and would like a refresher, see language syntax.
You are viewing the single-page version of the Device OS API reference manual.
It is also available divided into small sections if you prefer that style. Small sections also work better on mobile devices and small tablets.
API Field | < 0.8.0 | 0.8.0 - 2.x | ≥ 3.0.0 |
---|---|---|---|
Variable Key | 12 | 64 | 64 |
Variable Data | 622 | 622 | 8642 / 10243 |
Function Key | 12 | 64 | 64 |
Function Argument | 63 | 622 | 8642 / 10243 |
Publish/Subscribe Event Name | 64 | 64 | 64 |
Publish/Subscribe Event Data | 255 | 622 | 8642 / 10243 |
Instead of hardcoding these values, you should use these definitions:
particle::protocol::MAX_VARIABLE_KEY_LENGTH
particle::protocol::MAX_VARIABLE_VALUE_LENGTH
particle::protocol::MAX_FUNCTION_KEY_LENGTH
particle::protocol::MAX_FUNCTION_ARG_LENGTH
particle::protocol::MAX_EVENT_NAME_LENGTH
particle::protocol::MAX_EVENT_DATA_LENGTH
Additionally, some older Boron and B-Series SoM with a SARA-R410M-02B modem (LTE Cat M1) may have a limit of 782 bytes instead of 1024 bytes, see Particle.maxEventDataSize() for more information.
Expose a variable through the Cloud so that it can be called with GET /v1/devices/{DEVICE_ID}/{VARIABLE}
.
Returns a success value - true
when the variable was registered.
Particle.variable registers a variable, so its value can be retrieved from the cloud in the future. You only call Particle.variable once per variable, and the variable is typically a global variable. You can change the value of the underlying global variable as often as you want; the value is only retrieved when requested, so simply changing the global variable does not use any data operations. Each request of the value from the cloud is one data operation. You do not call Particle.variable when you change the value.
The variable must be one of:
static
local variable within a functionnew
, not stack allocated)new
, malloc
, etc.)The underlying variable must not be a local variable allocated on the stack within a function, such as setup(), as the storage for it will go away after the function exits and the variable will not work properly.
// EXAMPLE USAGE
bool flag = false;
int analogvalue = 0;
double tempC = 0;
char *message = "my name is particle";
String aString;
void setup()
{
Particle.variable("flag", flag);
Particle.variable("analogvalue", analogvalue);
Particle.variable("temp", tempC);
if (Particle.variable("mess", message) == false)
{
// variable not registered!
}
Particle.variable("mess2", aString);
pinMode(A0, INPUT);
}
void loop()
{
// Read the analog value of the sensor (TMP36)
analogvalue = analogRead(A0);
// Convert the reading into degree Celsius
tempC = (((analogvalue * 3.3) / 4095) - 0.5) * 100;
delay(200);
}
Each variable retrieval uses one Data Operation from your monthly or yearly quota. Setting the variable does not use Data Operations.
Up to 20 cloud variables may be registered and each variable name is limited to a maximum of 12 characters (prior to 0.8.0), 64 characters (since 0.8.0).
Note: Only use letters, numbers, underscores and dashes in variable names. Spaces and special characters may be escaped by different tools and libraries causing unexpected results.
Variables can only be read using the Particle API, or tools that use the API, like the console, CLI, and mobile apps. It's not possible to directly read a variable from another device, even on the same account. Publish and subscribe can be used if you need device-to-device communication.
For non-product devices, only the account that has claimed the device can read variable values from it.
For product devices, if the device is claimed, the device owner's account can read variable values from it. Additionally, the product owner can read variable values whether the device is claimed or not.
When using the default AUTOMATIC
system mode, the cloud variables must be registered in the setup()
function. The information about registered variables will be sent to the cloud when the setup()
function has finished its execution. In the SEMI_AUTOMATIC
and MANUAL
system modes, the variables must be registered before Particle.connect()
is called.
Before 1.5.0: Variable and function registrations are only sent up once, about 30 seconds after connecting to the cloud. When using the AUTOMATIC
system mode, make sure you register your cloud variables as early as possible in the setup()
function, before you do any lengthy operations, delays, or things like waiting for a key press. Calling Particle.variable()
after the registration information has been sent does not re-send the request and the variable will not work.
String data has a maximum size of 255 to 1024 bytes of UTF-8 characters; see API Field Limits as the limit varies depending on Device OS version and sometimes the device. String variables must be UTF-8 encoded. You cannot send arbitrary binary data or other character sets like ISO-8859-1. If you need to send binary data you can use a text-based encoding like Base64.
Prior to 0.4.7 firmware, variables were defined with an additional 3rd parameter to specify the data type of the variable. From 0.4.7 onward, the system can infer the type from the actual variable. Additionally, the variable address was passed via the address-of operator (&
). With 0.4.7 and newer, this is no longer required.
// EXAMPLE USAGE - pre-0.4.7 syntax
bool flag = false;
int analogvalue = 0;
double tempC = 0;
char *message = "my name is particle";
void setup()
{
Particle.variable("flag", &flag, BOOLEAN);
Particle.variable("analogvalue", &analogvalue, INT);
Particle.variable("temp", &tempC, DOUBLE);
if (Particle.variable("mess", message, STRING) == false)
{
// variable not registered!
}
pinMode(A0, INPUT);
}
There are four supported data types:
BOOLEAN
INT
DOUBLE
STRING
(UTF-8 encoded characters)# EXAMPLE REQUEST IN TERMINAL
# Device ID is 0123456789abcdef
# Your access token is f8a4d380cb6ffffffffffffffffffaf5e496ddf0c0
curl "https://api.particle.io/v1/devices/0123456789abcdef/flag" -H "Authorization: Bearer f8a4d380cb6ffffffffffffffffffaf5e496ddf0c0"
curl "https://api.particle.io/v1/devices/0123456789abcdef/analogvalue" -H "Authorization: Bearer f8a4d380cb6ffffffffffffffffffaf5e496ddf0c0"
curl "https://api.particle.io/v1/devices/0123456789abcdef/temp" -H "Authorization: Bearer f8a4d380cb6ffffffffffffffffffaf5e496ddf0c0"
curl "https://api.particle.io/v1/devices/0123456789abcdef/mess" -H "Authorization: Bearer f8a4d380cb6ffffffffffffffffffaf5e496ddf0c0"
# In return you'll get something like this:
false
960
27.44322344322344
my name is particle
Since 1.5.0: It is also possible to register a function to compute a cloud variable. This can be more efficient if the computation of a variable takes a lot of CPU or other resources. It can also be an alternative to using a Particle.function(). A function is limited to a single int (32-bit) return value, but you can return bool, double, int, String from a Particle.variable. String data has a maximum size of 255 to 1024 bytes of UTF-8 characters; see API Field Limits as the limit varies depending on Device OS version and sometimes the device.
Such a function should return a value of one of the supported variable types and take no arguments. The function will be called only when the value of the variable is requested.
The callback function is called application loop thread context, between calls to loop(), during Particle.process(), and delay().
// EXAMPLE USAGE - registering functions as cloud variables
bool flag() {
return false;
}
int analogvalue() {
// Read the analog value of the sensor (TMP36)
return analogRead(A0);
}
double tempC() {
// Convert the reading into degree Celsius
return (((analogvalue() * 3.3) / 4095) - 0.5) * 100;
}
String message() {
return "my name is particle";
}
void setup()
{
Particle.variable("flag", flag);
Particle.variable("analogvalue", analogvalue);
Particle.variable("temp", tempC);
Particle.variable("mess", message);
pinMode(A0, INPUT);
}
void loop()
{
}
It is also possible to pass a std::function
, allowing the calculation function to be a method of a class:
// CALCULATED FUNCTION IN CLASS EXAMPLE
class MyClass {
public:
MyClass();
virtual ~MyClass();
void setup();
String calculateCounter();
protected:
int counter = 0;
};
MyClass::MyClass() {
}
MyClass::~MyClass() {
}
void MyClass::setup() {
Particle.variable("counter", [this](){ return this->calculateCounter(); });
}
String MyClass::calculateCounter() {
return String::format("counter retrieved %d times", ++counter);
}
MyClass myClass;
void setup() {
myClass.setup();
}
void loop() {
}
Each variable retrieval uses one Data Operation from your monthly or yearly quota. Setting the variable does not use Data Operations.
Expose a function through the Cloud so that it can be called with POST /v1/devices/{DEVICE_ID}/{FUNCTION}
.
Particle.function allows code on the device to be run when requested from the cloud API. You typically do this when you want to control something on your device, say a LCD display or a buzzer, or control features in your firmware from the cloud.
// SYNTAX
bool success = Particle.function("funcKey", funcName);
// Cloud functions must return int and take one String
int funcName(String extra) {
return 0;
}
Each function call request and response uses one Data Operation from your monthly or yearly quota. Setting up function calls does not use Data Operations.
Up to 15 cloud functions may be registered and each function name is limited to a maximum of 12 characters (prior to 0.8.0), 64 characters (since 0.8.0).
Note: Only use letters, numbers, underscores and dashes in function names. Spaces and special characters may be escaped by different tools and libraries causing unexpected results. A function callback procedure needs to return as quickly as possible otherwise the cloud call will timeout.
The callback function is called application loop thread context, between calls to loop(), during Particle.process(), and delay().
In order to register a cloud function, the user provides the funcKey
, which is the string name used to make a POST request and a funcName
, which is the actual name of the function that gets called in your app. The cloud function has to return an integer; -1
is commonly used for a failed function call.
A cloud function is set up to take one argument of the String datatype. The argument has a maximum size of 64 to 1024 bytes of UTF-8 characters; see API Field Limits as the limit varies depending on Device OS version and sometimes the device.
Functions can only be triggered using the Particle API, or tools that use the API, like the console, CLI, and mobile apps. It's not possible to directly call a function from another device, even on the same account. Publish and subscribe can be used if you need device-to-device communication.
For non-product devices, only the account that has claimed the device can call a function on it.
For product devices, if the device is claimed, the device owner's account can call a function on it. Additionally, the product owner can call a function whether the device is claimed or not.
When using the default AUTOMATIC
system mode, the cloud functions must be registered in the setup()
function. The information about registered functions will be sent to the cloud when the setup()
function has finished its execution. In the SEMI_AUTOMATIC
and MANUAL
system modes, the functions must be registered before Particle.connect()
is called.
Before 1.5.0: Variable and function registrations are only sent up once, about 30 seconds after connecting to the cloud. When using the AUTOMATIC
system mode, make sure you register your cloud functions as early as possible in the setup()
function, before you do any lengthy operations, delays, or things like waiting for a key press. Calling Particle.function()
after the registration information has been sent does not re-send the request and the function will not work.
// EXAMPLE USAGE
int brewCoffee(String command);
void setup()
{
// register the cloud function
Particle.function("brew", brewCoffee);
}
void loop()
{
// this loops forever
}
// this function automagically gets called upon a matching POST request
int brewCoffee(String command)
{
// look for the matching argument "coffee" <-- max of 64 characters long
if(command == "coffee")
{
// some example functions you might have
//activateWaterHeater();
//activateWaterPump();
return 1;
}
else return -1;
}
You can expose a method on a C++ object to the Cloud.
// EXAMPLE USAGE WITH C++ OBJECT
class CoffeeMaker {
public:
CoffeeMaker() {
}
void setup() {
// You should not call Particle.function from the constructor
// of an object that will be declared as a global variable.
Particle.function("brew", &CoffeeMaker::brew, this);
}
int brew(String command) {
// do stuff
return 1;
}
};
CoffeeMaker myCoffeeMaker;
void setup() {
myCoffeeMaker.setup();
}
The API request will be routed to the device and will run your brew function. The response will have a return_value key containing the integer returned by brew.
COMPLEMENTARY API CALL
POST /v1/devices/{DEVICE_ID}/{FUNCTION}
# EXAMPLE REQUEST
curl https://api.particle.io/v1/devices/0123456789abcdef/brew \
-H "Authorization: Bearer f8a4d380cb6ffffffffffffffffffaf5e496ddf0c0" \
-d "args=coffee"
Replace the access token with a valid access token, such as from particle token create
.
Publish an event through the Particle Device Cloud. This can trigger a webhook to an external service, a Logic block to perform operations in the cloud, or another device using Particle.subscribe()
.
See Publish, below.
Subscribe to events published by devices.
See Subscribe, below.
Removes all subscription handlers previously registered with Particle.subscribe()
.
// SYNTAX
Particle.unsubscribe();
There is no function to unsubscribe a single event handler.
Since 6.1.0:
Request a ledger from the cloud. Returns a Ledger
object to read or write the data in the ledger.
This operation is asynchronous and the data will not be available until synchronized with the cloud. You will typically call this from setup()
and you can do so before connected to the cloud.
The type of ledger (device-to-cloud or cloud-to-device), as well as the ledger scope (organization, product, or device) is determined when the ledger is created on the cloud side, so it is not specified when you request the ledger. You must first create a ledger definition in the cloud; you cannot create a new ledger definition using the device-side API.
The first time a device comes online specifying a device to cloud ledger, a new ledger instance will be created for the device, however.
Ledger names consist only of lowercase alphanumeric and dash, up to 32 characters, and are unique across all scopes.
// PROTOTYPE
Ledger ledger(const char* name);
// EXAMPLE
Ledger sensors;
void setup() {
sensors = Particle.ledger("sensors");
}
Since 3.1.0:
// PROTOTYPE
int maxEventDataSize();
// SYNTAX
Log.info("eventDataSize=%d", Particle.maxEventDataSize());
Returns the maximum size of the data payload for events. This is normally specified per platform, however Boron and B-Series SoM with a SARA-R410M-02B that have an older version of the modem firmware (02.03 and earlier), the limit is 782 instead of 1024 bytes due to modem firmware limitations.
This value is only available when connected to the cloud. At other times, SYSTEM_ERROR_INVALID_STATE
(-210) is returned.
Since 3.1.0:
// PROTOTYPE
int maxVariableValueSize();
// SYNTAX
Log.info("maxVariableValueSize=%d", Particle.maxVariableValueSize());
Returns the maximum size of the string variable data.
Returns the maximum size of the data payload for events. This is normally specified per platform, however Boron and B-Series SoM with a SARA-R410M-02B that have an older version of the modem firmware (02.03 and earlier), the limit is 782 instead of 1024 bytes due to modem firmware limitations.
This value is only available when connected to the cloud. At other times, SYSTEM_ERROR_INVALID_STATE
(-210) is returned.
Since 3.1.0:
// PROTOTYPE
int maxFunctionArgumentSize();
// SYNTAX
Log.info("maxFunctionArgumentSize=%d", Particle.maxFunctionArgumentSize());
Returns the maximum size of the function argument data.
Returns the maximum size of the data payload for events. This is normally specified per platform, however Boron and B-Series SoM with a SARA-R410M-02B that have an older version of the modem firmware (02.03 and earlier), the limit is 782 instead of 1024 bytes due to modem firmware limitations.
This value is only available when connected to the cloud. At other times, SYSTEM_ERROR_INVALID_STATE
(-210) is returned.
Since 1.2.0:
// SYNTAX
system_error_t Particle.publishVitals(system_tick_t period_s = particle::NOW)
Particle.publishVitals(); // Publish vitals immediately
Particle.publishVitals(particle::NOW); // Publish vitals immediately
Particle.publishVitals(5); // Publish vitals every 5 seconds, indefinitely
Particle.publishVitals(0); // Publish immediately and cancel periodic publishing
Publish vitals information
Provides a mechanism to control the interval at which system diagnostic messages are sent to the cloud. Subsequently, this controls the granularity of detail on the fleet health metrics.
Argument(s):
period_s
The period (in seconds) at which vitals messages are to be sent to the cloud (default value: particle::NOW
)
particle::NOW
- A special value used to send vitals immediately0
- Publish a final message and disable periodic publishings
- Publish an initial message and subsequent messages every s
seconds thereafterReturns:
A system_error_t
result code
system_error_t::SYSTEM_ERROR_NONE
system_error_t::SYSTEM_ERROR_IO
Examples:
// EXAMPLE - Publish vitals intermittently
bool condition;
setup () {
}
loop () {
... // Some logic that either will or will not set "condition"
if ( condition ) {
Particle.publishVitals(); // Publish vitals immmediately
}
}
// EXAMPLE - Publish vitals periodically, indefinitely
setup () {
Particle.publishVitals(3600); // Publish vitals each hour
}
loop () {
}
// EXAMPLE - Publish vitals each minute and cancel vitals after one hour
size_t start = millis();
setup () {
Particle.publishVitals(60); // Publish vitals each minute
}
loop () {
// Cancel vitals after one hour
if (3600000 < (millis() - start)) {
Particle.publishVitals(0); // Publish immediately and cancel periodic publishing
}
}
Since 1.5.0:
You can also specify a value using chrono literals, for example: Particle.publishVitals(1h)
for 1 hour.
Sending device vitals does not consume Data Operations from your monthly or yearly quota. However, for cellular devices they do use cellular data, so unnecessary vitals transmission can lead to increased data usage, which could result in hitting the monthly data limit for your account.
NOTE: Diagnostic messages can be viewed in the Console. Select the device in question, and view the messages under the "EVENTS" tab.
Device vitals are sent:
Particle.publishVitals()
The actual device vitals are communicated to the cloud in a concise binary CoAP payload. The large JSON event you see in the event stream is a synthetic event. It looks like it's coming from the device but that format is not transmitted over the network connection.
It is not possible to disable the device vitals messages, however they do not count as a data operation.
Particle.connect()
connects the device to the Cloud. This will automatically activate the network connection and attempt to connect to the Particle cloud if the device is not already connected to the cloud.
void setup() {}
void loop() {
if (Particle.connected() == false) {
Particle.connect();
}
}
After you call Particle.connect()
, your loop will not be called again until the device finishes connecting to the Cloud. Typically, you can expect a delay of approximately one second.
In most cases, you do not need to call Particle.connect()
; it is called automatically when the device turns on. Typically you only need to call Particle.connect()
after disconnecting with Particle.disconnect()
or when you change the system mode.
Connecting to the cloud does not use Data Operation from your monthly or yearly quota. However, for cellular devices it does use cellular data, so unnecessary connection and disconnection can lead to increased data usage, which could result in hitting the monthly data limit for your account.
On Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker), prior to Device OS 2.0.0, you needed to call WiFi.on()
or Cellular.on()
before calling Particle.connect()
. This is not necessary on Gen 2 devices (any Device OS version) or with 2.0.0 and later.
Since 5.6.0:
Returns the current interface used for the Particle cloud connection in Device OS 5.6.0 and later.
// PROTOTYPE
spark::NetworkClass& connectionInterface();
// EXAMPLE
if (Particle.connectionInterface() == WiFi) {
// WiFi
} else if (Particle.connectionInterface() == Cellular) {
// Cellular
} else if (Particle.connectionInterface() == Network) {
// Generic
}
Particle.disconnect()
disconnects the device from the Cloud.
SerialLogHandler logHandler;
int counter = 10000;
void doConnectedWork() {
digitalWrite(D7, HIGH);
Log.info("Working online");
}
void doOfflineWork() {
digitalWrite(D7, LOW);
Log.info("Working offline");
}
bool needConnection() {
--counter;
if (0 == counter)
counter = 10000;
return (2000 > counter);
}
void setup() {
pinMode(D7, OUTPUT);
}
void loop() {
if (needConnection()) {
if (!Particle.connected())
Particle.connect();
doConnectedWork();
} else {
if (Particle.connected())
Particle.disconnect();
doOfflineWork();
}
}
Since 2.0.0:
When disconnecting from the Cloud, by default, the system does not wait for any pending messages, such as cloud events, to be actually sent to acknowledged by the Cloud. This behavior can be changed either globally via Particle.setDisconnectOptions()
or by passing an options object to Particle.disconnect()
. The timeout parameter controls how long the system can wait for the pending messages to be acknowledged by the Cloud.
// EXAMPLE - disconnecting from the Cloud gracefully
Particle.disconnect(CloudDisconnectOptions().graceful(true).timeout(5000));
// EXAMPLE - using chrono literals to specify a timeout
Particle.disconnect(CloudDisconnectOptions().graceful(true).timeout(5s));
Note that the actual disconnection happens asynchronously. If necessary, waitUntil(Particle.disconnected)
can be used to wait until the device has disconnected from the Cloud.
While this function will disconnect from the Cloud, it will keep the connection to the network. If you would like to completely deactivate the network module, use WiFi.off()
or Cellular.off()
as appropriate.
*NOTE: When the device is disconnected, many features are not possible, including over-the-air updates, reading Particle.variables, and calling Particle.functions.
If you disconnect from the Cloud, you will NOT BE ABLE to flash new firmware over the air. Safe mode can be used to reconnect to the cloud.
Since 5.6.0:
Automatic connection management occurs on the next connection following a disconnection from the Particle cloud. This happens automatically if the underlying network (cellular, Wi-Fi, or ethernet) becomes unavailable.
In some special cases you can manually disconnect to force automatic connection management. You should avoid doing this frequently as the device will not have cloud connectivity during the disconnection, discovery, and reconnection process. It will also use cellular data (but not data operations).
// EXAMPLE
Particle.disconnect();
waitFor(Particle.disconnected, 10000);
Particle.connect();
When your device connects to the Particle cloud, if often can do so by resuming the previous session. This dramatically reduces the amount of data used, from around 5-6 Kbytes of data for a full handshake to hundreds of bytes of data for a resume. While a full session handshake does not use data operations, if done excessively it can impact the total data usage on cellular devices. Sessions are automatically renegotiated every 3 days for security reasons.
Under normal circumstances you will never have to manually invalidate the current session. However, if you have a need to do so, this is the best way:
auto opts = CloudDisconnectOptions().clearSession(true);
Particle.disconnect(opts);
You may see references to spark/device/session/end
in the community forums, however that method should not be used and may be deprecated in the future. The clearSession flag should be used instead.
Returns true
when connected to the Cloud, and false
when disconnected from the Cloud.
// SYNTAX
Particle.connected();
// EXAMPLE USAGE
SerialLogHandler logHandler;
void setup() {
}
void loop() {
if (Particle.connected()) {
Log.info("Connected!");
}
delay(1000);
}
This call is fast and can be called frequently without performance degradation.
Returns true
when disconnected from the Cloud, and false
when connected to Cloud.
Since 2.0.0:
// EXAMPLE
Particle.setDisconnectOptions(CloudDisconnectOptions().graceful(true).timeout(5000));
// EXAMPLE
Particle.setDisconnectOptions(CloudDisconnectOptions().graceful(true).timeout(5s));
Sets the options for when disconnecting from the cloud, such as from Particle.disconnect()
. The default is to abruptly disconnect, however, you can use graceful disconnect mode to make sure pending events have been sent and the cloud notified that a disconnect is about to occur. Since this could take some time if there is poor cellular connectivity, a timeout can also be provided in milliseconds or using chrono literals. This setting will be used for future disconnects until the system is reset.
Note: This method sets the disconnection options globally, meaning that any method that causes the device to disconnect from the Cloud, such as System.reset()
, will do so gracefully.
On all Gen 3 devices (Argon, Boron, B-Series SoM, Tracker) and Gen 2 cellular devices:
Sets the duration between keep-alive messages used to maintain the connection to the cloud.
// SYNTAX
Particle.keepAlive(23 * 60); // send a ping every 23 minutes
A keep-alive is used to implement "UDP hole punching" which helps maintain the connection from the cloud to the device. A temporary port-forwarded back-channel is set up by the network to allow packets to be sent from the cloud to the device. As this is a finite resource, unused back-channels are periodically deleted by the network.
Should a device becomes unreachable from the cloud (such as a timed out function call or variable get), one possible cause of this is that the keep alives have not been sent often enough.
The keep-alive for cellular devices duration varies by mobile network operator. The default keep-alive is set to 23 minutes, which is sufficient to maintain the connection on Particle SIM cards. 3rd party SIM cards will need to determine the appropriate keep alive value, typically ranging from 30 seconds to several minutes.
Note: Each keep alive ping consumes 122 bytes of data (61 bytes sent, 61 bytes received).
For Ethernet, you will probably want to set a keepAlive to 25 seconds, like the Argon. In some cases, it could be raised to 2 to 5 minutes; this is dependent on how quickly the Internet router at the site releases port-forwarded back-channel.
For the Argon, the keep-alive is not generally needed. However, in unusual networking situations if the network router/firewall removes the port forwarded back-channels unusually aggressively, you may need to use a keep-alive.
Keep-alives do not use Data Operations from your monthly or yearly quota. However, for cellular devices they do use cellular data, so setting it to a very small value can cause increased data usage, which could result in hitting the monthly data limit for your account.
Device | Default Keep-Alive |
---|---|
All Cellular | 23 minutes |
Argon (< 3.0.0) | 30 seconds |
Argon (≥ 3.0.0) | 25 seconds |
Since 1.5.0:
You can also specify a value using chrono literals, for example: Particle.keepAlive(2min)
for 2 minutes.
Using SYSTEM_THREAD(ENABLED)
is recommended for most applications. When using threading mode you generally do not need to use Particle.process()
. With Device OS 6.2.0 and later, system thread is always enabled. For additional information, see non-threaded system mode.
If you are using SYSTEM_MODE(AUTOMATIC)
(the default if you do not specify), or SEMI_AUTOMATIC
you generally do not need to Particle.process()
unless your code blocks and prevents loop from returning and does not use delay()
in any inner blocking loop. In other words, if you block loop()
from returning you must call either delay()
or Particle.process()
within your blocking inner loop.
If you are using SYSTEM_MODE(MANUAL)
you must call Particle.process()
frequently, preferably on any call to loop()
as well as any locations where you are blocking within loop()
.
Particle.process()
checks for the incoming messages from the Cloud,
and processes any messages that have come in. It also sends keep-alive pings to the Cloud,
so if it's not called frequently, the connection to the Cloud may be lost.
It will also update the ApplicationWatchdog timer using ApplicationWatchdog::checkin()
.
Synchronize the time with the Particle Device Cloud. This happens automatically when the device connects to the Cloud. However, if your device runs continuously for a long time, you may want to synchronize once per day or so.
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)
unsigned long lastSync = millis();
void loop() {
if (millis() - lastSync > ONE_DAY_MILLIS) {
// Request time synchronization from the Particle Device Cloud
Particle.syncTime();
lastSync = millis();
}
}
Note that this function sends a request message to the Cloud and then returns.
The time on the device will not be synchronized until some milliseconds later
when the Cloud responds with the current time between calls to your loop.
See Particle.syncTimeDone()
, Particle.timeSyncedLast()
, Time.isValid()
and Particle.syncTimePending()
for information on how to wait for request to be finished.
Synchronizing time does not consume Data Operations from your monthly or yearly quota. However, for cellular devices they do use cellular data, so unnecessary time synchronization can lead to increased data usage, which could result in hitting the monthly data limit for your account.
For more information about real-time clocks on Particle devices, see Learn more about real-time clocks.
Since 0.6.1:
Returns true
if there is no syncTime()
request currently pending or there is no active connection to Particle Device Cloud. Returns false
when there is a pending syncTime()
request.
// SYNTAX
Particle.syncTimeDone();
// EXAMPLE
SerialLogHandler logHandler;
void loop()
{
// Request time synchronization from the Particle Device Cloud
Particle.syncTime();
// Wait until the device receives time from Particle Device Cloud (or connection to Particle Device Cloud is lost)
waitUntil(Particle.syncTimeDone);
// Print current time
Log.info("Current time: %s", Time.timeStr().c_str());
}
See also Particle.timeSyncedLast()
and Time.isValid()
.
Since 0.6.1:
Returns true
if there a syncTime()
request currently pending. Returns false
when there is no syncTime()
request pending or there is no active connection to Particle Device Cloud.
// PROTOTYPE
bool syncTimePending(void)
See also Particle.timeSyncedLast()
and Time.isValid()
.
// EXAMPLE
SerialLogHandler logHandler;
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)
void loop() {
time_t lastSyncTimestamp;
unsigned long lastSync = Particle.timeSyncedLast(lastSyncTimestamp);
if (millis() - lastSync > ONE_DAY_MILLIS) {
unsigned long cur = millis();
Log.info("Time was last synchronized %lu milliseconds ago", millis() - lastSync);
if (lastSyncTimestamp > 0)
{
Log.info("Time received from Particle Device Cloud was: ", Time.timeStr(lastSyncTimestamp).c_str());
}
// Request time synchronization from Particle Device Cloud
Particle.syncTime();
// Wait until the device receives time from Particle Device Cloud (or connection to Particle Device Cloud is lost)
waitUntil(Particle.syncTimeDone);
// Check if synchronized successfully
if (Particle.timeSyncedLast() >= cur)
{
// Print current time
Log.info("Current time: %s", Time.timeStr().c_str());
}
}
}
Since 0.6.1:
Used to check when time was last synchronized with Particle Device Cloud.
// PROTOTYPES
system_tick_t timeSyncedLast(void);
system_tick_t timeSyncedLast(time_t& tm);
Returns the number of milliseconds since the device began running the current program when last time synchronization with Particle Device Cloud was performed.
This function takes one optional argument:
timestamp
: time_t
variable that will contain a UNIX timestamp received from Particle Device Cloud during last time synchronizationIt is possible that the call will block for an indeterminate amount of time, possibly for as long as 10 minutes. This can occur if the system thread is busy trying to reconnect to cellular and is unable to do so. Doing operations that access the cellular modem or require access to the system thread (as is the case for Particle.timeSyncedLast()
) from a separate worker thread is a good workaround.
Using this feature, the device can programmatically know its own public IP address.
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler;
bool nameRequested = false;
// Open a serial terminal and see the IP address printed out
void subscriptionHandler(const char *topic, const char *data)
{
Log.info("topic=%s data=%s", topic, data);
}
void setup()
{
Particle.subscribe("particle/device/ip", subscriptionHandler);
}
void loop() {
if (Particle.connected() && !nameRequested) {
nameRequested = true;
Particle.publish("particle/device/ip");
}
}
Requesting the public IP uses two data operations, one for the request and one for the response.
This gives you the device name that is stored in the cloud.
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler;
bool nameRequested = false;
// Open a serial terminal and see the IP address printed out
void subscriptionHandler(const char *topic, const char *data)
{
Log.info("topic=%s data=%s", topic, data);
}
void setup()
{
Particle.subscribe("particle/device/name", subscriptionHandler);
}
void loop() {
if (Particle.connected() && !nameRequested) {
nameRequested = true;
Particle.publish("particle/device/name");
}
}
Instead of fetching the name from the cloud each time, you can fetch it and store it in retained memory or EEPROM. The DeviceNameHelperRK library makes this easy. The link includes instructions and the library is available in Particle Workbench by using Particle: Install Library or in the Web IDE by searching for DeviceNameHelperRK.
Requesting the device name uses two data operations, one for the request and one for the response.
Grab 40 bytes of randomness from the cloud and {e}n{c}r{y}p{t} away!
LogHandler logHandler;
void handler(const char *topic, const char *data) {
Log.info("topic=%s data=%s", topic, data);
}
void setup() {
Serial.begin(115200);
Particle.subscribe("particle/device/random", handler);
Particle.publish("particle/device/random");
}
Requesting a random seed uses two data operations, one for the request and one for the response.
Publish an event through the Particle Device Cloud. This can trigger a webhook to an external service, a Logic block to perform operations in the cloud, or another device using Particle.subscribe()
.
This feature allows the device to generate an event based on a condition. For example, you could connect a motion sensor to the device and have the device generate an event whenever motion is detected.
Each event contains:
The size and contents of the event data vary depending on the version of Device OS running on the device.
Device OS Version | Gen | Size Limit | Binary Data |
---|---|---|---|
> 6.2.0 | Gen 3 & 4 | 1024 bytes | Allowed |
3.x - 6.1.x | Gen 3 & 4 | 1024 UTF-8 characters | |
3.x | Gen 2 | 864 UTF-8 characters | |
0.8.0 - 2.x | Gen 2 & 3 | 622 UTF-8 characters | |
< 0.8.0 | All | 255 UTF-8 characters |
Since 6.2.0:
In Device OS 6.2 and later, typed publish is supported. This allows the Content-Type of the publish data to be specified, and allows for binary data payloads. The following types are supported:
Content Type Constant | MIME Type |
---|---|
ContentType::TEXT |
text/plain; charset=utf-8 |
ContentType::JPEG |
image/jpeg |
ContentType::PNG |
image/png |
ContentType::BINARY |
application/octet-stream |
Additionally, JSON (text/json) structured data is supported. This is encoded using an EventData
object,
which is a Variant
. The Variant
can be created from a JSON text string, or built programmatically
by adding fields.
Since 6.2.0:
Particle.publish publishes an event from a device. This can be received by zero or more subscribers, including:
Particle.subscribe()
There are a variety of overloads for this function, including:
// PROTOTYPES
particle::Future<bool> publish(const char* name, const char* data, particle::ContentType type, PublishFlags flags = PublishFlags());
particle::Future<bool> publish(const char* name, const String& data, particle::ContentType type, PublishFlags flags = PublishFlags());
particle::Future<bool> publish(const char* name, const char* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags());
particle::Future<bool> publish(const char* name, const particle::EventData& data, PublishFlags flags = PublishFlags());
This is the name of the event, which can be 1 - 64 ASCII characters.
The event data can consist of UTF-8 characters. The length depends on the device and Device OS version, but is typically limited to 1024 bytes.
This can be specified as a const char *
(pointer to c-string, null-terminated), or a reference to a String
object.
In Device OS 6.2 and later, the data can be specified with a pointer and length. This is typically used for binary data, and in particular, data that can contain a null byte. It can be used for text if your data source is not null-terminated.
The EventData
overload does not require a ContentType
value because it is used for structured data in a Variant
and is always
encoded as CBOR over-the-air, and JSON in webhooks and other locations. It can optionally include binary data.
Content Type Constant | MIME Type |
---|---|
ContentType::TEXT |
text/plain; charset=utf-8 |
ContentType::JPEG |
image/jpeg |
ContentType::PNG |
image/png |
ContentType::BINARY |
application/octet-stream |
The content type provides a hint to the receiver for what the data is. This includes the console, which can render some types of data in the event stream.
The return type of the Particle.publish
function is particle::Future<bool>
. There are three common ways to use this:
// EXAMPLE 1
Particle.publish("myEvent");
// EXAMPLE 2
bool bResult = Particle.publish("myEvent", eventData, WITH_ACK);
// EXAMPLE 3
Future<bool> futureResult = Particle.publish("myEvent");
In Example 1, you do not care about whether the call succeeds or not, and this will not wait for the data to be acknowledged by the cloud.
In Example 2, you can check bResult. If true, the publish was successfully made to the cloud. This will block until the packet is acknowledged by the cloud.
In Example 3, you use the future which decouples the waiting and the getting results. This can be useful if you are using a finite state machine. See Future for more information.
Particle.publish pushes the value out of the device at a time controlled by the device firmware. Particle.variable allows the value to be pulled from the device when requested from the cloud side.
Cloud events have the following properties:
Note: Only use letters, numbers, underscores, dashes and slashes in event names. Spaces and special characters may be escaped by different tools and libraries causing unexpected results.
A device may not publish events beginning with a case-insensitive match for "spark". Such events are reserved for officially curated data originating from the Cloud.
Calling Particle.publish()
when the cloud connection has been turned off will not publish an event. This is indicated by the return success code of false
.
If the cloud connection is turned on and trying to connect to the cloud unsuccessfully, Particle.publish()
may block for up to 20 seconds (normal conditions) to 10 minutes (unusual conditions). Checking Particle.connected()
can before calling Particle.publish()
can help prevent this.
String variables must be UTF-8 encoded. You cannot send arbitrary binary data or other character sets like ISO-8859-1. If you need to send binary data you can use a text-based encoding like Base64.
NOTE 1: Currently, a device can publish at rate of about 1 event/sec, with bursts of up to 4 allowed in 1 second. Back to back burst of 4 messages will take 4 seconds to recover.
NOTE 2: Particle.publish()
and the Particle.subscribe()
handler(s) share the same buffer. As such, calling Particle.publish()
within a Particle.subscribe()
handler will overwrite the subscribe buffer, corrupting the data! In these cases, copying the subscribe buffer's content to a separate char buffer prior to calling Particle.publish()
is recommended.
NOTE 3: Public events are not supported by the cloud as of August 2020. Specifying PUBLIC or leaving out the publish scope essentially results in a private event.
For non-product devices, events published by a device can be subscribed to:
For product devices, events published by a device can be subscribed to:
Public events could previously be received by anyone on the Internet, and anyone could generate events to send to your devices. This did not turn out to a common use-case, and the ease at which you could accidentally use this mode, creating a security hole, caused it to be removed.
Each publish uses one Data Operation from your monthly or yearly quota. This is true for both WITH_ACK and NO_ACK modes.
Particle.publish
returns true, there is no guarantee that any recipient (another device, webhook, or SSE) will receive it.Particle.publish
returns false, the event will still be received by the cloud later. This occurs because the 20-second timeout is reached, so false is returned, but the event is still buffered in Device OS and will be retransmitted if the reconnection to the cloud succeeds.Publish an event with the given name and no data.
// SYNTAX
Particle.publish(const char *eventName, PublishFlags flags);
Particle.publish(String eventName, PublishFlags flags);
Returns:
A bool
indicating success: (true or false)
// EXAMPLE USAGE
bool success;
success = Particle.publish("motion-detected");
if (!success) {
// get here if event publish did not work
}
// EXAMPLE USAGE - This format is no longer necessary
// PRIVATE is the default and only option now
bool success;
success = Particle.publish("motion-detected", PRIVATE);
if (!success) {
// get here if event publish did not work
}
// PROTOTYPES
particle::Future<bool> publish(const char* name);
particle::Future<bool> publish(const char* name, const char* data);
// EXAMPLE USAGE 1
Particle.publish("temperature", "19");
// EXAMPLE USAGE 2
int temp = 19;
Particle.publish("temperature", String(temp));
// EXAMPLE USAGE 3
float temp = 19.5;
Particle.publish("temperature", String::format("%.1f", temp);
Publish an event with the given name and data.
The data must be a string in the ASCII or UTF-8 character set. If you have an integer or floating point value, you'll need to convert it to a string first.
You cannot publish binary data with Particle.publish. To send binary data, convert it to a string using hex or Base 64 encoding.
While Base85 (Ascii85) provides a more dense encoding than Base64, it is not recommended if you are publishing data to be sent to an external server by a webhook. The Base85 alphabet includes the left curly bracket, and the {{
sequence is recognized as a mustache template delimiter during webhook processing, causing the data to be corrupted. The backslash can also cause unexpected data transformation.
// EXAMPLE - Check if event was queued for publishing successfully
float temp = 19.5;
bool success = Particle.publish("temperature", String::format("%.1f", temp);
// EXAMPLE - Check if event was queued for publishing successfully
float temp = 19.5;
bool success = Particle.publish("temperature", String::format("%.1f", temp);
Normally, you store or test the result of Particle.publish in a bool
variable that indicates that the event was queued for publishing successfully, or reached the cloud, when used with WITH_ACK
.
But what is the particle::Future<bool>
in the prototype above? See the application note AN009 Firmware Examples for how to use a Future to make the otherwise synchronous Particle.publish() call asynchronous.
COMPLEMENTARY API CALL
GET /v1/events/{EVENT_NAME}
# EXAMPLE REQUEST
curl -H "Authorization: Bearer {ACCESS_TOKEN_GOES_HERE}" \
https://api.particle.io/v1/events/motion-detected
# Will return a stream that echoes text when your event is published
event: motion-detected
data: {"data":"23:23:44","ttl":"60","published_at":"2014-05-28T19:20:34.638Z","deviceid":"0123456789abcdef"}
Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
// PROTOTYPES
particle::Future<bool> publish(const char* name, PublishFlags flags1, PublishFlags flags2 = PublishFlags());
particle::Future<bool> publish(const char* name, const char* data, PublishFlags flags1, PublishFlags flags2 = PublishFlags());
// SYNTAX
float temperature = sensor.readTemperature(); // by way of example, not part of the API
Particle.publish("t", String::format("%.2f",temperature), NO_ACK); // make sure to convert to const char * or String
On Gen 2 cellular devices (Electron, E-Series) and all Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM, E404X):
NO_ACK
flag
Unless specified otherwise, events sent to the cloud are sent as a reliable message. The device waits for acknowledgement from the cloud that the event has been received, resending the event in the background up to 3 times before giving up.
The NO_ACK
flag disables this acknowledge/retry behavior and sends the event only once. This reduces data consumption per event, with the possibility that the event may not reach the cloud.
For example, the NO_ACK
flag could be useful when many events are sent (such as sensor readings) and the occasional lost event can be tolerated.
// SYNTAX
bool success = Particle.publish("motion-detected", NULL, WITH_ACK);
// No longer necessary, PRIVATE is always used even when not specified
bool success = Particle.publish("motion-detected", NULL, PRIVATE, WITH_ACK);
WITH_ACK
flag
Since 0.6.1:
This flag causes Particle.publish()
to return only after receiving an acknowledgement that the published event has been received by the Cloud.
If you do not use WITH_ACK
then the request is still acknowledged internally, and retransmission attempted up to 3 times, but Particle.publish()
will return more quickly.
Since 0.7.0:
// EXAMPLE - combining Particle.publish() flags
// No longer necessary, PRIVATE is always used even when not specified
Particle.publish("motion-detected", PRIVATE | WITH_ACK);
Particle.publish()
flags can be combined using a regular syntax with OR operator (|
).
For products, it's possible receive product events sent by devices using webhooks or the Server-Sent-Events (SSE) data stream.
// SYNTAX - DEPRECATED
Particle.publish(const char *eventName, const char *data, int ttl, PublishFlags flags);
Particle.publish(String eventName, String data, int ttl, PublishFlags flags);
Previously, there were overloads with a ttl
(time-to-live) value. These have been deprecated as the ttl has never been supported by the Particle cloud. All events expire immediately if not subscribed to or exported from the Particle cloud using a webhook, integration like Google cloud, or the server-sent-events (SSE) stream.
Even if you use NO_ACK
mode and do not check the result code from Particle.publish()
it is possible that the call will still block for an indeterminate amount of time, possibly for as long as 10 minutes. This can occur if you publish while in the process of attempting to reconnect to the network. At a minimum, you should make sure that Particle.connected()
returns true before publishing. Doing publish operations from a separate worker thread is another option.
Subscribe to events published by devices.
This allows devices to talk to each other very easily. For example, one device could publish events when a motion sensor is triggered and another could subscribe to these events and respond by sounding an alarm.
You typically call Particle.subscribe()
once for each subscription during setup()
.
A subscription works like a prefix filter. If you subscribe to "foo", you will receive any event whose name begins with "foo", including "foo", "fool", "foobar", and "food/indian/sweet-curry-beans". The maximum length of the subscribe prefix is 64 characters.
You should not call Particle.subscribe()
from the constructor of a globally allocated C++ object. See Global Object Constructors for more information.
Each event delivery attempt to a subscription handler uses one Data Operation from your monthly or yearly quota. Setting up the subscription does not use a Data Operations. You should take advantage of the event prefix to avoid delivering events that you do not need. If poor connectivity results in multiple attempts, it could result in multiple Data Operations, up to 3. If the device is currently marked as offline, then no attempt will be made and no Data Operations will be incurred.
If you have multiple devices that subscribe to a hook-response but only want to monitor the response to their own request, as opposed to any device in the account sharing the webhook, you should include the Device ID in the custom hook response as described here. This will assure that you will not consume Data Operations for webhooks intended for other devices.
Particle.subscribe(System.deviceID() + "/hook-response/weather/", myHandler);
// PROTOTYPES
bool subscribe(const char* name, particle::EventHandlerWithContentType handler);
bool subscribe(const char* name, particle::EventHandlerWithContentTypeFn handler);
typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type);
typedef std::function<void(const char* name, const char* data, size_t size, ContentType type)> EventHandlerWithContentTypeFn;
// EventHandlerWithContentType handler function prototype
void myHandler(const char* name, const char* data, size_t size, ContentType type)
// EventHandler function prototype (classic API)
void myHandler(const char *event_name, const char *data)
The new API passes both a data
and a size
allowing it to be used with data that contains null bytes,
including binary data. The ContentType
is also passed to the handler for the handler to use as desired.
If you are expecting structured data, use the subscribe overload that takes a handler with a Variant.
// EXAMPLE
void myHandler(const char* name, Variant data) {
Log.info("a=%d b=%s", data.get("a").toInt(), data.get("b").toString().c_str());
}
void setup() {
Particle.subscribe("myEvent", myHandler);
}
If you publish this JSON:
{"a":123,"b":"testing"}
For example, with the Particle CLI:
particle publish myEvent '{"a":123,"b":"testing"}'
The USB serial debug log will show something like:
0000013271 [app] INFO: a=123 b=testing
The prototypes for subscribing to events with a Variant are:
// PROTOTYPES
bool subscribe(const char* name, particle::EventHandlerWithVariant handler);
bool subscribe(const char* name, particle::EventHandlerWithVariantFn handler);
// Handler definitions
typedef void (*EventHandlerWithVariant)(const char* name, EventData data);
typedef std::function<void(const char* name, EventData data)> EventHandlerWithVariantFn;
// EventHandlerWithVariant handler function prototype
void myHandler(const char* name, EventData data);
void myHandler(const char* name, Variant data);
// EventData definition
typedef Variant EventData;
The Variant
overload does not have a ContentType
because Variant data is always CBOR over-the-air and JSON via webhooks and SSE.
SerialLogHandler logHandler;
int i = 0;
void myHandler(const char *event, const char *data)
{
i++;
Log.info("%d: event=%s data=%s", i, event, (data ? data : "NULL"));
}
void setup()
{
Particle.subscribe("temperature", myHandler);
}
To use Particle.subscribe()
, define a handler function and register it, typically in setup()
.
You can register a method in a C++ object as a subscription handler.
#include "Particle.h"
SerialLogHandler logHandler;
class MyClass {
public:
MyClass();
virtual ~MyClass();
void setup();
void subscriptionHandler(const char *eventName, const char *data);
};
MyClass::MyClass() {
}
MyClass::~MyClass() {
}
void MyClass::setup() {
Particle.subscribe("myEvent", &MyClass::subscriptionHandler, this);
}
void MyClass::subscriptionHandler(const char *eventName, const char *data) {
Log.info("eventName=%s data=%s", eventName, data);
}
// In this example, MyClass is a globally constructed object.
MyClass myClass;
void setup() {
myClass.setup();
}
void loop() {
}
You should not call Particle.subscribe()
from the constructor of a globally allocated C++ object. See Global Object Constructors for more information.
Each event delivery attempt to a subscription handler uses one Data Operation from your monthly or yearly quota. Setting up the subscription does not use a Data Operations. You should take advantage of the event prefix to avoid delivering events that you do not need. If poor connectivity results in multiple attempts, it could result in multiple Data Operations, up to 3. If the device is currently marked as offline, then no attempt will be made and no Data Operations will be incurred.
If you have multiple devices that subscribe to a hook-response but only want to monitor the response to their own request, as opposed to any device in the account sharing the webhook, you should include the Device ID in the custom hook response as described here. This will assure that you will not consume Data Operations for webhooks intended for other devices.
Particle.subscribe(System.deviceID() + "/hook-response/weather/", myHandler, MY_DEVICES);
A subscription works like a prefix filter. If you subscribe to "foo", you will receive any event whose name begins with "foo", including "foo", "fool", "foobar", and "food/indian/sweet-curry-beans". The maximum length of the subscribe prefix is 64 characters.
Received events will be passed to a handler function similar to Particle.function()
.
A subscription handler (like myHandler
above) must return void
and take two arguments, both of which are C strings (const char *
).
Particle.subscribe()
returns a bool
indicating success. It is OK to register a subscription when
the device is not connected to the cloud - the subscription is automatically registered
with the cloud next time the device connects.
See special webhook events for more details about handling multipart responses from a webhook in your subscription handler.
A device can register up to 4 event handlers. This means you can call Particle.subscribe()
a maximum of 4 times; after that it will return false
. On some versions of Device OS on some devices the limit may be higher.
Particle.publish()
and the Particle.subscribe()
handler(s) share the same buffer. As such, calling Particle.publish()
within a Particle.subscribe()
handler will overwrite the subscribe buffer, corrupting the data! In these cases, copying the subscribe buffer's content to a separate char buffer prior to calling Particle.publish()
is recommended.
You can call Particle.subscribe from setup() or from loop(). The subscription list can be added to at any time, and more than once. The subscriptions are only updated once loop is running, however. During setup() the subscription will not yet be active. Very old versions of Device OS (prior to 1.0.0) may behave differently.
// PROTOTYPES
bool subscribe(const char* name, EventHandler handler);
bool subscribe(const char* name, wiring_event_handler_t handler);
template<typename T>
bool subscribe(const char* name, void (T::*handler)(const char*, const char*), T* instance);
// Handler definitions
typedef void (*EventHandler)(const char *event_name, const char *data);
typedef std::function<void (const char*, const char*)> wiring_event_handler_t;
// Handler function prototype
void eventHandler(const char *event_name, const char *data)
Prior to August 2020, you could subscribe to the public event stream using ALL_DEVICES
. This is no longer possible as the public event stream no longer exists. Likewise, MY_DEVICES
is no longer necessary as that is the only option now.
// This syntax is no longer necessary
Particle.subscribe("the_event_prefix", theHandler, MY_DEVICES);
Particle.subscribe("the_event_prefix", theHandler, ALL_DEVICES);
The behavior of unclaimed product devices with respect to subscribing to events changed in March 2023:
Additionally:
Particle.publish
returns true, there is no guarantee that any recipient (another device, webhook, or SSE) will receive it.Particle.publish
returns false, the event will still be received by the cloud later. This occurs because the 20-second timeout is reached, so false is returned, but the event is still buffered in Device OS and will be retransmitted if the reconnection to the cloud succeeds.The Future
class is used by Particle.publish
to optionally block, and to optionally obtain the success or failure result later.
// EXAMPLE PROTOTYPE
particle::Future<bool> publish(const char* name);
// EXAMPLE USAGE 1
Particle.publish("myEvent");
// EXAMPLE USAGE 2
bool result = Particle.publish("myEvent");
In example usage 1, the value is not checked, and the call will be asynchronous.
In example usage 2, the value is stored in a bool
, and the call will block until completion or timeout.
// Future methods
bool isDone() const;
ResultT result() const;
operator ResultT() const;
It is possible to store the Future<bool>
in a variable. This allows the operation to proceed asynchronously, then
check later to see if the is operation has completed (isDone()
) and what the underlying result was (result()
).
This is useful if you've implemented your code as finite state machine and you want to not block, transition to
the next state, and wait in that state until completion. You'd then transition to another state depending
on whether you succeeded or failed.
Note that you may still want to do publishes from a worker thread instead of the main loop, because the Particle.publish function can still block in some cases if there is no network connectivity. For an example, see the background-publish library. This is used by Tracker Edge and Monitor Edge firmware for location publishes.
Note:
Ethernet is available on the Photon 2, Argon, Boron when used with the Ethernet FeatherWing or with the B-Series SoM with the evaluation board or the equivalent circuitry on your base board. Circuitry can be added to your P2 or E404X base board for Ethernet as well.
On the P2, Photon 2, and M-SoM it is highly recommended that you use Device OS 5.9.0 or later when using Ethernet due to important bugs that were fixed in 5.6.0 and 5.7.0 that can affect Ethernet usability on RTL872x devices.
It is not available on Gen 2 devices (Photon, P1, Electron, and E-Series except the E404X).
For more information about Ethernet, see the application note AN037 Ethernet.
By default, Ethernet detection is not done because it will toggle GPIO that may affect circuits that are not using Ethernet. When you select Ethernet during device setup, it is enabled and the setting stored in configuration flash.
It's also possible to enable Ethernet detection from code. This is saved in configuration flash so you don't need to call it every time.
You should call it from setup() but make sure you are using SYSTEM_THREAD(ENABLED)
so it can be enabled before the connecting to the cloud. You should not call it from STARTUP().
SYSTEM_THREAD(ENABLED);
void setup()
{
System.enableFeature(FEATURE_ETHERNET_DETECTION);
}
Prior to Device OS 5.9.0, interrupt (nINTERRUPT) and reset (nRESET) pins were required to use Ethernet. With Device OS 5.9.0 you can operate without these connected.
If you are using the Adafruit Ethernet Feather Wing (instead of the Particle Feather Wing), by default nRESET and nINTERRUPT pins are not connected. You will either need to connect them (on the small header on the short side) to pins D3 and D4 with jumper wires, or use Device OS 5.9.0 and operate without these two hardware control pins.
Device | SPI MISO | SPI MOSI | SPI SCK | Chip Select | Reset | Interrupt |
---|---|---|---|---|---|---|
Particle Ethernet | MISO | MOSI | SCK | D5 | D3 | D4 |
Adafruit Ethernet | MISO | MOSI | SCK | D5 | 1 | 1 |
B-SoM eval board | MISO | MOSI | SCK | D8 | A7 | D22 |
M.2 SoM breakout board | MISO | MOSI | SCK | D8 | A7 | D22 |
Muon | MISO | MOSI | SCK | A3 | NC | A4 |
Tracker SoM | MISO | MOSI | SCK | D2 | D6 | D7 |
1On the Adafruit Ethernet FeatherWing this pin is not connected by default.
Since 5.3.0:
In Device OS 5.3.x, it is possible to reconfigure the pins that are used for Ethernet control signals CS, RESET, and INT. This may be desirable if you need to use pins D3-D5 for other purposes, such as SPI1
.
In Device OS 5.9.9 and later, it's possible to configure RESET and INT as PIN_INVALID, meaning you are not using hardware reset and interrupt.
The correct order of operations is:
Start with FEATURE_ETHERNET_DETECTION
disabled this will prevent probing the default GPIO that you are using for other purposes, which may have unintended consequences.
It is recommended that you enable threading and SEMI_AUTOMATIC mode because you will need to execute code before connecting to the cloud.
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);
Ethernet.isOn()
is false (it's off), then remap the pins. This setting is persistent on reset, cold boot, application firmware flash, and Device OS upgrade. It also is used when in safe mode so Device OS can be upgraded OTA over Ethernet.if_wiznet_pin_remap remap = {};
remap.base.type = IF_WIZNET_DRIVER_SPECIFIC_PIN_REMAP;
remap.cs_pin = A0;
remap.reset_pin = A2;
remap.int_pin = A1;
auto ret = if_request(nullptr, IF_REQ_DRIVER_SPECIFIC, &remap, sizeof(remap), nullptr);
// ret is SYSTEM_ERROR_NONE on success
System.enableFeature(FEATURE_ETHERNET_DETECTION);
System.reset();
In Device OS 6.1.1 and earlier, if you wanted to restore a pin to its default value, you could set it to PIN_INVALID
. This is no
longer possible as PIN_INVALID
is now a usable value for the int_pin
for operation without a hardware interrupt pin.
You must set it back to the actual default pin to restore default pin mapping.
Here's a full sample application for testing:
// SAMPLE APPLICATION
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);
Serial1LogHandler log1Handler(115200, LOG_LEVEL_ALL);
retained uint8_t resetRetry = 0;
void setup() {
#if HAL_PLATFORM_WIFI && !HAL_PLATFORM_WIFI_SCAN_ONLY
// To force Ethernet only, clear Wi-Fi credentials
Log.info("Clear Wi-Fi credentionals...");
WiFi.clearCredentials();
#endif // HAL_PLATFORM_WIFI && !HAL_PLATFORM_WIFI_SCAN_ONLY
// Disable Listening Mode if not required
if (System.featureEnabled(FEATURE_DISABLE_LISTENING_MODE)) {
Log.info("FEATURE_DISABLE_LISTENING_MODE enabled");
} else {
Log.info("Disabling Listening Mode...");
System.enableFeature(FEATURE_DISABLE_LISTENING_MODE);
}
Log.info("Checking if Ethernet is on...");
if (Ethernet.isOn()) {
Log.info("Ethernet is on");
uint8_t macAddrBuf[8] = {};
uint8_t* macAddr = Ethernet.macAddress(macAddrBuf);
if (macAddr != nullptr) {
Log.info("Ethernet MAC: %02x %02x %02x %02x %02x %02x",
macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
}
Ethernet.connect();
waitFor(Ethernet.ready, 30000);
Log.info("Ethernet.ready: %d", Ethernet.ready());
resetRetry = 0;
} else if (++resetRetry <= 3) {
Log.info("Ethernet is off or not detected, attmpting to remap pins: %d/3", resetRetry);
if_wiznet_pin_remap remap = {};
remap.base.type = IF_WIZNET_DRIVER_SPECIFIC_PIN_REMAP;
remap.cs_pin = D10;
remap.reset_pin = D6;
remap.int_pin = D7;
auto ret = if_request(nullptr, IF_REQ_DRIVER_SPECIFIC, &remap, sizeof(remap), nullptr);
if (ret != SYSTEM_ERROR_NONE) {
Log.error("Ethernet GPIO config error: %d", ret);
} else {
if (System.featureEnabled(FEATURE_ETHERNET_DETECTION)) {
Log.info("FEATURE_ETHERNET_DETECTION enabled");
} else {
Log.info("Enabling Ethernet...");
System.enableFeature(FEATURE_ETHERNET_DETECTION);
}
delay(500);
System.reset();
}
}
Particle.connect();
}
void loop() {
static system_tick_t lastPublish = millis();
static int count = 0;
static bool reconnect = false;
if (Particle.connected()) {
reconnect = false;
if (millis() - lastPublish >= 10000UL) {
Particle.publish("mytest", String(++count), PRIVATE, WITH_ACK);
lastPublish = millis();
}
}
// Detect a network dropout and reconnect quickly
if (!reconnect && !Ethernet.ready()) {
Log.info("Particle disconnect...");
Particle.disconnect();
waitFor(Particle.disconnected, 5000);
Log.info("Network disconnect...");
Network.disconnect();
Particle.connect();
reconnect = true;
}
}
The following code can be used to enable Ethernet on the Muon. This only needs to be done once and the device must be reset after configuration for the changes to take effect.
// Enable 3V3_AUX
SystemPowerConfiguration powerConfig = System.getPowerConfiguration();
powerConfig.auxiliaryPowerControlPin(D7).interruptPin(A7);
System.setPowerConfiguration(powerConfig);
// Enable Ethernet
if_wiznet_pin_remap remap = {};
remap.base.type = IF_WIZNET_DRIVER_SPECIFIC_PIN_REMAP;
System.enableFeature(FEATURE_ETHERNET_DETECTION);
remap.cs_pin = A3;
remap.reset_pin = PIN_INVALID;
remap.int_pin = A4;
auto ret = if_request(nullptr, IF_REQ_DRIVER_SPECIFIC, &remap, sizeof(remap), nullptr);
The following code can be used to enable Ethernet on the M.2 SoM breakout board. This only needs to be done once and the device must be reset after configuration for the changes to take effect.
// Enable 3V3_AUX
SystemPowerConfiguration powerConfig = System.getPowerConfiguration();
powerConfig.auxiliaryPowerControlPin(D23).interruptPin(A6);
System.setPowerConfiguration(powerConfig);
// Enable Ethernet
if_wiznet_pin_remap remap = {};
remap.base.type = IF_WIZNET_DRIVER_SPECIFIC_PIN_REMAP;
System.enableFeature(FEATURE_ETHERNET_DETECTION);
remap.cs_pin = D5;
remap.reset_pin = PIN_INVALID;
remap.int_pin = PIN_INVALID;
auto ret = if_request(nullptr, IF_REQ_DRIVER_SPECIFIC, &remap, sizeof(remap), nullptr);
Ethernet.on()
turns on the Ethernet module. Useful when you've turned it off, and you changed your mind.
Note that Ethernet.on()
does not need to be called unless you have changed the system mode or you have previously turned the Ethernet module off.
Ethernet.off()
turns off the Ethernet module.
Attempts to connect to the Ethernet network. When this function returns, the device may not have an IP address on the LAN; use Ethernet.ready()
to determine the connection status.
// SYNTAX
Ethernet.connect();
Disconnects from the Ethernet network, but leaves the Ethernet module on.
// SYNTAX
Ethernet.disconnect();
This function will return true
once the device is attempting to connect, and will return false
once the device has successfully connected to the Ethernet network.
// SYNTAX
Ethernet.connecting();
This function will return true
once the device is connected to the network and has been assigned an IP address, which means that it's ready to open TCP sockets and send UDP datagrams. Otherwise it will return false
.
// SYNTAX
Ethernet.ready();
In order for Ethernet to be considered ready the Ethernet link must be up, and IP address assigned, DNS configured, and a gateway set. Thus if you using isolated Ethernet (gateway of 0.0.0.0), you cannot use Ethernet.ready()
to determine if it's up. This is because Ethernet.ready() implies that it's ready to be used for the cloud connection, which is not true for isolated LANs. If you want to check for isolated LAN up, you can use !Ethernet.connecting() && Ethernet.localIP() != IPAddress()
Since 5.3.0:
Set a NetworkInterfaceConfig
for the Ethernet interface running Device OS 5.3.0 or later. This is used to set a static IP address or restore DHCP addressing (the default).
// PROTOTYPE
int setConfig(const particle::NetworkInterfaceConfig& conf);
// EXAMPLE
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0})
.gateway(SockAddr({192,168,1,1}))
.dns(SockAddr({192,168,1,1})));
Since 5.3.0:
Get the current NetworkInterfaceConfig
for the Ethernet interface running Device OS 5.3.0 or later.
// PROTOTYPE
particle::NetworkInterfaceConfig getConfig(String profile = String()) const;
Since 5.7.0:
You should normally let automatic connection management handle which network interface to use.
In some cases you may want to prefer cellular or Wi-Fi, and this can be done using the API. Note however:
Ethernet.prefer()
will clear Cellular.prefer()
for example.// PROTOTYPE
virtual NetworkClass& prefer(bool prefer = true);
// EXAMPLE - enable prefer Ethernet
Ethernet.prefer();
// EXAMPLE - disable prefer Ethernet
Ethernet.prefer(false);
Returns true if the preferred network interface is Ethernet. This only indicates that the interface has been preferred and does not reflect what interface is in use.
// PROTOTYPE
virtual bool isPreferred();
if (Ethernet.isPreferred()) {
// Do something here
}
This will enter or exit listening mode, which opens a Serial connection to get Ethernet credentials over USB, and also listens for credentials over Bluetooth.
// SYNTAX - enter listening mode
Ethernet.listen();
Listening mode blocks application code. Advanced cases that use multithreading, interrupts, or system events have the ability to continue to execute application code while in listening mode, and may wish to then exit listening mode, such as after a timeout. Listening mode is stopped using this syntax:
// SYNTAX - exit listening mode
Ethernet.listen(false);
// SYNTAX
Ethernet.listening();
Returns true if the device is in listening mode (blinking dark blue).
This is only relevant when using SYSTEM_THREAD(ENABLED)
. When not using threading, listening mode stops
user firmware from running, so you would not have an opportunity to test the value and calling this always
returns false. With Device OS 6.2.0 and later, system thread is always enabled.
// SYNTAX
Ethernet.setListenTimeout(seconds);
Ethernet.setListenTimeout(seconds)
is used to set a timeout value for Listening Mode. Values are specified in seconds
, and 0 disables the timeout. By default, Ethernet devices do not have any timeout set (seconds=0). As long as interrupts are enabled, a timer is started and running while the device is in listening mode (Ethernet.listening()==true). After the timer expires, listening mode will be exited automatically. If Ethernet.setListenTimeout() is called while the timer is currently in progress, the timer will be updated and restarted with the new value (e.g. updating from 10 seconds to 30 seconds, or 10 seconds to 0 seconds (disabled)).
Note: Enabling multi-threaded mode with SYSTEM_THREAD(ENABLED) will allow user code to update the timeout value while Listening Mode is active.
Since 1.5.0:
You can also specify a value using chrono literals, for example: Ethernet.setListenTimeout(5min)
for 5 minutes.
// SYNTAX
uint16_t seconds = Ethernet.getListenTimeout();
Ethernet.getListenTimeout()
is used to get the timeout value currently set for Listening Mode. Values are returned in (uint16_t)seconds
, and 0 indicates the timeout is disabled. By default, Ethernet devices do not have any timeout set (seconds=0).
Ethernet.macAddress()
gets the MAC address of the Ethernet interface.
// EXAMPLE
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
uint8_t addr[6];
Ethernet.macAddress(addr);
Log.info("mac: %02x-%02x-%02x-%02x-%02x-%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}
Ethernet.localIP()
is used to get the IP address of the Ethernet interface as an IPAddress
.
// EXAMPLE
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
Log.info("localIP: %s", Ethernet.localIP().toString().c_str());
}
Ethernet.subnetMask()
returns the subnet mask of the network as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the subnet mask over Serial.
Log.info(Ethernet.subnetMask());
}
Ethernet.gatewayIP()
returns the gateway IP address of the network as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
Serial.begin(9600);
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the gateway IP over Serial.
Log.info(Ethernet.gatewayIP());
}
Ethernet.dnsServerIP()
retrieves the IP address of the DNS server that resolves
DNS requests for the device's network connection. This will often be 0.0.0.0.
Ethernet.dhcpServerIP()
retrieves the IP address of the DHCP server that manages
the IP address used by the device's network connection. This often will be 0.0.0.0.
Wi-Fi Devices (P2, Photon 2, Argon, P1, Photon):
The WiFi
class is available on the P2, Photon 2, Argon, Photon, and P1.
The WiFi
class is not available on cellular devices such as the Boron and
B-Series SoM (Gen 3) or Electron and E-Series (Gen 2).
While the Tracker SoM has a Wi-Fi module for geolocation, it cannot be used for network
connectivity and thus it does not have the WiFi
class.
WiFi.on()
turns on the Wi-Fi module. Useful when you've turned it off, and you changed your mind.
Note that WiFi.on()
does not need to be called unless you have changed the system mode or you have previously turned the Wi-Fi module off.
// EXAMPLE:
Particle.disconnect();
WiFi.off();
WiFi.off()
turns off the Wi-Fi module. Useful for saving power, since most of the power draw of the device is the Wi-Fi module.
You must call Particle.disconnect()
before turning off the Wi-Fi manually, otherwise the cloud connection may turn it back on again.
This should only be used with SYSTEM_MODE(SEMI_AUTOMATIC)
(or MANUAL
) as the cloud connection and Wi-Fi are managed by Device OS in AUTOMATIC
mode.
Attempts to connect to the Wi-Fi network. If there are no credentials stored, this will enter listening mode (see below for how to avoid this.). If there are credentials stored, this will try the available credentials until connection is successful. When this function returns, the device may not have an IP address on the LAN; use WiFi.ready()
to determine the connection status.
// SYNTAX
WiFi.connect();
Since 0.4.5:
It's possible to call WiFi.connect()
without entering listening mode in the case where no credentials are stored:
// SYNTAX
WiFi.connect(WIFI_CONNECT_SKIP_LISTEN);
If there are no credentials then the call does nothing other than turn on the Wi-Fi module.
On Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker), prior to Device OS 2.0.0, you needed to call WiFi.on()
or Cellular.on()
before calling Particle.connect()
. This is not necessary on Gen 2 devices (any Device OS version) or with 2.0.0 and later.
On the Argon, starting with Device OS 1.5.0, a quick Wi-Fi scan is done before connecting. The list of available networks is compared with the configured SSIDs. The access point with the strongest signal is connected to. Prior to 1.5.0, only the original access point BSSID that was configured would ever be connected to, even if there was a different access point on the same SSID and network with a stronger signal.
Disconnects from the Wi-Fi network, but leaves the Wi-Fi module on.
// SYNTAX
WiFi.disconnect();
This function will return true
once the device is attempting to connect using stored Wi-Fi credentials, and will return false
once the device has successfully connected to the Wi-Fi network.
// SYNTAX
WiFi.connecting();
This function will return true
once the device is connected to the network and has been assigned an IP address, which means that it's ready to open TCP sockets and send UDP datagrams. Otherwise it will return false
.
// SYNTAX
WiFi.ready();
Since 5.3.0:
Set a NetworkInterfaceConfig
for the Wi-Fi interface on the Argon, P2, and Photon 2 running Device OS 5.3.0 or later. This is used to set a static IP address or restore DHCP addressing (the default).
// PROTOTYPE
int setConfig(const particle::NetworkInterfaceConfig& conf);
// EXAMPLE
WiFi.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0})
.gateway({192,168,1,1})
.dns({192,168,1,1});
Since 5.3.0:
Get the current NetworkInterfaceConfig
for the Wi-Fi interface on the Argon, P2, and Photon 2 running Device OS 5.3.0 or later.
// PROTOTYPE
particle::NetworkInterfaceConfig getConfig(String profile = String()) const;
Note:
On ths P2, Photon 2, and M-SoM this is only supported on Device OS 5.3.2 and later.
The Argon (Gen 3) does not have an antenna switch for Wi-Fi; it can only use an external antenna. There is a separate setting for the Argon BLE antenna (BLE.selectAntenna).
On the Photon and P1 (Gen 2), selectAntenna selects which antenna the device should connect to Wi-Fi with and remembers that setting until it is changed. Resetting Wi-Fi credentials does not clear the antenna setting.
// SYNTAX
STARTUP(WiFi.selectAntenna(ANT_INTERNAL)); // selects the CHIP antenna
STARTUP(WiFi.selectAntenna(ANT_EXTERNAL)); // selects the u.FL antenna
STARTUP(WiFi.selectAntenna(ANT_AUTO)); // continually switches at high speed between antennas
WiFi.selectAntenna()
selects one of three antenna modes. It takes one argument: ANT_AUTO
, ANT_INTERNAL
or ANT_EXTERNAL
.
Note that the antenna selection is remembered even after power off or when entering safe mode. This is to allow your device to be configured once and then continue to function with the selected antenna when applications are flashed that don't specify which antenna to use. Resetting Wi-Fi credentials (long press of MODE) does not clear the antenna selection.
You may specify in code which antenna to use as the default at boot time using the STARTUP() macro. If you are using system thread enabled mode or SEMI_AUTOMATIC or MANUAL system mode, you can select the antenna on setup or loop, however you should not set it continuously.
If no antenna has been previously selected, the ANT_INTERNAL
antenna will be chosen by default.
WiFi.selectAntenna()
returns 0 on success, or a non-zero error code.
// Use the STARTUP() macro to set the default antenna
// to use system boot time.
// In this case it would be set to the chip antenna
STARTUP(WiFi.selectAntenna(ANT_INTERNAL));
void setup() {
// your setup code
}
void loop() {
// your loop code
}
Note:
On ths P2, Photon 2, and M-SoM this is only supported on Device OS 5.3.2 and later.
// PROTOTYPE
WLanSelectAntenna_TypeDef getAntenna()
This function returns the currently selected antenna, one of:
ANT_AUTO
ANT_INTERNAL
ANT_EXTERNAL
Since 5.7.0:
You should normally let automatic connection management handle which network interface to use.
In some cases you may want to prefer cellular or Wi-Fi, and this can be done using the API. Note however:
WiFi.prefer()
will clear Cellular.prefer()
for example.// PROTOTYPE
virtual NetworkClass& prefer(bool prefer = true);
// EXAMPLE - enable prefer Wi-Fi
WiFi.prefer();
// EXAMPLE - disable prefer Wi-Fi
WiFi.prefer(false);
Returns true if the preferred network interface is Wi-Fi. This only indicates that the interface has been preferred and does not reflect what interface is in use.
// PROTOTYPE
virtual bool isPreferred();
if (WiFi.isPreferred()) {
// Do something here
}
This will enter or exit listening mode, which opens a Serial connection to get Wi-Fi credentials over USB, and also listens for credentials over Soft AP on the Photon or BLE on the Argon.
// SYNTAX - enter listening mode
WiFi.listen();
Listening mode blocks application code. Advanced cases that use multithreading, interrupts, or system events have the ability to continue to execute application code while in listening mode, and may wish to then exit listening mode, such as after a timeout. Listening mode is stopped using this syntax:
// SYNTAX - exit listening mode
WiFi.listen(false);
// SYNTAX
WiFi.listening();
Returns true if the device is in listening mode (blinking dark blue).
This is only relevant when using SYSTEM_THREAD(ENABLED)
. When not using threading, listening mode stops
user firmware from running, so you would not have an opportunity to test the value and calling this always
returns false. With Device OS 6.2.0 and later, system thread is always enabled.
Since 0.6.1:
// SYNTAX
WiFi.setListenTimeout(seconds);
WiFi.setListenTimeout(seconds)
is used to set a timeout value for Listening Mode. Values are specified in seconds
, and 0 disables the timeout. By default, Wi-Fi devices do not have any timeout set (seconds=0). As long as interrupts are enabled, a timer is started and running while the device is in listening mode (WiFi.listening()==true). After the timer expires, listening mode will be exited automatically. If WiFi.setListenTimeout() is called while the timer is currently in progress, the timer will be updated and restarted with the new value (e.g. updating from 10 seconds to 30 seconds, or 10 seconds to 0 seconds (disabled)). Note: Enabling multi-threaded mode with SYSTEM_THREAD(ENABLED) will allow user code to update the timeout value while Listening Mode is active.
// EXAMPLE
// If desired, use the STARTUP() macro to set the timeout value at boot time.
STARTUP(WiFi.setListenTimeout(60)); // set listening mode timeout to 60 seconds
void setup() {
// your setup code
}
void loop() {
// update the timeout later in code based on an expression
if (disableTimeout) WiFi.setListenTimeout(0); // disables the listening mode timeout
}
Since 1.5.0:
You can also specify a value using chrono literals, for example: WiFi.setListenTimeout(5min)
for 5 minutes.
Since 0.6.1:
// SYNTAX
uint16_t seconds = WiFi.getListenTimeout();
WiFi.getListenTimeout()
is used to get the timeout value currently set for Listening Mode. Values are returned in (uint16_t)seconds
, and 0 indicates the timeout is disabled. By default, Wi-Fi devices do not have any timeout set (seconds=0).
Allows the application to set credentials for the Wi-Fi network from within the code. These credentials will be added to the device's memory, and the device will automatically attempt to connect to this network in the future.
// Connects to an unsecured network.
WiFi.setCredentials(ssid);
WiFi.setCredentials("My_Router_Is_Big");
// Connects to a network secured with WPA2 credentials.
WiFi.setCredentials(ssid, password);
WiFi.setCredentials("My_Router", "mypasswordishuge");
// Connects to a network with a specified authentication procedure.
// Options are WPA2, WPA, or WEP.
WiFi.setCredentials(ssid, password, auth);
WiFi.setCredentials("My_Router", "wepistheworst", WEP);
When used with hidden or offline networks, the security cipher is also required. This is only supported on the Photon and P1. The Argon, P2, and Photon 2 do not support connecting to hidden networks.
// for hidden and offline networks on the Photon, the security cipher is also needed
// Cipher options are WLAN_CIPHER_AES, WLAN_CIPHER_TKIP and WLAN_CIPHER_AES_TKIP
WiFi.setCredentials(ssid, password, auth, cipher);
WiFi.setCredentials("SSID", "PASSWORD", WPA2, WLAN_CIPHER_AES);
// Connects to a network with an authentication procedure specified by WiFiCredentials object
WiFi.setCredentials(credentials);
WiFiCredentials credentials;
credentials.setSsid("My_Router")
.setSecurity(WEP)
.setPassword("wepistheworst");
WiFi.setCredentials(credentials);
The password is limited to 64 7-bit ASCII characters. If you pass in a longer password, only the first 64 characters will be saved.
Since 0.7.0:
Note:
WPA Enterprise is only supported on the Photon and P1.
It is not supported on the Argon, P2, and Photon 2.
Credentials can be set using WiFiCredentials class.
For information on setting up WPA2 Enterprise from the Particle CLI, see this article.
// WPA2 Enterprise with EAP-TLS
// We are setting WPA2 Enterprise credentials
WiFiCredentials credentials("My_Enterprise_AP", WPA2_ENTERPRISE);
// EAP type: EAP-TLS
credentials.setEapType(WLAN_EAP_TYPE_TLS);
// Client certificate in PEM format
credentials.setClientCertificate("-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n\r\n"
);
// Private key in PEM format
credentials.setPrivateKey("-----BEGIN RSA PRIVATE KEY-----\r\n" \
/* ... */ \
"-----END RSA PRIVATE KEY-----\r\n\r\n"
);
// Root (CA) certificate in PEM format (optional)
credentials.setRootCertificate("-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n\r\n"
);
// EAP outer identity (optional, default - "anonymous")
credentials.setOuterIdentity("anonymous");
// Save credentials
WiFi.setCredentials(credentials);
// WPA Enterprise with PEAP/MSCHAPv2
// We are setting WPA Enterprise credentials
WiFiCredentials credentials("My_Enterprise_AP", WPA_ENTERPRISE);
// EAP type: PEAP/MSCHAPv2
credentials.setEapType(WLAN_EAP_TYPE_PEAP);
// Set username
credentials.setIdentity("username");
// Set password
credentials.setPassword("password");
// Set outer identity (optional, default - "anonymous")
credentials.setOuterIdentity("anonymous");
// Root (CA) certificate in PEM format (optional)
credentials.setRootCertificate("-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n\r\n"
);
// Save credentials
WiFi.setCredentials(credentials);
Parameters:
ssid
: SSID (string)password
: password (string)auth
: see SecurityType enum.cipher
: see WLanSecurityCipher enum.credentials
: an instance of WiFiCredentials class.This function returns true
if credentials were successfully saved, or false
in case of an error.
Note: Setting WPA/WPA2 Enterprise credentials requires use of WiFiCredentials class.
Note: In order for WiFi.setCredentials()
to work, the Wi-Fi module needs to be on (if switched off or disabled via non_AUTOMATIC SYSTEM_MODEs call WiFi.on()
).
Since 0.4.9:
Lists the Wi-Fi networks with credentials stored on the device. Returns the number of stored networks.
Note that this returns details about the Wi-Fi networks, but not the actual password.
// DEFINITION
typedef struct WiFiAccessPoint {
size_t size;
char ssid[33];
uint8_t ssidLength;
uint8_t bssid[6];
WLanSecurityType security;
WLanSecurityCipher cipher;
uint8_t channel;
int maxDataRate;
int rssi;
} WiFiAccessPoint;
// EXAMPLE
LogHandler logHandler;
WiFiAccessPoint ap[5];
int found = WiFi.getCredentials(ap, 5);
for (int i = 0; i < found; i++) {
Log.info("ssid: %s", ap[i].ssid);
// security is one of WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA, WLAN_SEC_WPA2, WLAN_SEC_WPA_ENTERPRISE, WLAN_SEC_WPA2_ENTERPRISE
Log.info("security: %d", (int) ap[i].security);
// cipher is one of WLAN_CIPHER_AES, WLAN_CIPHER_TKIP or WLAN_CIPHER_AES_TKIP
Log.info("cipher: %d", (int) ap[i].cipher);
}
This will clear all saved credentials from the Wi-Fi module's memory. This will return true
on success and false
if the Wi-Fi module has an error.
// SYNTAX
WiFi.clearCredentials();
Will return true
if there are Wi-Fi credentials stored in the Wi-Fi module's memory.
// SYNTAX
WiFi.hasCredentials();
WiFi.macAddress()
returns the MAC address of the device.
// EXAMPLE USAGE
SerialLogHandler logHandler;
byte mac[6];
void setup() {
WiFi.on();
// wait up to 10 seconds for USB host to connect
// requires firmware >= 0.5.3
waitFor(Serial.isConnected, 10000);
WiFi.macAddress(mac);
Log.info("mac: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
WiFi.SSID()
returns the SSID of the network the device is currently connected to as a char*
.
WiFi.BSSID()
retrieves the 6-byte MAC address of the access point the device is currently connected to.
SerialLogHandler logHandler;
byte bssid[6];
void setup() {
Serial.begin(9600);
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
WiFi.BSSID(bssid);
Log.info("%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
}
WiFi.RSSI()
returns the signal strength of a Wi-Fi network from -127 (weak) to -1dB (strong) as an int
. Positive return values indicate an error with 1 indicating a Wi-Fi chip error and 2 indicating a time-out error.
// SYNTAX
int rssi = WiFi.RSSI();
WiFiSignal rssi = WiFi.RSSI();
Since 0.8.0
WiFi.RSSI()
returns an instance of WiFiSignal
class.
// SYNTAX
WiFiSignal sig = WiFi.RSSI();
If you are passing the RSSI value as a variable argument, such as with Serial.printlnf, Log.info, snprintf, etc. make sure you add a cast:
Log.info("RSSI=%d", (int8_t) WiFi.RSSI()).
This is necessary for the compiler to correctly convert the WiFiSignal class into a number.
This class allows to query a number of signal parameters of the currently connected WiFi network.
Gets the signal strength as a percentage (0.0 - 100.0). See getStrengthValue()
on how strength values are mapped to 0%-100% range.
// SYNTAX
WiFiSignal sig = WiFi.RSSI();
float strength = sig.getStrength();
// EXAMPLE
WiFiSignal sig = WiFi.RSSI();
Log.info("WiFi signal strength: %.02f%%", sig.getStrength());
Returns: float
Gets the signal quality as a percentage (0.0 - 100.0). See getQualityValue()
on how quality values are mapped to 0%-100% range.
// SYNTAX
WiFiSignal sig = WiFi.RSSI();
float quality = sig.getQuality();
// EXAMPLE
WiFiSignal sig = WiFi.RSSI();
Log.info("WiFi signal quality: %.02f%%", sig.getQuality());
Returns: float
// SYNTAX
WiFiSignal sig = WiFi.RSSI();
float strength = sig.getStrengthValue();
Gets the raw signal strength value in dBm. Range: [-90, 0].
Returns: float
// SYNTAX
WiFiSignal sig = WiFi.RSSI();
float quality = sig.getQualityValue();
Gets the raw signal quality value (SNR) in dB. Range: [0, 90].
Returns: float
WiFi.ping()
allows you to ping an IP address and returns the number of packets received as an int
. It takes two forms:
WiFi.ping(IPAddress remoteIP)
takes an IPAddress
and pings that address.
WiFi.ping(IPAddress remoteIP, uint8_t nTries)
and pings that address a specified number of times.
Note:
WiFi.ping() is only available on the Photon and P1.
It is not available on the P2, Photon 2, or Argon.
Returns information about access points within range of the device.
The first form is the simplest, but also least flexible. You provide a
array of WiFiAccessPoint
instances, and the call to WiFi.scan()
fills out the array.
If there are more APs detected than will fit in the array, they are dropped.
Returns the number of access points written to the array.
// EXAMPLE - retrieve up to 20 Wi-Fi APs
SerialLogHandler logHandler;
WiFiAccessPoint aps[20];
int found = WiFi.scan(aps, 20);
for (int i=0; i<found; i++) {
WiFiAccessPoint& ap = aps[i];
Log.info("ssid=%s security=%d channel=%d rssi=%d", ap.ssid, (int)ap.security, (int)ap.channel, ap.rssi);
}
The more advanced call to WiFi.scan()
uses a callback function that receives
each scanned access point.
// EXAMPLE using a callback
void wifi_scan_callback(WiFiAccessPoint* wap, void* data)
{
WiFiAccessPoint& ap = *wap;
Log.info("ssid=%s security=%d channel=%d rssi=%d", ap.ssid, (int)ap.security, (int)ap.channel, ap.rssi);
}
void loop()
{
int result_count = WiFi.scan(wifi_scan_callback);
Log.info("result_count=%d", result_count);
}
The main reason for doing this is that you gain access to all access points available without having to know in advance how many there might be.
You can also pass a 2nd parameter to WiFi.scan()
after the callback, which allows
object-oriented code to be used.
// EXAMPLE - class to find the strongest AP
class FindStrongestSSID
{
char strongest_ssid[33];
int strongest_rssi;
// This is the callback passed to WiFi.scan()
// It makes the call on the `self` instance - to go from a static
// member function to an instance member function.
static void handle_ap(WiFiAccessPoint* wap, FindStrongestSSID* self)
{
self->next(*wap);
}
// determine if this AP is stronger than the strongest seen so far
void next(WiFiAccessPoint& ap)
{
if ((ap.rssi < 0) && (ap.rssi > strongest_rssi)) {
strongest_rssi = ap.rssi;
strcpy(strongest_ssid, ap.ssid);
}
}
public:
/**
* Scan Wi-Fi Access Points and retrieve the strongest one.
*/
const char* scan()
{
// initialize data
strongest_rssi = -128;
strongest_ssid[0] = 0;
// perform the scan
WiFi.scan(handle_ap, this);
return strongest_ssid;
}
};
// Now use the class
FindStrongestSSID strongestFinder;
const char* ssid = strongestFinder.scan();
}
WiFi.resolve()
finds the IP address for a domain name.
// SYNTAX
ip = WiFi.resolve(name);
Parameters:
name
: the domain name to resolve (string)It returns the IP address if the domain name is found, otherwise a blank IP address.
// EXAMPLE USAGE
IPAddress ip;
void setup() {
ip = WiFi.resolve("www.google.com");
if(ip) {
// IP address was resolved
} else {
// name resolution failed
}
}
WiFi.localIP()
returns the local IP address assigned to the device as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the local IP over Serial.
Log.info("ip address: %s", WiFi.localIP().toString().c_str());
}
WiFi.subnetMask()
returns the subnet mask of the network as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the subnet mask over Serial.
Log.info("subnet mask: %s" WiFi.subnetMask().toString().c_str());
}
WiFi.gatewayIP()
returns the gateway IP address of the network as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the gateway IP over Serial.
Log.info("gateway: %s", WiFi.gatewayIP().toString().c_str());
}
WiFi.dnsServerIP()
retrieves the IP address of the DNS server that resolves
DNS requests for the device's network connection.
Note that for this value to be available requires calling Particle.process()
after Wi-Fi
has connected.
WiFi.dhcpServerIP()
retrieves the IP address of the DHCP server that manages
the IP address used by the device's network connection.
Note that for this value to be available requires calling Particle.process()
after Wi-Fi
has connected.
This API is only used for Gen 2 devices (Photon and P1).
Defines the static IP addresses used by the system to connect to the network when static IP is activated.
This API is only available for the Photon and P1 (Gen 2).
On the Argon, P2, and Photon 2, static IP addressing requires Device OS 5.3.0 or later and uses a different API. See NetworkInterfaceConfig
.
// SYNTAX
void setup() {
IPAddress myAddress(192,168,1,100);
IPAddress netmask(255,255,255,0);
IPAddress gateway(192,168,1,1);
IPAddress dns(192,168,1,1);
WiFi.setStaticIP(myAddress, netmask, gateway, dns);
// now let's use the configured IP
WiFi.useStaticIP();
}
The addresses are stored persistently so that they are available in all subsequent application and also in safe mode.
Instructs the system to connect to the network using the IP addresses provided to
WiFi.setStaticIP()
The setting is persistent and is remembered until WiFi.useDynamicIP()
is called.
This API is only available for the Photon and P1 (Gen 2).
On the Argon, P2, and Photon 2, static IP addressing requires Device OS 5.3.0 or later and uses a different API. See NetworkInterfaceConfig
.
Instructs the system to connect to the network using a dynamically allocated IP address from the router.
A note on switching between static and dynamic IP. If static IP addresses have been previously configured using WiFi.setStaticIP()
, they continue to be remembered
by the system after calling WiFi.useDynamicIP()
, and so are available for use next time WiFi.useStaticIP()
is called, without needing to be reconfigured using WiFi.setStaticIP()
This API is only available for the Photon and P1 (Gen 2).
On the Argon, P2, and Photon 2, static IP addressing requires Device OS 5.3.0 or later and uses a different API. See NetworkInterfaceConfig
.
Since 0.7.0:
Sets a custom hostname to be used as DHCP client name (DHCP option 12).
Parameters:
hostname
: the hostname to set (string)// SYNTAX
WiFi.setHostname("photon-123");
By default the device uses its device ID as hostname.
The hostname is stored in persistent memory. In order to reset the hostname to its default value (device ID) setHostname()
needs to be called with hostname
argument set to NULL
.
// Reset hostname to default value (device ID)
WiFi.setHostname(NULL);
// Both these functions should return the same value.
Serial.println(WiFi.getHostname());
Serial.println(System.deviceID());
Hostname setting is only available on the Photon and P1 (Gen 2). It is not available on the Argon or Ethernet (Gen 3), P2, or Photon 2.
Since 0.7.0:
Retrieves device hostname used as DHCP client name (DHCP option 12).
This function does not take any arguments and returns a String
.
// SYNTAX
String hostname = WiFi.hostname();
By default the device uses its device ID as hostname. See WiFi.setHostname() for documentation on changing the hostname.
Hostname setting is only available on the Photon and P1 (Gen 2). It is not available on the Argon or Ethernet (Gen 3), P2, or Photon 2.
Since 5.0.0:
Gets the Wi-Fi country code or region code. The default is WLAN_CC_US. This is not automatically set; if you need to use a different country code it must be set manually.
// PROTOTYPE
int wlan_get_country_code(void* reserved);
// EXAMPLE
wlan_country_code_t ccType = wlan_get_country_code(nullptr);
See the list of code in wlan_set_country_code.
This API is available only on the P2, Photon 2, and M-SoM in Device OS 5.0.0 and later.
Since 5.0.0:
Sets the Wi-Fi country code or region code. The default is WLAN_CC_US. This is not automatically set; you must manually set it if the country you are using the device in requires setting a specific Wi-Fi channel plan. The setting is stored in the DCT (configration flash memory) and is not reset when flashing user firmware, Device OS, or when clearing Wi-Fi credentials.
// PROTOTYPE
int wlan_set_country_code(wlan_country_code_t country, void* reserved);
// EXAMPLE
wlan_set_country_code(wlan_country_code_t::WLAN_CC_JP, nullptr);
Country Code | Value |
---|---|
wlan_country_code_t::WLAN_CC_UNSET | 0x0000 |
wlan_country_code_t::WLAN_CC_US | 0x5553 |
wlan_country_code_t::WLAN_CC_EU | 0x4555 |
wlan_country_code_t::WLAN_CC_JP | 0x4A50 |
wlan_country_code_t::WLAN_CC_CA | 0x4341 |
wlan_country_code_t::WLAN_CC_MX | 0x4D58 |
wlan_country_code_t::WLAN_CC_GB | 0x4742 |
wlan_country_code_t::WLAN_CC_AU | 0x4155 |
wlan_country_code_t::WLAN_CC_KR | 0x4B52 |
wlan_country_code_t::WLAN_CC_WORLD | 0xFFFE |
This API is available only on the P2, Photon 2, and M-SoM in Device OS 5.0.0 and later.
This setting is stored at the DCT offset DCT_COUNTRY_CODE_OFFSET
(country_code
, offset 1758, 2 bytes) and thus can also be set in DFU mode using the -a 1
to store in alt bank 1 (DCT).
This class allows to define WiFi credentials that can be passed to WiFi.setCredentials() function.
// EXAMPLE - defining and using WiFiCredentials class
void setup() {
// Ensure that WiFi module is on
WiFi.on();
// Set up WPA2 access point "My AP" with password "mypassword" and AES cipher
WiFiCredentials credentials("My AP", WPA2);
credentials.setPassword("mypassword")
.setCipher(WLAN_CIPHER_AES);
// Connect if settings were successfully saved
if (WiFi.setCredentials(credentials)) {
WiFi.connect();
waitFor(WiFi.ready, 30000);
Particle.connect();
waitFor(Particle.connected, 30000);
}
}
void loop() {
}
Constructs an instance of the WiFiCredentials class. By default security type is initialized to unsecured (UNSEC
).
// SYNTAX
WiFiCredentials credentials(SecurityType security = UNSEC); // 1
WiFiCredentials credentials(const char* ssid, SecurityType security = UNSEC); // 2
// EXAMPLE - constructing WiFiCredentials instance
// Empty instance, security is set to UNSEC
WiFiCredentials credentials;
// No SSID, security is set to WPA2
WiFiCredentials credentials(WPA2);
// SSID set to "My AP", security is set to UNSEC
WiFiCredentials credentials("My AP");
// SSID set to "My WPA AP", security is set to WPA
WiFiCredentials credentials("My AP", WPA);
Parameters:
ssid
: SSID (string)security
: see SecurityType enum.Sets access point SSID.
// SYNTAX
WiFiCredentials& WiFiCredentials::setSsid(const char* ssid);
// EXAMPLE - setting ssid
WiFiCredentials credentials;
credentials.setSsid("My AP");
Parameters:
ssid
: SSID (string)Sets access point security type.
// SYNTAX
WiFiCredentials& WiFiCredentials::setSecurity(SecurityType security);
// EXAMPLE - setting security type
WiFiCredentials credentials;
credentials.setSecurity(WPA2);
Parameters:
security
: see SecurityType enum.Sets access point cipher.
// SYNTAX
WiFiCredentials& WiFiCredentials::setCipher(WLanSecurityCipher cipher);
// EXAMPLE - setting cipher
WiFiCredentials credentials;
credentials.setCipher(WLAN_CIPHER_AES);
Parameters:
cipher
: see WLanSecurityCipher enum.Sets access point password.
When configuring credentials for WPA/WPA2 Enterprise access point with PEAP/MSCHAPv2 authentication, this function sets password for username set by setIdentity().
// SYNTAX
WiFiCredentials& WiFiCredentials::setPassword(const char* password);
// EXAMPLE - setting password
WiFiCredentials credentials("My AP", WPA2);
credentials.setPassword("mypassword");
Parameters:
password
: WEP/WPA/WPA2 access point password, or user password for PEAP/MSCHAPv2 authentication (string)The password is limited to 64 7-bit ASCII characters. If you pass in a longer password, only the first 64 characters will be saved.
Since 5.5.0:
Allows connecting to a Wi-Fi network with a hidden SSID.
Normally, the P2, Photon 2, M-SoM, and Argon do a Wi-Fi scan and compare this against the list of configured networks to choose a network to connect to. If no connection made to non-hidden networks, then an attempt is made to connect to each hidden network. This will be slower, as each connection attempt needs to time out.
// PROTOTYPE
virtual WiFiCredentials& setHidden(bool hidden = true)
// EXAMPLE
WiFiCredentials creds;
creds.setSsid("My Secret Network");
creds.setPassword("secret");
creds.setSecurity(SecurityType::WPA2);
creds.setHidden(true);
WiFi.setCredentials(creds);
Since 5.8.0:
When set, validate that the Wi-Fi network can be connected to before setting the credentials. The default is to save without validating when using WiFi.setCredentials().
If Wi-Fi is off, it will be turned on to validate credentials.
If Wi-Fi is on and connected, the current network will be disconnected (including the cloud connection, if connected) to attempt to connect to the new network.
// PROTOTYPE
virtual WiFiCredentials& setValidate(bool validate = true)
// EXAMPLE
WiFiCredentials creds;
creds.setSsid("My Network");
creds.setPassword("secret");
creds.setSecurity(SecurityType::WPA2);
creds.setValidate(true);
WiFi.setCredentials(creds);
Sets access point channel.
// SYNYAX
WiFiCredentials& WiFiCredentials::setChannel(int channel);
// EXAMPLE - setting channel
WiFiCredentials credentials("My AP");
credentials.setChannel(10);
Parameters:
channel
: WLAN channel (int)Sets EAP type.
// SYNTAX
WiFiCredentials& WiFiCredentials::setEapType(WLanEapType type);
// EXAMPLE - setting EAP type
WiFiCredentials credentials("My Enterprise AP", WPA2_ENTERPRISE);
credentials.setEapType(WLAN_EAP_TYPE_PEAP);
Parameters:
type
: EAP type. See WLanEapType enum for a list of supported values.This is a feature of WPA Enterprise and is only available on the Photon and P1 (Gen 2). It is not available on the Argon (Gen 3), P2, or Photon 2.
Sets EAP inner identity (username in case of PEAP/MSCHAPv2).
// SYNTAX
WiFiCredentials& WiFiCredentials::setIdentity(const char* identity);
// EXAMPLE - setting PEAP identity (username)
WiFiCredentials credentials("My Enterprise AP", WPA2_ENTERPRISE);
credentials.setEapType(WLAN_EAP_TYPE_PEAP);
credentials.setIdentity("username");
Parameters:
identity
: inner identity (string)Sets EAP outer identity. Defaults to "anonymous".
// SYNTAX
WiFiCredentials& WiFiCredentials::setOuterIdentity(const char* identity);
// EXAMPLE - setting outer identity
WiFiCredentials credentials("My Enterprise AP", WPA2_ENTERPRISE);
credentials.setOuterIdentity("notanonymous");
Parameters:
identity
: outer identity (string)This is a feature of WPA Enterprise and is only available on the Photon and P1 (Gen 2). It is not available on the Argon (Gen 3), P2, or Photon 2.
Sets client certificate used for EAP-TLS authentication.
// SYNTAX
WiFiCredentials& WiFiCredentials::setClientCertificate(const char* cert);
// EXAMPLE - setting client certificate
WiFiCredentials credentials;
credentials.setClientCertificate("-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n\r\n"
);
Parameters:
cert
: client certificate in PEM format (string)This is a feature of WPA Enterprise and is only available on the Photon and P1 (Gen 2). It is not available on the Argon (Gen 3), P2, or Photon 2.
Sets private key used for EAP-TLS authentication.
// SYNTAX
WiFiCredentials& WiFiCredentials::setPrivateKey(const char* key);
// EXAMPLE - setting private key
WiFiCredentials credentials;
credentials.setPrivateKey("-----BEGIN RSA PRIVATE KEY-----\r\n" \
/* ... */ \
"-----END RSA PRIVATE KEY-----\r\n\r\n"
);
Parameters:
key
: private key in PEM format (string)This is a feature of WPA Enterprise and is only available on the Photon and P1 (Gen 2). It is not available on the Argon (Gen 3), P2, or Photon 2.
Sets one more root (CA) certificates.
// SYNTAX
WiFiCredentials& WiFiCredentials::setRootCertificate(const char* cert);
// EXAMPLE - setting one root certificate
WiFiCredentials credentials;
credentials.setClientCertificate("-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n\r\n"
);
// EXAMPLE - setting multiple root certificates
WiFiCredentials credentials;
credentials.setClientCertificate("-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n"
"-----BEGIN CERTIFICATE-----\r\n" \
/* ... */ \
"-----END CERTIFICATE-----\r\n\r\n"
);
Parameters:
cert
: one or multiple concatenated root certificates in PEM format (string)This is a feature of WPA Enterprise and is only available on the Photon and P1 (Gen 2). It is not available on the Argon (Gen 3), P2, or Photon 2.
This enum defines EAP types.
Name | Description |
---|---|
WLAN_EAP_TYPE_PEAP |
PEAPv0/EAP-MSCHAPv2 (draft-josefsson-pppext-eap-tls-eap-06.txt) |
WLAN_EAP_TYPE_TLS |
EAP-TLS (RFC 2716) |
This is a feature of WPA Enterprise and is only available on the Photon and P1 (Gen 2). It is not available on the Argon (Gen 3), P2, or Photon 2.
This enum defines wireless security types.
Name | Description |
---|---|
UNSEC |
Unsecured |
WEP |
Wired Equivalent Privacy |
WPA |
Wi-Fi Protected Access |
WPA2 |
Wi-Fi Protected Access II |
WPA_ENTERPRISE |
Wi-Fi Protected Access-Enterprise |
WPA2_ENTERPRISE |
Wi-Fi Protected Access-Enterprise II |
This enum defines wireless security ciphers.
Name | Description |
---|---|
WLAN_CIPHER_NOT_SET |
No cipher |
WLAN_CIPHER_AES |
AES cipher |
WLAN_CIPHER_TKIP |
TKIP cipher |
WLAN_CIPHER_AES_TKIP |
AES or TKIP cipher |
Since 6.2.0:
Tethering allows another device to use the cellular network of certain Particle devices. See tethering for more information.
The TetherSerialConfig
class configures the serial port parameters for the Tethering PPP connection.
Select the hardware serial port to use for the tethering PPP connection. This must be a hardware serial port (not a USB serial port) and hardware flow control (RTS/CTS) is recommended.
// EXAMPLE
Tether.bind(TetherSerialConfig().baudrate(921600).serial(Serial1));
// PROTOTYPES
TetherSerialConfig& serial(USARTSerial& s);
USARTSerial& serial() const;
Select serial port configuration options. The values depend on the device and port.
The default is SERIAL_8N1 | SERIAL_FLOW_CONTROL_RTS_CTS
.
// PROTOTYPES
TetherSerialConfig& config(unsigned conf);
unsigned config() const;
Select the hardware serial port baud rate to use for the tethering PPP connection.
The default is 921600.
// EXAMPLE
Tether.bind(TetherSerialConfig().baudrate(921600).serial(Serial1));
// PROTOTYPES
TetherSerialConfig& baudrate(unsigned baud);
unsigned baudrate() const;
Tether.on()
turns on the tethering support. It defaults to off.
// EXAMPLE
Tether.bind(TetherSerialConfig().baudrate(921600).serial(Serial1));
Tether.on();
Tether.connect();
Tether.off()
turns off tethering support.
Starts the PPP server so the other device can send requests to it. You will typically turn it on, then connect, after connecting to cellular.
// EXAMPLE
Tether.bind(TetherSerialConfig().baudrate(921600).serial(Serial1));
Tether.on();
Tether.connect();
// SYNTAX
Tether.connect();
Disconnects from the tethering, but leaves it on.
// SYNTAX
Tether.disconnect();
This function will return true
while the PPP server is starting up, and false
once running.
// SYNTAX
Tether.connecting();
This function will return true
once PPP server is running and can accept requests from the other device. Otherwise it will return false
.
// SYNTAX
Tether.ready();
Tether.localIP()
is used to get the IP address of the PPP server as an IPAddress
.
// EXAMPLE
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
Log.info("localIP: %s", Tether.localIP().toString().c_str());
}
Tether.subnetMask()
returns the subnet mask of the PPP server as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the subnet mask over Serial.
Log.info(Tether.subnetMask());
}
Tether.gatewayIP()
returns the gateway IP address of the PPP server as an IPAddress
.
SerialLogHandler logHandler;
void setup() {
Serial.begin(9600);
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
// Prints out the gateway IP over Serial.
Log.info(Tether.gatewayIP());
}
The Network
class can be used instead of Cellular
or WiFi
and represents the set of features common across all supported networking types: Cellular, Wi-Fi, and Ethernet.
There are two common uses for this:
#ifdef
.// EXAMPLE
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler;
class ExampleModule {
public:
ExampleModule &withNetwork(NetworkClass &network) {
this->network = network;
return *this;
}
void setup() {
}
void loop() {
bool ready = network.ready();
if (ready != lastReady) {
lastReady = ready;
Log.info("ready=%d", (int) ready);
}
}
NetworkClass &network = Network;
bool lastReady = false;
};
ExampleModule exampleModule;
void setup() {
exampleModule
.withNetwork(Ethernet)
.setup();
}
void loop() {
exampleModule.loop();
}
This example prints when the network changes to or from ready state. By default, it uses Network
, the default networking for this device. However, the caller can use the withNetwork()
method to pass in a different network. In this example, it changes the behavior to monitor the Ethernet
network instead.
When using the switchable network, it uses network
(lowercase, the member variable), instead of Network
(default interface), or a specific interface like Cellular
or WiFi
.
Network.on()
turns on the the default network interface for this device, typically Cellular or Wi-Fi.
Note that Network.on()
does not need to be called unless you have changed the system mode or you have previously turned the network off.
// EXAMPLE:
Particle.disconnect();
Network.off();
Network.off()
turns off the default network interface for this device, typically Cellular or Wi-Fi.
You must call Particle.disconnect()
before turning off the network manually, otherwise the cloud connection may turn it back on again.
This should only be used with SYSTEM_MODE(SEMI_AUTOMATIC)
(or MANUAL
) as the cloud connection and Wi-Fi are managed by Device OS in AUTOMATIC
mode.
Attempts to connect to default network interface for this device, typically Cellular or Wi-Fi.
For Wi-Fi devices, if there are no credentials stored, this will enter listening mode, blinking dark blue. If there are credentials stored, this will try the available credentials until connection is successful. When this function returns, the device may not have an IP address on the LAN; use Network.ready()
to determine the connection status.
// SYNTAX
Network.connect();
On Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker), prior to Device OS 2.0.0, you needed to call WiFi.on()
or Cellular.on()
before calling Particle.connect()
. This is not necessary on Gen 2 devices (any Device OS version) or with 2.0.0 and later.
On the Argon, starting with Device OS 1.5.0, a quick Wi-Fi scan is done before connecting. The list of available networks is compared with the configured SSIDs. The access point with the strongest signal is connected to. Prior to 1.5.0, only the original access point BSSID that was configured would ever be connected to, even if there was a different access point on the same SSID and network with a stronger signal.
Disconnects from the network, but leaves the network module on.
// SYNTAX
Network.disconnect();
This function will return true
once the device is attempting to connect, and will return false
once the device has successfully connected to the network.
// SYNTAX
Network.connecting();
This function will return true
once the device is connected to the network. Otherwise it will return false
.
// SYNTAX
Network.ready();
Since 5.3.0:
Set a NetworkInterfaceConfig
for a generic network interface on the Argon, P2, and Photon 2 running Device OS 5.3.0 or later. This can be used with Ethernet and WiFi. This is used to set a static IP address or restore DHCP addressing (the default).
// PROTOTYPE
int setConfig(const particle::NetworkInterfaceConfig& conf);
// EXAMPLE
Network.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0})
.gateway(SockAddr({192,168,1,1}))
.dns(SockAddr({192,168,1,1})));
Since 5.3.0:
Get the current NetworkInterfaceConfig
for a generic network interface interface on the Argon, P2, and Photon 2 running Device OS 5.3.0 or later. This can be used with Ethernet and WiFi.
// PROTOTYPE
particle::NetworkInterfaceConfig getConfig(String profile = String()) const;
Since 5.7.0:
You should normally let automatic connection management handle which network interface to use. There are prefer()
methods in the Cellular
, WiFi
, and Ethernet
classes that can be used if you have a need to steer the connection management toward a specific interface.
WiFi.prefer()
will clear Cellular.prefer()
for example.Network
as the preferred interface will clear other preferences such as WiFi
and Cellular
.// PROTOTYPE
virtual NetworkClass& prefer(bool prefer = true);
// EXAMPLE - Enable automatic connection management (disable other preferences)
Network.prefer();
Since 5.6.0:
Returns true if automatic connection management is in use, which is to say that no other interfaces such as WiFi
and Cellular
have been selected as preferred.
// PROTOTYPE
virtual bool isPreferred();
if (Network.isPreferred()) {
// Automatic connection management is enabled
}
This will enter or exit listening mode, blinking dark blue. The cloud connection is not available in listening mode.
// SYNTAX - enter listening mode
Network.listen();
Listening mode blocks application code. Advanced cases that use multithreading, interrupts, or system events have the ability to continue to execute application code while in listening mode, and may wish to then exit listening mode, such as after a timeout. Listening mode is stopped using this syntax:
// SYNTAX - exit listening mode
Network.listen(false);
// SYNTAX
Network.listening();
Returns true if the device is in listening mode (blinking dark blue).
This is only relevant when using SYSTEM_THREAD(ENABLED)
. When not using threading, listening mode stops
user firmware from running, so you would not have an opportunity to test the value and calling this always
returns false. With Device OS 6.2.0 and later, system thread is always enabled.
Since 0.6.1:
// SYNTAX
Network.setListenTimeout(seconds);
Network.setListenTimeout(seconds)
is used to set a timeout value for Listening Mode. Values are specified in seconds
, and 0 disables the timeout. This function is rarely needed.
// EXAMPLE
// If desired, use the STARTUP() macro to set the timeout value at boot time.
STARTUP(Network.setListenTimeout(60)); // set listening mode timeout to 60 seconds
void setup() {
// your setup code
}
void loop() {
// update the timeout later in code based on an expression
if (disableTimeout) Network.setListenTimeout(0); // disables the listening mode timeout
}
Since 1.5.0:
You can also specify a value using chrono literals, for example: Network.setListenTimeout(5min)
for 5 minutes.
Since 0.6.1:
// SYNTAX
uint16_t seconds = Network.getListenTimeout();
Network.getListenTimeout()
is used to get the timeout value currently set for Listening Mode. Values are returned in (uint16_t)seconds
, and 0 indicates the timeout is disabled. This function is rarely needed.
Since 5.3.0:
With Device OS 5.3.0 and later, this class is used to hold a configuration for network interface addresses, typically to enable static IP addressing for Ethernet or Wi-Fi instead of using DHCP.
Sets the network interface to DHCP (the default), or static IP addressing. The setting is persistent and saved in the flash file system.
NetworkInterfaceConfigSource::DHCP
NetworkInterfaceConfigSource::STATIC
// PROTOTYPES
NetworkInterfaceConfig& source(NetworkInterfaceConfigSource source, int family = AF_INET);
NetworkInterfaceConfigSource source(int family = AF_INET) const;
// EXAMPLE - Restore DHCP addressing for Ethernet
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::DHCP);
Sets the IP address when using static IP addressing. The setting is persistent and saved in the flash file system.
Typically you use the last overload which takes two IPAddress
class references, one for the address and one for the subnet mask.
Note that the syntax {192,168,1,20}
uses commas, not the more typical period or dot, because this is a C++ initializer, not a string.
// PROTOTYPES
NetworkInterfaceConfig& address(const NetworkInterfaceAddress& addr);
NetworkInterfaceConfig& address(SockAddr addr, SockAddr mask);
NetworkInterfaceConfig& address(IPAddress addr, IPAddress mask);
// EXAMPLE
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0}));
Sets the gateway IP address when using static IP addressing. The setting is persistent and saved in the flash file system.
Even though the parameter is a SockAddr
, you can initialize the gateway address as if it were an IPAddress
.
Note that the syntax {192,168,1,1}
uses commas, not the more typical period or dot, because this is a C++ initializer, not a string.
When using Ethernet, if the link is active and the gateway is reachable, by default the Ethernet interface is the default route, including for the cloud connection, even if the Ethernet LAN is not connected to the Internet (isolated LAN). To prevent this behavior with Device OS 5.3.0 and later, set the gateway to {0,0,0,0}
which will prevent the Ethernet interface from being used to access the Internet, including the Particle cloud. This can be done for both static and DHCP addressing. You can still access hosts on the Ethernet local LAN by TCP or UDP. You might do this for an isolated LAN that has Modbus TCP devices on it, for example. For more information, see Isolated LAN.
// PROTOTYPES
NetworkInterfaceConfig& gateway(SockAddr addr);
SockAddr gateway(int family = AF_INET) const;
// EXAMPLE
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0})
.gateway(SockAddr({192,168,1,1})));
// EXAMPLE - Ethernet is isolated, do not use for Internet or cloud access
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0})
.gateway(SockAddr({0,0,0,0})));
// EXAMPLE - Ethernet is isolated, do not use for Internet or cloud access but does have a DHCP server
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::DHCP)
.gateway(SockAddr({0,0,0,0})));
Sets the DNS server address. The setting is persistent and saved in the flash file system. You can set multiple DNS server addresses if desired.
Even though the parameter is a SockAddr
, you can initialize the gateway address as if it were an IPAddress
.
// PROTOTYPE
NetworkInterfaceConfig& dns(SockAddr dns);
// EXAMPLE
Ethernet.setConfig(NetworkInterfaceConfig()
.source(NetworkInterfaceConfigSource::STATIC)
.address({192,168,1,20}, {255,255,255,0})
.gateway(SockAddr({192,168,1,1}))
.dns(SockAddr({192,168,1,1}))
.dns(SockAddr({8,8,8,8})));
Note:
SoftAP is available only on the Photon and P1 (Gen 2).
It is not available on cellular devices, the Argon (Gen 3 Wi-Fi), the Photon 2, or P2.
Since 0.5.0:
When the device is in listening mode, it creates a temporary access point (AP) and a HTTP server on port 80. The HTTP server is used to configure the Wi-Fi access points the device attempts to connect to. As well as the system providing HTTP URLs, applications can add their own pages to the SoftAP HTTP server.
SoftAP HTTP Pages is presently an advanced feature, requiring moderate C++ knowledge. To begin using the feature:
#include "Particle.h"
below that, then#include "softap_http.h"
below that still// SYNTAX
void myPages(const char* url, ResponseCallback* cb, void* cbArg, Reader* body, Writer* result, void* reserved);
STARTUP(softap_set_application_page_handler(myPages, nullptr));
The softap_set_application_page_handler
is set during startup. When the system is in setup mode (listening mode, blinking dark blue), and a request is made for an unknown URL, the system
calls the page handler function provided by the application (here, myPages
.)
The page handler function is called whenever an unknown URL is requested. It is called with these parameters:
url
: the path of the file requested by the client. It doesn't include the server name or port. Examples: /index
, /someimage.jpg
.cb
: a response callback - this is used by the application to indicate the type of HTTP response, such as 200 (OK) or 404 (not found). More on this below.cbArg
: data that should be passed as the first parameter to the callback function cb
.body
: a reader object that the page handler uses to retrieve the HTTP request bodyresult
: a writer object that the page handler uses to write the HTTP response bodyreserved
: reserved for future expansion. Will be equal to nullptr
and can be ignored.The application MUST call the page callback function cb
to provide a response for the requested page. If the requested page url isn't recognized by the application, then a 404 response should be sent, as described below.
When your page handler function is called, the system passes a result callback function as the cb
parameter.
The callback function takes these parameters:
cbArg
: this is the cbArg
parameter passed to your page callback function. It's internal state used by the HTTP server.flags
: presently unused. Set to 0.status
: the HTTP status code, as an integer, such as 200 for OK
, or 404 for page not found
.mime-type
: the mime-type of the response as a string, such as text/html
or application/javascript
.header
: an optional pointer to a Header
that is added to the response sent to the client.For example, to send a "not found" error for a page that is not recognized, your application code would call
// EXAMPLE - send a 404 response for an unknown page
cb(cbArg, 0, 404, "text/plain", nullptr);
When the HTTP request contains a request body (such as with a POST request), the Reader
object provided by the body
parameter can be used
to retrieve the request data.
// EXAMPLE
if (body->bytes_left) {
char* data = body->fetch_as_string();
// handle the body data
dostuff(data);
// free the data! IMPORTANT!
free(data);
}
When sending a page, the page function responds with a HTTP 200 code, meaning the content was found, followed by the page data.
// EXAMPLE - send a page
if (!stricmp(url, '/helloworld') {
// send the response code 200, the mime type "text/html"
cb(cbArg, 0, 200, "text/html", nullptr);
// send the page content
result->write("<h2>hello world!</h2>");
}
When a browser requests the default page (http://192.168.0.1/
) the system internally redirects this to /index
so that it can be handled
by the application.
The application may provide an actual page at /index
or redirect to another page if the application prefers to have another page as its launch page.
The application can send a redirect response for a given page in order to manage the URL namespace, such as providing aliases for some resources.
The code below sends a redirect from the default page /index
to /index.html
// EXAMPLE - redirect from /index to /index.html
// add this code in the page hanler function
if (strcmp(url,"/index")==0) {
Header h("Location: /index.html\r\n");
cb(cbArg, 0, 301, "text/plain", &h);
return;
}
The example source code can be downloaded here.
Here's a complete example providing a Web UI for setting up WiFi via HTTP. Credit for the HTTP pages goes to GitHub user @mebrunet! (Included from PR #909 here) (Source code here)
Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
The Cellular
class is available on the the Boron, B-Series SoM, and Tracker (Gen 3)
and Electron and E-Series (Gen 2).
It is not available on Wi-Fi devices including the P2, Photon 2, Argon, Photon, and P1.
Cellular.on()
turns on the Cellular module. Useful when you've turned it off, and you changed your mind.
Note that Cellular.on()
does not need to be called unless you have changed the system mode or you have previously turned the Cellular module off. When turning on the Cellular module, it will go through a full re-connect to the Cellular network which will take anywhere from 30 to 60 seconds in most situations.
// SYNTAX
Cellular.on();
Note: Cellular.on()
API is non-blocking on all platforms except for Electron with threading disabled.
Since 2.1.0:
Cellular.isOn()
can be used to actively wait for when the modem gets powered on. Alternatively network system events can be used to track the power state of the modem.
Cellular.off()
turns off the Cellular module. Useful for saving power, since most of the power draw of the device is the Cellular module. Note: turning off the Cellular module will force it to go through a full re-connect to the Cellular network the next time it is turned on.
// SYNTAX
Cellular.off();
// EXAMPLE
Particle.disconnect();
Cellular.off();
You must not turn off and on cellular more than every 10 minutes (6 times per hour). Your SIM can be blocked by your mobile carrier for aggressive reconnection if you reconnect to cellular very frequently.
If you are manually managing the cellular connection in case of connection failures, you should wait at least 5 minutes before stopping the connection attempt. When retrying on failure, you should implement a back-off scheme waiting 5 minutes, 10 minutes, 15 minutes, 20 minutes, 30 minutes, then 60 minutes between retries. Repeated failures to connect can also result in your SIM being blocked.
You must call Particle.disconnect()
before turning off the cellular modem manually, otherwise the cloud connection may turn the cellular modem back on.
This should only be used with SYSTEM_MODE(SEMI_AUTOMATIC)
(or MANUAL
) as the cloud connection and cellular modem are managed by Device OS in AUTOMATIC
mode.
Note: Cellular.off()
API is non-blocking on all platforms except for Electron with threading disabled.
Since 2.1.0:
Cellular.isOff()
can be used to actively wait for when the modem gets powered off. Alternatively network system events can be used to track the power state of the modem.
Since 2.1.0:
This function will return true
if the cellular modem is powered on and went through low level initialization. Otherwise it will return false
.
// SYNTAX
Cellular.isOn();
// EXAMPLE
Cellular.on();
waitFor(Cellular.isOn, 30000);
Since 2.1.0:
This function will return true
if the cellular modem is powered off. Otherwise it will return false
.
// SYNTAX
Cellular.isOff();
// EXAMPLE
Cellular.off();
waitFor(Cellular.isOff, 60000);
Attempts to connect to the Cellular network. If there are no credentials entered, the default Particle APN for Particle SIM cards will be used. If no SIM card is inserted, the device will enter listening mode. If a 3rd party APN is set, these credentials must match the inserted SIM card for the device to connect to the cellular network. When this function returns, the device may not have a local (private) IP address; use Cellular.ready()
to determine the connection status.
// SYNTAX
Cellular.connect();
On Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker), prior to Device OS 2.0.0, you needed to call WiFi.on()
or Cellular.on()
before calling Particle.connect()
. This is not necessary on Gen 2 devices (any Device OS version) or with 2.0.0 and later.
Disconnects from the Cellular network, but leaves the Cellular module on.
// SYNTAX
Cellular.disconnect();
This function will return true
once the device is attempting to connect using the default Particle APN or 3rd party APN cellular credentials, and will return false
once the device has successfully connected to the cellular network.
// SYNTAX
Cellular.connecting();
This function will return true
once the device is connected to the cellular network and has been assigned an IP address, which means it has an activated PDP context and is ready to open TCP/UDP sockets. Otherwise it will return false
.
// SYNTAX
Cellular.ready();
Since 5.7.0:
You should normally let automatic connection management handle which network interface to use.
In some cases you may want to prefer cellular, Wi-Fi, or Ethernet, and this can be done using the API. Note however:
Cellular.prefer()
will clear WiFi.prefer()
for example.// PROTOTYPE
virtual NetworkClass& prefer(bool prefer = true);
// EXAMPLE - enable prefer cellular
Cellular.prefer();
// EXAMPLE - disable prefer cellular
Cellular.prefer(false);
Returns true if the preferred network interface is cellular. This only indicates that the interface has been preferred and does not reflect what interface is in use.
// PROTOTYPE
virtual bool isPreferred();
if (Cellular.isPreferred()) {
// Do something here
}
This will enter or exit listening mode, which opens a Serial connection to get Cellular information such as the IMEI or CCID over USB.
// SYNTAX - enter listening mode
Cellular.listen();
Listening mode blocks application code. Advanced cases that use multithreading, interrupts, or system events have the ability to continue to execute application code while in listening mode, and may wish to then exit listening mode, such as after a timeout. Listening mode is stopped using this syntax:
// SYNTAX - exit listening mode
Cellular.listen(false);
// SYNTAX
Cellular.listening();
Returns true if the device is in listening mode (blinking dark blue).
This is only relevant when using SYSTEM_THREAD(ENABLED)
. When not using threading, listening mode stops
user firmware from running, so you would not have an opportunity to test the value and calling this always
returns false. With Device OS 6.2.0 and later, system thread is always enabled.
Since 0.6.1:
// SYNTAX
Cellular.setListenTimeout(seconds);
Cellular.setListenTimeout(seconds)
is used to set a timeout value for Listening Mode. This is rarely needed on cellular devices as cellular devices do not enter listening mode by default.
Since 1.5.0:
You can also specify a value using chrono literals, for example: Cellular.setListenTimeout(5min)
for 5 minutes.
Since 0.6.1:
// SYNTAX
uint16_t seconds = Cellular.getListenTimeout();
Cellular.getListenTimeout()
is used to get the timeout value currently set for Listening Mode. Values are returned in (uint16_t)seconds
, and 0 indicates the timeout is disabled. By default, Cellular devices have a 5 minute timeout set (seconds=300).
The Cellular
object does not have built-in thread-safety. If you want to use things like Cellular.command()
from multiple threads, including from a software timer, you must lock and unlock it to prevent data from multiple thread from being interleaved or corrupted.
A call to lock lock()
must be balanced with a call to unlock()
and not be nested. To make sure every lock is released, it's good practice to use WITH_LOCK
like this:
// EXAMPLE USAGE
void loop()
{
WITH_LOCK(Cellular) {
Cellular.command("AT\r\n");
}
}
Never use lock()
or WITH_LOCK()
within a SINGLE_THREADED_BLOCK()
as deadlock can occur.
Unlocks the Cellular
mutex. See lock()
.
Sets 3rd party SIM credentials for the Cellular network from within the user application.
Only a subset of Particle cellular devices are able to use a plastic 4FF nano SIM card from a 3rd-party carrier. This table lists external SIM capability and includes the Electron and Boron only. There are also limits on the number of devices with 3rd-party SIM cards in an account. For more information, see the 3rd-party SIM guide.
Device | Internal SIM | SIM Card Slot | APN Saved |
---|---|---|---|
Boron 2G/3G (BRN310) | ✓ | ✓ | ✓ |
Boron LTE (BRN402) | ✓ | ✓ | ✓ |
Electron 3G Americas (E260) | ✓ | ||
Electron 3G Europe/Asia/Africa (E270) | ✓ | ||
Electron 2G (E350) | ✓ | ||
Electron LTE (ELC402) | ✓ | n/a |
Gen 3 Devices (nRF52) (Boron):
Gen 3 devices (Boron 2G/3G, Boron LTE) have both an internal (MFF2 SMD) SIM and an external 4FF nano SIM card slot that can be used for a plastic nano SIM card.
On Gen 3 devices, cellular credentials added to the device's non-volatile memory and only need to be set once. The setting will be preserved across reset, power down, and firmware upgrades.
This is different than the Electron and E series where you must call cellular_credentials_set()
from all user firmware.
You may set credentials in 3 different ways:
The following example can be copied to a file called setcreds.ino
and compiled and flashed to your device over USB via the Particle CLI. With your device in DFU mode, the command for this is:
particle compile electron setcreds.ino --saveTo firmware.bin && particle flash --local firmware.bin
SYSTEM_MODE(SEMI_AUTOMATIC);
void setup() {
// Clears any existing credentials. Use this to restore the use of the Particle SIM.
Cellular.clearCredentials();
// You should only use one of the following three commands.
// Only one set of credentials can be stored.
// Connects to a cellular network by APN only
Cellular.setCredentials("broadband");
// Connects to a cellular network with USERNAME and PASSWORD only
Cellular.setCredentials("username", "password");
// Connects to a cellular network with a specified APN, USERNAME and PASSWORD
Cellular.setCredentials("some-apn", "username", "password");
Particle.connect();
}
void loop() {
}
Gen 3 cellular devices only use one set of credentials, and they
must be correctly matched to the SIM card that's used. If using a
Particle SIM, using Cellular.setCredentials()
is not necessary as the
default APN will be used. If you have set a different APN to use a 3rd-party
SIM card, you can restore the use of the Particle SIM by using
Cellular.clearCredentials()
.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Electron 2G, U260, U270, and ELC314, there is only a 4FF nano SIM card slot. There is no internal SIM so you must always have a SIM card in the SIM card holder on the bottom of the device for normal operation.
The Electron LTE (ELC404, and ELC402) and E-Series (E310, E314, E402, E404, and E404X), have a built-in MFF2 SMD SIM. Since you cannot use a 3rd-party SIM card, you do not have to set the APN as the Particle SIM APN is built-in.
On Gen 2 devices, cellular credentials are not added to the device's non-volatile memory and need to be set every time from the user application. You may set credentials in 3 different ways:
Note: When using the default SYSTEM_MODE(AUTOMATIC)
connection behavior, it is necessary to call cellular_credentials_set()
with the STARTUP()
macro outside of setup()
and loop()
so that the system will have the correct credentials before it tries to connect to the cellular network (see EXAMPLE).
The following examples can be copied to a file called setcreds.ino
and compiled and flashed to your device over USB via the Particle CLI. With your device in DFU mode, the command for this is:
particle compile electron setcreds.ino --saveTo firmware.bin && particle flash --local firmware.bin
Note: Your device only uses one set of credentials, and they
must be correctly matched to the SIM card that's used. If using a
Particle SIM, using cellular_credentials_set()
is not necessary as the
default APN of "spark.telefonica.com" with no username or password will
be used by Device OS. To switch back to using a Particle SIM after successfully connecting with a 3rd Party SIM, just flash any app that does not include cellular_credentials_set(). Then disconnect the battery and external power (such as USB) for 20 seconds to remove the settings from the modem’s volatile memory.
// SYNTAX
// Connects to a cellular network by APN only
STARTUP(cellular_credentials_set(APN, "", "", NULL));
// Connects to a cellular network with USERNAME and PASSWORD only
STARTUP(cellular_credentials_set("", USERNAME, PASSWORD, NULL));
// Connects to a cellular network with a specified APN, USERNAME and PASSWORD
#include “cellular_hal.h”
STARTUP(cellular_credentials_set(APN, USERNAME, PASSWORD, NULL));
// EXAMPLE - an AT&T APN with no username or password in AUTOMATIC mode
#include "cellular_hal.h"
STARTUP(cellular_credentials_set("broadband", "", "", NULL));
void setup() {
// your setup code
}
void loop() {
// your loop code
}
Note:
The Boron 2G/3G and Boron LTE can use either the built-in MFF2 embedded Particle SIM card or an external nano SIM card in the SIM card connector. The active SIM card setting is stored in non-volatile memory and only needs to be set once. The setting will be preserved across reset, power down, and firmware upgrades.
All other cellular devices either have only one type of SIM (either MFF2 or 4FF), but not both, and do
not support Cellular.setActiveSim()
.
For Boron LTE modules, a special command needs to be given to the cell radio after setting setActiveSim
. If this command is not given, the device may end up blinking green, and the device does not connect to cloud. Please refer to this support article if you are switching SIM cards with Boron LTE.
SYSTEM_MODE(SEMI_AUTOMATIC);
void setup() {
// Choose one of these:
Cellular.setActiveSim(EXTERNAL_SIM);
Cellular.setActiveSim(INTERNAL_SIM);
}
void loop() {
}
Get the current active SIM (internal or external):
void setup() {
Serial.begin();
}
void loop() {
SimType simType = Cellular.getActiveSim();
Serial.printlnf("simType=%d", simType);
delay(5000);
}
Note:
The data usage APIs are only available on the Electron 2G and 3G and E-Series E310. It is not available on other cellular devices.
A software implementation of Data Usage that pulls sent and received session and total bytes from the cellular modem's internal counters. The sent / received bytes are the gross payload evaluated by the protocol stack, therefore they comprise the TCP and IP header bytes and the packets used to open and close the TCP connection. I.e., these counters account for all overhead in cellular communications.
Note: Cellular.getDataUsage() should only be used for relative measurements on data at runtime. Do not rely upon these figures for absolute and total data usage of your SIM card.
Note: There is a known issue with Sara U260/U270 modem firmware not being able to set/reset the internal counters (which does work on the Sara G350). Because of this limitation, the set/reset function is implemented in software, using the modem's current count as the baseline.
Note: The internal modem counters are typically reset when the modem is power cycled (complete power removal, soft power down or Cellular.off()) or if the PDP context is deactivated and reactivated which can happen asynchronously during runtime. If the Cellular.getDataUsage() API has been read, reset or set, and then the modem's counters are reset for any reason, the next call to Cellular.getDataUsage() for a read will detect that the new reading would be less than the previous reading. When this is detected, the current reading will remain the same, and the now lower modem count will be used as the new baseline. Because of this mechanism, it is generally more accurate to read the getDataUsage() count often. This catches the instances when the modem count is reset, before the count starts to increase again.
Note: LTE Cat M1 devices (SARA-R510S-01B, SARA-R410M-02B), Quectel EG91-E (B-Series SoM B523), Quectel EG-91EX (Tracker SoM T523), and Quectel BG96-MC (TrackerSoM T402) do not support data usage APIs.
To use the data usage API, an instance of the CellularData
type needs to be created to read or set counters. All data usage API functions and the CellularData object itself return bool
- true
indicating the last operation was successful and the CellularData object was updated. For set and get functions, CellularData
is passed by reference Cellular.dataUsage(CellularData&);
and updated by the function. There are 5 integers and 1 boolean within the CellularData object:
false
when the CellularData object is initially created, and true
after the object has been successfully updated by the API. If the last reading failed and the counters were not changed from their previous value, this value is set to false
.CellularData is a Printable object, so using it directly with Serial.println(data);
will be output as follows:
cid,tx_session,rx_session,tx_total,rx_total
31,1000,300,1000,300
// SYNTAX
// Read Data Usage
CellularData data;
Cellular.getDataUsage(data);
// EXAMPLE
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available() > 0)
{
char c = Serial.read();
if (c == '1') {
Serial.println("Read counters of sent or received PSD data!");
CellularData data;
if (!Cellular.getDataUsage(data)) {
Serial.print("Error! Not able to get data.");
}
else {
Serial.printlnf("CID: %d SESSION TX: %d RX: %d TOTAL TX: %d RX: %d",
data.cid,
data.tx_session, data.rx_session,
data.tx_total, data.rx_total);
Serial.println(data); // Printable
}
}
else if (c == '2') {
Serial.println("Set all sent/received PSD data counters to 1000!");
CellularData data;
data.tx_session = 1000;
data.rx_session = 1000;
data.tx_total = 1000;
data.rx_total = 1000;
if (!Cellular.setDataUsage(data)) {
Serial.print("Error! Not able to set data.");
}
else {
Serial.printlnf("CID: %d SESSION TX: %d RX: %d TOTAL TX: %d RX: %d",
data.cid,
data.tx_session, data.rx_session,
data.tx_total, data.rx_total);
Serial.println(data); // Printable
}
}
else if (c == '3') {
Serial.println("Reset counter of sent/received PSD data!");
if (!Cellular.resetDataUsage()) {
Serial.print("Error! Not able to reset data.");
}
}
else if (c == 'p') {
Serial.println("Publish some data!");
Particle.publish("1","a");
}
while (Serial.available()) Serial.read(); // Flush the input buffer
}
}
Sets the Data Usage counters to the values indicated in the supplied CellularData object.
Returns bool
- true
indicating this operation was successful and the CellularData object was updated.
// SYNTAX
// Set Data Usage
CellularData data;
Cellular.setDataUsage(data);
Resets the Data Usage counters to all zero. No CellularData object is required. This is handy to call just before an operation where you'd like to measure data usage.
Returns: bool
true
indicates this operation was successful and the internally stored software offset has been reset to zero. If getDataUsage() was called immediately after without any data being used, the CellularData object would indicate zero data used.// SYNTAX
// Reset Data Usage
Cellular.resetDataUsage();
Cellular.RSSI()
returns an instance of CellularSignal
class that allows you to determine signal strength (RSSI) and signal quality of the currently connected Cellular network.
This function queries the cellular modem for the RSSI. This is normally quick, and does not use cellular data.
However, it is possible that the call will still block for an indeterminate amount of time, possibly for as long as 10 minutes. This can occur if the system thread is busy trying to reconnect to cellular and is unable to do so. Doing operations that access the cellular modem or require access to the system thread from a separate worker thread is a good workaround.
This class allows to query a number of signal parameters of the currently connected Cellular network.
// SYNTAX
CellularSignal sig = Cellular.RSSI();
int rat = sig.getAccessTechnology();
Gets the current radio access technology (RAT) in use.
The following radio technologies are defined:
NET_ACCESS_TECHNOLOGY_GSM
: 2G RATNET_ACCESS_TECHNOLOGY_EDGE
: 2G RAT with EDGENET_ACCESS_TECHNOLOGY_UMTS
/NET_ACCESS_TECHNOLOGY_UTRAN
/NET_ACCESS_TECHNOLOGY_WCDMA
: UMTS RATNET_ACCESS_TECHNOLOGY_LTE
: LTE RATNET_ACCESS_TECHNOLOGY_LTE_CAT_M1
: LTE Cat M1 RATGets the signal strength as a percentage (0.0 - 100.0). See getStrengthValue()
on how raw RAT-specific strength values are mapped to 0%-100% range.
// SYNTAX
CellularSignal sig = Cellular.RSSI();
float strength = sig.getStrength();
// EXAMPLE
CellularSignal sig = Cellular.RSSI();
Log.info("Cellular signal strength: %.02f%%", sig.getStrength());
Returns: float
Gets the signal quality as a percentage (0.0 - 100.0). See getQualityValue()
on how raw RAT-specific quality values are mapped to 0%-100% range.
// SYNTAX
CellularSignal sig = Cellular.RSSI();
float quality = sig.getQuality();
// EXAMPLE
CellularSignal sig = Cellular.RSSI();
Log.info("Cellular signal quality: %.02f%%", sig.getQuality());
Returns: float
Note: qual
is not supported on 2G Electrons (Model G350) and will return 0.
LTE Cat M1 devices (SARA-R410M-02B modem) only return qual with Device OS 1.5.0 and later. Earlier versions returned 0.
// SYNTAX
CellularSignal sig = Cellular.RSSI();
float strength = sig.getStrengthValue();
Gets the raw signal strength value. This value is RAT-specific. See getAccessTechnology()
for a list of radio access technologies.
Returns: float
// SYNTAX
CellularSignal sig = Cellular.RSSI();
float quality = sig.getQualityValue();
Gets the raw signal quality value. This value is RAT-specific. See getAccessTechnology()
for a list of radio access technologies.
Returns: float
Note: qual
is not supported on 2G Electrons (Model G350) and will return 0.
Before 0.8.0:
Before Device OS 0.8.0, the CellularSignal
class only had two member variables, rssi
, and qual
. These are removed in Device OS 3.0.0 and later and you should use getStrengthValue()
and getQualityValue()
instead.
// Prior to 0.8.0:
CellularSignal sig = Cellular.RSSI();
Serial.println(sig.rssi);
Serial.println(sig.qual);
Since 0.5.0:
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Band available and band select APIs are only available on Gen 2 (Electron and E-Series), and only for 2G/3G, not LTE Cat M1.
Gets the cellular bands currently available in the modem. Bands
are the carrier frequencies used to communicate with the cellular network. Some modems have 2 bands available (U260/U270) and others have 4 bands (G350).
To use the band select API, an instance of the CellularBand
type needs to be created to read or set bands. All band select API functions and the CellularBand object itself return bool
- true
indicating the last operation was successful and the CellularBand object was updated. For set and get functions, CellularBand
is passed by reference Cellular.getBandSelect(CellularBand&);
and updated by the function. There is 1 array, 1 integer, 1 boolean and 1 helper function within the CellularBand object:
false
when the CellularBand object is initially created, and true
after the object has been successfully updated by the API. If the last reading failed and the bands were not changed from their previous value, this value is set to false
.BAND_DEFAULT, BAND_0, BAND_700, BAND_800, BAND_850, BAND_900, BAND_1500, BAND_1700, BAND_1800, BAND_1900, BAND_2100, BAND_2600
. All elements set to 0 when CellularBand object is first created, but after getBandSelect() is called successfully the currently selected bands will be populated started with index 0, i.e., (.band[0]
). Can be 5 values when getBandAvailable() is called on a G350 modem, as it will return factory default value of 0 as an available option, i.e., 0,850,900,1800,1900
.CellularBand is a Printable object, so using it directly with Serial.println(CellularBand);
will print the number of bands that are retrieved from the modem. This will be output as follows:
// EXAMPLE PRINTABLE
CellularBand band_sel;
// populate band object with fake data
band_sel.band[0] = BAND_850;
band_sel.band[1] = BAND_1900;
band_sel.count = 2;
Log.info(band_sel);
// OUTPUT: band[0],band[1]
850,1900
Here's a full example using all of the functions in the Cellular Band Select API
There is one supported function for getting available bands using the CellularBand object:
bool Cellular.getBandAvailable(CellularBand &band_avail);
Note: getBandAvailable() always sets the first band array element .band[0]
as 0 which indicates the first option available is to set the bands back to factory defaults, which includes all bands.
// SYNTAX
CellularBand band_avail;
Cellular.getBandAvailable(band_avail);
// EXAMPLE
CellularBand band_avail;
if (Cellular.getBandSelect(band_avail)) {
Serial.print("Available bands: ");
for (int x=0; x<band_avail.count; x++) {
Serial.printf("%d", band_avail.band[x]);
if (x+1 < band_avail.count) Serial.printf(",");
}
Serial.println();
}
else {
Serial.printlnf("Bands available not retrieved from the modem!");
}
This call returns information about the connected cell tower, including the MCC, MNC, LAC, and Cell Identifier.
The device must be connected to a tower (breathing green or cyan) for this call to be used.
// PROTOTYPES
cellular_result_t cellular_global_identity(CellularGlobalIdentity* cgi, void* reserved = NULL);
typedef int cellular_result_t;
// EXAMPLE
CellularGlobalIdentity cgi = {0};
cgi.size = sizeof(CellularGlobalIdentity);
cgi.version = CGI_VERSION_LATEST;
cellular_result_t res = cellular_global_identity(&cgi, NULL);
if (res == SYSTEM_ERROR_NONE) {
Variant tower;
tower.set("mcc", cgi.mobile_country_code);
tower.set("mnc", cgi.mobile_network_code);
tower.set("cid", cgi.cell_id);
tower.set("lac", cgi.location_area_code);
// Do something with tower here, like add to a ledger
}
Result | Description |
---|---|
SYSTEM_ERROR_NONE |
Success (0) |
SYSTEM_ERROR_AT_NOT_OK |
Cellular modem returned an error |
SYSTEM_ERROR_AT_RESPONSE_UNEXPECTED |
Cellular modem returned unexpected data |
SYSTEM_ERROR_BAD_DATA |
Cellular modem returned unexpected data |
SYSTER_ERROR_INVALID_ARGUMENT |
Invalid CellularGlobalIdentity structure |
SYSTEM_ERROR_INVALID_STATE |
The device is not connected to cellular |
SYSTER_ERROR_UNKNOWN |
Unknown error |
Type | Field | Description |
---|---|---|
uint16_t |
size; |
Initialize to sizeof(CellularGlobalIdentity) |
uint16_t |
version |
Initialize to CGI_VERSION_LATEST |
uint8_t |
cgi_flags |
Filled in with flag bits, see list below |
uint16_t |
mobile_country_code |
Mobile Country Code (MCC) |
uint16_t |
mobile_network_code |
Mobile Network Code (MNC) |
uint16_t |
location_area_code |
Location Area Code (LAC) |
uint32_t |
cell_id |
Cell Identifier |
// Type definitions
typedef enum __attribute__((__packed__)) CgiVersion
{
CGI_VERSION_1,
CGI_VERSION_LATEST = CGI_VERSION_1,
} CgiVersion;
typedef enum __attribute__((__packed__)) CgiFlags
{
CGI_FLAG_TWO_DIGIT_MNC = 0b00000001, /*!< Indicates two-digit format of Mobile Network Code */
} CgiFlags;
typedef struct __attribute__((__packed__))
{
uint16_t size; /*!< \c size is specified by the user application, to inform the device-os of the
amount of space allocated to the structure */
uint16_t version; /*!< \c version is specified by the user application, to inform the
device-os of the version of the information to be returned */
uint8_t reserved; /*!< \c reserved is allocated for future usage */
uint8_t cgi_flags; /*!< \c cgi_flags Indicates the structure/configuration of the values in
CellularGlobalIdentity */
uint16_t mobile_country_code; /*!< \c mobile_country_code consists of three decimal digits and
identifies uniquely the country of domicile of the mobile
subscription */
uint16_t mobile_network_code; /*!< \c mobile_network_code consists of three decimal digits and
identifies the home PLMN of the mobile subscription */
uint16_t location_area_code; /*!< \c location_area_code is a fixed length code (of 2 octets)
identifying a location area within a PLMN */
uint32_t cell_id; /*!< \c cell_id is of fixed length with 4 octets and it can be coded using a
full hexadecimal representation */
} CellularGlobalIdentity;
// Example initialization
CellularGlobalIdentity cgi = {0};
cgi.size = sizeof(CellularGlobalIdentity);
cgi.version = CGI_VERSION_LATEST;
Since 0.5.0:
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Band available and band select APIs are only available on Gen 2 (Electron and E-Series), and only for 2G/3G, not LTE Cat M1.
Gets the cellular bands currently set in the modem. Bands
are the carrier frequencies used to communicate with the cellular network.
There is one supported function for getting selected bands using the CellularBand object:
bool Cellular.getBandSelect(CellularBand &band_sel);
// SYNTAX
CellularBand band_sel;
Cellular.getBandSelect(band_sel);
// EXAMPLE
CellularBand band_sel;
if (Cellular.getBandSelect(band_sel)) {
Serial.print("Selected bands: ");
for (int x=0; x<band_sel.count; x++) {
Serial.printf("%d", band_sel.band[x]);
if (x+1 < band_sel.count) Serial.printf(",");
}
Serial.println();
}
else {
Serial.printlnf("Bands selected not retrieved from the modem!");
}
Since 0.5.0:
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Band available and band select APIs are only available on Gen 2 (Electron and E-Series), and only for 2G/3G, not LTE Cat M1.
Sets the cellular bands currently set in the modem. Bands
are the carrier frequencies used to communicate with the cellular network.
Caution: The Band Select API is an advanced feature designed to give users selective frequency control over their device. When changing location or between cell towers, you may experience connectivity issues if you have only set one specific frequency for use. Because these settings are permanently saved in non-volatile memory, it is recommended to keep the factory default value of including all frequencies with mobile applications. Only use the selective frequency control for stationary applications, or for special use cases.
count
to the appropriate number of bands set in the CellularBand object before calling setBandSelect()
..isBand(int)
helper function to determine if an integer value is a valid band. It still may not be valid for the particular modem you are using, in which case setBandSelect()
will return false
and .ok
will also be set to false
..band[0]
to BAND_0
or BAND_DEFAULT
and .count
to 1 will restore factory defaults.There are two supported functions for setting bands, one uses the CellularBand object, and the second allow you to pass in a comma delimited string of bands:
bool Cellular.setBandSelect(const char* band_set);
bool Cellular.setBandSelect(CellularBand &band_set);
// SYNTAX
CellularBand band_set;
Cellular.setBandSelect(band_set);
// or
Cellular.setBandSelect("850,1900"); // set two bands
Cellular.setBandSelect("0"); // factory defaults
// EXAMPLE
Serial.println("Setting bands to 850 only");
CellularBand band_set;
band_set.band[0] = BAND_850;
band_set.band[1] = BAND_1900;
band_set.count = 2;
if (Cellular.setBandSelect(band_set)) {
Serial.print(band_set);
Serial.println(" band(s) set! Value will be saved in NVM when powering off modem.");
}
else {
Serial.print(band_set);
Serial.println(" band(s) not valid! Use getBandAvailable() to query for valid bands.");
}
// EXAMPLE
Serial.println("Restoring factory defaults with the CellularBand object");
CellularBand band_set;
band_set.band[0] = BAND_DEFAULT;
band_set.count = 1;
if (Cellular.setBandSelect(band_set)) {
Serial.println("Factory defaults set! Value will be saved in NVM when powering off modem.");
}
else {
Serial.println("Restoring factory defaults failed!");
}
// EXAMPLE
Serial.println("Restoring factory defaults with strings");
if (Cellular.setBandSelect("0")) {
Serial.println("Factory defaults set! Value will be saved in NVM when powering off modem.");
}
else {
Serial.println("Restoring factory defaults failed!");
}
Since 0.6.1:
Cellular.resolve()
finds the IP address for a domain name.
// SYNTAX
IPAddress ip = Cellular.resolve(name);
Parameters:
name
: the domain name to resolve (string)It returns the IP address if the domain name is found, otherwise a blank IP address.
// EXAMPLE USAGE
IPAddress ip;
void setup() {
ip = Cellular.resolve("www.google.com");
if(ip) {
// IP address was resolved
} else {
// name resolution failed
}
}
Since 0.5.0:
Cellular.localIP()
returns the local (private) IP address assigned to the device as an IPAddress
.
// EXAMPLE
SerialLogHandler logHandler;
void setup() {
// Prints out the local (private) IP over Serial
Log.info("localIP: %s", Cellular.localIP().toString().c_str());
}
Cellular.command()
is a powerful API that allows users access to directly send AT commands to, and parse responses returned from, the Cellular module. Commands may be sent with and without printf style formatting. The API also includes the ability pass a callback function pointer which is used to parse the response returned from the cellular module.
Since 0.9.0: On the Boron, Cellular.command requires Device OS 0.9.0 or later.
Note: Obviously for this command to work the cellular module needs to be switched on, which is not automatically the case in SYSTEM_MODE(MANUAL)
or SYSTEM_MODE(SEMI_AUTOMATIC)
. This can be achieved explicitly via Cellular.on()
or implicitly by calling Cellular.connect()
or Particle.connect()
.
References:
The prototype definition is as follows:
int Cellular.command(_CALLBACKPTR_MDM cb, void* param, system_tick_t timeout, const char* format, ...);
Cellular.command()
takes one or more arguments in 4 basic types of signatures.
// SYNTAX (4 basic signatures with/without printf style formatting)
int ret = Cellular.command(cb, param, timeout, format, ...);
int ret = Cellular.command(cb, param, timeout, format);
int ret = Cellular.command(cb, param, format, ...);
int ret = Cellular.command(cb, param, format);
int ret = Cellular.command(timeout, format, ...);
int ret = Cellular.command(timeout, format);
int ret = Cellular.command(format, ...)
int ret = Cellular.command(format);
cb
: callback function pointer to a user specified function that parses the results of the AT command response.param
: (void*
) a pointer to the variable that will be updated by the callback function.timeout
: (system_tick_t
) the timeout value specified in milliseconds (default is 10000 milliseconds).format
: (const char*
) contains your AT command and any desired format specifiers, followed by \r\n
....
: additional arguments optionally required as input to their respective format
string format specifiers.Cellular.command()
returns an int
with one of the following 6 enumerated AT command responses:
NOT_FOUND
= 0WAIT
= -1 // TIMEOUTRESP_OK
= -2RESP_ERROR
= -3RESP_PROMPT
= -4RESP_ABORTED
= -5// EXAMPLE - Get the ICCID number of the inserted SIM card
SerialLogHandler logHandler;
int callbackICCID(int type, const char* buf, int len, char* iccid)
{
if ((type == TYPE_PLUS) && iccid) {
if (sscanf(buf, "\r\n+CCID: %[^\r]\r\n", iccid) == 1)
/*nothing*/;
}
return WAIT;
}
void setup()
{
char iccid[32] = "";
if ((RESP_OK == Cellular.command(callbackICCID, iccid, 10000, "AT+CCID\r\n"))
&& (strcmp(iccid,"") != 0))
{
Log.info("SIM ICCID = %s\r\n", iccid);
}
else
{
Log.info("SIM ICCID NOT FOUND!");
}
}
void loop()
{
// your loop code
}
The cb
function prototype is defined as:
int callback(int type, const char* buf, int len, void* param);
The four Cellular.command() callback arguments are defined as follows:
type
: (int
) one of 13 different enumerated AT command response types.buf
: (const char*
) a pointer to the character array containing the AT command response.len
: (int
) length of the AT command response buf
.param
: (void*
) a pointer to the variable or structure being updated by the callback function.int
- user specified callback return value, default is return WAIT;
which keeps the system invoking the callback again as more of the AT command response is received. Optionally any one of the 6 enumerated AT command responses previously described can be used as a return type which will force the Cellular.command() to return the same value. AT commands typically end with an OK
response, so even after a response is parsed, it is recommended to wait for the final OK
response to be parsed and returned by the system. Then after testing if(Cellular.command() == RESP_OK)
and any other optional qualifiers, should you act upon the results contained in the variable/structure passed into the callback.There are 13 different enumerated AT command responses passed by the system into the Cellular.command() callback as type
. These are used to help qualify which type of response has already been parsed by the system.
TYPE_UNKNOWN
= 0x000000TYPE_OK
= 0x110000TYPE_ERROR
= 0x120000TYPE_RING
= 0x210000TYPE_CONNECT
= 0x220000TYPE_NOCARRIER
= 0x230000TYPE_NODIALTONE
= 0x240000TYPE_BUSY
= 0x250000TYPE_NOANSWER
= 0x260000TYPE_PROMPT
= 0x300000TYPE_PLUS
= 0x400000TYPE_TEXT
= 0x500000TYPE_ABORTED
= 0x600000It is possible that the call will block for an indeterminate amount of time, possibly for as long as 10 minutes. This can occur if the system thread is busy trying to reconnect to cellular and is unable to do so. Doing operations that access the cellular modem or require access to the system thread from a separate worker thread is a good workaround.
The Photon 2 does not have a fuel gauge chip, however you can determine the voltage of the LiPo battery, if present. The P2 does not include a LiPo battery connector, but if you connect your battery to VBAT_MEAS
, this technique also works with the P2.
float voltage = analogRead(A6) / 819.2;
The constant is from the ADC range (0 - 4095) mapped to the voltage from 0 - 5 VDC (the maximum supported on VBAT_MEAS).
There is a CHG
pin defined on the P2, however it does not work properly.
Show older information about CHG
The Argon device does not have a fuel gauge chip, however you can determine the voltage of the LiPo battery, if present.
float voltage = analogRead(BATT) * 0.0011224;
The constant 0.0011224 is based on the voltage divider circuit (R1 = 806K, R2 = 2M) that lowers the 3.6V LiPo battery output to a value that can be read by the ADC.
The charge indicator on the Argon can be read using:
bool charging = !digitalRead(CHG);
In other words, the CHG
input is 0 when charging and 1 when not charging, and the !
inverts this logic to make it easier to use.
On the Argon, the PWR
input is 1 when there is 5V power on the Micro USB connector or VUSB pin. You can use digitalRead(PWR)
to find the value.
On cellular devices with a PMIC, you should use System.powerSource()
instead.
Note:
This technique applies only to the Argon and Photon 2. For the Boron, Electron, and E-Series, see the FuelGauge, below.
The Photon and P1 don't have built-in support for a battery.
The on-board Fuel Gauge allows you to monitor the battery voltage, state of charge and set low voltage battery thresholds. Use an instance of the FuelGauge
library to call the various fuel gauge functions.
// EXAMPLE
FuelGauge fuel;
Note:
FuelGauge is available on all devices with a battery state-of-charge sensor, including the Boron, B-Series SoM, Tracker SoM (Gen 3) as well as the Electron and E-Series (Gen 2).
The Photon 2, P2, Argon, Photon, and P1 do not have FuelGauge support.
// PROTOTYPE
float getVCell();
// EXAMPLE
FuelGauge fuel;
Log.info( "voltage=%.2f", fuel.getVCell() );
Returns the battery voltage as a float
. Returns -1.0 if the fuel gauge cannot be read.
// PROTOTYPE
float getSoC();
// EXAMPLE
FuelGauge fuel;
Log.info( "SoC=%.2f", fuel.getSoC() );
Returns the State of Charge in percentage from 0-100% as a float
. Returns -1.0 if the fuel gauge cannot be read.
Note that in most cases, "fully charged" state (red charging LED goes off) will result in a SoC of 80%, not 100%. Using getNormalizedSoC()
normalizes the value based on the charge voltage so the SoC will be 100% when the charge LED goes off.
In some cases you can increase the charge voltage to get a higher SoC, but there are limits, based on temperature.
Since 1.5.0:
It may be preferable to use System.batteryCharge()
instead of using getSoC()
, which uses the value in device diagnostics, which eventually uses getNormalizedSoC()
.
// PROTOTYPE
float getNormalizedSoC()
Returns the State of Charge in percentage from 0-100% as a float
, normalized based on the charge voltage. Returns -1.0 if the fuel gauge cannot be read.
It may be easier to use System.batteryCharge()
instead of using getNormalizedSoC()
directly. System.batteryCharge()
uses the value in device diagnostics, which eventually uses getNormalizedSoC()
.
int getVersion();
byte getCompensateValue();
byte getAlertThreshold();
void setAlertThreshold(byte threshold);
boolean getAlert();
void clearAlert();
void reset();
void quickStart();
void sleep();
void wakeup();
Additional information on which pins can be used for which functions is available on the pin information page.
Gen 3 Devices (nRF52) (Tracker SoM, Tracker One):
The Tracker SoM has shared A and D pins. In other words, pin A0 is the same physical pin as pin D0, and is also the SDA pin. The alternate naming is to simplify porting code from other device types.
Pin | M8 Pin | Function | Function | Analog In | GPIO |
---|---|---|---|---|---|
A0 / D0 | Wire SDA | ✓ | ✓ | ||
A1 / D1 | Wire SCL | ✓ | ✓ | ||
A2 / D2 | Serial1 CTS | ✓ | ✓ | ||
A3 / D3 | 3 | Serial1 RTS | ✓ | ✓ | |
A4 / D4 | SPI MOSI | ✓ | ✓ | ||
A5 / D5 | SPI MISO | ✓ | ✓ | ||
A6 / D6 | SPI SCK | ✓ | ✓ | ||
A7 / D7 | SPI SS | WKP | ✓ | ✓ | |
TX / D8 | 5 | Serial1 TX | Wire3 SCL | ✓ | |
RX / D9 | 4 | Serial1 RX | Wire3 SDA | ✓ |
On the Tracker One and Tracker Carrier Board you must enable CAN_5V in order to use GPIO on M8 pins 3, 4, and 5 (A3, D8/TX/SCL, D9/RX/SDA). If CAN_5V is not powered, these pins are isolated from the MCU starting with version 1.1 of the Tracker One/Tracker Carrier Board (September 2020 and later). This is necessary to prevent an issue with shipping mode, see technical advisory note TAN002.
P2 and Photon 2 Devices (RTL872x):
On the P2 and Photon 2 in addition to the standard analog inputs A0, A1, A2, and A5, there are two shared pins. If you are not using pins D0 and D1 for I2C or digital GPIO, they can be used as ADC inputs:
Pin | M8 Pin | Function | Function | Analog In | GPIO |
---|---|---|---|---|---|
D0 / A3 | Wire SDA | ✓ | ✓ | ||
D1 / A4 | Wire SCL | ✓ | ✓ |
pinMode()
configures the specified pin to behave either as an input (with or without an internal weak pull-up or pull-down resistor), or an output.
// PROTOTYPE
void pinMode(uint16_t pin, PinMode mode)
// EXAMPLE USAGE
int button = D0; // button is connected to D0
int LED = D1; // LED is connected to D1
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
pinMode(button, INPUT_PULLDOWN); // sets pin as input
}
void loop()
{
// blink the LED as long as the button is pressed
while(digitalRead(button) == HIGH) {
digitalWrite(LED, HIGH); // sets the LED on
delay(200); // waits for 200mS
digitalWrite(LED, LOW); // sets the LED off
delay(200); // waits for 200mS
}
}
pinMode()
takes two arguments:
pin
: the pin you want to set the mode of (A0, A1, D0, D1, TX, RX, etc.). The type pin_t
can be used instead of uint16_t
to make it more obvious that the code accepts a pin number in your code.
mode
: the mode to set to pin to:
INPUT
digital input (the default at power-up)INPUT_PULLUP
digital input with a pull-up resistor to 3V3INPUT_PULLDOWN
digital input with a pull-down to GNDOUTPUT
an output (push-pull)OUTPUT_OPEN_DRAIN
an open-drain or open-collector output. HIGH (1) leaves the output in high impedance state, LOW (0) pulls the output low. Typically used with an external pull-up
resistor to allow any of multiple devices to set the value low safely.You do not need to set the pinMode()
to read an analog value using analogRead
as the pin will automatically be set to the correct mode when analogRead is called.
When porting code from Arudino, pin numbers are numbered (0, 1, 2, ...) in Arduino code. Pin D0 has a value of 0, but it's best to use Particle pin names like D0 instead of just 0. This is especially true as the numeric value of A0 varies depending on the device and how many digital pins it has. For example, on the Argon, A0 is 19 but on the Photon it's 10.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
Make sure the signal does not exceed 3.3V. Gen 3 devices (Tracker SoM as well as Argon, Boron, Xenon, and the B-Series SoM) are not 5V tolerant in any mode (digital or analog).
INPUT_PULLUP
and INPUT_PULLDOWN
are approximately 13K on Gen 3 devices.
If you are using the Particle Ethernet FeatherWing you cannot use the pins for GPIO as they are used for the Ethernet interface:
Argon, Boron, Xenon | B-Series SoM | Ethernet FeatherWing Pin |
---|---|---|
MISO | MISO | SPI MISO |
MOSI | MOSI | SPI MOSI |
SCK | SCK | SPI SCK |
D3 | A7 | nRESET |
D4 | D22 | nINTERRUPT |
D5 | D8 | nCHIP SELECT |
When using the FeatherWing Gen 3 devices (Argon, Boron, Xenon), pins D3, D4, and D5 are reserved for Ethernet control pins (reset, interrupt, and chip select).
When using Ethernet with the Boron SoM, pins A7, D22, and D8 are reserved for the Ethernet control pins (reset, interrupt, and chip select).
By default, on the B-Series SoM, the Tinker application firmware enables the use of the bq24195 PMIC and MAX17043 fuel gauge. This in turn uses I2C (D0 and D1) and pin A6 (PM_INT). If you are not using the PMIC and fuel gauge and with to use these pins for other purposes, be sure to disable system power configuration. This setting is persistent, so you may want to disable it with your manufacturing firmware only.
void disable()
{
SystemPowerConfiguration conf;
conf.feature(SystemPowerFeature::DISABLE);
System.setPowerConfiguration(conf);
}
STARTUP(disable());
B-Series SoM | Power Manager Usage |
---|---|
D0 | I2C SDA |
D1 | I2C SCL |
A6 | PM_INT (power manager interrupt) |
P2 and Photon 2 Devices (RTL872x):
Make sure the signal does not exceed 3.3V. The P2 and Photon 2 are not 5V tolerant in any mode (digital or analog).
INPUT_PULLUP
and INPUT_PULLDOWN
vary by pin and can be approximately 2.1K, 22K, or 42K depending on the pin.
On the P2, pins S4, S5, S6 do not support pull-up or pull-down in HIBERNATE sleep mode. Use an external pull resistor if this is required.
On the P2, make sure you do not hold down pins D8 or D7 at boot as this will cause the MCU to enter download or test mode and will not operate correctly.
On the P2, Pin RGBR (PA[30]) has a 10K hardware pull-up in the module because it's a trap pin that controls the behavior of the internal 1.1V regulator. This does not affect the RGB LED but could affect your design if you are repurposing this pin as GPIO. You must not hold this pin low at boot.
If you are using the Particle Ethernet FeatherWing you cannot use the pins for GPIO as they are used for the Ethernet interface:
Photon 2, P2 | Ethernet FeatherWing Pin |
---|---|
MISO | SPI MISO |
MOSI | SPI MOSI |
SCK | SPI SCK |
D3 | nRESET |
D4 | nINTERRUPT |
D5 | nCHIP SELECT |
When using the Ethernet FeatherWing with the Photon 2, pins D3, D4, and D5 are reserved for Ethernet control pins (reset, interrupt, and chip select).
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
INPUT_PULLUP
or INPUT_PULLDOWN
make sure a high level signal does not exceed 3.3V.INPUT_PULLUP
does not work as expected on TX on the P1, Electron, and E-Series and should not be used. INPUT_PULLDOWN
does not work as expected on D0 and D1 on the P1 because the P1 module has hardware pull-up resistors on these pins. INPUT_PULLUP
and INPUT_PULLDOWN
are approximately 40K on Gen 2 devicesOn Gen 2 devices (Photon, P1, Electron, and E-Series), GPIO pins are 5V tolerant if all of these conditions are met:
Also beware when using pins D3, D5, D6, and D7 as OUTPUT controlling external devices on Gen 2 devices. After reset, these pins will be briefly taken over for JTAG/SWD, before being restored to the default high-impedance INPUT state during boot.
The brief change in state (especially when connected to a MOSFET that can be triggered by the pull-up or pull-down) may cause issues when using these pins in certain circuits. You can see this with the D7 blue LED which will blink dimly and briefly at boot.
Retrieves the current pin mode.
// PROTOTYPE
PinMode getPinMode(uint16_t pin)
// EXAMPLE
if (getPinMode(D0)==INPUT) {
// D0 is an input pin
}
Write a HIGH
or a LOW
value to a GPIO pin.
// PROTOTYPE
void digitalWrite(uint16_t pin, uint8_t value)
If the pin has been configured as an OUTPUT
with pinMode()
or if previously used with analogWrite()
, its voltage will be set to the corresponding value: 3.3V for HIGH, 0V (ground) for LOW.
digitalWrite()
takes two arguments, pin
: the number of the pin whose value you wish to set and value
: HIGH
or LOW
.
digitalWrite()
does not return anything.
// EXAMPLE USAGE
int LED = D1; // LED connected to D1
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
}
void loop()
{
digitalWrite(LED, HIGH); // sets the LED on
delay(200); // waits for 200mS
digitalWrite(LED, LOW); // sets the LED off
delay(200); // waits for 200mS
}
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
For all Feather Gen 3 devices (Argon, Boron, Xenon) all GPIO pins (A0
..A5
, D0
..D13
) can be used for digital output as long they are not used otherwise (e.g. as Serial1
RX
/TX
).
For the B-Series SoM all GPIO pins (A0
..A7
, D0
..D13
, D22
, D23
) can be used for digital output as long they are not used otherwise (e.g. as Serial1
RX
/TX
).
For the Tracker SoM all GPIO pins (A0
..A7
, D0
..D9
) can be used for digital output as long they are not used otherwise (e.g. as Serial1
RX
/TX
). Note that on the Tracker SoM pins A0 - A7 and the same physical pins as D0 - D7 and are just alternate names for the same pins.
The default drive strength on Gen 3 devices is 2 mA per pin. This can be changed to 9 mA using pinSetDriveStrength()
.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
All GPIO pins (A0
, A1
, A2
, A5
, D0
..D10
, MOSI
, MISO
, SCK
, RX
, TX
) can be used as long they are not used otherwise (e.g. as Serial1
RX
/TX
).
Pins D0
and D1
can be used as analog inputs on the P2 (A3
and A4
) if I2C is not being used.
The drive strength on the M-SoM, P2, and Photon 2 is 4 mA per pin in normal drive and 12 mA per pin in high drive mode. Drive strength selection is only available in Device OS 5.5.0 and later. There is a maximum of 200 mA across all pins. On the P2, the total maximum could be further limited by your 3.3V regulator.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
All GPIO pins (A0
..A7
, D0
..D7
, DAC
, WKP
, RX
, TX
) can be used as long they are not used otherwise (e.g. as Serial1
RX
/TX
). For the Electron and Series B0
..B5
, C0
..C5
can be used as well.
The drive current on Gen 2 devices is 25 mA per pin, with a maximum of 125 mA across all GPIO.
Reads the value from a specified digital pin
, either HIGH
or LOW
.
// PROTOTYPE
int32_t digitalRead(uint16_t pin)
digitalRead()
takes one argument, pin
: the number of the digital pin you want to read.
digitalRead()
returns HIGH
or LOW
.
// EXAMPLE USAGE
int button = D0; // button is connected to D0
int LED = D1; // LED is connected to D1
int val = 0; // variable to store the read value
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
pinMode(button, INPUT_PULLDOWN); // sets pin as input
}
void loop()
{
val = digitalRead(button); // read the input pin
digitalWrite(LED, val); // sets the LED to the button's value
}
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
For all Feather Gen 3 devices (Argon, Boron, Xenon) all GPIO pins (A0
..A5
, D0
..D13
) can be used for digital input as long they are not used otherwise (e.g. as Serial1
RX
/TX
).
For the Boron SoM all GPIO pins (A0
..A7
, D0
..D13
, D22
, D23
) can be used for digital input as long they are not used otherwise (e.g. as Serial1
RX
/TX
).
For the Tracker SoM all GPIO pins (A0
..A7
, D0
..D9
) can be used for digital input as long they are not used otherwise (e.g. as Serial1
RX
/TX
). Note that on the Tracker SoM pins A0 - A7 and the same physical pins as D0 - D7 and are just alternate names for the same pins.
GPIO are not 5V tolerant on Gen 3 devices. Be sure the input voltage does not exceed 3.3V (typical) or 3.6V (absolute maximum).
P2 and Photon 2 Devices (RTL872x):
All GPIO pins (A0
, A1
, A2
, A5
, D0
..D10
, MOSI
, MISO
, SCK
, RX
, TX
) can be used as long they are not used otherwise (e.g. as Serial1
RX
/TX
).
GPIO are not 5V tolerant on the P2 and Photon 2. Be sure the input voltage does not exceed 3.3V (typical).
For the P2 and Photon pin, if you are using an external pull-down resistor, the resistance m be greater than or equal to 1K. It is typically larger than that, but it cannot be smaller.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
A0
..A7
, D0
..D7
, DAC
, WKP
, RX
, TX
) can be used as long they are not used otherwise (e.g. as Serial1
RX
/TX
). On the Electron and E-Series, there are additional GPIO B0
..B5
, C0
..C5
as well.INPUT_PULLUP
or INPUT_PULLDOWN
with 5V inputs.INPUT_PULLUP
or INPUT_PULLDOWN
with 5V inputs.
Since 2.0.0:
// PROTOTYPE
int pinSetDriveStrength(pin_t pin, DriveStrength drive);
Sets the pin drive strength on Gen 3 devices with Device OS 2.0.0 and later, or P2/Photon 2 (RTL872x) with Device OS 5.5.0 and later.
DriveStrength
is one of:
DriveStrength::DEFAULT
(STANDARD
)DriveStrength::STANDARD
DriveStrength::HIGH
Returns SYSTEM_ERROR_NONE
(0) on success, or a non-zero system error code on error.
The drive strength is typically 2 mA in standard drive mode (the default), and 9 mA in high drive mode.
Parameter | Symbol | Conditions | Min | Typ | Max | Unit |
---|---|---|---|---|---|---|
Current at GND+0.4 V, output set low, high drive | IOL,HDL | V3V3 >= 2.7V | 6 | 10 | 15 | mA |
Current at V3V3-0.4 V, output set high, high drive | IOH,HDH | V3V3 >= 2.7V | 6 | 9 | 14 | mA |
Current at GND+0.4 V, output set low, standard drive | IOL,SD | V3V3 >= 2.7V | 1 | 2 | 4 | mA |
Current at V3V3-0.4 V, output set high, standard drive | IOH,SD | V3V3 >= 2.7V | 1 | 2 | 4 | mA |
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Writes an analog value to a pin as a digital PWM (pulse-width modulated) signal. The default frequency of the PWM signal is 500 Hz.
Can be used to light a LED at varying brightnesses or drive a motor at various speeds. After a call to analogWrite(), the pin will generate a steady square wave of the specified duty cycle until the next call to analogWrite()
(or a call to digitalRead()
or digitalWrite()
on the same pin).
// SYNTAX
analogWrite(pin, value);
analogWrite(pin, value, frequency);
analogWrite()
takes two or three arguments:
pin
: the number of the pin whose value you wish to setvalue
: the duty cycle: between 0 (always off) and 255 (always on). Since 0.6.0: between 0 and 255 (default 8-bit resolution) or 2^(analogWriteResolution(pin)) - 1
in general.frequency
: the PWM frequency (optional). If not specified, the default is 500 Hz.NOTE: pinMode(pin, OUTPUT);
is required before calling analogWrite(pin, value);
or else the pin
will not be initialized as a PWM output and set to the desired duty cycle.
analogWrite()
does not return anything.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Photon, P1, Electron, and E-Series, pins A3 and A6 (DAC) are DAC (digital-to-analog converter) pins. The analogWrite() function sets an analog voltage, not a PWM frequency, when used on these pins.
When controlling LED brightness, you should always use PWM, not DAC.
// EXAMPLE USAGE
int ledPin = D1; // LED connected to digital pin D1
int analogPin = A0; // potentiometer connected to analog pin A0
int val = 0; // variable to store the read value
void setup()
{
pinMode(ledPin, OUTPUT); // sets the pin as output
}
void loop()
{
val = analogRead(analogPin); // read the input pin
analogWrite(ledPin, val/16); // analogRead values go from 0 to 4095,
// analogWrite values from 0 to 255.
delay(10);
}
NOTE: When used with PWM capable pins, the analogWrite()
function sets up these pins as PWM only.
Additional information on which pins can be used for PWM output is available on the pin information page.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On Gen 3 devices, the PWM frequency is from 5 Hz to analogWriteMaxFrequency(pin)
(default is 500 Hz).
On Gen 3 Feather devices (Argon, Boron), pins A0, A1, A2, A3, A4, A5, D2, D3, D4, D5, D6, D7, and D8 can be used for PWM. Pins are assigned a PWM group. Each group must share the same frequency and resolution, but individual pins in the group can have a different duty cycle.
On the Boron SoM, pins D4, D5, D6, D7, A0, A1, A6, and A7 can be used for PWM. Pins are assigned a PWM group. Each group must share the same frequency and resolution, but individual pins in the group can have a different duty cycle.
On the Tracker SoM, pins D0 - D9 can be used for PWM. Note that pins A0 - A7 are the same physical pin as D0 - D7. D8 is shared with TX (Serial1) and D9 is shared with RX (Serial1). When used for PWM, pins are assigned a PWM group. Each group must share the same frequency and resolution, but individual pins in the group can have a different duty cycle.
On the E404X, the following groups are defined. Each group must share the same frequency and resolution, but individual pins in the group can have a different duty cycle. Group 0 is shared with the RGB LED and must use the default resolution of 8 bits (0-255) and frequency of 500 Hz, but can have its own duty cycle.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
On the P2, Photon 2, and M-SoM, all PWM pins and the RGB LED share the same PWM timer and must share the same PWM frequency, though each pin can have a separate duty cycle.
The PWM pins for the P2 and Photon 2 are:
Pin | Pin Name | Description | MCU |
---|---|---|---|
23 | A5 / D14 | A5 Analog in, GPIO, PWM. | PB[4] |
35 | D1 / A4 | D1 GPIO, PWM, I2C SCL, A4 Analog In | PB[5] |
40 | S0 / D15 | S0 GPIO, PWM, SPI MOSI, Serial3 TX, I2S MCLK. (Was P1S0 on P1.) | PA[12] |
41 | S1 / D16 | S1 GPIO, PWM, SPI MISO, Serial3 RX. (Was P1S1 on P1.) | PA[13] |
49 | A2 / D13 | A2 Analog in, PWM, GPIO | PB[7] |
The PWM pins for the M-SoM are:
Pin | Pin Name | Description | MCU |
---|---|---|---|
23 | A0 / D19 | A0 Analog in, GPIO, PWM | PB[4] |
33 | A1 / D18 | A1 Analog in, GPIO, PWM | PB[5] |
36 | TX / D9 | Serial TX, PWM, GPIO, SPI1 MOSI, I2S MCLK | PA[12] |
38 | RX / D10 | Serial RX, PWM, GPIO, SPI1 MISO | PA[13] |
43 | A5 / D14 | A5 Analog in, PWM, GPIO, shared with pin 53 | PB[3] |
45 | A6 / D29 | A6 Analog in, GPIO, PWM, M.2 eval PMIC INT | PB[7] |
50 | MISO / D11 | D11 GPIO, PWM, SPI MISO | PA[17] |
52 | MOSI / D12 | D12 GPIO, PWM, SPI MOSI | PA[16] |
53 | A5 / D14 | A5 Analog in, PWM, GPIO, SWCLK, shared with pin 43 | PB[3] |
66 | D4 | D4 GPIO, PWM | PB[18] |
68 | D5 | D5 GPIO, PWM, I2S TX | PB[19] |
70 | D6 | D6 GPIO, PWM, I2S CLK | PB[20] |
72 | D7 | D7 GPIO, PWM, I2S WS | PB[21] |
Gen 4 devices share a single timer for both PWM (analogWrite)
and Servo. If you are using Servo, you must set the analogWrite frequency to 50 Hz, which matches
Servo, or your servo will not function properly. For example: analogWrite(A2, 255, 50);
using the
optional third parameter to set the frequency to 50 Hz for compatibility with Servo.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On Gen 2 devices, the PWM frequency is from 1 Hz to analogWriteMaxFrequency(pin)
(default is 500 Hz).
The PWM frequency must be the same for pins in the same timer group.
Since 0.6.0:
Sets or retrieves the resolution of analogWrite()
function of a particular pin.
analogWriteResolution()
takes one or two arguments:
pin
: the number of the pin whose resolution you wish to set or retrieveresolution
: (optional) resolution in bits. The value can range from 2 to 31 bits. If the resolution is not supported, it will not be applied. The default is 8.analogWriteResolution()
returns currently set resolution.
// EXAMPLE USAGE
pinMode(D1, OUTPUT); // sets the pin as output
analogWriteResolution(D1, 12); // sets analogWrite resolution to 12 bits
analogWrite(D1, 3000, 1000); // 3000/4095 = ~73% duty cycle at 1kHz
NOTE: The resolution also affects maximum frequency that can be used with analogWrite()
. The maximum frequency allowed with current resolution can be checked by calling analogWriteMaxFrequency()
.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Photon, P1, Electron, and E-Series, pins A3 and A6 (DAC) are DAC (digital-to-analog converter) pins and support only either 8-bit or 12-bit (default) resolutions.
Since 0.6.0:
Returns maximum frequency that can be used with analogWrite()
on this pin.
analogWriteMaxFrequency()
takes one argument:
pin
: the number of the pin// EXAMPLE USAGE
pinMode(D1, OUTPUT); // sets the pin as output
analogWriteResolution(D1, 12); // sets analogWrite resolution to 12 bits
int maxFreq = analogWriteMaxFrequency(D1);
analogWrite(D1, 3000, maxFreq / 2); // 3000/4095 = ~73% duty cycle
The Photon, P1, Electron, and E-Series support true analog output on pins DAC (DAC1
or A6
in code) and A3 (DAC2
or A3
in code). Using analogWrite(pin, value)
with these pins, the output of the pin is set to an analog voltage from 0V to 3.3V that corresponds to values
from 0-4095.
NOTE: This output is buffered inside the STM32 to allow for more output current at the cost of not being able to achieve rail-to-rail performance, i.e., the output will be about 50mV when the DAC is set to 0, and approx 50mV less than the 3V3 voltage when DAC output is set to 4095.
NOTE: Device OS version 0.4.6 and 0.4.7 only - not applicable to versions from 0.4.9 onwards: While for PWM pins one single call to pinMode(pin, OUTPUT);
sets the pin mode for multiple analogWrite(pin, value);
calls, for DAC pins you need to set pinMode(DAC, OUTPUT);
each time you want to perform an analogWrite()
.
// SYNTAX
pinMode(DAC1, OUTPUT);
analogWrite(DAC1, 1024);
// sets DAC pin to an output voltage of 1024/4095 * 3.3V = 0.825V.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
DAC is not supported on the P2, Photon 2, or Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM).
// SYNTAX
analogRead(pin);
// EXAMPLE USAGE
int ledPin = D1; // LED connected to digital pin D1
int analogPin = A0; // potentiometer connected to analog pin A0
int val = 0; // variable to store the read value
void setup()
{
// Note: analogPin pin does not require pinMode()
pinMode(ledPin, OUTPUT); // sets the ledPin as output
}
void loop()
{
val = analogRead(analogPin); // read the analogPin
analogWrite(ledPin, val/16); // analogRead values go from 0 to 4095, analogWrite values from 0 to 255
delay(10);
}
Reads the value from the specified analog pin.
analogRead()
takes one argument pin
: the number of the analog input pin to read from, such as A0 or A1.
analogRead()
returns an integer value ranging from 0 to 4095 (12-bit).
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
The Gen 3 Feather devices (Argon, Boron, Xenon) have 6 channels (A0 to A5) with a 12-bit resolution. This means that it will map input voltages between 0 and 3.3 volts into integer values between 0 and 4095. This yields a resolution between readings of: 3.3 volts / 4096 units or, 0.0008 volts (0.8 mV) per unit.
The sample time to read one analog value is 10 microseconds.
The Boron SoM has 8 channels, A0 to A7.
The Tracker SoM has 8 channels, A0 to A7, however these pins are the same physical pins D0 to D7.
The Tracker One only exposes one analog input, A3, on the external M8 connector. Pin A0 is connected to the NTC thermistor on the carrier board.
P2 and Photon 2 Devices (RTL872x):
The P2 and Photon 2 have four dedicated ADC pins, and two shared pins. If you are not using pins D0 and D1 for I2C or digital GPIO, you can use these pins are ADC inputs by using analogRead(A3)
and analogRead(A4)
.
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
23 | A5 / D14 | A5 Analog in, GPIO, PWM. | ADC_0 | PB[4] |
35 | D1 / A4 | D1 GPIO, PWM, I2C SCL, A4 Analog In | ADC_1 | PB[5] |
36 | D0 / A3 | D0 GPIO, I2C SDA, A3 Analog In | ADC_2 | PB[6] |
43 | A1 / D12 | A1 Analog in, PDM DAT, GPIO | ADC_5 | PB[2] |
49 | A2 / D13 | A2 Analog in, PWM, GPIO | ADC_3 | PB[7] |
50 | A0 / D11 | A0 Analog in, PDM CLK, GPIO | ADC_4 | PB[1] |
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
The device has 8 channels (A0 to A7) with a 12-bit resolution. This means that it will map input voltages between 0 and 3.3 volts into integer values between 0 and 4095. This yields a resolution between readings of: 3.3 volts / 4096 units or, 0.0008 volts (0.8 mV) per unit.
Before 0.5.3 Note: do not set the pinMode() with analogRead()
. The pinMode() is automatically set to AN_INPUT the first time analogRead() is called for a particular analog pin. If you explicitly set a pin to INPUT or OUTPUT after that first use of analogRead(), it will not attempt to switch it back to AN_INPUT the next time you call analogRead() for the same analog pin. This will create incorrect analog readings.
Since 0.5.3 Note: you do not need to set the pinMode() with analogRead(). The pinMode() is automatically set to AN_INPUT any time analogRead() is called for a particular analog pin, if that pin is set to a pinMode other than AN_INPUT. If you explicitly set a pin to INPUT, INPUT_PULLUP, INPUT_PULLDOWN or OUTPUT before using analogRead(), it will switch it back to AN_INPUT before taking the reading. If you use digitalRead() afterwards, it will automatically switch the pinMode back to whatever you originally explicitly set it to.
The function setADCSampleTime(duration)
is used to change the default sample time for analogRead()
.
On the Photon, P1, Electron, and E-Series this parameter can be one of the following values (ADC clock = 30MHz or 33.3ns per cycle):
The default is ADC_SampleTime_480Cycles. This means that the ADC is sampled for 16 us which can provide a more accurate reading, at the expense of taking longer than using a shorter ADC sample time. If you are measuring a high frequency signal, such as audio, you will almost certainly want to reduce the ADC sample time.
Furthermore, 5 consecutive samples at the sample time are averaged in analogRead(), so the time to convert is closer to 80 us, not 16 us, at 480 cycles.
Note:
setADCSampleTime is not supported on the P2, Photon 2, or Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM).
Since 4.1.0:
This feature is only present in 4.1.0 and later on the 4.x LTS branch, and in 5.3.0 and later in the 5.x developer prevent branch.
Set the ADC input reference. This does not affect the range of the ADC, which is always 0 - 3.3V with a 12-bit range (0 - 4095).
On nRF52840 devices, this call determines whether the ADC voltage reference is VCC (3V3), or the MCU internal voltage reference. On most devices, the default is AdcReference::VCC
(3V3). The exception is the Boron 404X, which defaults to AdcReference::INTERNAL
.
On RTL872x devices (P2, Photon 2), the ADC reference is always AdcReference::INTERNAL
and attempting to set it will return SYSTEM_ERROR_NOT_SUPPORTED
.
Returns 0 on success, or a non-zero system error code.
// PROTOTYPE
int analogSetReference(AdcReference reference);
// EXAPLE
analogSetReference(AdcReference::INTERNAL)
The valid values for analogSetReference are:
AdcReference::DEFAULT
Use the default value for this deviceAdcReference::INTERNAL
Use the MCU internal voltage reference if supported by the deviceAdcReference::VCC
Use VCC (3V3) as the voltage reference if supported by the deviceSince 4.1.0:
This feature is only present in 4.1.0 and later on the 4.x LTS branch, and in 5.3.0 and later in the 5.x developer prevent branch.
Gets the ADC reference voltage.
Returns:
AdcReference::INTERNAL
AdcReference::VCC
Note that if set to DEFAULT
, the actual reference is returned, not AdcReference::DEFAULT
.
// PROTOTYPE
AdcReference analogGetReference(void);
// EXAPLE
if (analogGetReference() == AdcReference::INTERNAL) {
// Do something
}
The Input/Ouput functions include safety checks such as making sure a pin is set to OUTPUT when doing a digitalWrite() or that the pin is not being used for a timer function. These safety measures represent good coding and system design practice.
There are times when the fastest possible input/output operations are crucial to an applications performance. The SPI, UART (Serial) or I2C hardware are examples of low level performance-oriented devices. There are, however, times when these devices may not be suitable or available. For example, One-wire support is done in software, not hardware.
In order to provide the fastest possible bit-oriented I/O, the normal safety checks must be skipped. As such, please be aware that the programmer is responsible for proper planning and use of the low level I/O functions.
Prior to using the following low-level functions, pinMode()
must be used to configure the target pin.
Write a HIGH
value to a digital pin.
// SYNTAX
pinSetFast(pin);
pinSetFast()
takes one argument, pin
: the number of the pin whose value you wish to set HIGH
.
pinSetFast()
does not return anything.
// EXAMPLE USAGE
int LED = D7; // LED connected to D7
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
}
void loop()
{
pinSetFast(LED); // set the LED on
delay(500);
pinResetFast(LED); // set the LED off
delay(500);
}
Write a LOW
value to a digital pin.
// SYNTAX
pinResetFast(pin);
pinResetFast()
takes one argument, pin
: the number of the pin whose value you wish to set LOW
.
pinResetFast()
does not return anything.
// EXAMPLE USAGE
int LED = D7; // LED connected to D7
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
}
void loop()
{
pinSetFast(LED); // set the LED on
delay(500);
pinResetFast(LED); // set the LED off
delay(500);
}
Write a HIGH
or LOW
value to a digital pin. This function will call pinSetFast() or pinResetFast() based on value
and is useful when value
is calculated. As such, this imposes a slight time overhead.
// SYNTAX
digitalWriteFast(pin, value);
digitalWriteFast()
pin
: the number of the pin whose value you wish to set and value
: HIGH
or LOW
.
digitalWriteFast()
does not return anything.
// EXAMPLE USAGE
int LED = D7; // LED connected to D7
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
}
void loop()
{
digitalWriteFast(LED, HIGH); // set the LED on
delay(500);
digitalWriteFast(LED, LOW); // set the LED off
delay(500);
}
Reads the value from a specified digital pin
, either HIGH
or LOW
.
// SYNTAX
pinReadFast(pin);
pinReadFast()
takes one argument, pin
: the number of the digital pin you want to read.
pinReadFast()
returns HIGH
or LOW
.
// EXAMPLE USAGE
int button = D0; // button is connected to D0
int LED = D1; // LED is connected to D1
int val = 0; // variable to store the read value
void setup()
{
pinMode(LED, OUTPUT); // sets pin as output
pinMode(button, INPUT_PULLDOWN); // sets pin as input
}
void loop()
{
val = pinReadFast(button); // read the input pin
digitalWriteFast(LED, val); // sets the LED to the button's value
}
Generates a square wave of the specified frequency and duration (and 50% duty cycle) on a timer channel pin which supports PWM. Use of the tone() function will interfere with PWM output on the selected pin. tone() is generally used to make sounds or music on speakers or piezo buzzers.
// SYNTAX
tone(pin, frequency, duration)
tone()
takes three arguments, pin
: the pin on which to generate the tone, frequency
: the frequency of the tone in hertz and duration
: the duration of the tone in milliseconds (a zero value = continuous tone).
The frequency range is from 20Hz to 20kHz. Frequencies outside this range will not be played.
tone()
does not return anything.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On Gen 3 Feather devices (Argon, Boron, Xenon), pins A0, A1, A2, A3, D2, D3, D4, D5, D6, and D8 can be used for tone(). Pins are assigned a PWM group. Each group must share the same frequency. Thus you can only output 3 different frequencies at the same time.
Group 3: Pins D2, D3, A4, and A5.
Group 2: Pins A0, A1, A2, and A3.
Group 1: Pins D4, D5, D6, and D8.
On the Boron SoM, pins D4, D5, D7, A0, A1, A6, and A7 can be used for PWM. Pins are assigned a PWM group. Pins are assigned a PWM group. Each group must share the same frequency.
On the Tracker SoM, pins D0 - D9 can be used for PWM. Pins are assigned a PWM group. Pins are assigned a PWM group. Each group must share the same frequency. Pins D8 and D9 can only be used for PWM if not being used for Serial1.
NOTE: The PWM pins / timer channels are allocated as per the following table. If multiple, simultaneous tone() calls are needed (for example, to generate DTMF tones), different timer numbers must be used to for each frequency:
On the Argon, Boron, and Xenon:
Pin | Timer |
---|---|
A0 | PWM2 |
A1 | PWM2 |
A2 | PWM2 |
A3 | PWM2 |
A4 | PWM3 |
A5 | PWM3 |
D2 | PWM3 |
D3 | PWM3 |
D4 | PWM1 |
D5 | PWM1 |
D6 | PWM1 |
D8 | PWM1 |
On the B-Series SoM:
Pin | Timer |
---|---|
A0 | PWM2 |
A1 | PWM2 |
A6 | PWM2 |
A7 | PWM2 |
D4 | PWM1 |
D5 | PWM1 |
D6 | PWM1 |
On the Tracker SoM:
Pin | Timer |
---|---|
D0 | PWM0 |
D1 | PWM0 |
D2 | PWM0 |
D3 | PWM0 |
D4 | PWM1 |
D5 | PWM1 |
D6 | PWM1 |
D7 | PWM1 |
D8 (TX) | PWM2 |
D9 (RX) | PWM2 |
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
All PWM compatible pins on the P2, Photon 2, and M-SoM share a single timer. Thus only one frequency of tone can be generated at a time.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Photon, P1 and Electron, this function works on pins D0, D1, D2, D3, A4, A5, WKP, RX and TX with a caveat: Tone timer peripheral is duplicated on two pins (A5/D2) and (A4/D3) for 7 total independent Tone outputs. For example: Tone may be used on A5 while D2 is used as a GPIO, or D2 for Tone while A5 is used as an analog input. However A5 and D2 cannot be used as independent Tone outputs at the same time.
Additionally on the Electron, this function works on pins B0, B1, B2, B3, C4, C5.
NOTE: The PWM pins / timer channels are allocated as per the following table. If multiple, simultaneous tone() calls are needed (for example, to generate DTMF tones), use pins allocated to separate timers to avoid stuttering on the output:
Pin | TMR1 | TMR3 | TMR4 | TMR5 |
---|---|---|---|---|
D0 | x | |||
D1 | x | |||
D2 | x | |||
D3 | x | |||
A4 | x | |||
A5 | x | |||
WKP | x | |||
RX | x | |||
TX | x |
On the P1:
Pin | TMR1 | TMR3 | TMR4 | TMR5 |
---|---|---|---|---|
D0 | x | |||
D1 | x | |||
D2 | x | |||
D3 | x | |||
A4 | x | |||
A5 | x | |||
WKP | x | |||
RX | x | |||
TX | x | |||
P1S0 | x | |||
P1S1 | x | |||
P1S6 | x |
On the Electron and E-Series:
Pin | TMR1 | TMR3 | TMR4 | TMR5 | TMR8 | |
---|---|---|---|---|---|---|
D0 | x | |||||
D1 | x | |||||
D2 | x | |||||
D3 | x | |||||
A4 | x | |||||
A5 | x | |||||
WKP | x | |||||
RX | x | |||||
TX | x | |||||
B0 | x | |||||
B1 | x | |||||
B2 | x | |||||
B3 | x | |||||
C4 | x | |||||
C5 | x |
Additional information on which pins can be used for tone() is available on the pin information page.
#include "application.h"
// The Photon has 9 PWM pins: D0, D1, D2, D3, A4, A5, A7, RX and TX.
//
// EXAMPLE USAGE
// Plays a melody - Connect small speaker to speakerPin
int speakerPin = D0;
// Notes defined in microseconds (Period/2)
// from note C to B, Octaves 3 through 7
int notes[] =
{0,
/* C, C#, D, D#, E, F, F#, G, G#, A, A#, B */
3817,3597,3401,3205,3030,2857,2703,2551,2404,2273,2146,2024, // 3 (1-12)
1908,1805,1701,1608,1515,1433,1351,1276,1205,1136,1073,1012, // 4 (13-24)
956, 903, 852, 804, 759, 716, 676, 638, 602, 568, 536, 506, // 5 (25-37)
478, 451, 426, 402, 379, 358, 338, 319, 301, 284, 268, 253, // 6 (38-50)
239, 226, 213, 201, 190, 179, 169, 159, 151, 142, 134, 127 }; // 7 (51-62)
#define NOTE_G3 2551
#define NOTE_G4 1276
#define NOTE_C5 956
#define NOTE_E5 759
#define NOTE_G5 638
#define RELEASE 20
#define BPM 100
// notes in the melody:
int melody[] = {NOTE_E5,NOTE_E5,0,NOTE_E5,0,NOTE_C5,NOTE_E5,0,NOTE_G5,0,0,NOTE_G4};
// note durations: 4 = quarter note, 2 = half note, etc.:
int noteDurations[] = {4,4,4,4,4,4,4,4,4,2,4,4};
void setup() {
// iterate over the notes of the melody:
for (int thisNote = 0; thisNote < 12; thisNote++) {
// to calculate the note duration, take one second
// divided by the note type.
// e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 60*1000/BPM/noteDurations[thisNote];
tone(speakerPin, (melody[thisNote]!=0)?(500000/melody[thisNote]):0,noteDuration-RELEASE);
// blocking delay needed because tone() does not block
delay(noteDuration);
}
}
Stops the generation of a square wave triggered by tone() on a specified pin. Has no effect if no tone is being generated.
The available pins are the same as for tone().
// SYNTAX
noTone(pin)
noTone()
takes one argument, pin
: the pin on which to stop generating the tone.
noTone()
does not return anything.
//See the tone() example
Shifts out a byte of data one bit at a time on a specified pin. Starts from either the most (i.e. the leftmost) or least (rightmost) significant bit. Each bit is written in turn to a data pin, after which a clock pin is pulsed (taken high, then low) to indicate that the bit is available.
NOTE: if you're interfacing with a device that's clocked by rising edges, you'll need to make sure that the clock pin is low before the call to shiftOut()
, e.g. with a call to digitalWrite(clockPin, LOW)
.
This is a software implementation; see also the SPI function, which provides a hardware implementation that is faster but works only on specific pins.
// SYNTAX
shiftOut(dataPin, clockPin, bitOrder, value)
// EXAMPLE USAGE
// Use digital pins D0 for data and D1 for clock
int dataPin = D0;
int clock = D1;
uint8_t data = 50;
setup() {
// Set data and clock pins as OUTPUT pins before using shiftOut()
pinMode(dataPin, OUTPUT);
pinMode(clock, OUTPUT);
// shift out data using MSB first
shiftOut(dataPin, clock, MSBFIRST, data);
// Or do this for LSBFIRST serial
shiftOut(dataPin, clock, LSBFIRST, data);
}
loop() {
// nothing to do
}
shiftOut()
takes four arguments, 'dataPin': the pin on which to output each bit, clockPin
: the pin to toggle once the dataPin has been set to the correct value, bitOrder
: which order to shift out the bits; either MSBFIRST or LSBFIRST (Most Significant Bit First, or, Least Significant Bit First) and value
: the data (byte) to shift out.
shiftOut()
does not return anything.
Shifts in a byte of data one bit at a time. Starts from either the most (i.e. the leftmost) or least (rightmost) significant bit. For each bit, the clock pin is pulled high, the next bit is read from the data line, and then the clock pin is taken low.
NOTE: if you're interfacing with a device that's clocked by rising edges, you'll need to make sure that the clock pin is low before the call to shiftOut(), e.g. with a call to digitalWrite(clockPin, LOW)
.
This is a software implementation; see also the SPI function, which provides a hardware implementation that is faster but works only on specific pins.
// SYNTAX
shiftIn(dataPin, clockPin, bitOrder)
// EXAMPLE USAGE
// Use digital pins D0 for data and D1 for clock
int dataPin = D0;
int clock = D1;
uint8_t data;
setup() {
// Set data as INPUT and clock pin as OUTPUT before using shiftIn()
pinMode(dataPin, INPUT);
pinMode(clock, OUTPUT);
// shift in data using MSB first
data = shiftIn(dataPin, clock, MSBFIRST);
// Or do this for LSBFIRST serial
data = shiftIn(dataPin, clock, LSBFIRST);
}
loop() {
// nothing to do
}
shiftIn()
takes three arguments, 'dataPin': the pin on which to input each bit, clockPin
: the pin to toggle to signal a read from dataPin, bitOrder
: which order to shift in the bits; either MSBFIRST or LSBFIRST (Most Significant Bit First, or, Least Significant Bit First).
shiftIn()
returns the byte value read.
Since 0.4.7:
Reads a pulse (either HIGH or LOW) on a pin. For example, if value is HIGH, pulseIn() waits for the pin to go HIGH, starts timing, then waits for the pin to go LOW and stops timing. Returns the length of the pulse in microseconds or 0 if no complete pulse was received within the timeout.
The timing of this function is based on an internal hardware counter derived from the system tick clock. Resolution is 1/120MHz for Photon/P1/Electron). Works on pulses from 10 microseconds to 3 seconds in length. Please note that if the pin is already reading the desired value
when the function is called, it will wait for the pin to be the opposite state of the desired value
, and then finally measure the duration of the desired value
. This routine is blocking and does not use interrupts. The pulseIn()
routine will time out and return 0 after 3 seconds.
// SYNTAX
pulseIn(pin, value)
pulseIn()
takes two arguments, pin
: the pin on which you want to read the pulse (this can be any GPIO, e.g. D1, A2, C0, B3, etc..), value
: type of pulse to read: either HIGH or LOW. pin
should be set to one of three pinMode()'s prior to using pulseIn(), INPUT
, INPUT_PULLUP
or INPUT_PULLDOWN
.
pulseIn()
returns the length of the pulse (in microseconds) or 0 if no pulse is completed before the 3 second timeout (unsigned long)
// EXAMPLE
SerialLogHandler logHandler;
unsigned long duration;
void setup()
{
Serial.begin(9600);
pinMode(D0, INPUT);
// Pulse generator, connect D1 to D0 with a jumper
// PWM output is 500Hz at 50% duty cycle
// 1000us HIGH, 1000us LOW
pinMode(D1, OUTPUT);
analogWrite(D1, 128);
}
void loop()
{
duration = pulseIn(D0, HIGH);
Log.info("%d us", duration);
delay(1000);
}
/* OUTPUT
* 1003 us
* 1003 us
* 1003 us
* 1003 us
*/
The Power Manager API provides a way to set PMIC (Power Management IC) settings such as input volage, input current limit, charge current, and charge termination voltage using a simple API. The Power Manager settings are persistent and saved in configuration flash so you can set them once and they will continue to be used.
To set the Power Manager configuration, create a SystemPowerConfiguration
object and use the methods below to adjust the settings:
Note:
Power Management is available on the M-SoM, Muon, Boron, B-Series SoM, Tracker SoM (Gen 3), Electron, and E-Series (Gen 2).
It is not available on the P2, Photon 2, Argon (Gen 3), Photon, or P1 (Gen 2).
Set maximum current the power source can provide. This applies only when powered through VIN. When powering by USB, the maximum current is negotiated with your computer or power adapter automatically.
The valid values are: 100, 150, 500, 900, 1200 or 1500 (mA). While the bq24195 PMIC has settings for 2000 and 3000, these will still be limited to around 1590 mA because the input current is limited to the lower of the powerSourceMaxCurrent and the hardware ILIM resistors, which are set to around 1500 mA.
When powering by a USB power adapter that implements DPDM (USB current limit discovery), the lower of that value, powerSourceMaxCurrent, and ILIM resistors will be used.
The default is 900 mA.
// PROTOTYPES
SystemPowerConfiguration& powerSourceMinVoltage(uint16_t voltage)
uint16_t powerSourceMinVoltage()
Set minimum voltage required for VIN to be used. This applies only when powered through VIN. The value is in millivolts or 1000ths of a volt, so 3880 is 3.880 volts.
The default is 3880 (3.88 volts).
// PROTOTYPES
SystemPowerConfiguration& powerSourceMinVoltage(uint16_t voltage)
uint16_t powerSourceMinVoltage()
Sets the battery charge current. The actual charge current is the lesser of powerSourceMaxCurrent and batteryChargeCurrent. The value is milliamps (mA).
The default is 896 mA.
// PROTOTYPES
SystemPowerConfiguration& batteryChargeCurrent(uint16_t current)
uint16_t batteryChargeCurrent() const
Sets the battery charge termination voltage. The value is in millivolts or 1000ths of a volt, so 3880 is 3.880 volts. When the battery reaches this voltage, charging is stopped.
The default is 4112 (4.112V).
// PROTOTYPES
SystemPowerConfiguration& batteryChargeVoltage(uint16_t voltage)
uint16_t batteryChargeVoltage() const
Since 5.9.0:
The value returned by the fuel gauge chip may have a different number of bits of precision depending on whether it's a MAX17043 (18, the default) or MAX17048 (19), but the chips are otherwise very similar. If you are building your own SoM base board, this allows you to use a MAX17048 fuel gauge chip and still get correct values.
// PROTOTYPES
SystemPowerConfiguration& socBitPrecision(uint8_t bits)
uint8_t socBitPrecision()
Since 5.9.0:
Devices using the Particle Power Module include a 3V3_AUX
power output
that can be controlled by a GPIO. On the M.2 SoM breakout board, this powers the Feather connector. On the Muon,
it powers the Ethernet port, LoRaWAN module, 40-pin expansion HAT connector, and QWIIC connector.
The main reason for this is that until the PMIC is configured, the input current with no battery
connected is limited to 100 mA. This is insufficient for the M-SoM to boot when
using a peripheral that requires a lot of current, like the WIZnet W5500 Ethernet module. The
system power manager prevents turning on 3V3_AUX
until after the PMIC is configured
and the PMIC has negotiated a higher current from the USB host (if powered by USB).
This setting is persistent and only needs to be set once. In fact, the PMIC initialization
normally occurs before user firmware is run. This is also necessary because if you are using Ethernet
and enter safe mode (breathing magenta), it's necessary to enable 3V3_AUX
so if you are using
Ethernet, you can still get OTA updates while in safe mode.
After changing the auxiliary power configuration you must reset the device.
// PROTOTYPES
SystemPowerConfiguration& auxiliaryPowerControlPin(uint8_t pin, bool activeLevel = 1)
uint8_t auxiliaryPowerControlPin() const
uint8_t auxiliaryPowerControlActiveLevel()
// EXAMPLE - Muon
SystemPowerConfiguration powerConfig = System.getPowerConfiguration();
powerConfig.auxiliaryPowerControlPin(D7).interruptPin(A7);
System.setPowerConfiguration(powerConfig);
// EXAMPLE - M.2 SoM Breakout board
SystemPowerConfiguration powerConfig = System.getPowerConfiguration();
powerConfig.auxiliaryPowerControlPin(D23).interruptPin(A6);
System.setPowerConfiguration(powerConfig);
Since 5.9.0:
This call allows the use of an alternate GPIO pin for the PMIC and fuel gauge interrupt input.
// PROTOTYPES
SystemPowerConfiguration& interruptPin(uint8_t pin)
uint8_t interruptPin() const
SerialLogHandler logHandler;
void setup() {
// Apply a custom power configuration
SystemPowerConfiguration conf;
conf.feature(SystemPowerFeature::DISABLE_CHARGING);
int res = System.setPowerConfiguration(conf);
Log.info("setPowerConfiguration=%d", res);
// returns SYSTEM_ERROR_NONE (0) in case of success
// Settings are persisted, you normally wouldn't do this on every startup.
}
System power features are enabled or disabled using the SystemPowerConfiguration::feature()
method. The settings are saved in non-volatile storage so you do not need to set them on every startup.
For devices with an external PMIC and Fuel Gauge like the B-Series SoM, enables detection of the bq24195 PMIC connected by I2C to the primary I2C interface (Wire). Since this requires the use of I2C, you should not use pins D0 and D1 for GPIO when using PMIC_DETECTION.
Normally, if a USB host is detected, the power limit settings will be determined by DPDM, the negotiation between the USB host and the PMIC to determine, for example, the maximum current available. If this feature is enabled, the VIN settings are used even when a USB host is detected. This is normally done if you are using USB for debugging but still have a power supply connected to VIN.
Since 3.0.0:
Disables LiPo battery charging. This may be useful if:
Disables the system power management features. If you set this mode you must manually set the values in the PMIC directly.
// EXAMPLE
SerialLogHandler logHandler;
void setup() {
// Apply a custom power configuration
SystemPowerConfiguration conf;
conf.powerSourceMaxCurrent(900)
.powerSourceMinVoltage(4300)
.batteryChargeCurrent(850)
.batteryChargeVoltage(4210);
int res = System.setPowerConfiguration(conf);
Log.info("setPowerConfiguration=%d", res);
// returns SYSTEM_ERROR_NONE (0) in case of success
// Settings are persisted, you normally wouldn't do this on every startup.
}
void loop() {
{
PMIC power(true);
Log.info("Current PMIC settings:");
Log.info("VIN Vmin: %u", power.getInputVoltageLimit());
Log.info("VIN Imax: %u", power.getInputCurrentLimit());
Log.info("Ichg: %u", power.getChargeCurrentValue());
Log.info("Iterm: %u", power.getChargeVoltageValue());
int powerSource = System.powerSource();
int batteryState = System.batteryState();
float batterySoc = System.batteryCharge();
constexpr char const* batteryStates[] = {
"unknown", "not charging", "charging",
"charged", "discharging", "fault", "disconnected"
};
constexpr char const* powerSources[] = {
"unknown", "vin", "usb host", "usb adapter",
"usb otg", "battery"
};
Log.info("Power source: %s", powerSources[std::max(0, powerSource)]);
Log.info("Battery state: %s", batteryStates[std::max(0, batteryState)]);
Log.info("Battery charge: %f", batterySoc);
}
delay(2000);
}
To reset all settings to the default values:
// Reset power manager settings to default values
void setup() {
// To restore the default configuration
SystemPowerConfiguration conf;
System.setPowerConfiguration(conf);
}
By default, on the B-Series SoM, the Tinker application firmware enables the use of the bq24195 PMIC and MAX17043 fuel gauge. This in turn uses I2C (D0 and D1) and pin A6 (PM_INT). If you are not using the PMIC and fuel gauge and with to use these pins for other purposes, be sure to disable system power configuration. This setting is persistent, so you may want to disable it with your manufacturing firmware only.
System.setPowerConfiguration(SystemPowerConfiguration());
B-Series SoM | Power Manager Usage |
---|---|
D0 | I2C SDA |
D1 | I2C SCL |
A6 | PM_INT (power manager interrupt) |
You should generally set the PMIC settings such as input volage, input current limit, charge current, and charge termination voltage using the Power Manager API, above. If you directly set the PMIC, the settings will likely be overridden by the system.
To find out the current power source (battery, VIN, USB), see System.powerSource()
. To find out if the battery is charging, discharging, disconnected, etc., see System.batteryState()
.
Note: This is advanced IO and for experienced users. This controls the LiPo battery management system and is handled automatically by the Device OS.
Note:
Power Management is available on the Boron, B-Series SoM, Tracker SoM (Gen 3), Electron, and E-Series (Gen 2).
It is not available on the P2, Photon 2, Argon (Gen 3), Photon, or P1 (Gen 2).
// PROTOTYPE
PMIC(bool _lock=false);
You can declare the PMIC object either as a global variable, or a stack-allocated local variable. If you use a stack local, pass true
as the parameter to the constructor. This will automatically call lock()
from the constructor and unlock()
from the destructor.
bool begin();
byte getVersion();
byte getSystemStatus();
byte getFault();
void lock();
You should always call lock()
and unlock()
, use WITH_LOCK()
, or stack allocate a PMIC
object nad pass true
to the constructor.
Since the PMIC can be accessed from both the system and user threads, locking it assures that a PMIC opertation will not be interrupted by another thread.
void unlock();
byte readInputSourceRegister(void);
bool enableBuck(void);
bool disableBuck(void);
bool setInputCurrentLimit(uint16_t current);
byte getInputCurrentLimit(void);
bool setInputVoltageLimit(uint16_t voltage);
byte getInputVoltageLimit(void);
bool enableCharging(void);
bool disableCharging(void);
bool enableOTG(void);
bool disableOTG(void);
bool resetWatchdog(void);
bool setMinimumSystemVoltage(uint16_t voltage);
uint16_t getMinimumSystemVoltage();
byte readPowerONRegister(void);
bool setChargeCurrent(bool bit7, bool bit6, bool bit5, bool bit4, bool bit3, bool bit2);
The total charge current is the 512mA + the combination of the current that the following bits represent
For example, to set a 1408 mA charge current:
PMIC pmic;
pmic.setChargeCurrent(0,0,1,1,1,0);
byte getChargeCurrent(void);
Returns the charge current register. This is the direct register value from the BQ24195 PMIC. The bits in this register correspond to the bits you pass into setChargeCurrent.
bool setPreChargeCurrent();
byte getPreChargeCurrent();
bool setTermChargeCurrent();
byte getTermChargeCurrent();
bool setChargeVoltage(uint16_t voltage);
Voltage can be:
The default charge voltage is 4112, which corresponds to 4.112 volts.
You can also set it 4208, which corresponds to 4.208 volts. This higher voltage should not be used if the battery will be charged in temperatures exceeding 45°C. Using a higher charge voltage will allow the battery to reach a higher state-of-charge (SoC) but could damage the battery at high temperatures.
void setup() {
PMIC power;
power.setChargeVoltage(4208);
}
Note: Do not use 4208 with Device OS 0.4.8 or 0.5.0, as a bug will cause an incorrect, even higher, voltage to be used.
uint16_t getChargeVoltageValue();
Returns the charge voltage constant that could pass into setChargeVoltage, typically 4208 or 4112.
byte getChargeVoltage();
Returns the charge voltage register. This is the direct register value from the BQ24195 PMIC.
byte readChargeTermRegister();
bool disableWatchdog(void);
bool setWatchdog(byte time);
bool setThermalRegulation();
byte getThermalRegulation();
byte readOpControlRegister();
bool enableDPDM(void);
bool disableDPDM(void);
bool enableBATFET(void);
bool disableBATFET(void);
bool safetyTimer();
bool enableChargeFaultINT();
bool disableChargeFaultINT();
bool enableBatFaultINT();
bool disableBatFaultINT();
byte getVbusStat();
byte getChargingStat();
bool getDPMStat();
bool isPowerGood(void);
bool isHot(void);
bool getVsysStat();
(inherits from Stream
)
Device | Serial | USBSerial1 | Serial1 | Serial2 | Serial3 | Serial4 | Serial5 |
---|---|---|---|---|---|---|---|
Argon | ✓ | ✓ | |||||
Boron | ✓ | ✓ | |||||
B-Series SoM | ✓ | ✓ | |||||
Tracker SoM | ✓ | 2 | |||||
P2 | ✓ | ✓ | ✓ | ✓ | |||
Photon 2 | ✓ | ✓ | ✓ | ✓ | |||
Photon | ✓ | ✓ | ✓ | 1 | |||
P1 | ✓ | ✓ | ✓ | ✓ | |||
Electron | ✓ | ✓ | ✓ | 1 | ✓ | ✓ | |
E-Series | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Serial2
on the Photon and Electron uses the same pins as the RGB status LED, and cannot be used without
physically disconnecting the status LED on the device by removing the LED or current limiting resistors.Serial1
on the Tracker One shares the same pins as Wire3
on the external M8 connector.Serial
is a USB serial emulation, not a hardware UART.USBSerial1
is available on Device OS 0.6.0 and later, and is a second USB serial emulation.There are also other interfaces that can be used for communication:
Interface | Maximum Speed (Gen2) | Maximum Speed (Gen 3) | Maximum Peripheral Devices |
---|---|---|---|
UART Serial | 230 Kbit/sec | 1 Mbit/sec | 1 (point-to-point) |
I2C | 400 Kbit/sec | 400 Kbit/sec | Many (limited by addresses) |
SPI | 60 Mbit/sec | 32 Mbit/sec | Many (limited by CS GPIO pins) |
Serial:
This channel communicates through the USB port and when connected to a computer, will show up as a virtual COM port.
// EXAMPLE USAGE - NOT RECOMMENDED
void setup()
{
Serial.begin();
Serial.println("Hello World!");
}
// EXAMPLE USAGE - PREFERRED
SerialLogHandler logHandler;
void setup()
{
Log.info("Hello World!");
}
Instead of using Serial
directly, you should use it using the the SerialLogHandler
if you are writing debugging messages. Using the Log
method makes it easy to switch between Serial
and Serial1
(or both!) as well as providing thread-safety. It also allows log messages to be intercepted by code, and even saved to things like SD cards, with additional libraries.
You should also avoid mixing the use of Serial.printlnf
and Log.info
(and similar calls). Since Serial.print
is not thread safe, it can interfere with the use of Log
calls.
Serial1:
This channel is available via the device's TX and RX pins.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
Hardware flow control for Serial1 is optionally available on pins D3(CTS) and D2(RTS) on the Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker SoM).
The Tracker SoM can use the TX and RX pins as either Wire3
or Serial1
. If you use Serial1.begin()
the pins will be used for UART serial. If you use Wire3.begin()
, RX
will be SDA
and TX
will be SCL
. You cannot use Wire3
and Serial1
at the same time. Likewise, you cannot use Wire
and Wire3
at the same time, as there is only one I2C peripheral, just different pin mappings. This is primarily use with the Tracker One as TX/RX are exposed by the external M8 connector. By using Wire3.begin()
you can repurpose these pins as I2C, allowing external expansion by I2C instead of serial.
P2 and Photon 2 Devices (RTL872x):
The P2 and Photon 2 support three serial interfaces, two with hardware flow control:
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
30 | D10 / WKP | D10 GPIO, Serial 3 CTS, WKP. (Was WKP/A7 on P1.) | Serial3 (CTS) | PA[15] |
40 | S0 / D15 | S0 GPIO, PWM, SPI MOSI, Serial3 TX, I2S MCLK. (Was P1S0 on P1.) | Serial3 (TX) | PA[12] |
41 | S1 / D16 | S1 GPIO, PWM, SPI MISO, Serial3 RX. (Was P1S1 on P1.) | Serial3 (RX) | PA[13] |
42 | S2 / D17 | S2 GPIO, SPI SCK, Serial3 RTS. (Was P1S2 on P1.) | Serial3 (RTS) | PA[14] |
45 | D2 | D2 GPIO, Serial2 RTS, SPI1 MOSI | Serial2 (RTS) | PA[16] |
51 | D3 | D3 GPIO, Serial2 CTS, SPI1 MISO | Serial2 (CTS) | PA[17] |
52 | D4 | D4 GPIO, Serial2 TX, SPI1 SCK | Serial2 (TX) | PA[18] |
53 | D5 | D5 GPIO, Serial2 RX, SPI1 SS | Serial2 (RX) | PA[19] |
63 | RX / D9 | Serial1 RX (received data), GPIO | Serial1 (RX) | PA[8] |
64 | TX / D8 | Serial1 TX (transmitted data), GPIO | Serial1 (TX) | PA[7] |
M-SoM Devices (RTL872x):
The P2 and Photon 2 support two serial interfaces, one with hardware flow control:
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
36 | TX / D9 | Serial TX, PWM, GPIO, SPI1 MOSI, I2S MCLK | Serial1 (TX) | PA[12] |
38 | RX / D10 | Serial RX, PWM, GPIO, SPI1 MISO | Serial1 (RX) | PA[13] |
40 | D3 | D3 GPIO, Serial1 CTS flow control (optional), SPI1 SS | Serial1 (CTS) | PA[15] |
42 | D2 | D2 GPIO, Serial RTS flow control (optional), SPI1 SCK | Serial1 (RTS) | PA[14] |
58 | D24 | D24 GPIO, Serial2 TX, do not pull down at boot | Serial2 (TX) | PA[7] |
60 | D25 | GPIO25, Serial2 RX | Serial2 (RX) | PA[8] |
Serial2:
On the P2, Photon 2, and M-SoM, this channel is available. It uses the same pins as SPI1
, so you can only use Serial2
or SPI1
, not both.
On the Photon, this channel is optionally available via pins 28/29 (RGB LED Blue/Green). These pins are accessible via the pads on the bottom of the PCB See PCB Land Pattern. The Blue and Green current limiting resistors should be removed. If the user enables Serial2, they should also consider using RGB.onChange() to move the RGB functionality to an external RGB LED on some PWM pins.
On the Electron, this channel is shared with the RGB Green (TX) and Blue (RX) LED pins. If used for Serial2, the LED or current limiting resistors should be removed. As there are no test pads for these LED pins on the Electron, Serial2 will be difficult to use.
On the P1 and E-Series, this channel is shared with the RGB Green (TX) and Blue (RX) LED pins. Since you supply your own LEDs on these devices, you can move them to other pins using RGB.mirrorTo().
Other devices do not support Serial2
. On the Argon and Boron, the hardware port is used to communicate with the network coprocessor (NCP) and is not available for user use.
To use Serial2, add #include "Serial2/Serial2.h"
near the top of your app's main code file.
Serial3
: This channel is optionally available on the P2 and Photon 2 only. It optionally supports hardware flow control. To use Serial3, add #include "Serial3/Serial3.h"
near the top of your app's main code file.
Serial4:
This channel is optionally available via the Electron and E-Series C3(TX) and C2(RX) pins. To use Serial4, add #include "Serial4/Serial4.h"
near the top of your app's main code file. This port is not available on other devices.
Serial5:
This channel is optionally available via the Electron and E-Series C1(TX) and C0(RX) pins. To use Serial5, add #include "Serial5/Serial5.h"
near the top of your app's main code file. This port is not available on other devices.
USBSerial1
: Available on Gen 2 (Photon, P1, Electron, E-Series) with Device OS 0.6.0. and later: This channel communicates through the USB port and when connected to a computer, will show up as a second virtual COM port. This channel is disabled by default.
To use the Serial1 or other hardware UART pins to communicate with your personal computer, you will need an additional USB-to-serial adapter. To use them to communicate with an external TTL serial device, connect the TX pin to your device's RX pin, the RX to your device's TX pin, and ground.
// EXAMPLE USAGE
void setup()
{
Serial1.begin(9600);
Serial1.println("Hello World!");
}
// EXAMPLE USAGE Serial4 on Electron and E-Series
#include "Serial4/Serial4.h"
void setup()
{
Serial4.begin(9600);
Serial4.println("Hello World!");
}
To use the hardware serial pins of (Serial1, etc.) to communicate with your personal computer, you will need an additional USB-to-serial adapter. To use them to communicate with an external TTL serial device, connect the TX pin to your device's RX pin, the RX to your device's TX pin, and the ground of your device to your device's ground.
NOTE: Please take into account that the voltage levels on these pins operate at 0V to 3.3V and should not be connected directly to a computer's RS232 serial port which operates at +/- 12V and will damage the device.
NOTE: On Windows 10, using USBSerial1
on the Electron, E-Series, and P1 may not be reliable due to limitations of the USB peripheral used for those 2 devices. Characters may be dropped between the computer and device. USBSerial1
is reliable on other operating systems and also on the Photon. USBSerial1
is not available on Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker SoM).
For more information about serial ports, see learn more about serial.
Enables serial channel with specified configuration.
// SYNTAX
Serial.begin(); // via USB port
// Photon, P1, Electron, and E-Series only
USBSerial1.begin(); // via USB port
Serial1.begin(speed); // via TX/RX pins
Serial1.begin(speed, config); // "
Serial1.begin(9600, SERIAL_9N1); // via TX/RX pins, 9600 9N1 mode
Serial1.begin(9600, SERIAL_DATA_BITS_8 | SERIAL_STOP_BITS_1_5 | SERIAL_PARITY_EVEN); // via TX/RX pins, 9600 8E1.5
// P2, Photon 2, Photon, P1, Electron, and E-Series
#include "Serial2/Serial2.h"
Serial2.begin(speed); // RGB-LED green(TX) and blue (RX) pins
Serial2.begin(speed, config); // "
Serial2.begin(9600); // via RGB Green (TX) and Blue (RX) LED pins
Serial2.begin(9600, SERIAL_DATA_BITS_8 | SERIAL_STOP_BITS_1_5 | SERIAL_PARITY_EVEN); // via RGB Green (TX) and Blue (RX) LED pins, 9600 8E1.5
// Electron and E-Series only
#include "Serial4/Serial4.h"
Serial4.begin(speed); // via C3(TX)/C2(RX) pins
Serial4.begin(speed, config); // "
// Electron and E-Series only
#include "Serial5/Serial5.h"
Serial5.begin(speed); // via C1(TX)/C0(RX) pins
Serial5.begin(speed, config); // "
Parameters:
speed
: parameter that specifies the baud rate (long) (optional for Serial
and USBSerial1
config
: parameter that specifies the number of data bits used, parity and stop bits (long) (not used with Serial
and USBSerial1
)// EXAMPLE USAGE
void setup()
{
Serial.begin(9600); // open serial over USB
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
Serial1.begin(9600); // open serial over TX and RX pins
Serial.println("Hello Computer");
Serial1.println("Hello Serial 1");
}
void loop() {}
Since 0.5.0: 28800 baudrate set by the Host on Serial
will put the device in Listening Mode, where a YMODEM download can be started by additionally sending an f
character. Baudrate 14400 can be used to put the device into DFU Mode.
When using hardware serial channels (Serial1, Serial2, etc.), the configuration of the serial channel may also specify the number of data bits, stop bits, parity, flow control and other settings. The default is SERIAL_8N1 (8 data bits, no parity and 1 stop bit) and does not need to be specified to achieve this configuration. To specify one of the following configurations, add one of these defines as the second parameter in the begin()
function, e.g. Serial1.begin(9600, SERIAL_8E1);
for 8 data bits, even parity and 1 stop bit.
Pre-defined Serial configurations available:
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On Gen 3 (Argon, Boron, B-Series SoM, Tracker SoM) devices:
Hardware serial port baud rates are: 1200, 2400, 4800, 9600, 19200, 28800, 38400, 57600, 76800, 115200, 230400, 250000, 460800, 921600 and 1000000.
Configuration options include:
SERIAL_8N1
- 8 data bits, no parity, 1 stop bit (default)SERIAL_8E1
- 8 data bits, even parity, 1 stop bitOther options, including odd parity, and 7 and 9 bit modes, are not available on Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM).
Flow control is available on Serial1 D3(CTS) and D2(RTS). If you are not using flow control (the default), then these pins can be used as regular GPIO.
SERIAL_FLOW_CONTROL_NONE
- no flow controlSERIAL_FLOW_CONTROL_RTS
- RTS flow controlSERIAL_FLOW_CONTROL_CTS
- CTS flow controlSERIAL_FLOW_CONTROL_RTS_CTS
- RTS/CTS flow controlGen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
On the P2, Photon 2, and M-SoM:
Supported baud rates: 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 76800, 115200, 128000, 153600, 230400, 380400, 460800, 500000, 921600, 1000000, 1382400, 1444400, 1500000, 1843200, 2000000, 2100000, 2764800, 3000000, 3250000, 3692300, 3750000, 4000000, 6000000
Configuration options include:
SERIAL_8N1
- 8 data bits, no parity, 1 stop bit (default)SERIAL_8N2
- 8 data bits, no parity, 2 stop bitsSERIAL_8E1
- 8 data bits, even parity, 1 stop bitSERIAL_8E2
- 8 data bits, even parity, 2 stop bitsSERIAL_8O1
- 8 data bits, odd parity, 1 stop bitSERIAL_8O2
- 8 data bits, odd parity, 2 stop bitsSERIAL_7O1
- 7 data bits, odd parity, 1 stop bitSERIAL_7O2
- 7 data bits, odd parity, 1 stop bitSERIAL_7E1
- 7 data bits, even parity, 1 stop bitSERIAL_7E2
- 7 data bits, even parity, 1 stop bitFlow control is available on Serial2 and Serial3. If you are not using flow control (the default), then these pins can be used as regular GPIO.
SERIAL_FLOW_CONTROL_NONE
- no flow controlSERIAL_FLOW_CONTROL_RTS
- RTS flow controlSERIAL_FLOW_CONTROL_CTS
- CTS flow controlSERIAL_FLOW_CONTROL_RTS_CTS
- RTS/CTS flow controlGen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On Gen 2 devices (Photon, P1, Electron, E-Series):
Hardware serial port baud rates are: 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, and 230400.
Configuration options include:
SERIAL_8N1
- 8 data bits, no parity, 1 stop bit (default)SERIAL_8N2
- 8 data bits, no parity, 2 stop bitsSERIAL_8E1
- 8 data bits, even parity, 1 stop bitSERIAL_8E2
- 8 data bits, even parity, 2 stop bitsSERIAL_8O1
- 8 data bits, odd parity, 1 stop bitSERIAL_8O2
- 8 data bits, odd parity, 2 stop bitsSERIAL_9N1
- 9 data bits, no parity, 1 stop bitSERIAL_9N2
- 9 data bits, no parity, 2 stop bitsSince 0.6.0:
SERIAL_7O1
- 7 data bits, odd parity, 1 stop bitSERIAL_7O2
- 7 data bits, odd parity, 1 stop bitSERIAL_7E1
- 7 data bits, even parity, 1 stop bitSERIAL_7E2
- 7 data bits, even parity, 1 stop bitLIN_MASTER_13B
- 8 data bits, no parity, 1 stop bit, LIN Master mode with 13-bit break generationLIN_SLAVE_10B
- 8 data bits, no parity, 1 stop bit, LIN Slave mode with 10-bit break detectionLIN_SLAVE_11B
- 8 data bits, no parity, 1 stop bit, LIN Slave mode with 11-bit break detectionNOTE: SERIAL_7N1 or (SERIAL_DATA_BITS_7 | SERIAL_PARITY_NO | SERIAL_STOP_BITS_1) is NOT supported
Alternatively, configuration may be constructed manually by ORing (|
) the following configuration constants:
Data bits:
SERIAL_DATA_BITS_7
- 7 data bitsSERIAL_DATA_BITS_8
- 8 data bitsSERIAL_DATA_BITS_9
- 9 data bitsStop bits:
SERIAL_STOP_BITS_1
- 1 stop bitSERIAL_STOP_BITS_2
- 2 stop bitsSERIAL_STOP_BITS_0_5
- 0.5 stop bitsSERIAL_STOP_BITS_1_5
- 1.5 stop bitsParity:
SERIAL_PARITY_NO
- no paritySERIAL_PARITY_EVEN
- even paritySERIAL_PARITY_ODD
- odd parityHardware flow control, available only on Serial2 (CTS
- A7
, RTS
- RGBR
):
SERIAL_FLOW_CONTROL_NONE
- no flow controlSERIAL_FLOW_CONTROL_RTS
- RTS flow controlSERIAL_FLOW_CONTROL_CTS
- CTS flow controlSERIAL_FLOW_CONTROL_RTS_CTS
- RTS/CTS flow controlLIN configuration:
LIN_MODE_MASTER
- LIN MasterLIN_MODE_SLAVE
- LIN SlaveLIN_BREAK_13B
- 13-bit break generationLIN_BREAK_10B
- 10-bit break detectionLIN_BREAK_11B
- 11-bit break detectionNOTE: LIN break detection may be enabled in both Master and Slave modes.
NOTE Since 0.6.0: When USBSerial1
is enabled by calling USBSerial1.begin()
in setup()
or during normal application execution, the device will quickly disconnect from Host and connect back with USBSerial1
enabled. If such behavior is undesirable, USBSerial1
may be enabled with STARTUP()
macro, which will force the device to connect to the Host with both Serial
and USBSerial1
by default.
// EXAMPLE USAGE
STARTUP(USBSerial1.begin());
void setup()
{
while(!Serial.isConnected())
Particle.process();
Serial.println("Hello Serial!");
while(!USBSerial1.isConnected())
Particle.process();
USBSerial1.println("Hello USBSerial1!");
}
Disables serial channel.
When used with hardware serial channels (Serial1, Serial2, etc.), disables serial communication, allowing channel's RX and TX pins to be used for general input and output. To re-enable serial communication, call SerialX.begin()
.
Since 0.6.0:
When used with USB serial channels (Serial
or USBSerial1
), end()
will cause the device to quickly disconnect from Host and connect back without the selected serial channel.
// SYNTAX
Serial1.end();
Get the number of bytes (characters) available for reading from the serial port. This is data that's already arrived and stored in the serial receive buffer.
The receive buffer size for hardware UART serial channels (Serial1, Serial2, etc.) is 128 bytes on Gen 3 (Argon, Boron, B-Series SoM, Tracker SoM) and 64 or 128 bytes depending on the UART mode on Gen 2 (Photon, P1, Electron, E-Series). Since 3.2.0: See also acquireSerial1Buffer
.
For USB serial (Serial, USBSerial1), the receive buffer is 256 bytes. Also see acquireSerialBuffer
acquireSerial1Buffer
.
// EXAMPLE USAGE
void setup()
{
Serial.begin(9600);
Serial1.begin(9600);
}
void loop()
{
// read from port 0, send to port 1:
if (Serial.available())
{
int inByte = Serial.read();
Serial1.write(inByte);
}
// read from port 1, send to port 0:
if (Serial1.available())
{
int inByte = Serial1.read();
Serial.write(inByte);
}
}
Since 0.4.9: Available on Serial1, Serial2, etc..
Since 0.5.0: Available on USB Serial (Serial)
Since 0.6.0: Available on USBSerial1
Retrieves the number of bytes (characters) that can be written to this serial port without blocking.
If blockOnOverrun(false)
has been called, the method returns the number of bytes that can be written to the buffer without causing buffer overrun, which would cause old data to be discarded and overwritten.
// SYNTAX
HAL_USB_USART_Config acquireSerialBuffer()
{
HAL_USB_USART_Config conf = {0};
// The usable buffer size will be 128
static uint8_t serial_rx_buffer[129];
static uint8_t serial_tx_buffer[129];
conf.rx_buffer = serial_rx_buffer;
conf.tx_buffer = serial_tx_buffer;
conf.rx_buffer_size = 129;
conf.tx_buffer_size = 129;
return conf;
}
HAL_USB_USART_Config acquireUSBSerial1Buffer()
{
HAL_USB_USART_Config conf = {0};
// The usable buffer size will be 128
static uint8_t usbserial1_rx_buffer[129];
static uint8_t usbserial1_tx_buffer[129];
conf.rx_buffer = usbserial1_rx_buffer;
conf.tx_buffer = usbserial1_tx_buffer;
conf.rx_buffer_size = 129;
conf.tx_buffer_size = 129;
return conf;
}
Since 0.6.0:
It is possible for the application to allocate its own buffers for Serial
(USB serial) by implementing acquireSerialBuffer
. Minimum receive buffer size is 65 bytes.
On Gen 2 devices (Photon, P1, Electron. E-Series), the USBSerial1
receive buffer can be resized using acquireUSBSerial1Buffer
. Minimum receive buffer size is 65 bytes.
Since 3.2.0: This is also available for hardware UART ports like Serial1
, Serial2
, etc, see acquireSerial1Buffer
.
// SYNTAX
hal_usart_buffer_config_t acquireSerial1Buffer()
{
#if !HAL_PLATFORM_USART_9BIT_SUPPORTED
const size_t bufferSize = 64;
#else
// If 9-bit mode is supported (e.g. on Gen 2 platforms)
// and it's planned to use this mode, it's necessary to allocate
// 2x the number of bytes.
const size_t bufferSize = 64 * sizeof(uint16_t);
#endif // HAL_PLATFORM_USART_9BIT_SUPPORTED
hal_usart_buffer_config_t config = {
.size = sizeof(hal_usart_buffer_config_t),
.rx_buffer = new (std::nothrow) uint8_t[bufferSize],
.rx_buffer_size = bufferSize,
.tx_buffer = new (std::nothrow) uint8_t[bufferSize],
.tx_buffer_size = bufferSize
};
return config;
}
hal_usart_buffer_config_t acquireSerial2Buffer()
{
const size_t bufferSize = 64;
hal_usart_buffer_config_t config = {
.size = sizeof(hal_usart_buffer_config_t),
.rx_buffer = new (std::nothrow) uint8_t[bufferSize],
.rx_buffer_size = bufferSize,
.tx_buffer = new (std::nothrow) uint8_t[bufferSize],
.tx_buffer_size = bufferSize
};
return config;
}
Since 3.2.0:
It is possible for the application to allocate its own buffers for Serial1
and other hardware UART ports by implementing acquireSerial1Buffer
, acquireSerial2Buffer
etc. Minimum receive buffer size is 64 bytes.
Depending on UART mode it might be required to allocate double the number of bytes (e.g. for 9-bit modes).
Since 0.4.9: Available on Serial1, Serial2, ....
Since 0.5.0: Available on USB Serial (Serial)
Since 0.6.0: Available on USBSerial1
on Gen 2 devices (Photon, P1, Electron, E-Series)
Defines what should happen when calls to write()/print()/println()/printlnf()
that would overrun the buffer.
blockOnOverrun(true)
- this is the default setting. When there is no room in the buffer for the data to be written, the program waits/blocks until there is room. This avoids buffer overrun, where data that has not yet been sent over serial is overwritten by new data. Use this option for increased data integrity at the cost of slowing down realtime code execution when lots of serial data is sent at once.
blockOnOverrun(false)
- when there is no room in the buffer for data to be written, the data is written anyway, causing the new data to replace the old data. This option is provided when performance is more important than data integrity.
// EXAMPLE - fast and furious over Serial1
Serial1.blockOnOverrun(false);
Serial1.begin(115200);
A family of application-defined functions that are called whenever there is data to be read from a serial peripheral.
Serial
USBSerial1
if this port is availableSerial1
Serial2
if this port is availableSerial4
if this port is availableSerial5
if this port is availableThe serialEvent
functions are called in between calls to the application loop()
. This means that if loop()
runs for a long time due to delay()
calls or other blocking calls the serial buffer might become full between subsequent calls to serialEvent
and serial characters might be lost. Avoid long delay()
calls in your application if using serialEvent
.
Since serialEvent
functions are an
extension of the application loop, it is ok to call any functions that you would also call from loop()
. Because of this, there is little advantage to using serial events over just reading serial from loop().
// EXAMPLE - echo all characters typed over serial
void setup()
{
Serial.begin(9600);
}
void serialEvent()
{
char c = Serial.read();
Serial.print(c);
}
Returns the next byte (character) of incoming serial data without removing it from the internal serial buffer. That is, successive calls to peek() will return the same character, as will the next call to read()
.
// SYNTAX
Serial.peek();
Serial1.peek();
peek()
returns the first byte of incoming serial data available (or -1
if no data is available) - int
Writes binary data to the serial port. This data is sent as a byte or series of bytes; to send the characters representing the digits of a number use the print()
function instead.
// SYNTAX
Serial.write(val);
Serial.write(str);
Serial.write(buf, len);
// EXAMPLE USAGE
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.write(45); // send a byte with the value 45
int bytesSent = Serial.write(“hello”); //send the string “hello” and return the length of the string.
}
Parameters:
val
: a value to send as a single bytestr
: a string to send as a series of bytesbuf
: an array to send as a series of byteslen
: the length of the bufferwrite()
will return the number of bytes written, though reading that number is optional.
Reads incoming serial data.
// SYNTAX
Serial.read();
Serial1.read();
read()
returns the first byte of incoming serial data available (or -1 if no data is available) - int
// EXAMPLE USAGE
int incomingByte = 0; // for incoming serial data
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
// send data only when you receive data:
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
// say what you got:
Serial.print("I received: ");
Serial.println(incomingByte, DEC);
}
}
Prints data to the serial port as human-readable ASCII text. This command can take many forms. Numbers are printed using an ASCII character for each digit. Floats are similarly printed as ASCII digits, defaulting to two decimal places. Bytes are sent as a single character. Characters and strings are sent as is. For example:
An optional second parameter specifies the base (format) to use; permitted values are BIN (binary, or base 2), OCT (octal, or base 8), DEC (decimal, or base 10), HEX (hexadecimal, or base 16). For floating point numbers, this parameter specifies the number of decimal places to use. For example:
Prints data to the serial port as human-readable ASCII text followed by a carriage return character (ASCII 13, or '\r') and a newline character (ASCII 10, or '\n'). This command takes the same forms as Serial.print()
.
// SYNTAX
Serial.println(val);
Serial.println(val, format);
Parameters:
val
: the value to print - any data typeformat
: specifies the number base (for integral data types) or number of decimal places (for floating point types)println()
returns the number of bytes written, though reading that number is optional - size_t (long)
// EXAMPLE
//reads an analog input on analog in A0, prints the value out.
int analogValue = 0; // variable to hold the analog value
void setup()
{
// Make sure your Serial Terminal app is closed before powering your device
Serial.begin(9600);
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
}
void loop() {
// read the analog input on pin A0:
analogValue = analogRead(A0);
// print it out in many formats:
Serial.println(analogValue); // print as an ASCII-encoded decimal
Serial.println(analogValue, DEC); // print as an ASCII-encoded decimal
Serial.println(analogValue, HEX); // print as an ASCII-encoded hexadecimal
Serial.println(analogValue, OCT); // print as an ASCII-encoded octal
Serial.println(analogValue, BIN); // print as an ASCII-encoded binary
// delay 10 milliseconds before the next reading:
delay(10);
}
Since 0.4.6:
Provides printf-style formatting over serial.
printf
allows strings to be built by combining a number of values with text.
Serial.printf("Reading temperature sensor at %s...", Time.timeStr().c_str());
float temp = readTemp();
Serial.printf("the temperature today is %f Kelvin", temp);
Serial.println();
Running this code prints:
Reading temperature sensor at Thu 01 Oct 2015 12:34...the temperature today is 293.1 Kelvin.
The last printf()
call could be changed to printlnf()
to avoid a separate call to println()
.
Since 0.4.6:
formatted output followed by a newline. Produces the same output as printf which is then followed by a newline character, so to that subsequent output appears on the next line.
Waits for the transmission of outgoing serial data to complete.
// SYNTAX
Serial.flush();
Serial1.flush();
flush()
neither takes a parameter nor returns anything.
Puts Serial1 into half-duplex mode. In this mode both the transmit and receive are on the TX pin. This mode can be used for a single wire bus communications scheme between microcontrollers.
// SYNTAX
Serial1.halfduplex(true); // Enable half-duplex mode
Serial1.halfduplex(false); // Disable half-duplex mode
// EXAMPLE
// Initializes Serial1 at 9600 baud and enables half duplex mode
Serial1.begin(9600);
Serial1.halfduplex(true);
halfduplex()
takes one argument: true
enables half-duplex mode, false
disables half-duplex mode
halfduplex()
returns nothing
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Half duplex mode is only available on Gen 2 devices (Photon, P1, Electron, E-Series) on hardware UART ports (Serial1, Serial2, ...).
// EXAMPLE USAGE
void setup()
{
Serial.begin(); // open serial over USB
while(!Serial.isConnected()) // wait for Host to open serial port
Particle.process();
Serial.println("Hello there!");
}
Another technique is to use waitFor
which makes it easy to time-limit the waiting period.
// EXAMPLE USAGE
void setup()
{
Serial.begin(); // open serial over USB
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
Serial.println("Hello there!");
}
Since 0.5.3 Available on Serial
.
Since 0.6.0 Available on Serial
and USBSerial1
.
Used to check if host has serial port (virtual COM port) open.
Returns:
true
when Host has virtual COM port open.The USB serial objects does not have built-in thread-safety. If you want to read or write the object from multiple threads, including from a software timer, you must lock and unlock it to prevent data from multiple thread from being interleaved or corrupted.
A call to lock lock()
must be balanced with a call to unlock()
and not be nested. To make sure every lock is released, it's good practice to use WITH_LOCK
like this:
// EXAMPLE USAGE
void loop()
{
WITH_LOCK(Serial) {
Serial.println("Hello there!");
}
}
Never use lock()
or WITH_LOCK()
within a SINGLE_THREADED_BLOCK()
as deadlock can occur.
The UART serial objects such as Serial1
allow multiple threads to read and write at the byte level. They do not support WITH_LOCK, however, so if you need to control access at the line, transaction, block, etc. level you need to implement an external mutex.
Unlocks the Serial mutex. See lock()
.
// EXAMPLE USAGE
// Use STARTUP() macro to avoid USB disconnect/reconnect (see begin() documentation)
STARTUP(Mouse.begin());
void setup() {
// Set screen size to 1920x1080 (to scale [0, 32767] absolute Mouse coordinates)
Mouse.screenSize(1920, 1080);
// Move mouse to the center of the screen and click left button
Mouse.moveTo(1920 / 2, 1080 / 2);
Mouse.click(MOUSE_LEFT);
// Move mouse from the current position by 100 points (not pixels) left
Mouse.move(-100, 0);
// Press right mouse button (and leave it pressed)
Mouse.press(MOUSE_RIGHT);
// Scroll wheel in the negative direction
Mouse.scroll(-127);
// Release right mouse button
Mouse.release(MOUSE_RIGHT);
}
void loop() {
}
Since 0.6.0:
This object allows devices to act as a native USB HID Mouse.
In terms of USB HID, the device presents itself as two separate devices: Mouse (supporting relative movement) and Digitizer (supporting absolute movement).
Full capabilities include:
NOTE: Linux X11 doesn't support HID devices reporting both absolute and relative coordinates. By default only absolute movement is possible by using Mouse.moveTo()
. In order for regular relative Mouse.move()
to work, a call to Mouse.enableMoveTo(false)
is required.
Keyboard and Mouse support is only available on some devices and Device OS versions:
Device | Device OS Version |
---|---|
P2, Photon 2 | 5.4.0 and later |
Photon, P1, Electron, E-Series | 0.6.0 and later |
Boron, B-Series SoM, Argon, Tracker, E404X | Not Supported |
// SYNTAX
Mouse.begin();
Initializes Mouse library and enables USB HID stack.
// Example
STARTUP(Mouse.begin());
void setup() {
// At this point the device is already connected to Host with Mouse enabled
}
NOTE: When Mouse.begin()
is called in setup()
or during normal application execution, the device will quickly disconnect from Host and connect back with USB HID enabled. If such behavior is undesirable, Mouse
may be enabled with STARTUP()
macro, which will force the device to connect to the Host after booting with Mouse
already enabled.
This function takes no parameters and does not return anything.
// SYNTAX
Mouse.end();
Disables USB Mouse functionality.
// Example
// Enable both Keyboard and Mouse on startup
STARTUP(Mouse.begin());
STARTUP(Keyboard.begin());
void setup() {
// A call to Mouse.end() here will not cause the device to disconnect and connect back to the Host
Mouse.end();
// Disabling both Keyboard and Mouse at this point will trigger the behavior explained in NOTE.
Keyboard.end();
}
NOTE: Calling Mouse.end()
will cause the device to quickly disconnect from Host and connect back without USB HID enabled if Keyboard
is disabled as well.
This function takes no parameters and does not return anything.
// SYNTAX
Mouse.move(x, y);
Mouse.move(x, y, wheel);
Moves the cursor relative to the current position.
Parameters:
x
: amount to move along the X axis - int16_t
[-32767, 32767]y
: amount to move along the Y axis - int16_t
[-32767, 32767]wheel
: amount to move the scroll wheel - int8_t
[-127, 127]move()
does not return anything.
// SYNTAX
Mouse.moveTo(x, y);
Moves the cursor to an absolute position. (0, 0) position is the top left corner of the screen. By default both X and Y axes span from 0 to 32767.
The default range [0, 32767] can be mapped to actual screen resolution by calling screenSize()
. After the call to screenSize()
, moveTo()
will accept screen coordinates and automatically map them to the default range.
Parameters:
x
: X coordinate - uint16_t
[0, 32767] (default)y
: Y coordinate - uint16_t
[0, 32767] (default)moveTo()
does not return anything.
// SYNTAX
Mouse.scroll(wheel);
Scrolls the mouse wheel by the specified amount.
Parameters:
wheel
: amount to move the scroll wheel - int8_t
[-127, 127]scroll()
does not return anything.
// SYNTAX
Mouse.click();
Mouse.click(button);
Momentarily clicks specified mouse button at the current cursor position. A click is a press()
quickly followed by release()
.
// EXAMPLE USAGE
// Click left mouse button
Mouse.click(MOUSE_LEFT);
// Click right mouse button
Mouse.click(MOUSE_RIGHT);
// Click middle mouse button
Mouse.click(MOUSE_MIDDLE);
// Click both left and right mouse buttons at the same time
Mouse.click(MOUSE_LEFT | MOUSE_RIGHT);
Parameters:
button
: which mouse button to click - uint8_t
- MOUSE_LEFT
(default), MOUSE_RIGHT
, MOUSE_MIDDLE
or any ORed (|
) combination of buttons for simultaneous clicksclick()
does not return anything.
// SYNTAX
Mouse.press();
Mouse.press(button);
Presses specified mouse button at the current cursor position and holds it pressed. A press can be cancelled by release()
.
// EXAMPLE USAGE
// Press left mouse button
Mouse.press(MOUSE_LEFT);
// Press right mouse button
Mouse.press(MOUSE_RIGHT);
// Press middle mouse button
Mouse.press(MOUSE_MIDDLE);
// Press both left and right mouse buttons at the same time
Mouse.press(MOUSE_LEFT | MOUSE_RIGHT);
Parameters:
button
: which mouse button to press - uint8_t
- MOUSE_LEFT
(default), MOUSE_RIGHT
, MOUSE_MIDDLE
or any ORed (|
) combination of buttons for simultaneous presspress()
does not return anything.
// SYNTAX
Mouse.release();
Mouse.release(button);
Releases previously pressed mouse button at the current cursor position.
// EXAMPLE USAGE
// Release left mouse button
Mouse.release(MOUSE_LEFT);
// Release right mouse button
Mouse.release(MOUSE_RIGHT);
// Release middle mouse button
Mouse.release(MOUSE_MIDDLE);
// Release both left and right mouse buttons at the same time
Mouse.release(MOUSE_LEFT | MOUSE_RIGHT);
Parameters:
button
: which mouse button to release - uint8_t
- MOUSE_LEFT
(default), MOUSE_RIGHT
, MOUSE_MIDDLE
or any ORed (|
) combination of buttons to release simultaneously. To release all buttons simultaneously, MOUSE_ALL
can also be used.release()
does not return anything.
// SYNTAX
Mouse.isPressed();
Mouse.isPressed(button);
This function checks the current state of mouse buttons and returns if they are currently pressed or not.
// EXAMPLE USAGE
bool pressed;
// Check if left mouse button is currently pressed
pressed = Mouse.isPressed(MOUSE_LEFT);
// Check if right mouse button is currently pressed
pressed = Mouse.isPressed(MOUSE_RIGHT);
// Check if middle mouse button is currently pressed
pressed = Mouse.isPressed(MOUSE_MIDDLE);
Parameters:
button
: which mouse button to check - uint8_t
- MOUSE_LEFT
(default), MOUSE_RIGHT
, MOUSE_MIDDLE
isPressed()
returns true
if provided button is currently pressed.
// SYNTAX
Mouse.screenSize(screenWidth, screenHeight);
Mouse.screenSize(screenWidth, screenHeight,
marginLeft, marginRight,
marginTop, marginBottom);
Mouse.screenSize(screenWidth, screenHeight,
std::array<4, float>);
Maps the default absolute movement range [0, 32767] used by moveTo()
to actual screen resolution. After setting the screen size, moveTo()
will accept screen coordinates and automatically map them to the default range.
// EXAMPLE USAGE
// Use STARTUP() macro to avoid USB disconnect/reconnect (see begin() documentation)
STARTUP(Mouse.begin());
void setup() {
// Set screen size to 1920x1080 (to scale [0, 32767] absolute Mouse coordinates)
Mouse.screenSize(1920, 1080);
// Move mouse to the center of the screen
Mouse.moveTo(1920 / 2, 1080 / 2);
}
void loop() {
}
Parameters:
screenWidth
: screen width in pixels - uint16_t
screenHeight
: screen height in pixels - uint16_t
marginLeft
: (optional) left screen margin in percent (e.g. 10.0) - float
marginRight
: (optional) right screen margin in percent (e.g. 10.0) - float
marginTop
: (optional) top screen margin in percent (e.g. 10.0) - float
marginBottom
: (optional) bottom screen margin in percent (e.g. 10.0) - float
screenSize()
does not return anything.
// SYNTAX
Mouse.enableMoveTo(false);
Mouse.enableMoveTo(true);
Disables or enables absolute mouse movement (USB HID Digitizer).
// EXAMPLE USAGE
// Use STARTUP() macro to avoid USB disconnect/reconnect (see begin() documentation)
STARTUP(Mouse.begin());
// Disable absolute mouse movement
STARTUP(Mouse.enableMoveTo(false));
void setup() {
// Move cursor by 100 points along X axis and by 100 points Y axis
Mouse.move(100, 100);
// Mouse.moveTo() calls do nothing
Mouse.moveTo(0, 0);
}
void loop() {
}
NOTE: Linux X11 doesn't support HID devices reporting both absolute and relative coordinates. By default only absolute movement is possible by using Mouse.moveTo()
. In order for regular relative Mouse.move()
to work, a call to Mouse.enableMoveTo(false)
is required.
NOTE: When Mouse.enableMoveTo()
is called in setup()
or during normal application execution, the device will quickly disconnect from Host and connect back with new settings. If such behavior is undesirable, moveTo()
may be disable or enabled with STARTUP()
macro, which will force the device to connect to the Host after booting with correct settings already in effect.
Parameters:
state
: true
to enable absolute movement functionality, false
to disable - bool
enableMoveTo()
does not return anything.
// EXAMPLE USAGE
// Use STARTUP() macro to avoid USB disconnect/reconnect (see begin() documentation)
STARTUP(Keyboard.begin());
void setup() {
// Type 'SHIFT+h', 'e', 'l', 'l', 'o', 'SPACE', 'w', 'o', 'r', 'l', 'd', 'ENTER'
Keyboard.println("Hello world!");
// Type 'SHIFT+t', 'e', 's', 't', 'SPACE', '1', '2', '3', '.', '4', '0', 'ENTER'
Keyboard.printf("%s %.2f\n", "Test", 123.4f);
// Quickly press and release Ctrl-Alt-Delete
Keyboard.click(KEY_DELETE, MOD_LCTRL | MOD_LALT);
// Press Ctrl, then Alt, then Delete and release them all
Keyboard.press(KEY_LCTRL);
Keyboard.press(KEY_LALT);
Keyboard.press(KEY_DELETE);
Keyboard.releaseAll();
}
void loop() {
}
Since 0.6.0:
This object allows your device to act as a native USB HID Keyboard.
Keyboard and Mouse support is only available on some devices and Device OS versions:
Device | Device OS Version |
---|---|
P2, Photon 2 | 5.4.0 and later |
Photon, P1, Electron, E-Series | 0.6.0 and later |
Boron, B-Series SoM, Argon, Tracker, E404X | Not Supported |
// SYNTAX
Keyboard.begin();
Initializes Keyboard library and enables USB HID stack.
// Example
STARTUP(Keyboard.begin());
void setup() {
// At this point the device is already connected to Host with Keyboard enabled
}
NOTE: When Keyboard.begin()
is called in setup()
or during normal application execution, the device will quickly disconnect from Host and connect back with USB HID enabled. If such behavior is undesirable, Keyboard
may be enabled with STARTUP()
macro, which will force the device to connect to the Host after booting with Keyboard
already enabled.
This function takes no parameters and does not return anything.
// SYNTAX
Keyboard.end();
Disables USB Keyboard functionality.
// Example
// Enable both Keyboard and Mouse on startup
STARTUP(Mouse.begin());
STARTUP(Keyboard.begin());
void setup() {
// A call to Mouse.end() here will not cause the device to disconnect and connect back to the Host
Mouse.end();
// Disabling both Keyboard and Mouse at this point will trigger the behavior explained in NOTE.
Keyboard.end();
}
NOTE: Calling Keyboard.end()
will cause the device to quickly disconnect from Host and connect back without USB HID enabled if Mouse
is disabled as well.
This function takes no parameters and does not return anything.
// SYNTAX
Keyboard.write(character);
Momentarily clicks a keyboard key. A click is a press()
quickly followed by release()
. This function works only with ASCII characters. ASCII characters are translated into USB HID keycodes according to the conversion table. For example ASCII character 'a' would be translated into 'a' keycode (leftmost middle row letter key on a QWERTY keyboard), whereas 'A' ASCII character would be sent as 'a' keycode with SHIFT modifier.
// EXAMPLE USAGE
STARTUP(Keyboard.begin());
void setup() {
const char hello[] = "Hello world!\n";
// This for-loop will type "Hello world!" followed by ENTER
for (int i = 0; i < strlen(hello); i++) {
Keyboard.write(hello[i]);
}
}
This function is used by print()
, println()
, printf()
, printlnf()
which provide an easy way to type text.
Parameters:
ch
: ASCII character - char
write()
does not return anything.
// SYNTAX
Keyboard.click(key);
Keyboard.click(key, modifiers);
Momentarily clicks a keyboard key as well as one or more modifier keys (e.g. ALT, CTRL, SHIFT etc.). A click is a press()
quickly followed by release()
. This function works only with USB HID keycodes (defined in enum UsbKeyboardScanCode
) and modifiers (defined in enum UsbKeyboardModifier
). Keyboard
implementation supports keycodes ranging from 0x04 (KEY_A / Keyboard a and A)
to 0xDD (KEY_KPHEX / Keypad Hexadecimal)
.
// EXAMPLE USAGE
STARTUP(Keyboard.begin());
void setup() {
// Quickly press and release Ctrl-Alt-Delete
Keyboard.click(KEY_DELETE, MOD_LCTRL | MOD_LALT);
}
Parameters:
key
: USB HID key code (see enum UsbKeyboardScanCode
) - uint16_t
modifier
: (optional) one or more ORed (|
) USB HID modifier codes (see enum UsbKeyboardModifier
- uint16_t
click()
does not return anything.
// SYNTAX
Keyboard.press(key);
Keyboard.press(key, modifier);
Presses specified keyboard key as well as one or more modifier keys and holds them pressed. A press can be cancelled by release()
or releaseAll()
.
Up to 8 keys can be pressed simultaneously. Modifier keys (e.g. CTRL, ALT, SHIFT etc) are sent separately and do not add to the currently pressed key count, i.e. it is possible to press and keep pressing 8 regular keyboard keys and all the modifiers (LSHIFT, LALT, LGUI, LCTRL, RSHIFT, RALT, RSHIFT, RCTRL) at the same time.
See Keyboard.click()
documentation for information about keycodes and modifier keys.
// EXAMPLE USAGE
STARTUP(Keyboard.begin());
void setup() {
// Press Ctrl, then Alt, then Delete and release them all
Keyboard.press(KEY_LCTRL);
Keyboard.press(KEY_LALT);
Keyboard.press(KEY_DELETE);
Keyboard.releaseAll();
}
Parameters:
key
: USB HID key code (see enum UsbKeyboardScanCode
) - uint16_t
modifier
: (optional) one or more ORed (|
) USB HID modifier codes (see enum UsbKeyboardModifier
- uint16_t
press()
does not return anything.
// SYNTAX
Keyboard.release(key);
Keyboard.release(key, modifier);
Releases previously pressed keyboard key as well as one or more modifier keys.
// EXAMPLE USAGE
STARTUP(Keyboard.begin());
void setup() {
// Press Delete and two modifiers (left ALT and left CTRL) simultaneously
Keyboard.press(KEY_DELETE, MOD_LCTRL | MOD_LALT);
// Release Delete and two modifiers (left ALT and left CTRL) simultaneously
Keyboard.release(KEY_DELETE, MOD_LCTRL | MOD_LALT);
}
See Keyboard.click()
documentation for information about keycodes and modifier keys.
Parameters:
key
: USB HID key code (see enum UsbKeyboardScanCode
) - uint16_t
modifier
: (optional) one or more ORed (|
) USB HID modifier codes (see enum UsbKeyboardModifier
- uint16_t
release()
does not return anything.
// SYNTAX
Keyboard.releaseAll();
Releases any previously pressed keyboard keys and modifier keys.
// EXAMPLE USAGE
STARTUP(Keyboard.begin());
void setup() {
// Press Ctrl, then Alt, then Delete and release them all
Keyboard.press(KEY_LCTRL);
Keyboard.press(KEY_LALT);
Keyboard.press(KEY_DELETE);
Keyboard.releaseAll();
}
This function takes no parameters and does not return anything.
See Keyboard.write()
and Serial.print()
documentation.
See Keyboard.write()
and Serial.println()
documentation.
See Keyboard.write()
and Serial.printf()
documentation.
See Keyboard.write()
and Serial.printlnf()
documentation.
This object allows you to communicate with SPI ("Serial Peripheral Interface") devices, with the device as the master device.
Interface | Maximum Speed (Gen2) | Maximum Speed (Gen 3) | Maximum Peripheral Devices |
---|---|---|---|
UART Serial | 230 Kbit/sec | 1 Mbit/sec | 1 (point-to-point) |
I2C | 400 Kbit/sec | 400 Kbit/sec | Many (limited by addresses) |
SPI | 60 Mbit/sec | 32 Mbit/sec | Many (limited by CS GPIO pins) |
SPI slave mode is supported as well (since DeviceOS 0.5.0). On Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker SoM), SPI slave can only be used on SPI1.
The hardware SPI pin functions, which can
be used via the SPI
object, are mapped as follows:
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On the Argon, Boron, and Xenon:
SS
=> A5 (D14)
(but can use any available GPIO)SCK
=> SCK (D13)
MISO
=> MISO (D11)
MOSI
=> MOSI (D12)
On the B-Series SoM:
SS
=> D8
(but can use any available GPIO)SCK
=> SCK (D13)
MISO
=> MISO (D11)
MOSI
=> MOSI (D12)
On the Tracker SoM:
SS
=> A7
/D7
(but can use any available GPIO)SCK
=> A6
/D6
MISO
=> A5
/D5
MOSI
=> A4
/D4
There is a second hardware SPI interface available, which can
be used via the SPI1
object. This second port is mapped as follows:
SCK
=> D2
MOSI
=> D3
MISO
=> D4
Note: On Gen 3 devices, the SPI1 pins different than 2nd-generation (Photon/Electron), so you cannot use SPI1 on a Gen 3 device with the classic adapter.
P2 and Photon 2 Devices (RTL872x):
The P2 and Photon 2 supports two SPI (serial peripheral interconnect) ports.
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
40 | S0 / D15 | S0 GPIO, PWM, SPI MOSI, Serial3 TX, I2S MCLK. (Was P1S0 on P1.) | SPI (MOSI) | PA[12] |
41 | S1 / D16 | S1 GPIO, PWM, SPI MISO, Serial3 RX. (Was P1S1 on P1.) | SPI (MISO) | PA[13] |
42 | S2 / D17 | S2 GPIO, SPI SCK, Serial3 RTS. (Was P1S2 on P1.) | SPI (SCK) | PA[14] |
44 | S3 / D18 | S3 GPIO, I2S TX. (Was P1S3 on P1.), SPI SS | SPI (SS) | PB[26] |
45 | D2 | D2 GPIO, Serial2 RTS, SPI1 MOSI | SPI1 (MOSI) | PA[16] |
51 | D3 | D3 GPIO, Serial2 CTS, SPI1 MISO | SPI1 (MISO) | PA[17] |
52 | D4 | D4 GPIO, Serial2 TX, SPI1 SCK | SPI1 (SCK) | PA[18] |
53 | D5 | D5 GPIO, Serial2 RX, SPI1 SS | SPI1 (SS) | PA[19] |
If you are using SPI, Device OS 5.3.1 or later is recommended. Prior to that version, SPI ran at half of the set speed, and SPI1 ran at double the set speed. Timing has also been improved for large DMA transfers; prior to 5.3.1, there could be 1 µs gaps for every 16 bytes of data transferred.
M-SoM Devices (RTL872x):
The M-SoM supports two SPI (serial peripheral interconnect) ports.
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
36 | TX / D9 | Serial TX, PWM, GPIO, SPI1 MOSI, I2S MCLK | SPI1 (MOSI) | PA[12] |
38 | RX / D10 | Serial RX, PWM, GPIO, SPI1 MISO | SPI1 (MISO) | PA[13] |
40 | D3 | D3 GPIO, Serial1 CTS flow control (optional), SPI1 SS | SPI1 (SS) | PA[15] |
42 | D2 | D2 GPIO, Serial RTS flow control (optional), SPI1 SCK | SPI1 (SCK) | PA[14] |
48 | D8 | D8 GPIO, SPI SS | SPI (SS) | PA[19] |
50 | MISO / D11 | D11 GPIO, PWM, SPI MISO | SPI (MISO) | PA[17] |
52 | MOSI / D12 | D12 GPIO, PWM, SPI MOSI | SPI (MOSI) | PA[16] |
54 | SCK / D13 | D13 GPIO, SPI SCK | SPI (SCK) | PA[18] |
If you are using SPI, Device OS 5.3.1 or later is recommended. Prior to that version, SPI ran at half of the set speed, and SPI1 ran at double the set speed. Timing has also been improved for large DMA transfers; prior to 5.3.1, there could be 1 µs gaps for every 16 bytes of data transferred.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
SS
=> A2
(default)SCK
=> A3
MISO
=> A4
MOSI
=> A5
There is a second hardware SPI interface available, which can
be used via the SPI1
object. This second port is mapped as follows:
SS
=> D5
(default)SCK
=> D4
MISO
=> D3
MOSI
=> D2
Additionally on the Electron and E-Series, there is an alternate pin location for the second SPI interface, which can
be used via the SPI2
object. As this is just an alternate pin mapping you cannot use both SPI1
and SPI2
at the same time.
This alternate location is mapped as follows:
SS
=> D5
(default)SCK
=> C3
MISO
=> C2
MOSI
=> C1
Initializes the SPI bus by setting SCK, MOSI, and a user-specified slave-select pin to outputs, MISO to input. SCK is pulled either high or low depending on the configured SPI data mode (default high for SPI_MODE3
). Slave-select is pulled high.
Note: The SPI firmware ONLY initializes the user-specified slave-select pin as an OUTPUT
. The user's code must control the slave-select pin with digitalWrite()
before and after each SPI transfer for the desired SPI slave device. Calling SPI.end()
does NOT reset the pin mode of the SPI pins.
// SYNTAX
SPI.begin(ss);
SPI1.begin(ss);
// Example with no SS pin configured:
SPI.begin(PIN_INVALID);
Where, the parameter ss
is the SPI
device slave-select pin to initialize. The default SS pin varies by port and device, see above.
// Example using SPI1, with D5 as the SS pin:
SPI1.begin();
// or
SPI1.begin(D5);
Since 0.5.0:
Initializes the device SPI peripheral in master or slave mode.
Note: MISO, MOSI and SCK idle in high-impedance state when SPI peripheral is configured in slave mode and the device is not selected.
Parameters:
mode
: SPI_MODE_MASTER
or SPI_MODE_SLAVE
ss_pin
: slave-select pin to initialize. The default SS pin varies by port and device, see above.// Example using SPI in master mode, with the default SS pin:
SPI.begin(SPI_MODE_MASTER);
// Example using SPI1 in slave mode, with D5 as the SS pin
SPI1.begin(SPI_MODE_SLAVE, D5);
// Example using SPI2 in slave mode, with C0 as the SS pin
// (Electron and E-Series only)
SPI2.begin(SPI_MODE_SLAVE, C0);
On Gen 3 devices (Argon, Boron, and Xenon), SPI slave can only be used on SPI1. It is not supported on SPI. The maximum speed is 8 MHz on SPI1.
Disables the SPI bus (leaving pin modes unchanged).
// SYNTAX
SPI.end();
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.end()
.
Sets the order of the bits shifted out of and into the SPI bus, either LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first).
// SYNTAX
SPI.setBitOrder(order);
Where, the parameter order
can either be LSBFIRST
or MSBFIRST
.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.setBitOrder()
.
Sets the SPI clock speed. The value can be specified as a direct value, or as as a value plus a multiplier.
// SYNTAX
SPI.setClockSpeed(value, scale);
SPI.setClockSpeed(frequency);
// EXAMPLE
// Set the clock speed as close to 15MHz (but not over)
SPI.setClockSpeed(15, MHZ);
SPI.setClockSpeed(15000000);
The clock speed cannot be set to any arbitrary value, but is set internally by using a
divider (see SPI.setClockDivider()
) that gives the highest clock speed not greater
than the one specified.
This method can make writing portable code easier, since it specifies the clock speed absolutely, giving comparable results across devices. In contrast, specifying the clock speed using dividers is typically not portable since is dependent upon the system clock speed.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.setClockSpeed()
.
Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker SoM) support SPI speeds up to 32 MHz on SPI and 8 MHz on SPI1.
This function aims to ease porting code from other platforms by setting the clock speed that
SPI.setClockDivider
is relative to.
For example, when porting an Arduino SPI library, each to SPI.setClockDivider()
would
need to be changed to reflect the system clock speed of the device being used.
This can be avoided by placing a call to SPI.setClockDividerReference()
before the other SPI calls.
// setting divider reference
// place this early in the library code
SPI.setClockDividerReference(SPI_CLK_ARDUINO);
// then all following calls to setClockDivider() will give comparable clock speeds
// to running on the Arduino Uno
// sets the clock to as close to 4MHz without going over.
SPI.setClockDivider(SPI_CLK_DIV4);
The default clock divider reference is the system clock.
On Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker SoM), system clock speed is 64 MHz.
On the Gen 2 (Photon, P1, Electron, and E-Series), the system clock speeds are:
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.setClockDividerReference()
.
Sets the SPI clock divider relative to the selected clock reference. The available dividers are 2, 4, 8, 16, 32, 64, 128 or 256. The default setting is SPI_CLOCK_DIV4, which sets the SPI clock to one-quarter the frequency of the system clock.
// SYNTAX
SPI.setClockDivider(divider);
Where the parameter, divider
can be:
SPI_CLOCK_DIV2
SPI_CLOCK_DIV4
SPI_CLOCK_DIV8
SPI_CLOCK_DIV16
SPI_CLOCK_DIV32
SPI_CLOCK_DIV64
SPI_CLOCK_DIV128
SPI_CLOCK_DIV256
The clock reference varies depending on the device.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.setClockDivider()
.
Sets the SPI data mode: that is, clock polarity and phase. See the Wikipedia article on SPI for details.
// SYNTAX
SPI.setDataMode(mode);
Where the parameter, mode
can be:
SPI_MODE0
SPI_MODE1
SPI_MODE2
SPI_MODE3
Transfers one byte over the SPI bus, both sending and receiving.
// SYNTAX
SPI.transfer(val);
SPI1.transfer(val);
Where the parameter val
, can is the byte to send out over the SPI bus.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.setDataMode()
.
transfer()
acquires the SPI peripheral lock before sending a byte, blocking other threads from using the selected SPI peripheral during the transmission. If you call transfer
in a loop, call beginTransaction()
function before the loop and endTransaction()
after the loop, to avoid having another thread interrupt your SPI operations.
For transferring a large number of bytes, this form of transfer() uses DMA to speed up SPI data transfer and at the same time allows you to run code in parallel to the data transmission. The function initializes, configures and enables the DMA peripheral’s channel and stream for the selected SPI peripheral for both outgoing and incoming data and initiates the data transfer. If a user callback function is passed then it will be called after completion of the DMA transfer. This results in asynchronous filling of RX buffer after which the DMA transfer is disabled till the transfer function is called again. If NULL is passed as a callback then the result is synchronous i.e. the function will only return once the DMA transfer is complete.
Note: The SPI protocol is based on a one byte OUT / one byte IN interface. For every byte expected to be received, one (dummy, typically 0x00 or 0xFF) byte must be sent.
// PROTOTYPE
void transfer(const void* tx_buffer, void* rx_buffer, size_t length, wiring_spi_dma_transfercomplete_callback_t user_callback);
typedef void (*wiring_spi_dma_transfercomplete_callback_t)(void);
// SYNTAX
SPI.transfer(tx_buffer, rx_buffer, length, myFunction);
Parameters:
tx_buffer
: array of Tx bytes that is filled by the user before starting the SPI transfer. If NULL
, default dummy 0xFF bytes will be clocked out.rx_buffer
: array of Rx bytes that will be filled by the slave during the SPI transfer. If NULL
, the received data will be discarded.length
: number of data bytes that are to be transferredmyFunction
: user specified function callback to be called after completion of the SPI DMA transfer. It takes no argument and returns nothing, e.g.: void myHandler()
. Can be NULL if not using a callback.NOTE: tx_buffer
and rx_buffer
sizes MUST be identical (of size length
)
Since 0.5.0 When SPI peripheral is configured in slave mode, the transfer will be canceled when the master deselects this slave device. The user application can check the actual number of bytes received/transmitted by calling available()
.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.transfer()
.
If you are using the callback, it is called as an interrupt service routine (ISR) and there are many restrictions when calling from an interrupt context. Specifically for SPI, you cannot SPI.endTransaction()
or start another DMA transaction using SPI.transfer()
.
You should not set the CS pin high from the callback ISR. The callback is called when the DMA transaction is completed, which can be before the data is actually transmitted out of the SPI FIFO.
Thus in practice it's often better to perform your SPI operations from a worker thread instead of chained interrupts if you want asynchronous execution.
Things you should not do from an ISR:
%f
(float) value.Since 3.0.0:
On Gen 3 devices with Device OS 3.0.0 and later you can pass byte arrays stored in the program flash in tx_buffer
. The nRF52 DMA controller does not support transferring directly out of flash memory, but in Device OS 3.0.0 the data will be copied in chunks to a temporary buffer in RAM automatically. Prior to 3.0.0 you had to do this manually in your code.
Since 0.5.0:
Aborts the configured DMA transfer and disables the DMA peripheral’s channel and stream for the selected SPI peripheral for both outgoing and incoming data.
Note: The user specified SPI DMA transfer completion function will still be called to indicate the end of DMA transfer. The user application can check the actual number of bytes received/transmitted by calling available()
.
Since 0.5.0:
Registers a function to be called when the SPI master selects or deselects this slave device by pulling configured slave-select pin low (selected) or high (deselected). This function is called as an interrupt service routine (ISR) so you must be careful about what calls you make from it.
On Gen 3 devices (Argon, Boron, and Xenon), SPI slave can only be used on SPI1.
// SYNTAX
SPI.onSelect(myFunction);
void myFunction(uint8_t state) {
// called when selected or deselected
}
Parameters: handler
: the function to be called when the slave is selected or deselected; this should take a single uint8_t parameter (the current state: 1
- selected, 0
- deselected) and return nothing, e.g.: void myHandler(uint8_t state)
// SPI1 slave example
static uint8_t rx_buffer[64];
static uint8_t tx_buffer[64];
static uint32_t select_state = 0x00;
static uint32_t transfer_state = 0x00;
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
void onTransferFinished() {
transfer_state = 1;
}
void onSelect(uint8_t state) {
if (state)
select_state = state;
}
/* executes once at startup */
void setup() {
for (int i = 0; i < sizeof(tx_buffer); i++)
tx_buffer[i] = (uint8_t)i;
SPI1.onSelect(onSelect);
SPI1.begin(SPI_MODE_SLAVE, A5);
}
/* executes continuously after setup() runs */
void loop() {
while (1) {
while(select_state == 0);
select_state = 0;
transfer_state = 0;
SPI1.transfer(tx_buffer, rx_buffer, sizeof(rx_buffer), onTransferFinished);
while(transfer_state == 0);
if (SPI1.available() > 0) {
Log.dump(LOG_LEVEL_TRACE, rx_buffer, SPI1.available());
Log.info("Received %d bytes", SPI1.available());
}
}
}
On Gen 2 devices, this example can use SPI
instead of SPI1
. Makes sure you change all of the calls!
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
You can call the DMA-based SPI.transfer from an onSelect handler. You should use a callback, because if you don't, it will busy wait.
You must, however:
The problem is that in the prohibited cases, a temporary buffer is located in RAM, and heap allocation is not allowed from an ISR.
Since 0.5.0:
Returns the number of bytes available for reading in the rx_buffer
supplied in transfer()
. In general, it returns the actual number of bytes received/transmitted during the ongoing or finished DMA transfer.
// SYNTAX
SPI.available();
Returns the number of bytes available.
Since 0.6.2:
The SPISettings
object specifies the SPI peripheral settings. This object can be used with beginTransaction()
function and can replace separate calls to setClockSpeed()
, setBitOrder()
and setDataMode()
.
SPISettings | __SPISettings | |
---|---|---|
Available in Device OS | 0.6.1 | 0.6.2 |
Requires #include "Arduino.h |
0.6.2 - 1.5.2 | No |
Available in 2.0.0 and later | ✓ | ✓ |
You should use SPISettings()
with 2.0.0 and later, with or without #include "Arduino.h
.
__SPISettings()
can be used in 0.6.2 and later for backward compatibility with those versions of Device OS, but is unnecessary for 2.0.0 and later.
// SYNTAX
SPI.beginTransaction(SPISettings(4*MHZ, MSBFIRST, SPI_MODE0));
// Pre-declared SPISettings object
SPISettings settings(4*MHZ, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(settings);
Parameters:
clockSpeed
: maximum SPI clock speed (see setClockSpeed()
)bitOrder
: bit order of the bits shifted out of and into the SPI bus, either LSBFIRST
(least significant bit first) or MSBFIRST
(most-significant bit first)dataMode
: SPI_MODE0
, SPI_MODE1
, SPI_MODE2
or SPI_MODE3
(see setDataMode()
)Since 0.6.1:
Reconfigures the SPI peripheral with the supplied settings (see SPISettings
documentation).
In addition to reconfiguring the SPI peripheral, beginTransaction()
also acquires the SPI peripheral lock, blocking other threads from using the selected SPI peripheral until endTransaction()
is called. See Synchronizing Access to Shared System Resources section for additional information on shared resource locks.
It is required that you use beginTransaction()
and endTransaction()
if:
You must not use beginTransaction()
within a SINGLE_THREADED_BLOCK
as deadlock can occur.
// SYNTAX
SPI.beginTransaction(SPISettings(4*MHZ, MSBFIRST, SPI_MODE0));
// Pre-declared SPISettings object
SPI.beginTransaction(settings);
Parameters:
settings
: SPISettings
object with chosen settingsReturns: Negative integer in case of an error.
You should set your SPI CS/SS pin between the calls to beginTransaction()
and endTransaction()
. You typically use pinResetFast()
right after beginTransaction()
and pinSetFast()
right before endTransaction()
.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.beginTransaction()
.
Since 0.6.1:
Releases the SPI peripheral.
This function releases the SPI peripheral lock, allowing other threads to use it. See Synchronizing Access to Shared System Resources section for additional information on shared resource locks.
// SYNTAX
SPI.endTransaction();
Returns: Negative integer in case of an error.
Note that you must use the same SPI
object as used with SPI.begin()
so if you used SPI1.begin()
also use SPI1.endTransaction()
.
(inherits from Stream
)
This object allows you to communicate with I2C / TWI (Two Wire Interface) devices. For an in-depth tutorial on I2C, see learn more about I2C.
Interface | Maximum Speed (Gen2) | Maximum Speed (Gen 3) | Maximum Peripheral Devices |
---|---|---|---|
UART Serial | 230 Kbit/sec | 1 Mbit/sec | 1 (point-to-point) |
I2C | 400 Kbit/sec | 400 Kbit/sec | Many (limited by addresses) |
SPI | 60 Mbit/sec | 32 Mbit/sec | Many (limited by CS GPIO pins) |
The I2C bus must have pull-up resistors, one on the SDA line and one on the SCL line. They're typically 4.7K or 10K ohm, but should be in the range of 2K to 10K.
Many of the breakout boards you can buy at Adafruit or SparkFun already have the pull-up resistors on them, typically 10K but sometimes 4.7K. If you buy a bare chip that's a sensor, it typically won't have a built-in pull-up resistors so you'll need to add the external resistors.
On the Photon and Electron, a 40K weak pull-up is added on SDA/SCL (D0/D1) when in I2C mode, but this is not sufficient for most purposes and you should add external pull-up resistors.
On the P1, there are 2.1K hardware pull-up resistors inside the P1 module. You should not add external pull-ups on the P1.
On Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM), a 13K pull-up is added on I2C interfaces. This will sometimes work, but is still too large of a pull-up to be reliable so you should add external pull-ups as well.
These pins are used via the Wire
object.
SCL
=> D1
SDA
=> D0
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On the Argon/Boron/Xenon, D0 is the Serial Data Line (SDA) and D1 is the Serial Clock (SCL). Additionally, there is a second optional I2C interface on D2 and D3 on the Argon and Xenon only.
Both SCL and SDA pins are open-drain outputs that only pull LOW and typically operate with 3.3V logic. Connect a pull-up resistor (1.5k to 10k) on the SDA line to 3V3. Connect a pull-up resistor (1.5k to 10k) on the SCL line to 3V3. If you are using a breakout board with an I2C peripheral, check to see if it already incorporates pull-up resistors.
Note that unlike Gen 2 devices (Photon/P1/Electron), Gen 3 devices are not 5V tolerant.
Tracker
The Tracker SoM allows an alternate mapping of the Wire
(I2C interface) from D0/D1 to RX/TX. The Wire3
interface allows you to use RX as SDA and TX as SCL. You cannot use Wire3
and Serial1
at the same time. Likewise, you cannot use Wire
and Wire3
at the same time, as there is only one I2C peripheral, just different pin mappings.
This is primarily use with the Tracker One as TX/RX are exposed by the external M8 connector. By using Wire3.begin()
you can repurpose these pins as I2C, allowing external expansion by I2C instead of serial.
Argon and B-Series SoM
Additionally, on the Argon and B-Series SoM there is a second I2C port that can be used with the Wire1
object. This a separate I2C peripheral and can be used at the same time as Wire
.
SCL
=> D3
SDA
=> D2
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
On the P2 and Photon there there is a single I2C port, D0 is SDA and D1 is SCL.
Both SCL and SDA pins are open-drain outputs that only pull LOW and typically operate with 3.3V logic. Connect a pull-up resistor (1.5k to 10k) on the SDA line to 3V3. Connect a pull-up resistor (1.5k to 10k) on the SCL line to 3V3. If you are using a breakout board with an I2C peripheral, check to see if it already incorporates pull-up resistors.
Note that unlike Gen 2 devices (Photon/P1/Electron), the P2 and Photon 2 are not 5V tolerant.
The P2 does not contain internal strong pull-up resistors on D0 and D1, unlike the P1.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Gen 2 devices (Photon, P1, Electron, E-Series), D0 is the Serial Data Line (SDA) and D1 is the Serial Clock (SCL).
Additionally on the Electron, there is an alternate pin location for the I2C interface: C4 is the Serial Data Line (SDA) and C5 is the Serial Clock (SCL).
Both SCL and SDA pins are open-drain outputs that only pull LOW and typically operate with 3.3V logic, but are tolerant to 5V. Connect a pull-up resistor(1.5k to 10k) on the SDA line to 3V3. Connect a pull-up resistor(1.5k to 10k) on the SCL line to 3V3. If you are using a breakout board with an I2C peripheral, check to see if it already incorporates pull-up resistors.
On the P1, there are 2.1K hardware pull-up resistors inside the P1 module.
Electron
Additionally on the Electron, there is an alternate pin location for the I2C interface, which can
be used via the Wire1
object. This alternate location is mapped as follows:
SCL
=> C5
SDA
=> C4
Note that you cannot use both Wire and Wire1. These are merely alternative pin locations for a
single hardware I2C port.Sets the I2C clock speed. This is an optional call (not from the original Arduino specs.) and must be called once before calling begin(). The default I2C clock speed is 100KHz and the maximum clock speed is 400KHz.
// SYNTAX
Wire.setSpeed(clockSpeed);
Wire.begin();
Parameters:
clockSpeed
: CLOCK_SPEED_100KHZ, CLOCK_SPEED_400KHZ or a user specified speed in hertz (e.g. Wire.setSpeed(20000)
for 20kHz)Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
On the P2 and Photon 2 the only valid values are CLOCK_SPEED_100KHZ
and CLOCK_SPEED_400KHZ
. Other speeds are not supported at this time.
Enables or Disables I2C clock stretching. This is an optional call (not from the original Arduino specs.) and must be called once before calling begin(). I2C clock stretching is only used with I2C Slave mode. The default I2C clock stretching mode is enabled.
// SYNTAX
Wire.stretchClock(stretch);
Wire.begin(4); // I2C Slave mode, address #4
Parameters:
stretch
: boolean. true
will enable clock stretching (default). false
will disable clock stretching.Initiate the Wire library and join the I2C bus as a master or slave. This should normally be called only once.
// SYNTAX
Wire.begin(); // I2C master mode
Wire.begin(address); // I2C slave mode
Parameters: address
: the 7-bit slave address (optional); if not specified, join the bus as an I2C master. If address is specified, join the bus as an I2C slave. If you are communicating with I2C sensors, displays, etc. you will almost always use I2C master mode (no address specified).
This mode is generally used when you are connected to a different MCU that is the I2C master. For example, connecting to a Raspberry Pi using I2C where it is the I2C master.
I2C slave mode cannot be used at the same time as I2C master mode on the Particle device on the same pins; you generally will need to use a separate Wire
interface on different physical pins if you need both.
One scenario where this can be confusing is when there are built-in I2C peripherals on the device. Some examples include:
Device | Interface | Purpose |
---|---|---|
B-SoM | Wire (D0/D1) |
PMIC and Fuel Gauge on eval board |
Boron | Wire1 |
PMIC and Fuel Gauge |
M-SoM | Wire (D0/D1) |
PMIC and Fuel Gauge on eval board |
Muon | Wire (D0/D1) |
PMIC, Fuel Gauge, Temperature sensor, EEPROM |
Monitor One | Wire (A0/A1) |
Temperature Sensor, User LEDs |
Monitor One | Wire1 |
PMIC and Fuel Gauge |
Tracker SoM | Wire1 |
PMIC and Fuel Gauge |
For example, if you are using the B-SoM on the evaluation board, you cannot use Wire
(D0/D1) in I2C slave mode because the power manager is already using that interface in master mode if the power manager is enabled.
If you must use the same physical interface as the built-in PMIC and Fuel Gauge, you must disable the power manager using the following code:
void setup() {
SystemPowerConfiguration conf;
System.setPowerConfiguration(conf);
}
Disabling the power manager will affect charge rates and input current limits, and may cause unpredictable behavior, especially with no battery connected.
Since 0.4.6:
Releases the I2C bus so that the pins used by the I2C bus are available for general purpose I/O.
Used to check if the Wire library is enabled already. Useful if using multiple slave devices on the same I2C bus. Check if enabled before calling Wire.begin() again.
// SYNTAX
Wire.isEnabled();
Returns: boolean true
if I2C enabled, false
if I2C disabled.
// EXAMPLE USAGE
// Initialize the I2C bus if not already enabled
if (!Wire.isEnabled()) {
Wire.begin();
}
Used by the master to request bytes from a slave device. The bytes may then be retrieved with the available()
and read()
functions.
// SYNTAX
Wire.requestFrom(address, quantity);
Wire.requestFrom(address, quantity, stop);
Parameters:
address
: the 7-bit address of the device to request bytes fromquantity
: the number of bytes to request (maximum 32 unless acquireWireBuffer() is used, see below)stop
: boolean. true
will send a stop message after the request, releasing the bus. false
will continually send a restart after the request, keeping the connection active. The bus will not be released, which prevents another master device from transmitting between messages. This allows one master device to send multiple transmissions while in control. If no argument is specified, the default value is true
.Returns: byte
: the number of bytes returned from the slave device. If a timeout occurs, will return 0
.
Since 1.5.0:
Instead of passing address, quantity, and/or stop, a WireTransmission
object can be passed to Wire.requestFrom()
allowing the address and optional parameters such as a timeout to be set. I2C_ADDRESS
is a constant specifying the Wire address (0-0x7e) for your specific I2C device.
// EXAMPLE
Wire.requestFrom(WireTransmission(I2C_ADDRESS).quantity(requestedLength).timeout(100ms));
// EXAMPLE
Wire.requestFrom(WireTransmission(I2C_ADDRESS).quantity(requestedLength).stop(false));
// EXAMPLE
Wire.requestFrom(WireTransmission(I2C_ADDRESS).quantity(requestedLength).timeout(100ms).stop(false));
Since 0.4.6:
Attempts to reset the I2C bus. This should be called only if the I2C bus has has hung. In 0.4.6 additional rework was done for the I2C bus on the Photon and Electron, so we hope this function isn't required, and it's provided for completeness.
Begin a transmission to the I2C slave device with the given address. Subsequently, queue bytes for transmission with the write()
function and transmit them by calling endTransmission()
.
// SYNTAX
Wire.beginTransmission(address);
Parameters: address
: the 7-bit address of the device to transmit to.
Since 1.5.0:
Instead of passing only an address, a WireTransmission
object can be passed to Wire.beginTransmission()
allowing the address and optional parameters such as a timeout to be set. I2C_ADDRESS
is a constant specifying the Wire address (0-0x7e) for your specific I2C device.
// EXAMPLE
Wire.beginTransmission(WireTransmission(I2C_ADDRESS));
// EXAMPLE WITH TIMEOUT
Wire.beginTransmission(WireTransmission(I2C_ADDRESS).timeout(100ms));
Ends a transmission to a slave device that was begun by beginTransmission()
and transmits the bytes that were queued by write()
.
// SYNTAX
Wire.endTransmission();
Wire.endTransmission(stop);
Parameters: stop
: boolean.
true
will send a stop message after the last byte, releasing the bus after transmission. false
will send a restart, keeping the connection active. The bus will not be released, which prevents another master device from transmitting between messages. This allows one master device to send multiple transmissions while in control. If no argument is specified, the default value is true
.
Returns: byte
, which indicates the status of the transmission:
Queues bytes for transmission from a master to slave device (in-between calls to beginTransmission()
and endTransmission()
), or writes data from a slave device in response to a request from a master.
The default buffer size is 32 bytes; writing bytes beyond 32 before calling endTransmission() will be ignored. The buffer size can be increased by using acquireWireBuffer()
.
// SYNTAX
Wire.write(value);
Wire.write(string);
Wire.write(data, length);
Parameters:
value
: a value to send as a single bytestring
: a string to send as a series of bytesdata
: an array of data to send as byteslength
: the number of bytes to transmit (Max. 32)Returns: byte
write()
will return the number of bytes written, though reading that number is optional.
// EXAMPLE USAGE
// Master Writer running on Device No.1 (Use with corresponding Slave Reader running on Device No.2)
void setup() {
Wire.begin(); // join i2c bus as master
}
byte x = 0;
void loop() {
Wire.beginTransmission(4); // transmit to slave device #4
Wire.write("x is "); // sends five bytes
Wire.write(x); // sends one byte
Wire.endTransmission(); // stop transmitting
x++;
delay(500);
}
Returns the number of bytes available for retrieval with read()
. This should be called on a master device after a call to requestFrom()
or on a slave inside the onReceive()
handler.
Wire.available();
Returns: The number of bytes available for reading.
Reads a byte that was transmitted from a slave device to a master after a call to requestFrom()
or was transmitted from a master to a slave. read()
inherits from the Stream
utility class.
// SYNTAX
Wire.read() ;
Returns: The next byte received
// EXAMPLE USAGE
// Master Reader running on Device No.1 (Use with corresponding Slave Writer running on Device No.2)
void setup() {
Wire.begin(); // join i2c bus as master
Serial.begin(9600); // start serial for output
}
void loop() {
Wire.requestFrom(2, 6); // request 6 bytes from slave device #2
while(Wire.available()){ // slave may send less than requested
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
delay(500);
}
Similar in use to read(). Reads (but does not remove from the buffer) a byte that was transmitted from a slave device to a master after a call to requestFrom()
or was transmitted from a master to a slave. read()
inherits from the Stream
utility class. Useful for peeking at the next byte to be read.
// SYNTAX
Wire.peek();
Returns: The next byte received (without removing it from the buffer)
The Wire
object does not have built-in thread-safety. If you want to use read or write from multiple threads, including from a software timer, you must lock and unlock it to prevent data from multiple thread from being interleaved or corrupted.
A call to lock lock()
must be balanced with a call to unlock()
and not be nested. To make sure every lock is released, it's good practice to use WITH_LOCK
like this:
// EXAMPLE USAGE
void loop()
{
WITH_LOCK(Wire) {
Wire.requestFrom(2, 6); // request 6 bytes from slave device #2
while(Wire.available()){ // slave may send less than requested
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
}
}
Never use lock()
or WITH_LOCK()
within a SINGLE_THREADED_BLOCK()
as deadlock can occur.
Unlocks the Wire
mutex. See lock()
.
Registers a function to be called when a slave device receives a transmission from a master.
Parameters: handler
: the function to be called when the slave receives data; this should take a single int parameter (the number of bytes read from the master) and return nothing, e.g.: void myHandler(int numBytes)
Note: This handler will lock up the device if System calls such as Particle.publish() are made within, due to interrupts being disabled for atomic operations during this handler. Do not overload this handler with extra function calls other than what is immediately required to receive I2C data. Post process outside of this handler.
// EXAMPLE USAGE
// Slave Reader running on Device No.2 (Use with corresponding Master Writer running on Device No.1)
// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
while(1 < Wire.available()) { // loop through all but the last
char c = Wire.read(); // receive byte as a character
Serial.print(c); // print the character
}
int x = Wire.read(); // receive byte as an integer
Serial.println(x); // print the integer
}
void setup() {
Wire.begin(4); // join i2c bus with address #4
Wire.onReceive(receiveEvent); // register event
Serial.begin(9600); // start serial for output
}
void loop() {
delay(100);
}
Register a function to be called when a master requests data from this slave device.
Parameters: handler
: the function to be called, takes no parameters and returns nothing, e.g.: void myHandler()
Note: This handler will lock up the device if System calls such as Particle.publish() are made within, due to interrupts being disabled for atomic operations during this handler. Do not overload this handler with extra function calls other than what is immediately required to send I2C data. Post process outside of this handler.
// EXAMPLE USAGE
// Slave Writer running on Device No.2 (Use with corresponding Master Reader running on Device No.1)
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
Wire.write("hello "); // respond with message of 6 bytes as expected by master
}
void setup() {
Wire.begin(2); // join i2c bus with address #2
Wire.onRequest(requestEvent); // register event
}
void loop() {
delay(100);
}
Creating a function acquireWireBuffer()
that returns an HAL_I2C_Config
struct allows custom buffer sizes to be used. If you do not include this function, the default behavior of 32 byte rx and tx buffers will be used.
This example sets a 512 byte buffer size instead of the default 32 byte size.
constexpr size_t I2C_BUFFER_SIZE = 512;
HAL_I2C_Config acquireWireBuffer() {
HAL_I2C_Config config = {
.size = sizeof(HAL_I2C_Config),
.version = HAL_I2C_CONFIG_VERSION_1,
.rx_buffer = new (std::nothrow) uint8_t[I2C_BUFFER_SIZE],
.rx_buffer_size = I2C_BUFFER_SIZE,
.tx_buffer = new (std::nothrow) uint8_t[I2C_BUFFER_SIZE],
.tx_buffer_size = I2C_BUFFER_SIZE
};
return config;
}
For devices with Wire1
(Electron, E-Series) or Wire3
(Tracker SoM), the equivalent functions are acquireWire1Buffer
and acquireWire3Buffer
.
Note:
This CAN API is supported only on Gen 2 devices (Photon, P1, Electron, and E-Series).
The Tracker SoM supports CAN, but uses an external library. See Tracker CAN.
The Argon, Boron, B-Series SoM, P2, and Photon 2 do not include CAN hardware, but it can be added with an external CAN interface chip and library, like the Tracker SoM.
Since 0.4.9:
Controller area network (CAN bus) is a bus used in most automobiles, as well as some industrial equipment, for communication between different microcontrollers.
The Photon and Electron support communicating with CAN devices via the CAN bus.
Note: an additional CAN transceiver integrated circuit is needed to convert the logic-level voltages of the Photon or Electron to the voltage levels of the CAN bus.
On the Photon or Electron, connect pin D1 to the TX pin of the CAN transceiver and pin D2 to the RX pin.
On the Electron only, connect pin C4 to the TX pin of the CAN transceiver and pin C5 to the RX pin.
// EXAMPLE USAGE on pins D1 & D2
CANChannel can(CAN_D1_D2);
void setup() {
can.begin(125000); // pick the baud rate for your network
// accept one message. If no filter added by user then accept all messages
can.addFilter(0x100, 0x7FF);
}
void loop() {
CANMessage message;
message.id = 0x100;
can.transmit(message);
delay(10);
if(can.receive(message)) {
// message received
}
}
The CAN message struct has these members:
struct CANMessage
{
uint32_t id;
bool extended;
bool rtr;
uint8_t len;
uint8_t data[8];
}
Create a CANChannel
global object to connect to a CAN bus on the specified pins.
// SYNTAX
CANChannel can(pins, rxQueueSize, txQueueSize);
Parameters:
pins
: the Photon and Electron support pins CAN_D1_D2
, and the Electron only, supports pins CAN_C4_C5
rxQueueSize
(optional): the receive queue size (default 32 message)txQueueSize
(optional): the transmit queue size (default 32 message)// EXAMPLE
CANChannel can(CAN_D1_D2);
// Buffer 10 received messages and 5 transmitted messages
CANChannel can(CAN_D1_D2, 10, 5);
Joins the bus at the given baud
rate.
// SYNTAX
can.begin(baud, flags);
Parameters:
baud
: common baud rates are 50000, 100000, 125000, 250000, 500000, 1000000flags
(optional): CAN_TEST_MODE
to run the CAN bus in test mode where every transmitted message will be received back// EXAMPLE
CANChannel can(CAN_D1_D2);
can.begin(500000);
// Use for testing without a CAN transceiver
can.begin(500000, CAN_TEST_MODE);
Disconnect from the bus.
// SYNTAX
CANChannel can(CAN_D1_D2);
can.end();
The number of received messages waiting in the receive queue.
Returns: uint8_t
: the number of messages.
// SYNTAX
uint8_t count = can.available();
// EXAMPLE
CANChannel can(CAN_D1_D2);
if(can.available() > 0) {
// there are messages waiting
}
Take a received message from the receive queue. This function does not wait for a message to arrive.
// SYNTAX
can.receive(message);
Parameters:
message
: where the received message will be copiedReturns: boolean true
if a message was received, false
if the receive queue was empty.
// EXAMPLE
CANChannel can(CAN_D1_D2);
CANMessage message;
if(can.receive(message)) {
Log.info("id=%lu len=%u", message.id, message.len);
}
Add a message to the queue to be transmitted to the CAN bus as soon as possible.
// SYNTAX
can.transmit(message);
Parameters:
message
: the message to be transmittedReturns: boolean true
if the message was added to the queue, false
if the transmit queue was full.
// EXAMPLE
CANChannel can(CAN_D1_D2);
CANMessage message;
message.id = 0x100;
message.len = 1;
message.data[0] = 42;
can.transmit(message);
Note: Since the CAN bus requires at least one other CAN node to acknowledge transmitted messages if the Photon or Electron is alone on the bus (such as when using a CAN shield with no other CAN node connected) then messages will never be transmitted and the transmit queue will fill up.
Filter which messages will be added to the receive queue.
// SYNTAX
can.addFilter(id, mask);
can.addFilter(id, mask, type);
By default all messages are received. When filters are added, only messages matching the filters will be received. Others will be discarded.
Parameters:
id
: the id pattern to matchmask
: the mask pattern to matchtype
(optional): CAN_FILTER_STANDARD
(default) or CAN_FILTER_EXTENDED
Returns: boolean true
if the filter was added, false
if there are too many filters (14 filters max).
// EXAMPLES
CANChannel can(CAN_D1_D2);
// Match only message ID 0x100
can.addFilter(0x100, 0x7FF);
// Match any message with the highest ID bit set
can.addFilter(0x400, 0x400);
// Match any message with the higest ID bit cleared
can.addFilter(0x0, 0x400);
// Match only messages with extended IDs
can.addFilter(0, 0, CAN_FILTER_EXTENDED);
Clear filters and accept all messages.
// SYNTAX
CANChannel can(CAN_D1_D2);
can.clearFilters();
Used to check if the CAN bus is enabled already. Check if enabled before calling can.begin() again.
// SYNTAX
CANChannel can(CAN_D1_D2);
can.isEnabled();
Returns: boolean true
if the CAN bus is enabled, false
if the CAN bus is disabled.
Get the current error status of the CAN bus.
// SYNTAX
int status = can.errorStatus();
Returns: int CAN_NO_ERROR
when everything is ok, CAN_ERROR_PASSIVE
when not attempting to transmit messages but still acknowledging messages, CAN_BUS_OFF
when not transmitting or acknowledging messages.
// EXAMPLE
CANChannel can(CAN_D1_D2);
if(can.errorStatus() == CAN_BUS_OFF) {
Log.info("Not properly connected to CAN bus");
}
This value is only updated when attempting to transmit messages.
The two most common causes of error are: being alone on the bus (such as when using a CAN shield not connected to anything) or using the wrong baud rate. Attempting to transmit in those situations will result in CAN_BUS_OFF
.
Errors heal automatically when properly communicating with other microcontrollers on the CAN bus.
Creates an IP address that can be used with TCPServer, TCPClient, and UDP objects.
// EXAMPLE USAGE
IPAddress localIP;
IPAddress server(8,8,8,8);
IPAddress IPfromInt( 167772162UL ); // 10.0.0.2 as 10*256^3+0*256^2+0*256+2
uint8_t server[] = { 10, 0, 0, 2};
IPAddress IPfromBytes( server );
The IPAddress also allows for comparisons.
if (IPfromInt == IPfromBytes)
{
Log.info("Same IP addresses");
}
You can also use indexing the get or change individual bytes in the IP address.
// PING ALL HOSTS ON YOUR SUBNET EXCEPT YOURSELF
IPAddress localIP = WiFi.localIP();
uint8_t myLastAddrByte = localIP[3];
for(uint8_t ipRange=1; ipRange<255; ipRange++)
{
if (ipRange != myLastAddrByte)
{
localIP[3] = ipRange;
WiFi.ping(localIP);
}
}
You can also assign to an IPAddress from an array of uint8's or a 32-bit unsigned integer.
IPAddress IPfromInt; // 10.0.0.2 as 10*256^3+0*256^2+0*256+2
IPfromInt = 167772162UL;
uint8_t server[] = { 10, 0, 0, 2};
IPAddress IPfromBytes;
IPfromBytes = server;
Finally IPAddress can be used directly with print.
// PRINT THE DEVICE'S IP ADDRESS IN
// THE FORMAT 192.168.0.10
IPAddress myIP = WiFi.localIP();
Serial.println(myIP); // prints the device's IP address
The P2, Photon 2, and Gen 3 devices (Argon, Boron, B-Series SoM, and Tracker SoM) support Bluetooth LE (BLE) in both peripheral and central modes. For more information about BLE, see the BLE Tutorial.
BLE is intended for low data rate sensor applications. Particle devices do not support Bluetooth A2DP and can't be used with Bluetooth headsets, speakers, and other audio devices. Particle devices do not support Bluetooth 5 mesh.
The BLE radio can use the built-in chip or trace antenna, or an external antenna if you have installed and configured one.
The B-Series SoM (system-on-a-module) requires the external BLE antenna connected to the BT connector. The SoMs do not have built-in antennas.
BLE is supported in Device OS 1.3.1 and later. BLE support was in beta test in Device OS 1.3.0. It is not available in earlier Device OS versions. Some differences between 1.3.0 and 1.3.1 include:
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Gen 2 devices (Photon, P1, Electron, E-Series) do not support BLE.
A BLE peripheral can periodically publish data to all nearby devices using advertising. Once you've set up the BleAdvertisingData
object, call BLE.advertise()
to continuously advertise the data to all BLE devices scanning for it.
Optionally, you can provide a scanResponse
data object. If provided, the central device can ask for it during the scan process. Both the advertising data and scan data are public - any device can see and request the data without authentication.
// PROTOTYPE
int advertise(BleAdvertisingData* advertisingData, BleAdvertisingData* scanResponse = nullptr) const;
// EXAMPLE
BleAdvertisingData advData;
// While we support both the health thermometer service and the battery service, we
// only advertise the health thermometer. The battery service will be found after
// connecting.
advData.appendServiceUUID(healthThermometerService);
// Continuously advertise when not connected
BLE.advertise(&advData);
Returns 0 on success or a non-zero error code.
You cannot use BLE advertising while in listening mode (blinking dark blue). The device will be advertising its configuration capabilities to the Particle app when in listening mode.
Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
You can advertise as an Apple iBeacon. See the iBeacon
section for more information.
// PROTOTYPE
int advertise(const iBeacon& beacon) const;
Returns 0 on success or a non-zero error code.
You cannot use BLE advertising including iBeacon while in listening mode (blinking dark blue). The device will be advertising its configuration capabilities to the Particle app when in listening mode.
Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
The advertising data set using the previous two calls is saved. You can using BLE.stopAdvertising()
to stop and BLE.advertise()
to resume advertising.
// PROTOTYPE
int advertise() const;
// EXAMPLE
BLE.advertise();
Returns 0 on success or a non-zero error code.
Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
Stops advertising. You can resume advertising using BLE.advertise()
. Advertising automatically stops while connected to a central device and resumes when disconnected.
// PROTOTYPE
int stopAdvertising() const;
// EXAMPLE
BLE.stopAdvertising();
Returns true
(1) if advertising is currently on or false
(0) if not.
// PROTOTYPE
bool advertising() const;
You can get the advertising data that you previously set using getAdvertisingData()
. Initially the data will be empty.
// PROTOTYPE
ssize_t getAdvertisingData(BleAdvertisingData* advertisingData) const;
See also BleAdvertisingData
.
You can set the advertising data using setAdvertisingData
. You might want to do this if you want to change the data while continuously advertising.
// PROTOTYPE
int setAdvertisingData(BleAdvertisingData* advertisingData) const;
See also BleAdvertisingData
.
Sets the advertising interval. The unit is 0.625 millisecond (625 microsecond) intervals. The default value is 160, or 100 milliseconds.
The range is from 20 milliseconds (32) to 10.24 seconds (16383).
Interval Value | Milliseconds | Description |
---|---|---|
32 | 20 ms | Minimum value |
160 | 100 ms | Default value |
400 | 250 ms | |
800 | 500 ms | |
1600 | 1 sec | |
3200 | 2 sec | Upper end of recommended range |
16383 | 10.24 sec | Maximum value |
Returns 0 on success or a non-zero error code.
// PROTOTYPE
int setAdvertisingInterval(uint16_t interval) const;
// EXAMPLE
// Set advertising interval to 500 milliseconds
BLE.setAdvertisingInterval(800);
Normally, advertising continues until stopAdvertising()
is called or a connection is made from a central device.
You can also have advertising automatically stop after a period of time. The parameter is in units of 10 milliseconds, so if you want to advertise for 10 seconds you'd set the parameter to 1000.
Pass 0 to advertise until stopped. This is the default.
Returns 0 on success or a non-zero error code.
// PROTOTYPE
int setAdvertisingTimeout(uint16_t timeout) const;
The advertising type can be set with this method. This is not typically necessary.
Type | Value |
---|---|
CONNECTABLE_SCANNABLE_UNDIRECTED |
0 |
CONNECTABLE_UNDIRECTED |
1 |
CONNECTABLE_DIRECTED |
2 |
NON_CONNECTABLE_NON_SCANNABLE_UNDIRECTED |
3 |
NON_CONNECTABLE_NON_SCANNABBLE_DIRECTED |
4 |
SCANNABLE_UNDIRECTED |
5 |
SCANNABLE_DIRECTED |
6 |
The default is CONNECTABLE_SCANNABLE_UNDIRECTED
(0).
// PROTOTYPE
int setAdvertisingType(BleAdvertisingEventType type) const;
See BleAdvertisingEventType
for more information.
Gets the advertising parameters.
// PROTOTYPE
int getAdvertisingParameters(BleAdvertisingParams* params) const;
// EXAMPLE
BleAdvertisingParams param;
param.version = BLE_API_VERSION;
param.size = sizeof(BleAdvertisingParams);
int res = BLE.getAdvertisingParameters(¶m);
See BleAdvertisingParameters
for more information.
Sets the advertising parameters using individual values for interval, timeout, and type.
// PROTOTYPE
int setAdvertisingParameters(uint16_t interval, uint16_t timeout, BleAdvertisingEventType type) const;
interval
Advertising interval in 0.625 ms units. Default is 160.timeout
Advertising timeout in 10 ms units. Default is 0.type
BleAdvertisingEventType
. Default is CONNECTABLE_SCANNABLE_UNDIRECTED
(0).Sets the advertising parameters from the BleAdvertisingParams struct.
// PROTOTYPE
int setAdvertisingParameters(const BleAdvertisingParams* params) const;
See BleAdvertisingParameters
for more information.
Since 3.1.0:
Sets the advertising phy mode to allow for coded Phy (long range mode). Supported in Device OS 3.1 and later only. The P2 and Photon 2 do not support coded PHY mode.
// PROTOTYPE
int setAdvertisingPhy(EnumFlags<BlePhy> phy) const;
// EXAMPLE
BLE.setAdvertisingPhy(BlePhy::BLE_PHYS_CODED);
The valid values are:
BlePhy::BLE_PHYS_AUTO
(default)BlePhy::BLE_PHYS_1MBPS
BlePhy::BLE_PHYS_CODED
(long-range)You can only specify a single phy mode for advertising. You cannot logically OR multiple values on the advertiser as you can on the scanner.
Coded Phy mode employs redundancy and error-correction, trading off speed in favor of noise immunity. In theory it could double the range achievable, but in practice you can expect closer to a 50% increase in range.
BLE long-range (coded PHY) is not supported on the P2 and Photon 2.
For an example of using this API, see the RSSI Meter (Long Range) in the BLE tutorial.
In addition to the advertising data, there is an additional 31 bytes of data referred to as the scan response data. This data can be requested during the scan process. It does not require connection or authentication. This is only used from scannable peripheral devices.
// PROTOTYPE
ssize_t getScanResponseData(BleAdvertisingData* scanResponse) const;
See also BleAdvertisingData
.
In addition to the advertising data, there is an additional 31 bytes of data referred to as the scan response data. This data can be requested during the scan process. It does not require connection or authentication. This is only used from scannable peripheral devices.
// PROTOTYPE
int setScanResponseData(BleAdvertisingData* scanResponse) const;
See also BleAdvertisingData
.
Adds a characteristic to this peripheral device from a BleCharacteristic
object.
// PROTOTYPE
BleCharacteristic addCharacteristic(BleCharacteristic& characteristic) const;
// EXAMPLE
// Global variables:
BleUuid serviceUuid("09b17c16-3498-4c02-beb6-3d5792528181");
BleUuid buttonCharacteristicUuid("fe0a8cd7-9f69-45c7-b7a1-3ecb0c9e97c7");
BleCharacteristic buttonCharacteristic("b", BleCharacteristicProperty::NOTIFY, buttonCharacteristicUuid, serviceUuid);
// In setup():
BLE.addCharacteristic(buttonCharacteristic);
BleAdvertisingData data;
data.appendServiceUUID(serviceUuid);
BLE.advertise(&data);
There is a limit of 20 user characteristics, with a maximum description of 20 characters.
You can have up to 20 user services, however you typically cannot advertise that many services as the advertising payload is too small. You normally only advertise your primary service that you device will searched by. Other, less commonly used services can be found after connecting to the device.
Instead of setting the parameters in a BleCharacteristic object you can pass them directly to addCharacteristic.
The parameters are the same as the BleCharacteristic constructors and are described in more detail in the BleCharacteristic
documentation.
// PROTOTYPE
BleCharacteristic addCharacteristic(const char* desc, BleCharacteristicProperty properties, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;
BleCharacteristic addCharacteristic(const String& desc, BleCharacteristicProperty properties, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;
template<typename T>
BleCharacteristic addCharacteristic(const char* desc, BleCharacteristicProperty properties, T charUuid, T svcUuid, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;
template<typename T>
BleCharacteristic addCharacteristic(const String& desc, BleCharacteristicProperty properties, T charUuid, T svcUuid, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;
There is a limit of 20 user characteristics, with a maximum description of 20 characters.
You can have up to 20 user services, however you typically cannot advertise that many services as the advertising payload is too small. You normally only advertise your primary service that you device will searched by. Other, less commonly used services can be found after connecting to the device.
Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.
See also BleCharacteristicProperty
.
Scan for BLE devices. This is typically used by a central device to find nearby BLE devices that can be connected to. It can also be used by an observer to find nearby beacons that continuously transmit data.
There are three overloads of BLE.scan()
, however all of them are synchronous. The calls do not return until the scan is complete. The BLE.setScanTimeout()
method determines how long the scan will run for.
The default is 5 seconds, however you can change it using setScanTimeout()
.
// PROTOTYPE
int scan(BleScanResult* results, size_t resultCount) const;
The BleScanResult
is described below. It contains:
address
The BLE address of the deviceadvertisingData
The advertising data sent by the devicescanData
The scan data (optional)rssi
The signal strength of the advertising message.Prior to Device OS 3.0, these were member variables.
In Device OS 3.0 and later, they are methods, so you must access them as:
address()
The BLE address of the deviceadvertisingData()
The advertising data sent by the devicescanData()
The scan data (optional)rssi()
The signal strength of the advertising message.For example:
len = scanResults[ii].advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);
In Device OS 3.1.0 only, you may get error -270 when attempting to scan. To work around this issue, add this to setup():
#if SYSTEM_VERSION == SYSTEM_VERSION_v310
// This is required with 3.1.0 only
BLE.setScanPhy(BlePhy::BLE_PHYS_AUTO);
#endif
Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
The Vector
version of scan does not require guessing the number of scan items ahead of time. However, it does dynamically allocate memory to hold all of the scan results.
This call does not return until the scan is complete. The setScanTimeout()
function determines how long the scan will run for.
The default is 5 seconds, however you can change it using setScanTimeout()
.
// PROTOTYPE
Vector<BleScanResult> scan() const;
// EXAMPLE - Device OS 3.0 and later
#include "Particle.h"
SYSTEM_MODE(MANUAL);
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
void setup() {
(void)logHandler; // Does nothing, just to eliminate warning for unused variable
}
void loop() {
Vector<BleScanResult> scanResults = BLE.scan();
if (scanResults.size()) {
Log.info("%d devices found", scanResults.size());
for (int ii = 0; ii < scanResults.size(); ii++) {
Log.info("MAC: %s | RSSI: %dBm", scanResults[ii].address().toString().c_str(), scanResults[ii].rssi());
String name = scanResults[ii].advertisingData().deviceName();
if (name.length() > 0) {
Log.info("Advertising name: %s", name.c_str());
}
}
}
delay(3000);
}
// EXAMPLE - Device OS 2.0 and earlier
#include "Particle.h"
SYSTEM_MODE(MANUAL);
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
void setup() {
(void)logHandler; // Does nothing, just to eliminate warning for unused variable
}
void loop() {
Vector<BleScanResult> scanResults = BLE.scan();
if (scanResults.size()) {
Log.info("%d devices found", scanResults.size());
for (int ii = 0; ii < scanResults.size(); ii++) {
// For Device OS 2.x and earlier, use scanResults[ii].address[0], etc. without the ()
Log.info("MAC: %02X:%02X:%02X:%02X:%02X:%02X | RSSI: %dBm",
scanResults[ii].address[5], scanResults[ii].address[4], scanResults[ii].address[3],
scanResults[ii].address[2], scanResults[ii].address[1], scanResults[ii].address[0], scanResults[ii].rssi);
String name = scanResults[ii].advertisingData().deviceName();
if (name.length() > 0) {
Log.info("Advertising name: %s", name.c_str());
}
}
}
delay(3000);
}
The BleScanResult
is described below. It contains:
address
The BLE address of the deviceadvertisingData
The advertising data sent by the devicescanData
The scan data (optional)rssi
The signal strength of the advertising message.Prior to Device OS 3.0, these were member variables.
In Device OS 3.0 and later, they are methods, so you must access them as:
address()
The BLE address of the deviceadvertisingData()
The advertising data sent by the devicescanData()
The scan data (optional)rssi()
The signal strength of the advertising message.Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
The callback version of scan does not return until the scan has reached the end of the scan timeout. There are still advantages of using this, however:
The default is 5 seconds, however you can change it using setScanTimeout()
.
Note: You cannot call BLE.connect()
from a BLE.scan()
callback! If you want to scan then connect, you must save the BleAddress
and then do the connect after BLE.scan()
returns.
// PROTOTYPE
int scan(BleOnScanResultCallback callback, void* context) const;
int scan(BleOnScanResultCallbackRef callback, void* context) const;
// EXAMPLE
#include "Particle.h"
SYSTEM_MODE(MANUAL);
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
void scanResultCallback(const BleScanResult *scanResult, void *context);
void setup() {
(void)logHandler; // Does nothing, just to eliminate warning for unused variable
}
void loop() {
Log.info("starting scan");
int count = BLE.scan(scanResultCallback, NULL);
if (count > 0) {
Log.info("%d devices found", count);
}
delay(3000);
}
void scanResultCallback(const BleScanResult *scanResult, void *context) {
Log.info("MAC: %s | RSSI: %dBm", scanResult->address().toString().c_str(), scanResult->rssi());
String name = scanResult->advertisingData().deviceName();
if (name.length() > 0) {
Log.info("deviceName: %s", name.c_str());
}
}
The callback has this prototype:
void scanResultCallback(const BleScanResult *scanResult, void *context)
The BleScanResult
is described below. It contains:
address
The BLE address of the deviceadvertisingData
The advertising data sent by the devicescanData
The scan data (optional)rssi
The signal strength of the advertising message.Prior to Device OS 3.0, these were member variables.
In Device OS 3.0 and later, they are methods, so you must access them as:
address()
The BLE address of the deviceadvertisingData()
The advertising data sent by the devicescanData()
The scan data (optional)rssi()
The signal strength of the advertising message.For example:
len = scanResult->advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);
The context
parameter is often used if you implement your scanResultCallback in a C++ object. You can store the object instance pointer (this
) in the context.
The callback is called from the BLE thread. It has a smaller stack than the normal loop stack, and you should avoid doing any lengthy operations that block from the callback. For example, you should not try to use functions like Particle.publish()
and you should not use delay()
. You should beware of thread safety issues.
Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.
Since 3.0.0:
// PROTOTYPES
typedef std::function<void(const BleScanResult& result)> BleOnScanResultStdFunction;
int scan(const BleOnScanResultStdFunction& callback) const;
template<typename T>
int scan(void(T::*callback)(const BleScanResult&), T* instance) const;
In Device OS 3.0.0 and later, you can implement the BLE.scan()
callback as a C++ member function or a C++11 lambda.
Since 3.0.0:
In Device OS 3.0.0 and later, it's also possible to pass to have the scan result passed by reference instead of pointer.
void scanResultCallback(const BleScanResult &scanResult, void *context)
Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
void scan() {
BleScanFilter filter;
filter.deviceName("MyDevice").minRssi(-50).serviceUUID(0x1234);
Vector<BleScanResult> scanResults = BLE.scanWithFilter(filter);
}
Since 3.0.0:
You can also BLE scan with a filter with Device OS 3.0.0 and later. The result is saved in a vector.
The scanResults vector only contains the scanned devices those comply with all of the following conditions in the example above:
See BLEScanFilter
for additional options.
The stopScanning()
method interrupts a BLE.scan()
in progress before the end of the timeout. It's typically called from the scan callback function.
You might want to do this if you're looking for a specific device - you can stop scanning after you find it instead of waiting until the end of the scan timeout.
// PROTOTYPE
int stopScanning() const;
// EXAMPLE
void scanResultCallback(const BleScanResult *scanResult, void *context) {
// Stop scanning if we encounter any BLE device in range
BLE.stopScanning();
}
Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.
Sets the length of time scan()
will run for. The default is 5 seconds.
// PROTOTYPE
int setScanTimeout(uint16_t timeout) const;
// EXAMPLE
// Scan for 1 second
setScanTimeout(100);
The unit is 10 millisecond units, so the default timeout parameter is 500.
Returns 0 on success or a non-zero error code.
Gets the parameters used for scanning.
// PROTOTYPE
int getScanParameters(BleScanParams* params) const;
// EXAMPLE
BleScanParams scanParams;
scanParams.version = BLE_API_VERSION;
scanParams.size = sizeof(BleScanParams);
int res = BLE.getScanParameters(&scanParams);
See BleScanParams
for more information.
Sets the parameters used for scanning. Typically you will only ever need to change the scan timeout, but if you need finer control you can use this function.
// PROTOTYPE
int setScanParameters(const BleScanParams* params) const;
See BleScanParams
for more information.
Since 3.1.0:
Sets the scanning phy mode to allow for Coded Phy (long range mode). Supported in Device OS 3.1 and later only.
// PROTOTYPE
int setScanPhy(EnumFlags<BlePhy> phy) const;
// EXAMPLE
BLE.setScanPhy(BlePhy::BLE_PHYS_CODED | BlePhy::BLE_PHYS_1MBPS);
The valid values are:
BlePhy::BLE_PHYS_AUTO
BlePhy::BLE_PHYS_1MBPS
BlePhy::BLE_PHYS_CODED
You can logically OR the two values to scan for both phy modes. Do not combine BLE_PHYS_AUTO
with other values, however.
Note: In Device OS 3.0.0 (only), a bug sets an invalid default value for the scan mode, causing no devices to be found when scanning. To work around this issue, set the scan phy mode to automatic explictly in setup():
BLE.setScanPhy(BlePhy::BLE_PHYS_AUTO);
BLE long-range (coded PHY) is not supported on the P2 and Photon 2.
For an example of using this API, see the RSSI Meter (Long Range) in the BLE tutorial.
In a central device the logic typically involves:
Scanning for devices provides its address as well as additional data (advertising data). With the address you can connect:
// PROTOTYPE
BlePeerDevice connect(const BleAddress& addr, bool automatic = true) const;
BlePeerDevice connect(const BleAddress& addr, const BleConnectionParams* params, bool automatic = true) const;
BlePeerDevice connect(const BleAddress& addr, uint16_t interval, uint16_t latency, uint16_t timeout, bool automatic = true) const;
addr
The BleAddress
to connect to.This call is synchronous and will block until a connection is completed or the operation times out.
Returns a BlePeerDevice
object. You typically use a construct like this:
// EXAMPLE - Device OS 3.0.0 and later:
BlePeerDevice peer = BLE.connect(scanResults[ii].address());
if (peer.connected()) {
Log.info("successfully connected %s!", scanResults[ii].address().toString().c_str());
// ...
}
else {
Log.info("connection failed");
// ...
}
// EXAMPLE - Device OS 2.x and earlier:
BlePeerDevice peer = BLE.connect(scanResults[ii].address);
if (peer.connected()) {
Log.info("successfully connected %02X:%02X:%02X:%02X:%02X:%02X!",
scanResults[ii].address[5], scanResults[ii].address[4], scanResults[ii].address[3],
scanResults[ii].address[2], scanResults[ii].address[1], scanResults[ii].address[0]);
// ...
}
else {
Log.info("connection failed");
// ...
}
It is possible to save the address and avoid scanning, however the address could change on some BLE devices, so you should be prepared to scan again if necessary.
A central device can connect to up to 3 peripheral devices at a time. (It's limited to 1 in Device OS 1.3.0.)
See the next section for the use of BleConnectionParams
as well as interval, latency, and timeout.
This version of connect allows parameters for the connection to be set.
// PROTOTYPE
BlePeerDevice connect(const BleAddress& addr, uint16_t interval, uint16_t latency, uint16_t timeout) const;
addr
The BleAddress
to connect to.interval
The minimum connection interval in units of 1.25 milliseconds. The default is 24 (30 milliseconds).latency
Use default value 0.timeout
Connection timeout in units of 10 milliseconds. Default is 500 (5 seconds). Minimum value is 100 (1 second).Note that the timeout is the timeout after the connection is established, to determine that the other side is no longer available. It does not affect the initial connection timeout.
Returns a BlePeerDevice
object.
Sets the connection parameter defaults so future calls to connect() without options will use these values.
// PROTOTYPE
int setPPCP(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) const;
minInterval
the minimum connection interval in units of 1.25 milliseconds. The default is 24 (30 milliseconds).maxInterval
the minimum connection interval in units of 1.25 milliseconds. The default is 24 (30 milliseconds). The connect(options) call sets both minInterval and maxInterval to the interval parameter.latency
Use default value 0.timeout
Connection timeout in units of 10 milliseconds. Default is 500 (5 seconds). Minimum value is 100 (1 second).Note that the timeout is the timeout after the connection is established, to determine that the other side is no longer available. It does not affect the initial connection timeout.
Returns true
(1) if a connected to a device or false
(0) if not.
// PROTOTYPE
bool connected() const;
Can be used in central or peripheral mode, however if central mode if you are supporting more than one peripheral you may want to use the connected()
method of the BlePeerDevice
object to find out the status of individual connections.
Disconnects all peers.
// PROTOTYPE
int disconnect() const;
Returns 0 on success or a non-zero error code.
BLE.disconnect()
cannot be called from a BLE callback function.
Typically used in central mode when making connections to multiple peripherals to disconnect a single peripheral.
// PROTOTYPE
int disconnect(const BlePeerDevice& peripheral) const;
The BlePeerDevice
is described below. You typically get it from BLE.connect()
.
Returns 0 on success or a non-zero error code.
BLE.disconnect()
cannot be called from a BLE callback function.
Turns the BLE radio on. It defaults to on in SYSTEM_MODE(AUTOMATIC)
. In SYSTEM_MODE(SEMI_AUTOMATIC)
or SYSTEM_MODE(MANUAL)
you must turn on BLE.
// PROTOTYPE
int on();
// EXAMPLE
void setup() {
BLE.on();
}
Returns 0 on success, or a non-zero error code.
Turns the BLE radio off. You normally do not need to do this.
// PROTOTYPE
int off();
Returns 0 on success, or a non-zero error code.
Starts the BLE service. It defaults to started, so you normally do not need to call this unless you have stopped it.
// PROTOTYPE
int begin();
Returns 0 on success, or a non-zero error code.
Stops the BLE service. You normally do not need to do this.
// PROTOTYPE
int end();
Returns 0 on success, or a non-zero error code.
Registers a callback function that is called when a connection is established. This is only called for peripheral devices. On central devices, BLE.connect()
is synchronous.
You can use this method, or you can simply monitor BLE.connected()
(for peripheral devices) or peer.connected()
for central devices.
// PROTOTYPE
void onConnected(BleOnConnectedCallback callback, void* context);
The prototype for the callback function is:
void callback(const BlePeerDevice& peer, void* context);
The callback parameters are:
peer
The BlePeerDevice
object that has connected.context
The value you passed to onConnected
when you registered the connected callback.The callback is called from the BLE thread. It has a smaller stack than the normal loop stack, and you should avoid doing any lengthy operations that block from the callback. For example, you should not try to use functions like Particle.publish()
and you should not use delay()
. You should beware of thread safety issues.
Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.
Since 3.0.0:
// PROTOTYPES
typedef std::function<void(const BlePeerDevice& peer)> BleOnConnectedStdFunction;
void onConnected(const BleOnConnectedStdFunction& callback) const;
template<typename T>
void onConnected(void(T::*callback)(const BlePeerDevice& peer), T* instance) const;
in Device OS 3.0.0 and later, the onConnected()
callback can be a C++ class member function or a C++11 lambda.
Registers a callback function that is called when a connection is disconnected on a peripheral device.
You can use this method, or you can simply monitor BLE.connected()
(for peripheral devices) or peer.connected()
for central devices.
// PROTOTYPE
void onDisconnected(BleOnDisconnectedCallback callback, void* context);
`
The prototype for the callback function is:
void callback(const BlePeerDevice& peer, void* context);
The callback parameters are the same as for onConnected().
Since 3.0.0:
// PROTOTYPES
typedef std::function<void(const BlePeerDevice& peer)> BleOnDisconnectedStdFunction;
void onDisconnected(const BleOnDisconnectedStdFunction& callback) const;
template<typename T>
void onDisconnected(void(T::*callback)(const BlePeerDevice& peer), T* instance) const;
in Device OS 3.0.0 and later, the onDisconnected()
callback can be a C++ class member function or a C++11 lambda.
Sets the BLE transmit power. The default is 0 dBm.
Valid values are: -20, -16, -12, -8, -4, 0, 4, 8.
-20 is the lowest power, and 8 is the highest power.
Returns 0 on success or a non-zero error code.
// PROTOTYPE
int setTxPower(int8_t txPower) const;
// EXAMPLE
BLE.setTxPower(-12); // Use lower power
Returns 0 on success or a non-zero error code.
Gets the current txPower. The default is 0.
Returns 0 on success or a non-zero error code.
// PROTOTYPE
int txPower(int8_t* txPower) const;
// EXAMPLE
int8_t txPower;
txPower = BLE.tx(&txPower);
Log.info("txPower=%d", txPower);
Get the BLE address of this device.
// PROTOTYPE
const BleAddress address() const;
See BleAddress
for more information.
Since 1.3.1:
Selects which antenna is used by the BLE radio stack. This is a persistent setting.
Note: B-Series SoM devices do not have an internal (chip) antenna and require an external antenna to use BLE. It's not necessary to select the external antenna on the B-Series SoM as there is no internal option.
On the P2 and Photon 2, this method sets the antenna used for both BLE and Wi-Fi.
// Select the internal antenna
BLE.selectAntenna(BleAntennaType::INTERNAL);
// Select the external antenna
BLE.selectAntenna(BleAntennaType::EXTERNAL);
If you get this error when using BleAntennaType::EXTERNAL
:
Error: .../wiring/inc/spark_wiring_arduino_constants.h:44:21: expected unqualified-id before numeric constant
After all of your #include statements at the top of your source file, add:
#undef EXTERNAL
This occurs because the Arduino compatibility modules has a #define
for EXTERNAL
which breaks the BleAntennaType
enumeration. This will only be necessary if a library you are using enables Arduino compatibility.
// PROTOTYPE
int setPairingIoCaps(BlePairingIoCaps ioCaps) const;
Since 3.0.0:
Sets the capabilities of this side of the BLE connection.
Value | Description |
---|---|
BlePairingIoCaps::NONE |
This side has no display or keyboard |
BlePairingIoCaps::DISPLAY_ONLY |
Has a display for the 6-digit passcode |
BlePairingIoCaps::DISPLAY_YESNO |
Display and a yes-no button |
BlePairingIoCaps::KEYBOARD_ONLY |
Keyboard or numeric keypad only |
BlePairingIoCaps::KEYBOARD_DISPLAY |
Display and a keyboard (or a touchscreen) |
Ideally you want one side to have a keyboard and one side to have a display. This allows for an authenticated connection to assure that both sides the device they say they are. This prevents man-in-the-middle attack ("MITM") where a rogue device could pretend to be the device you are trying to pair with.
Even if neither side has a display or keyboard, the connection can still be paired and encrypted, but there will be no authentication.
For more information about pairing, see BLE pairing.
// PROTOTYPE
int setPairingAlgorithm(BlePairingAlgorithm algorithm) const;
Since 3.0.0:
Value | Description |
---|---|
BlePairingAlgorithm::AUTO |
Automatic selection |
BlePairingAlgorithm::LEGACY_ONLY |
Legacy Pairing mode only |
BlePairingAlgorithm::LESC_ONLY |
Bluetooth LE Secure Connection Pairing (LESC) only |
// PROTOTYPE
int startPairing(const BlePeerDevice& peer) const;
Since 3.0.0:
When using BLE pairing, one side is the initiator. It's usually the BLE central device, but either side can initiate pairing if the other side is able to accept the request to pair. See BLE.onPairingEvent()
, below, for how to respond to a request.
The results is 0 (SYSTEM_ERROR_NONE
) on success, or a non-zero error code on failure.
For more information about pairing, see BLE pairing.
// PROTOTYPE
int rejectPairing(const BlePeerDevice& peer) const;
Since 3.0.0:
If you are the pairing responder (typically the BLE peripheral, but could be either side) and you do not support being the responder, call BLE.rejectPairing()
from your BLE.onPairingEvent()
handler.
The results is 0 (SYSTEM_ERROR_NONE
) on success, or a non-zero error code on failure.
// PROTOTYPE
int setPairingNumericComparison(const BlePeerDevice& peer, bool equal) const;
Since 3.0.0:
This is used with BlePairingEventType::NUMERIC_COMPARISON
to confirm that two LESC passcodes are identical. This requires a keypad or a yes-no button.
The results is 0 (SYSTEM_ERROR_NONE
) on success, or a non-zero error code on failure.
// PROTOTYPE
int setPairingPasskey(const BlePeerDevice& peer, const uint8_t* passkey) const;
// EXAMPLE
BLE.setPairingPasskey(peer, (const uint8_t *)"123456");
Since 3.0.0:
When you have a keyboard and the other side has a display, you may need to prompt the user to enter a passkey on your
keyboard. This is done by the BlePairingEventType::PASSKEY_INPUT
event. Once they have entered it, call this function
to set the code that they entered.
The passkey is BLE_PAIRING_PASSKEY_LEN bytes long (6). The passkey parameter does not need to be null terminated.
Since the parameter is not a null terminated c-string, it's declared as const uint8_t *
instead of const char *
but you can pass a c-string of 6 ASCII digits by casting it to a const uint8_t *
.
The results is 0 (SYSTEM_ERROR_NONE
) on success, or a non-zero error code on failure.
// PROTOTYPE
bool isPairing(const BlePeerDevice& peer) const;
Since 3.0.0:
Returns true if the pairing negotiation is still in progress. This is several steps but is asynchrnouous.
// PROTOTYPE
bool isPaired(const BlePeerDevice& peer) const;
Since 3.0.0:
Returns true if pairing has been successfully completed.
// PROTOTYPES
void onPairingEvent(BleOnPairingEventCallback callback, void* context = nullptr) const;
void onPairingEvent(const BleOnPairingEventStdFunction& callback) const;
template<typename T>
void onPairingEvent(void(T::*callback)(const BlePairingEvent& event), T* instance) const {
return onPairingEvent((callback && instance) ? std::bind(callback, instance, _1) : (BleOnPairingEventStdFunction)nullptr);
}
// BleOnPairingEventCallback declaration
typedef void (*BleOnPairingEventCallback)(const BlePairingEvent& event, void* context);
// BleOnPairingEventCallback example function
void myPairingEventCallback(const BlePairingEvent& event, void* context);
// BleOnPairingEventStdFunction declaration
typedef std::function<void(const BlePairingEvent& event)> BleOnPairingEventStdFunction;
Since 3.0.0:
For more information about pairing, see BLE pairing.
BlePairingEventType::REQUEST_RECEIVED
Informational event that indicates that pairing was requested by the other side.
event.peer
is the BLE peer that requested the pairing.
If you are not able to respond to pairing requests, you should call BLE.rejectPairing()
.
BlePairingEventType::PASSKEY_DISPLAY
If you have specified that you have a display, the negotiation process may require that you show a passkey on your display. This is indicated to your code by this event. The passkey is specified by the other side; you just need to display it. It's in the event payload:
event.payload.passkey
is a character array of passcode charactersBLE_PAIRING_PASSKEY_LEN
is the length in characters. Currently 6.// Example using USB serial as the display and keyboard
if (event.type == BlePairingEventType::PASSKEY_DISPLAY) {
Serial.print("Passkey display: ");
for (uint8_t i = 0; i < BLE_PAIRING_PASSKEY_LEN; i++) {
Serial.printf("%c", event.payload.passkey[i]);
}
Serial.println("");
}
Note that event.payload.passkey
is not a null terminated string, so you can't just print it as a c-string. It's a fixed-length array of bytes of ASCII digits (0x30 to 0x39, inclusive).
BlePairingEventType::PASSKEY_INPUT
If you specified that you have a keyboard, the negotiation process may require that the user enter the passcode that's displayed on the other side into the keyboard or keypad. When this is required, this event is passed to your callback.
// Example using USB serial as the display and keyboard
if (event.type == BlePairingEventType::PASSKEY_INPUT) {
Serial.print("Passkey input: ");
uint8_t i = 0;
uint8_t passkey[BLE_PAIRING_PASSKEY_LEN];
while (i < BLE_PAIRING_PASSKEY_LEN) {
if (Serial.available()) {
passkey[i] = Serial.read();
Serial.write(passkey[i++]);
}
}
Serial.println("");
BLE.setPairingPasskey(event.peer, passkey);
}
BlePairingEventType::STATUS_UPDATED
The pairing status was updated. These fields may be of interest:
event.payload.status.status
event.payload.status.bonded
event.payload.status.lesc
LESC pairing is supported in Device OS 3.1 and later only.
BlePairingEventType::NUMERIC_COMPARISON
Numeric comparison mode is being used to pair devices in LESC pairing mode. You should display the passkey in the same was as BlePairingEventType::PASSKEY_DISPLAY
.
struct BlePairingEvent {
BlePeerDevice& peer;
BlePairingEventType type;
size_t payloadLen;
BlePairingEventPayload payload;
};
The structure passed to the callback has these elements. The payload varies depending on the event type. For example, for the BlePairingEventType::PASSKEY_DISPLAY
event, the payload is the passkey to display on your display.
The payload is either the passkey, or a status:
union BlePairingEventPayload {
const uint8_t* passkey;
BlePairingStatus status;
};
Fields of the payload used for BlePairingEventType::STATUS_UPDATED
events.
struct BlePairingStatus {
int status;
bool bonded;
bool lesc;
};
Since 3.3.0:
Sets the provisioning service UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.
You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.
The T
parameter is any type that can be passed to the BleUuid
constructor. It's typically a UUID string but other formats are supported.
You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE
and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true)
.
// PROTOTYPE
template<typename T>
int setProvisioningSvcUuid(T svcUuid) const
// EXAMPLE
BLE.setProvisioningSvcUuid("6E400021-B5A3-F393-E0A9-E50E24DCCA9E");
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.3.0:
Sets the provisioning service transmit characteristic UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.
You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.
The T
parameter is any type that can be passed to the BleUuid
constructor. It's typically a UUID string but other formats are supported.
You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE
and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true)
.
// PROTOTYPE
template<typename T>
int setProvisioningTxUuid(T svcUuid) const
// EXAMPLE
BLE.setProvisioningTxUuid("6E400022-B5A3-F393-E0A9-E50E24DCCA9E");
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.3.0:
Sets the provisioning service receive characteristic UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.
You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.
The T
parameter is any type that can be passed to the BleUuid
constructor. It's typically a UUID string but other formats are supported.
You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE
and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true)
.
// PROTOTYPE
template<typename T>
int setProvisioningRxUuid(T svcUuid) const
// EXAMPLE
BLE.setProvisioningRxUuid("6E400023-B5A3-F393-E0A9-E50E24DCCA9E");
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.3.0:
Sets the provisioning service version characteristic UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.
You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.
The T
parameter is any type that can be passed to the BleUuid
constructor. It's typically a UUID string but other formats are supported.
You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE
and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true)
.
// PROTOTYPE
template<typename T>
int setProvisioningVerUuid(T svcUuid) const
// EXAMPLE
BLE.setProvisioningVerUuid("6E400024-B5A3-F393-E0A9-E50E24DCCA9E");
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.3.0:
Sets the provisioning mode BLE device name. This is typically only done when using BLE provisioning mode with a product. This will likely be displayed in the user interface of your mobile app, so you may want to replace the default of "ARGON" or "P2".
Note that your mobile app will typically filter on the service UUID, not the name.
You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE
and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true)
.
The maximum name length is BLE_MAX_DEV_NAME_LEN
, or 20 ASCII characters. In practice, however, this must must be 14 or fewer characters because it will not fit in the default advertising packet (which is limited to 31 bytes) if longer.
// PROTOTYPES
int setDeviceName(const char* name, size_t len) const;
int setDeviceName(const char* name) const;
int setDeviceName(const String& name) const;
// EXAMPLE
BLE.setDeviceName("Acme Sensor");
Since 3.3.0:
Gets the device name that is displayed when selecting a device for BLE provisioning.
// PROTOTYPES
ssize_t getDeviceName(char* name, size_t len) const;
String getDeviceName() const;
Since 3.3.0:
Sets the provisioning mode BLE company code. If not set, the Particle company ID (0x0662) is used.
The company ID is a 16-bit integer and values are assigned by the Bluetooth SIG. The value not typically prominently displayed and you will typically filter on the service UUID, not the company name, so you can skip setting this, if desired.
// PROTOTYPE
int setProvisioningCompanyId(uint16_t companyId);
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.3.0:
BLE provisioning mode is an alternative to listening mode which allows customization of the user experience. For example:
Unlike listening mode (blinking dark blue), BLE provisioning mode:
// PROTOTYPE
int provisioningMode(bool enabled) const;
// EXAMPLE
BLE.provisioningMode(true);
Be sure to set all desired parameters, such as service UUIDs before enabling provisioning mode.
Since listening mode and BLE provisioning mode are mutually exclusive and listening mode takes precedence, if you are using BLE provisioning mode you will typically want to disable listening mode:
// Disable listening mode when using BLE provisioning mode
STARTUP(System.enableFeature(FEATURE_DISABLE_LISTENING_MODE));
Since BLE provisioning mode can run concurrently with your firmware while cloud connected, you do not need to disable it after setting Wi-Fi credentials. This also means your end-users will be able to reconfigure Wi-Fi credentials without having to press the MODE button, which may eliminate the need for an external button in your product.
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.3.0:
Returns true if BLE provisioning mode is enabled.
bool getProvisioningStatus() const;
See Wi-Fi setup options for more information about BLE provisioning mode.
Since 3.0.0:
The BLEScanFilter
object is used with BLE.scanWithFilter()
to return a subset of the available BLE peripherals near you.
// PROTOTYPES
template<typename T>
BleScanFilter& deviceName(T name);
BleScanFilter& deviceNames(const Vector<String>& names);
const Vector<String>& deviceNames() const;
You can match on device names. You can call deviceName()
more than once to add more than one device name to scan for. Or you can use deviceNames()
to pass in a vector of multiple device names in a single call.
// PROTOTYPES
template<typename T>
BleScanFilter& serviceUUID(T uuid);
BleScanFilter& serviceUUIDs(const Vector<BleUuid>& uuids);
const Vector<BleUuid>& serviceUUIDs() const;
You can match on service UUIDs. You can call serviceUUID()
more than once to add more than one UUID to scan for. Or you can use serviceUUIDs()
to pass in a vector of multiple UUIDs in a single call. You can match both well-known and private UUIDs.
// PROTOTYPES
template<typename T>
BleScanFilter& address(T addr);
BleScanFilter& addresses(const Vector<BleAddress>& addrs);
const Vector<BleAddress>& addresses() const;
You can match on BLE addresses. You can call address()
more than once to add more than one UUID to scan for. Or you can use addresses()
to pass in a vector of multiple addresses in a single call.
// PROTOTYPES
BleScanFilter& appearance(const ble_sig_appearance_t& appearance);
BleScanFilter& appearances(const Vector<ble_sig_appearance_t>& appearances);
const Vector<ble_sig_appearance_t>& appearances() const;
You can match on BLE apperance codes. These are defined by the BLE special interest group for clases of devices with known characteristics, such as BLE_SIG_APPEARANCE_THERMOMETER_EAR
or BLE_SIG_APPEARANCE_GENERIC_HEART_RATE_SENSOR
.
You can call appearance()
more than once to add more than one appearance scan for. Or you can use apperances()
to pass in a vector of multiple apperances in a single call.
// PROTOTYPES
BleScanFilter& minRssi(int8_t minRssi);
BleScanFilter& maxRssi(int8_t maxRssi);
int8_t minRssi() const;
int maxRssi() const;
You can require a minimim of maximum RSSI to the peripipheral to be included in the scan list.
// PROTOTYPES
BleScanFilter& customData(const uint8_t* const data, size_t len);
const uint8_t* const customData(size_t* len) const;
You can require custom advertising data to match the specified data.
There isn't a separate class for configuring BLE Services. A service is identified by its UUID, and this UUID passed in when creating the BleCharacteristic
object(s) for the service. For example:
// The "Health Thermometer" service is 0x1809.
// See https://www.bluetooth.com/specifications/gatt/services/
BleUuid healthThermometerService(0x1809);
// We're using a well-known characteristics UUID. They're defined here:
// https://www.bluetooth.com/specifications/gatt/characteristics/
// The temperature-measurement is 16-bit UUID 0x2A1C
BleCharacteristic temperatureMeasurementCharacteristic("temp", BleCharacteristicProperty::NOTIFY, BleUuid(0x2A1C), healthThermometerService);
The health thermometer only has a single characteristic, however if your service has multiple characteristics you can add them all this way.
You can also create custom services by using a long UUID. You define what characteristic data to include for a custom service. UUIDs are described below.
For more information about characteristics, see the BLE tutorial and BleCharacteristicProperty
.
In a BLE peripheral role, each service has one or more characteristics. Each characteristic may have one of more values.
In a BLE central role, you typically have a receive handler to be notified when the peripheral updates each characteristic value that you care about.
For assigned characteristics, the data will be in a defined format as defined by the Bluetooth SIG. They are listed here.
For more information about characteristics, see the BLE tutorial.
You typically construct a characteristic as a global variable with no parameters when you are using central mode and will be receiving values from the peripheral. For example, this is done in the heart rate central tutorial to receive values from a heart rate sensor. It's associated with a specific characteristic UUID after making the BLE connection.
// PROTOTYPE
BleCharacteristic();
// EXAMPLE
// Global variable
BleCharacteristic myCharacteristic;
Once you've created your characteristic in setup()
you typically hook in its onDataReceived handler.
// In setup():
myCharacteristic.onDataReceived(onDataReceived, NULL);
The onDataReceived
function has this prototype:
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context)
The BlePeerDevice
object is described below.
The context
parameter can be used to pass extra data to the callback. It's typically used when you implement the callback in a C++ class to pass the object instance pointer (this
).
The onDataReceived()
handler is run from the BLE thread. You should avoid lengthy or blocking operations since it will affect other BLE processing. Additionally, the BLE thread has a smaller stack than the main application (loop) thread, so you avoid functions that require a large amount of stack space. To prevent these issues, you should set a flag in the data received handler and perform lengthy or stack-intensive operations from the loop()
instead. For example, you should not call Particle.publish()
, WiFi.clearCredentials()
, and many other functions directly from the onDataReceived handler.
Since 3.0.0:
// PROTOTYPES
typedef std::function<void(const uint8_t*, size_t, const BlePeerDevice& peer)> BleOnDataReceivedStdFunction;
void onDataReceived(const BleOnDataReceivedStdFunction& callback);
template<typename T>
void onDataReceived(void(T::*callback)(const uint8_t*, size_t, const BlePeerDevice& peer), T* instance);
In Device OS 3.0.0 and later, the onDataReceived callback can be a C++ member function or C++11 lambda.
In a peripheral role, you typically define a value that you send out using this constructor. The parameters are:
// PROTOTYPE
BleCharacteristic(const char* desc, BleCharacteristicProperty properties, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr);
BleCharacteristic(const String& desc, BleCharacteristicProperty properties, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr);
// EXAMPLE
// Global variable
BleCharacteristic batteryLevelCharacteristic("bat", BleCharacteristicProperty::NOTIFY, BleUuid(0x2A19), batteryLevelService);
"bat"
a short string to identify the characteristicBleCharacteristicProperty::NOTIFY
The BLE characteristic property. This is typically NOTIFY for values you send out. See also BleCharacteristicProperty
.BleUuid(0x2A19)
The UUID of this characteristic. In this example it's an assigned (short) UUID. See BleUuid
.batteryLevelService
The UUID of the service this characteristic is part of.The UUIDs for the characteristic and service can be a number of formats but are typically either:
BleUuid(0x2A19)
const char *
, like "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
Both must be the same, so if you use a string literal service UUID, you must also use a string literal for the characteristic UUID as well.
You typically register your characteristic in setup()
:
// In setup():
BLE.addCharacteristic(batteryLevelCharacteristic);
In a peripheral role if you are receiving data from the central device, you typically assign your characteristic like this.
// PROTOTYPE
// Type T is any type that can be passed to BleUuid, such as const char * or a string-literal.
template<typename T>
BleCharacteristic(const char* desc, BleCharacteristicProperty properties, T charUuid, T svcUuid, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr)
template<typename T>
BleCharacteristic(const String& desc, BleCharacteristicProperty properties, T charUuid, T svcUuid, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr)
// EXAMPLE
BleCharacteristic rxCharacteristic("rx", BleCharacteristicProperty::WRITE_WO_RSP, rxUuid, serviceUuid, onDataReceived, NULL);
"rx"
a short string to identify the characteristicBleCharacteristicProperty::WRITE_WO_RSP
The BLE characteristic property. This is typically WRITE_WO_RSP for values you receive. See also BleCharacteristicProperty
.rxUuid
The UUID of this characteristic. serviceUuid
The UUID of the service this characteristic is part of.onDataReceived
The function that is called when data is received.NULL
Context pointer. If your data received handler is part of a C++ class, this is a good place to put the class instance pointer (this
).The data received handler has this prototype.
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context)
The onDataReceived()
handler is run from the BLE thread. You should avoid lengthy or blocking operations since it will affect other BLE processing. Additionally, the BLE thread has a smaller stack than the main application (loop) thread, so you avoid functions that require a large amount of stack space. To prevent these issues, you should set a flag in the data received handler and perform lengthy or stack-intensive operations from the loop()
instead. For example, you should not call Particle.publish()
, WiFi.clearCredentials()
, and many other functions directly from the onDataReceived handler.
You typically register your characteristic in setup()
in peripheral devices:
// In setup():
BLE.addCharacteristic(rxCharacteristic);
The callback is called from the BLE thread. It has a smaller stack than the normal loop stack, and you should avoid doing any lengthy operations that block from the callback. For example, you should not try to use functions like Particle.publish()
and you should not use delay()
. You should beware of thread safety issues.
Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.
Get the UUID of this characteristic.
// PROTOTYPE
BleUuid UUID() const;
// EXAMPLE
BleUuid uuid = batteryLevelCharacteristic.UUID();
See also BleUuid
.
Get the BLE characteristic properties for this characteristic. This indicates whether it can be read, written, etc..
// PROTOTYPE
BleCharacteristicProperty properties() const;
// EXAMPLE
BleCharacteristicProperty prop = batteryLevelCharacteristic.properties();
See also BleCharacteristicProperty
.
This overload of getValue()
is typically used when you have a complex characteristic with a packed data structure that you need to manually extract.
For example, the heart measurement characteristic has a flags byte followed by either a 8-bit or 16-bit value. You typically extract that to a uint8_t buffer using this method and manually extract the data.
// PROTOTYPE
ssize_t getValue(uint8_t* buf, size_t len) const;
If your characteristic has a string value, you can read it out using this method.
// PROTOTYPE
ssize_t getValue(String& str) const;
// EXAMPLE
String value;
characteristic.getValue(value);
You can read out arbitrary data types (int8_t, uint8_t, uint16_t, uint32_t, struct, etc.) by passing a pointer to the object.
// PROTOTYPE
template<typename T>
ssize_t getValue(T* val) const;
// EXAMPLE
uint16_t value;
characteristic.getValue(&value);
To set the value of a characteristic to arbitrary data, you can use this function.
// PROTOTYPE
ssize_t setValue(const uint8_t* buf, size_t len);
To set the value of the characteristic to a string value, use this method. The terminating null is not included in the characteristic; the length is set to the actual string length.
// PROTOTYPE
ssize_t setValue(const String& str);
ssize_t setValue(const char* str);
You can write out arbitrary data types (int8_t, uint8_t, uint16_t, uint32_t, struct, etc.) by passing the value to setValue.
// PROTOTYPE
template<typename T>
ssize_t setValue(T val) const;
// EXAMPLE
uint16_t value = 0x1234;
characteristic.setValue(value);
To be notified when a value is received to add a data received callback.
The context is used when you've implemented the data received handler in a C++ class. You can pass the object instance (this
) in context. If you have a global function, you probably don't need the context and can pass NULL.
// PROTOTYPE
void onDataReceived(BleOnDataReceivedCallback callback, void* context);
// BleOnDataReceivedCallback
void myCallback(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);
The callback is called from the BLE thread. It has a smaller stack than the normal loop stack, and you should avoid doing any lengthy operations that block from the callback. For example, you should not try to use functions like Particle.publish()
and you should not use delay()
. You should beware of thread safety issues.
Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.
This bitfield defines what access is available to the property. It's assigned by the peripheral, and provides information to the central device about how the characteristic can be read or written.
BleCharacteristicProperty::BROADCAST
(0x01) The value can be broadcast.BleCharacteristicProperty::READ
(0x02) The value can be read.BleCharacteristicProperty::WRITE_WO_RSP
(0x04) The value can be written without acknowledgement. For example, the UART peripheral example uses this characteristic properly to receive UART data from the central device.BleCharacteristicProperty::WRITE
(0x08) The value can be written to the peripheral from the central device.BleCharacteristicProperty::NOTIFY
(0x10) The value is published by the peripheral without acknowledgement. This is the standard way peripherals periodically publish data.BleCharacteristicProperty::INDICATE
(0x20) The value can be indicated, basically publish with acknowledgement.BleCharacteristicProperty::AUTH_SIGN_WRITES
(0x40) The value supports signed writes. This is operation not supported.BleCharacteristicProperty::EXTENDED_PROP
(0x80) The value supports extended properties. This operation is not supported.You only assign these values as a peripheral device, and will typically use one of two values:
BleCharacteristicProperty::NOTIFY
when your peripheral periodically publishes dataBleCharacteristicProperty::WRITE_WO_RSP
when your peripheral receives data from the central device.For bidirectional data transfer you typically assign two different characteristics, one for receive and one for transmit, as shown in the UART examples.
Services and characteristics are typically identified by their UUID. There are two types:
The 16-bit service IDs are assigned by the Bluetooth SIG and are listed here.
The 16-bit characteristic IDs are listed here.
You can create a 16-bit UUID like this:
// PROTOTYPE
BleUuid(uint16_t uuid16);
// EXAMPLE
BleUuid healthThermometerService(0x1809);
The value 0x1809 is from the assigned list of service IDs.
The 128-bit UUIDs are used for your own custom services and characteristics. These are not assigned by any group. You can use any UUID generator, such as the online UUID generator or tools that you run on your computer. There is no central registry; they are statistically unlikely to ever conflict.
A 128-bit (16 byte) UUID is often written like this: 240d5183-819a-4627-9ca9-1aa24df29f18
. It's a series of 32 hexadecimal digits (0-9, a-f) written in a 8-4-4-4-12 pattern.
// PROTOTYPE
BleUuid(const String& uuid);
BleUuid(const char* uuid);
// EXAMPLE
BleUuid myCustomService("240d5183-819a-4627-9ca9-1aa24df29f18");
You can also construct a UUID from an array of bytes (uint8_t):
// PROTOTYPE
BleUuid(const uint8_t* uuid128, BleUuidOrder order = BleUuidOrder::LSB);
// EXAMPLE
BleUuid myCustomService({0x24, 0x0d, 0x51, 0x83, 0x81, 0x9a, 0x46, 0x27, 0x9c, 0xa9, 0x1a, 0xa2, 0x4d, 0xf2, 0x9f, 0x18});
// PROTOTYPE
BleUuidType type() const;
// EXAMPLE
BleUuidType uuidType = uuid.type();
Returns a constant:
BleUuidType::SHORT
for 16-bit UUIDsBleUuidType::LONG
for 128-bit UUIDs// PROTOTYPE
bool isValid() const;
bool valid() const;
// EXAMPLE
bool isValid = uuid.isValid();
Return true
if the UUID is valid or false
if not.
Since 3.0.0:
In Device OS 3.0.0 and later,, valid()
can be used as well. Before, the isValid()
method was used.
You can test two UUIDs for equality.
// PROTOTYPE
bool operator==(const BleUuid& uuid) const;
bool operator==(const char* uuid) const;
bool operator==(const String& uuid) const;
bool operator==(uint16_t uuid) const;
bool operator==(const uint8_t* uuid128) const;
// EXAMPLE 1
BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
if (rxUuid == txUuid) {
// This code won't be executed since they're not equal
}
// EXAMPLE 2
BleUuid foundServiceUUID;
size_t svcCount = scanResult->advertisingData().serviceUUID(&foundServiceUUID, 1);
if (svcCount > 0 && foundServiceUUID == serviceUuid) {
// Device advertises our custom service "serviceUuid" so try to connect to it
}
Get the underlying bytes of the UUID.
// PROTOTYPE
void rawBytes(uint8_t uuid128[BLE_SIG_UUID_128BIT_LEN]) const;
const uint8_t* rawBytes() const;
const uint8_t* rawBytes() const;
uint8_t operator[](uint8_t i) const;
Since 3.0.0:
In Device OS 3.0.0 and later, you can retrieve a raw bytes of the UUID using the []
operator.
Other, less commonly used constructors include:
// PROTOTYPE
BleUuid(const hal_ble_uuid_t& uuid);
BleUuid(const BleUuid& uuid);
BleUuid(const uint8_t* uuid128, BleUuidOrder order = BleUuidOrder::LSB);
BleUuid(const uint8_t* uuid128, uint16_t uuid16, BleUuidOrder order = BleUuidOrder::LSB);
BleUuid(uint16_t uuid16);
BleUuid(const String& uuid);
BleUuid(const char* uuid);
You normally don't need to set the value of a BleUuid after construction, but the following methods are available:
// PROTOTYPE
BleUuid& operator=(const BleUuid& uuid);
BleUuid& operator=(const uint8_t* uuid128);
BleUuid& operator=(uint16_t uuid16);
BleUuid& operator=(const String& uuid);
BleUuid& operator=(const char* uuid);
BleUuid& operator=(const hal_ble_uuid_t& uuid);
The BleAdvertisingData is used in two ways:
For more information about advertising, see the BLE tutorial.
Construct a BleAdvertisingData
object. You typically do this in a peripheral role before adding new data.
In the central role, you get a filled in object in the BleScanResult
object.
// PROTOTYPE
BleAdvertisingData();
Appends advertising data of a specific type to the advertising data object.
// PROTOTYPE
size_t append(BleAdvertisingDataType type, const uint8_t* buf, size_t len, bool force = false);
// EXAMPLE
BleAdvertisingData advData;
uint8_t flagsValue = BLE_SIG_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
advData.append(BleAdvertisingDataType::FLAGS, &flagsValue, sizeof(flagsValue));
type
The type of data to append. The valid types are listed in the BleAdvertisingDataType
section, below.buf
Pointer to the buffer containing the datalen
The length of the data in bytesforce
If true, then multiple blocks of the same type can be appended. The default is false, which replaces an existing block and is the normal behavior.Note that advertising data is limited to 31 bytes (BLE_MAX_ADV_DATA_LEN
), and each block includes a type and a length byte, so you are quite limited in what you can add.
You normally don't need to include BleAdvertisingDataType::FLAGS
, however. If you do not include it, one will be inserted automatically.
Appends custom advertising data to the advertising data object.
// PROTOTYPE
size_t appendCustomData(const uint8_t* buf, size_t len, bool force = false);
buf
Pointer to the buffer containing the datalen
The length of the data in bytesforce
If true, then multiple blocks of the same type can be appended. The default is false, which replaces an existing block and is the normal behavior.Note that advertising data is limited to 31 bytes (BLE_MAX_ADV_DATA_LEN
), and each block includes a type and a length byte, so you are quite limited in what you can add.
Since 3.2.0:
If BlePhy::BLE_PHYS_CODED
advertising PHY is set, it's possible to provide up to 255 (BLE_MAX_ADV_DATA_LEN_EXT_CONNECTABLE
) of advertising data.
The first two bytes of the company data are typically the unique company identifier assigned by the Bluetooth SIG. You need to be a member of the Bluetooth SIG to get a company ID, and the field is only 16 bits wide, so there can only be 65534 companies.
The special value of 0xffff is reserved for internal use and testing.
If you are using private custom data it's recommended to begin it with two 0xff bytes, that way your data won't confuse apps that are scanning for company-specific custom data.
The maximum custom data size is 26 bytes, including the company ID. Adding data that is too large will cause it to be omitted (not truncated).
An optional field in the advertising data is the local name. This can be useful if the user needs to select from multiple devices.
The name takes up the length of the name plus two bytes (type and length). The total advertising data is limited to 31 bytes (BLE_MAX_ADV_DATA_LEN
), and if you include service identifiers there isn't much left space for the name.
// PROTOTYPE
size_t appendLocalName(const char* name);
size_t appendLocalName(const String& name);
Appends a service UUID to the advertisement (short or long). You typically only advertise the primary service.
For example, the health thermometer advertises the health thermometer service. Upon connecting, the central device can discover that it also supports the battery service. Put another way, a user or app would most likely only want to discover a nearby thermometer, not any battery powered device nearby.
// PROTOTYPE
// Type T is any type that can be passed to the BleUuid constructor
template<typename T>
size_t appendServiceUUID(T uuid, bool force = false)
// EXAMPLE
advData.appendServiceUUID(healthThermometerService);
uuid
The UUID to add. This can be a BleUuid
object, a uint16_t (short UUID), or a const char * or string literal specifying a long UUID.force
If true, then multiple blocks of the same type can be appended. The default is false, which replaces an existing block and is the normal behavior.Since long UUIDs are long (16 bytes plus 2 bytes of overhead) they will use a lot of the 31 byte payload, leaving little room for other things like short name.
Remove all existing data from the BleAdvertisingData object.
// PROTOTYPE
void clear();
Remove a specific data type from the BleAdvertisingData object.
// PROTOTYPE
void remove(BleAdvertisingDataType type);
type
The BleAdvertisingDataType
to remove.In a peripheral role, you sometimes will want to build your advertising data complete by hand. You can then copy your pre-build structure into the BleAdvertisingData object using set()
.
// PROTOTYPE
size_t set(const uint8_t* buf, size_t len);
In the central role, if you want to get a specific block of advertising data by type, you can use this method.
// PROTOTYPE
size_t get(BleAdvertisingDataType type, uint8_t* buf, size_t len) const;
type
The BleAdvertisingDataType
to get.buf
A pointer to the buffer to hold the data.len
The length of the buffer in bytes.Returns the number of bytes copied, which will be <= len
. Returns 0 if the type does not exist in the advertising data.
In the central role, if you want to get the advertising data as a complete block of data, you can use the get method with a buffer and length.
Advertising data is limited to 31 bytes (BLE_MAX_ADV_DATA_LEN
) and you should make your buffer at least that large to be able to receive the largest possible data.
// PROTOTYPE
size_t get(uint8_t* buf, size_t len) const;
buf
A pointer to the buffer to hold the data.len
The length of the buffer in bytes.Returns the number of bytes copied, which will be <= len
.
Return the length of the data in bytes.
// PROTOTYPE
size_t length() const;
uint8_t operator[](uint8_t i) const;
Since 3.0.0:
In Device OS 3.0.0 and later, you can retrieve a byte from the advertising data using the []
operator. This uses get()
internally.
Gets the deviceName (SHORT_LOCAL_NAME
or COMPLETE_LOCAL_NAME
). Returns a String object or an empty string if the advertising data does not contain a name.
// PROTOTYPE
String deviceName() const;
Gets the deviceName (SHORT_LOCAL_NAME
or COMPLETE_LOCAL_NAME
).
// PROTOTYPE
size_t deviceName(char* buf, size_t len) const;
buf
Buffer to hold the name. A buffer that is BLE_MAX_ADV_DATA_LEN
bytes long will always be large enough.len
Length of the buffer in bytes.Returns the size of the name in bytes. Returns 0 if there is no name.
Note that the buf will not be null-terminated (not a c-string).
Returns an array of service UUIDs in the advertising data.
// PROTOTYPE
size_t serviceUUID(BleUuid* uuids, size_t count) const;
uuids
Pointer to an array of BleUuid
objects to fill inlen
The number of array entries (not bytes)Returns the number of UUIDs in the advertisement.
This includes all UUIDs in the following advertising data types:
SERVICE_UUID_16BIT_MORE_AVAILABLE
SERVICE_UUID_16BIT_COMPLETE
SERVICE_UUID_128BIT_MORE_AVAILABLE
SERVICE_UUID_128BIT_COMPLETE
There is often a single UUID advertised, even for devices that have multiple services. For example, a heart rate monitor might only advertise that it's a heart rate monitor even though it also supports the battery service. The additional services can be discovered after connecting.
Since the advertisement data is limited to 31 bytes, the maximum number of services that could be advertised is 14 16-bit UUIDs.
Returns the MANUFACTURER_SPECIFIC_DATA
data in an advertisement.
// PROTOTYPE
size_t customData(uint8_t* buf, size_t len) const;
buf
Buffer to hold the data. A buffer that is BLE_MAX_ADV_DATA_LEN
bytes long will always be large enough.len
Length of the buffer in bytes.Returns the size of the data in bytes. Returns 0 if there is no MANUFACTURER_SPECIFIC_DATA
.
Return true if the advertising data contains the specified type.
// PROTOTYPE
bool contains(BleAdvertisingDataType type) const;
type
The data type to look for. For example: BleAdvertisingDataType::SHORT_LOCAL_NAME
. See BleAdvertisingDataType
.The following are the valid values for BleAdvertisingDataType
. In many cases you won't need to use the directly as you can use methods like serviceUUID
in the BleAdvertisingData
to set both the type and data automatically.
You would typically use these constants like this: BleAdvertisingDataType::FLAGS
.
You normally don't need to include BleAdvertisingDataType::FLAGS
, however. If you do not include it, one will be inserted automatically.
Similarly, you typically use appendCustomData()
instead of directly using MANUFACTURER_SPECIFIC_DATA
. The appendLocalName
() and appendServiceUUID()
functions of the BleAdvertisingData
also set the appropriate advertising data type.
FLAGS
SERVICE_UUID_16BIT_MORE_AVAILABLE
SERVICE_UUID_16BIT_COMPLETE
SERVICE_UUID_32BIT_MORE_AVAILABLE
SERVICE_UUID_32BIT_COMPLETE
SERVICE_UUID_128BIT_MORE_AVAILABLE
SERVICE_UUID_128BIT_COMPLETE
SHORT_LOCAL_NAME
COMPLETE_LOCAL_NAME
TX_POWER_LEVEL
CLASS_OF_DEVICE
SIMPLE_PAIRING_HASH_C
SIMPLE_PAIRING_RANDOMIZER_R
SECURITY_MANAGER_TK_VALUE
SECURITY_MANAGER_OOB_FLAGS
SLAVE_CONNECTION_INTERVAL_RANGE
SOLICITED_SERVICE_UUIDS_16BIT
SOLICITED_SERVICE_UUIDS_128BIT
SERVICE_DATA
PUBLIC_TARGET_ADDRESS
RANDOM_TARGET_ADDRESS
APPEARANCE
ADVERTISING_INTERVAL
LE_BLUETOOTH_DEVICE_ADDRESS
SIMPLE_PAIRING_HASH_C256
SIMPLE_PAIRING_RANDOMIZER_R256
SERVICE_SOLICITATION_32BIT_UUID
SERVICE_DATA_32BIT_UUID
SERVICE_DATA_128BIT_UUID
LESC_CONFIRMATION_VALUE
LESC_RANDOM_VALUE
INDOOR_POSITIONING
TRANSPORT_DISCOVERY_DATA
LE_SUPPORTED_FEATURES
CHANNEL_MAP_UPDATE_INDICATION
PB_ADV
MESH_MESSAGE
MESH_BEACON
THREE_D_INFORMATION_DATA
MANUFACTURER_SPECIFIC_DATA
The valid values for advertising data type flags are:
BLE_SIG_ADV_FLAG_LE_LIMITED_DISC_MODE
(0x01)BLE_SIG_ADV_FLAG_LE_GENERAL_DISC_MODE
(0x02)BLE_SIG_ADV_FLAG_BR_EDR_NOT_SUPPORTED
(0x04)BLE_SIG_ADV_FLAG_LE_BR_EDR_CONTROLLER
(0x08)BLE_SIG_ADV_FLAG_LE_BR_EDR_HOST
(0x10)BLE_SIG_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE
(0x05)BLE_SIG_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE
(0x06)The most commonly used is BLE_SIG_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE
: supports Bluetooth LE only in general discovery mode. Any nearby device can discover it by scanning. Bluetooth Basic Rate/Enhanced Data Rate (BR/EDT) is not supported.
You normally don't need to include BleAdvertisingDataType::FLAGS
, however. If you do not include it, one will be inserted automatically.
// EXAMPLE
// In setup():
BleAdvertisingData advData;
uint8_t flagsValue = BLE_SIG_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
advData.append(BleAdvertisingDataType::FLAGS, &flagsValue, sizeof(flagsValue));
advData.appendServiceUUID(healthThermometerService);
BLE.advertise(&advData);
When using a Particle device in BLE central mode, connecting a peripheral returns a BlePeerDevice
object. This object can be used to see if the connection is still open and get BleCharacteristic
objects for the peripheral device.
Typically you'd get the BlePeerDevice
from calling BLE.connect()
.
// Device OS 3.0 and later
BlePeerDevice peer = BLE.connect(scanResults[ii].address());
if (peer.connected()) {
peerTxCharacteristic = peer.getCharacteristicByUUID(txUuid);
peerRxCharacteristic = peer.getCharacteristicByUUID(rxUuid);
// ...
}
// Device OS 2.x and earlier
BlePeerDevice peer = BLE.connect(scanResults[ii].address);
if (peer.connected()) {
peerTxCharacteristic = peer.getCharacteristicByUUID(txUuid);
peerRxCharacteristic = peer.getCharacteristicByUUID(rxUuid);
// ...
}
Once you have the BlePeerDevice
object you can use the following methods:
Returns true if the peer device is currently connected.
// PROTOTYPE
bool connected();
// EXAMPLE
if (peer.connected()) {
// Peripheral is connected
}
else {
// Peripheral has disconnected
}
Get the BLE address of the peripheral device.
// PROTOTYPE
const BleAddress& address() const;
// EXAMPLE 1
Log.trace("Received data from: %s", peer.address().toString().c_str());
// EXAMPLE 2
Log.trace("Received data from: %02X:%02X:%02X:%02X:%02X:%02X", peer.address()[5], peer.address()[4], peer.address()[3], peer.address()[2], peer.address()[1], peer.address()[0]);
See BleAddress
for more information.
Get a characteristic by its UUID, either short or long UUID. See also BleUuid
. Returns true if the characteristic was found.
You often do this from the central device after making a connection.
// PROTOTYPE
bool getCharacteristicByUUID(BleCharacteristic& characteristic, const BleUuid& uuid) const;
// EXAMPLE
BleCharacteristic characteristic;
bool bResult = peer.getCharacteristicByUUID(characteristic, BleUuid(0x180d));
// EXAMPLE
BleCharacteristic characteristic;
bool bResult = peer.getCharacteristicByUUID(characteristic, BleUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"));
Get the characteristic by its description. As these strings are not standardized like UUIDs, it's best to use UUIDs instead. Returns true if the characteristic was found.
// PROTOTYPE
bool getCharacteristicByDescription(BleCharacteristic& characteristic, const char* desc) const;
bool getCharacteristicByDescription(BleCharacteristic& characteristic, const String& desc) const;
// EXAMPLE
BleCharacteristic characteristic;
bool bResult = peer.getCharacteristicByDescription(characteristic, "rx");
When scanning, you get back one of:
BleScanResult
recordsBleScanResult
recordsBleScanResult
.The following fields are provided:
address
The BLE address of the peripheral. You use this if you want to connect to it. See BleAddress
.advertisingData
The advertising data provided by the peripheral. It's small (up to 31 bytes).scanResponse
The scan response data. This is an optional extra 31 bytes of data that can be provided by the peripheral. It requires an additional request to the peripheral, but is less overhead than connecting.rssi
The signal strength, which is a negative number of dBm. Numbers closer to 0 are a stronger signal.Prior to Device OS 3.0, these were member variables.
In Device OS 3.0 and later, they are methods, so you must access them as:
address()
The BLE address of the deviceadvertisingData()
The advertising data sent by the devicescanData()
The scan data (optional)rssi()
The signal strength of the advertising message.// PROTOTYPE
Vector<BleCharacteristic> discoverAllCharacteristics();
ssize_t discoverAllCharacteristics(BleCharacteristic* characteristics, size_t count);
Since 3.0.0:
In Device OS 3.0.0 and later, once you're connected to a BLE peripherals, you can optionally query all of the characteristics available on that peer. Normally you would know the characteristic you wanted and would get the single characteristic by UUID or description, but it is also possible to retrieve all characteristics.
Each Bluetooth device has an address, which is encapsulated in the BleAddress object. The address is 6 octets, and the BleAddress object has two additional bytes of overhead. The BleAddress object can be trivially copied.
You can copy and existing BleAddress.
// PROTOTYPE
BleAddress& operator=(hal_ble_addr_t addr)
BleAddress& operator=(const uint8_t addr[BLE_SIG_ADDR_LEN])
The BleAddress is 6 octets long (constant: BLE_SIG_ADDR_LEN
). You can access individual bytes using array syntax:
// PROTOTYPE
uint8_t operator[](uint8_t i) const;
// EXAMPLE - Device OS 3.0 and later
Log.info("rssi=%d address=%02X:%02X:%02X:%02X:%02X:%02X ",
scanResults[ii].rssi(),
scanResults[ii].address()[5], scanResults[ii].address()[4], scanResults[ii].address()[3],
scanResults[ii].address()[2], scanResults[ii].address()[1], scanResults[ii].address()[0]);
// EXAMPLE - Device OS 2.x and earlier
Log.info("rssi=%d address=%02X:%02X:%02X:%02X:%02X:%02X ",
scanResults[ii].rssi,
scanResults[ii].address[5], scanResults[ii].address[4], scanResults[ii].address[3],
scanResults[ii].address[2], scanResults[ii].address[1], scanResults[ii].address[0]);
See also toString() for an easier way to print address string.
Since 1.3.1:
// PROTOTYPE
String toString(bool stripped = false) const;
// EXAMPLE
Log.info("rssi=%d address=%%s", scanResults[ii].rssi(), scanResults[ii].address().toString().c_str());
Converts a BleAddress to a String
. The default value of stripped
is false, which results in the
standard colon-separated format. If stripped
is true, then the raw 12 hex bytes with no separators
is returned.
You can test two BleAddress objects for equality (same address).
// PROTOTYPE
bool operator==(const BleAddress& addr) const
// PROTOTYPE
bool valid() const;
Since 3.0.0:
You can test if a BLEAddress object is valid using the valid()
method in Device OS 3.0.0 and later.
Often you will get the value of a BleAddress for debugging purposes.
// PROTOTYPE
BleAddressType type() const;
void octets(uint8_t addr[BLE_SIG_ADDR_LEN]) const;
String toString(bool stripped = false) const;
size_t toString(char* buf, size_t len, bool stripped = false) const;
hal_ble_addr_t halAddress() const;
uint8_t operator[](uint8_t i) const;
// EXAMPLE 1
Log.info("found device %s", scanResult->address.toString().c_str());
// EXAMPLE 2
Log.info("found device %02X:%02X:%02X:%02X:%02X:%02X",
scanResult->address[5], scanResult->address[4], scanResult->address[3],
scanResult->address[2], scanResult->address[1], scanResult->address[0]);
Normally you won't construct a BleAddress as you get one back from scanning using BLE.scan()
. However there are numerous options:
// PROTOTYPE
BleAddress(const hal_ble_addr_t& addr);
BleAddress(const uint8_t addr[BLE_SIG_ADDR_LEN], BleAddressType type = BleAddressType::PUBLIC);
BleAddress(const char* address, BleAddressType type = BleAddressType::PUBLIC);
BleAddress(const String& address, BleAddressType type = BleAddressType::PUBLIC);
You will not normally need to set the value of a BleAddress, but there are methods to do so:
// PROTOTYPE
int type(BleAddressType type);
int set(const uint8_t addr[BLE_SIG_ADDR_LEN], BleAddressType type = BleAddressType::PUBLIC);
int set(const char* address, BleAddressType type = BleAddressType::PUBLIC);
int set(const String& address, BleAddressType type = BleAddressType::PUBLIC);
This enumeration specifies the type of BLE address. The default is PUBLIC. The type is part of the BleAddress class.
BleAddressType::PUBLIC
Public (identity address).BleAddressType::RANDOM_STATIC
Random static (identity) address.BleAddressType::RANDOM_PRIVATE_RESOLVABLE
Random private resolvable address.BleAddressType::RANDOM_PRIVATE_NON_RESOLVABLE
Random private non-resolvable address.You will not typically need to change this. The default is CONNECTABLE_SCANNABLE_UNDIRECTED
(0).
Valid values include:
CONNECTABLE_SCANNABLE_UNDIRECTED
CONNECTABLE_UNDIRECTED
CONNECTABLE_DIRECTED
NON_CONNECTABLE_NON_SCANABLE_UNDIRECTED
NON_CONNECTABLE_NON_SCANABLE_DIRECTED
SCANABLE_UNDIRECTED
SCANABLE_DIRECTED
To set the parameters for advertising, you can use the BleAdvertisingParams structure:
// PROTOTYPE
uint16_t version;
uint16_t size;
uint16_t interval;
uint16_t timeout;
hal_ble_adv_evt_type_t type;
uint8_t filter_policy;
uint8_t inc_tx_power;
uint8_t reserved;
// EXAMPLE
BleAdvertisingParams param;
param.version = BLE_API_VERSION;
param.size = sizeof(BleAdvertisingParams);
int res = BLE.getAdvertisingParameters(¶m);
version
Always set to BLE_API_VERSION
.size
Always set to sizeof(BleAdvertisingParams)
.interval
Advertising interval in 0.625 ms units. Default is 160.timeout
Advertising timeout in 10 ms units. Default is 0.type
See BleAdvertisingEventType
. Default is CONNECTABLE_SCANNABLE_UNDIRECTED
(0).filter_policy
Default is 0.inc_tx_power
Default is 0.The BleScanParams structure specifies the settings for scanning on a central device.
// PROTOTYPE
uint16_t version;
uint16_t size;
uint16_t interval;
uint16_t window;
uint16_t timeout; uint8_t active;
uint8_t filter_policy;
// EXAMPLE
BleScanParams scanParams;
scanParams.version = BLE_API_VERSION;
scanParams.size = sizeof(BleScanParams);
int res = BLE.getScanParameters(&scanParams);
version
Always set to BLE_API_VERSION
.size
Always set to sizeof(BleScanParams)
.interval
Advertising interval in 0.625 ms units. Default is 160.window
Scanning window in 0.625 ms units. Default is 80.timeout
Scan timeout in 10 ms units. Default value is 500.active
Boolean value, typically 1.filter_policy
Default is 0.Apple iOS iBeacon can be used to customize mobile app content based on nearby beacons.
There are three parameters of interest:
Field | Size | Description |
---|---|---|
UUID | 16 bytes | Application developers should define a UUID specific to their app and deployment use case. |
Major | 2 bytes | Further specifies a specific iBeacon and use case. For example, this could define a sub-region within a larger region defined by the UUID. |
Minor | 2 bytes | Allows further subdivision of region or use case, specified by the application developer. |
(From the Getting Started with iBeacon guide.)
In other words, you'll assign a single UUID to all of the beacons in your fleet of beacons and figure out which one you're at using the major and minor values. When searching for an iBeacon, you need to know the UUID of the beacon you're looking for, so you don't want to assign too many.
// PROTOTYPE
// Type T is any type that can be passed to the BleUuid constructor, such as a BleUuid object, const char *, or Uint16_t.
template<typename T>
iBeacon(uint16_t major, uint16_t minor, T uuid, int8_t measurePower)
// EXAMPLE
void setup() {
iBeacon beacon(1, 2, "9c1b8bdc-5548-4e32-8a78-b9f524131206", -55);
BLE.advertise(beacon);
}
The parameters to the iBeacon constructor in the example are:
Since 3.0.0:
// PROTOTYPES
uint16_t major() const;
uint16_t minor() const;
const BleUuid& UUID() const;
int8_t measurePower() const;
In Device OS 3.0.0 and later there are accessors to read the values out of the iBeacon class.
Since 6.1.0:
Ledger provides device to cloud and cloud to device data synchronization. See Ledger for an introduction to Ledger.
You will typically request a ledger using Particle.ledger() like this:
Ledger sensors;
void setup() {
sensors = Particle.ledger("sensors");
}
The Particle.ledger
function is asynchronous and the data will not be available until the device comes online and has synchronized with the cloud.
The type of ledger (device-to-cloud or cloud-to-device), as well as the ledger scope (organization, product, or device) is determined when the ledger is created on the cloud side, so it is not specified when you request the ledger. You must first create a ledger definition in the cloud; you cannot create a new ledger definition using the device-side API.
The first time a device comes online specifying a device to cloud ledger, a new ledger instance will be created for the device, however.
You will typically store the Ledger
object you receive from Particle.ledger()
as a global variable so you can access it again later easily.
Ledger names consist only of lowercase alphanumeric and dash, up to 32 characters, and are unique across all scopes.
Sets a value in the ledger. The data is saved to local flash storage immediately, but will be synchronized to the cloud at a later time.
// PROTOTYPE
int set(const LedgerData& data, SetMode mode = SetMode::REPLACE);
// EXAMPLE
Variant data;
data.set("sensor", sensorValue);
data.set("time", Time.format(TIME_FORMAT_ISO8601_FULL));
sensors.set(data);
While the parameter data
is a LedgerData
object, you will typically pass a Variant
. See Ledger sensor for the full example.
See SetMode, below, for valid values.
Constant | Description |
---|---|
SetMode::REPLACE |
Replace the current ledger data |
SetMode::MERGE |
Update some of the entries of the ledger data |
When a ledger is first modified, Device OS initiates a sync right away and start a 5 seconds cooldown timer.
A ledger can be modified while the device is offline, and will be synchronized when the device comes back online.
A cloud to device ledger is stored locally in the flash file system on the device. This allows it to be used before cloud connecting, but if you do this, the device will not have received any updates queued in the cloud. If the ledger has not changed since the last synchronization, it will not be transferred again, saving data.
// PROTOTYPE
LedgerData get() const;
The get() method gets the Ledger data. This call does not block, so the data may be empty if the ledger has not yet been retrieved from the cloud.
Get the time the ledger was last updated, in milliseconds since the Unix epoch (January 1, 1970), or 0 if unknown.
// PROTOTYPE
int64_t lastUpdated() const;
Get the time the ledger was last synchronized with the Cloud, in milliseconds since the Unix epoch (January 1, 1970), or 0 if unknown.
// PROTOTYPE
int64_t lastSynced() const;
Returns the size of the ledger data in bytes.
// PROTOTYPE
size_t dataSize() const;
Returns the name of the ledger. This is typically set during setup().
//
const char* name() const;
// PROTOTYPE
LedgerScope scope() const;
See LedgerScope, below, for constants.
The scope of a ledger is defined on the cloud side, so before the ledger is first retrieved the scope will be unknown on the device.
Constant | Description |
---|---|
LedgerScope::UNKNOWN |
Unknown |
LedgerScope::DEVICE |
Device |
LedgerScope::PRODUCT |
Product |
LedgerScope::OWNER |
User or organization |
Returns true if the ledger can be written to. Cloud to device ledgers cannot be written to.
// PROTOTYPE
bool isWritable() const;
Registers a function to be called when the ledger is synchronized. This method takes a C function and an arg
value. The arg can be a pointer to a C++ object instance, a pointer to some other structure, or can be 0 if you don't need an additional argument.
// PROTOTYPE
int onSync(OnSyncCallback callback, void* arg = nullptr);
// CALLBACK DEFINITION
typedef void (*OnSyncCallback)(Ledger ledger, void* arg);
// CALLBACK PROTOTYPE
void myCallback(Ledger ledger, void* arg)
Registers a function to be called when the ledger is synchronized. This method takes a std::function which can be a C++ lambda, which can be useful for calling a method implemented in a non-static C++ class.
// PROTOTYPE
int onSync(OnSyncFunction callback);
// CALLBACK DEFINITIONS
typedef std::function<void(Ledger)> OnSyncFunction;
// CALLBACK PROTOTYPE
void myCallback(Ledger ledger)
// CALLBACK LAMBDA
myLedger.onSync([](Ledger ledger) {
// Code to run when synchronization completes goes here
}
Remove any data associated with a ledger from the device.
The device must not be connected to the Cloud. The operation will fail if any of the ledgers are in use.
The data is not guaranteed to be removed in an irrecoverable way.
See also Ledger::removeAll to remove all ledger data and includes a code example.
// PROTOTYPE
static int remove(const char* name);
Remove any ledger data from the device.
The device must not be connected to the Cloud. The operation will fail if any of the ledgers are in use.
The data is not guaranteed to be removed in an irrecoverable way.
// PROTOTYPE
static int removeAll();
// EXAMPLE
#include "Particle.h"
SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler(LOG_LEVEL_INFO);
void setup() {
// Remove any ledger data from the device.
// The device must not be connected to the Cloud. The operation will fail if any of the ledgers are in use.
Ledger::removeAll();
Particle.connect();
}
void loop() {
}
The LedgerData
class provides a subset of methods of the Variant
class that are relevant to map operations. The Ledger class set() and get() take a LedgerData object.
Because a Variant
is transparently converted into LedgerData
you may never need to create a LedgerData
object. See Ledger sensor for an example of this technique.
Construct a LedgerData from a Variant. The Variant must be a VariantMap
, if not then the LedgerData will be empty.
// PROTOTYPE
LedgerData(Variant var);
Construct a LedgerData option from statically defined entries.
// PROTOTYPE
LedgerData(std::initializer_list<Entry> entries);
// EXAMPLE
LedgerData data = { { "key1", "value1" }, { "key2", 2 } };
Set a top-level key in the LedgerData to a value stored in a Variant
.
// PROTOTYPES
bool set(const char* name, Variant val);
bool set(const String& name, Variant val);
bool set(String&& name, Variant val);
Remove a top-level key in the LedgerData.
// PROTOTYPES
bool remove(const char* name);
bool remove(const String& name);
Get the value of an entry with the name name
.
This method is inefficient for complex value types, as it returns a copy of
the value. Use operator[]
to get a reference to the value.
A null Variant
is returned if the entry doesn't exist.
// PROTOTYPES
Variant get(const char* name) const;
Variant get(const String& name) const;
Get a reference to the entry value with the given name
.
The entry is created if it doesn't exist.
The device will panic if it fails to allocate memory for the new entry. Use set()
or the methods provided by VariantMap
if you need more control over how memory allocation
errors are handled.
// PROTOTYPES
Variant& operator[](const char* name);
Variant& operator[](const String& name);
// EXAMPLE - Create an array if necessary and add an element to the array.
data["alarm"].append(reasonCode);
// EXAMPLE - Create an object if necessary and add a key/value pair to it.
data["sensors"].set("temp", tempValue);
Check if an entry with the given name exists.
// PROTOTYPES
bool has(const char* name) const;
bool has(const String& name) const;
Returns true if the ledger data is empty (has no entries).
// PROTOTYPE
bool isEmpty() const;
See Variant and Map for additional information.
Since 6.2.0:
The Buffer
class is a container for data in a dynamically allocated buffer. It's essentially a wrapper
around std::vector<char>
with additional convenience methods for encoding the data.
The Variant
class has been extended to store arbitrary binary data in a Buffer
. A Variant containing a Buffer
cannot be serialized to JSON or deserialized from JSON on-device using the toJSON()
and fromJSON()
methods
but can when publishing an event to the cloud. See Typed publish for more information.
Constuct a buffer pre-allocated to size
.
// PROTOTYPE
explicit Buffer(size_t size);
Construct a buffer from a pointer (data
) and size
.
// PROTOTYPE
Buffer(const char* data, size_t size)
The buffer contains a copy of the data in a newly allocated block of memory. The source data does not need to be null-terminated and can contain arbitrary binary data.
Access the internal data buffer as a char *
.
// PROTOTYPE
char* data()
Even though this type is normally used for c-strings, the buffer is arbitrary binary data and is not encoded with a null-terminator.
Access the internal data buffer as a const char *
.
// PROTOTYPE
const char* data() const
Even though this type is normally used for c-strings, the buffer is arbitrary binary data and is not encoded with a null-terminator.
Return the size of the buffer (number of bytes).
// PROTOTYPE
size_t size() const
This is the size of the data, see also reserve()
and capacity()
for more information.
Returns true
if the size of the buffer is 0.
// PROTOTYPE
bool isEmpty() const
Sets the size of the buffer.
// PROTOTYPE
bool resize(size_t size)
If size is larger than the capacity of the buffer it will be reallocated and copied.
Returns true
except when increasing the size of the buffer and the memory allocation
fails. In this case, false
is return and the size is left unchanged.
Returns the capacity of the buffer.
// PROTOTYPE
size_t capacity() const
It is possible to reserve additional bytes at the end of the buffer that are larger than the size. This allows for more efficient appending to the buffer when you know the eventual size, and eliminates multiple reallocations.
Adds reserved bytes to the buffer. The size must be larger than the current capacity.
// PROTOTYPE
bool reserve(size_t size)
This can only be used to reserve additional space. To make the allocated buffer smaller, use trimToSize().
It is possible to reserve additional bytes at the end of the buffer that are larger than the data size. This allows for more efficient appending to the buffer when you know the eventual size, and eliminates multiple reallocations.
Normally you will use resize()
to change the number of bytes used; this call is only
for affecting the underlying memory used.
Reduce the memory allocation for the buffer.
// PROTOTYPE
bool trimToSize()
This cannot be used to make the allocated buffer larger. See reserve()
.
It is possible to reserve additional bytes at the end of the buffer that are larger than the data size. This call removes these bytes. It's normally not necessary to use this, as the size is separate from capacity and you will normally use the size.
Normally you will use resize()
to change the number of bytes used; this call is only
for affecting the underlying memory used.
Convert the buffer to hexaedcimal encoding. Returns a String
object containing the hex.
// PROTOTYPE
String toHex() const
Convert the buffer to hexaedcimal encoding. Writes the hex data into the specified pointer and size. The out string is null terminated if the size is large enough to hold it.
// PROTOTYPE
size_t toHex(char* out, size_t size) const
Returns true if the Buffer
object are the same size and have the same byte values.
// PROTOTYPE
bool operator==(const Buffer& buf) const
Returns true if the Buffer
object are not the same size or do not have the same byte values.
// PROTOTYPE
bool operator!=(const Buffer& buf) const
Create a Buffer
object from hexadecimal data in a String
object.
// PROTOTYPE
static Buffer fromHex(const String& str)
Create a Buffer
object from hexadecimal data in a c-string (null-terminated).
// PROTOTYPE
static Buffer fromHex(const char* str)
Create a Buffer
object from hexadecimal data specified by pointer and length.
// PROTOTYPE
static Buffer fromHex(const char* str, size_t len)
When specifing str
and len
, the string and length should specify the data
only. A null terminator is not required and must not be included in len
.
Since 6.1.0:
The Variant
class holds typed data. It is used by Ledger. See also VariantArray and VariantMap to hold data that will be converted to JSON.
In Device OS 6.2 and later, a Variant is the best way to send JSON data as it can encode the data more efficiently over the air. See Typed publish for more information.
A Variant can hold arbitary binary data using the Buffer
class in Device OS 6.2 and later. A Variant containing a Buffer
cannot be serialized to JSON or deserialized from JSON as JSON does not support binary values.
If you have a JSON object and want to create code stubs for generating a Variant
with the same
shape, see the JSON tool which has a Variant code generator.
One common use-case of Variant is setting values in a ledger. In this code, a fake sensor value is stored in int sensorValue
. In a real application, you'd read an actual sensor there.
Then a Variant
object is created on the stack that will be filled with data.
The set()
method is described below in VariantMap. It takes a key name (such as "sensor" or "time") and a value, which is also a Variant
. Because there are Variant constructors for many variable types (see variant constructor, below), you can pass a variable directly to set and the compiler will handle it appropriately. In the time example, the returned object from Time.format
is a String
.
int sensorValue = readSensor();
Variant data;
data.set("sensor", sensorValue);
data.set("time", Time.format(TIME_FORMAT_ISO8601_FULL));
sensors.set(data);
See Ledger sensor for the full example.
Variants have an explicit type, unlike JSON. The following
Constant | C++ Type |
---|---|
Variant::Type::NULL | Null type (std::monostate ) |
Variant::Type::BOOL | bool |
Variant::Type::INT | int |
Variant::Type::UINT | unsigned |
Variant::Type::INT64 | int64_t |
Variant::Type::UINT64 | uint64_t |
Variant::Type::DOUBLE | double |
Variant::Type::STRING | String |
Variant::Type::BUFFER | Buffer |
Variant::Type::ARRAY | VariantArray |
Variant::Type::MAP | VariantMap |
You can construct a Variant
object with a parameter of an explict type to create a variant of that type.
// PROTOTYPES
Variant();
Variant(const std::monostate& val);
Variant(bool val);
Variant(int val);
Variant(unsigned val);
Variant(long val);
Variant(unsigned long val);
Variant(long val);
Variant(unsigned long val);
Variant(long long val);
Variant(unsigned long long val);
Variant(double val);
Variant(const char* val);
Variant(String val);
Variant(Buffer val)
Variant(VariantArray val);
Variant(VariantMap val);'
The value
method is a template that returns a reference to the stored value of the variant. The allowable types are listed in Variant::Type. This can be done to either get or set a value.
// PROTOTYPES
template<typename T>
T& value();
template<typename T>
const T& value() const;
// EXAMPLES
int value1 = variant1.value<int>();
bool value2 = variant2.value<bool>();
String value3 = variant3.value<String>();
See value, as, and to for when to use the value() vs. other accessors.
There are several variations of accessors to Variant values used for different purposes:
Accessor | Description |
---|---|
value |
Returns a reference to the currently held value. Requires it be of the type T otherwise would cause undefined behavior. |
as |
converts the underlying value of the variant to T in place, modifying the variant, and returns a reference to it. Safe to call if the types don't match but requires T to be one of the supported types, otherwise it will cause a compilation error. |
to |
converts the underlying value to T without modifying the variant and returns the result by value. Safe to call if the types don't match, T can be arbitrary. |
Variant
, the asXXX()
methods can be set the variant to be that type. Useful when building the data structure to set ledger values.Variant
, the toXXX()
methods can be used to copy the data convert it as necessary, without modifying the original.Return the type of the Variant
. See Variant::Type for a list of valid types. There are also accessors to check for a specific type such as isInt()
.
// PROTOTYPE
Type type() const;
Returns true if the Variant is of the specified type.
// PROTOTYPES
bool isNull() const;
bool isBool() const;
bool isInt() const;
bool isUInt() const;
bool isInt64() const;
bool isUInt64() const;
bool isDouble() const;
bool isNumber() const;
bool isString() const;
bool isBuffer() const
bool isArray() const;
bool isMap() const;
Returns value of the variant to an bool
(boolean, true
or false
), converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | unchanged |
numeric | zero (0 ) → false , non-zero → true |
String | false → false , 'true' → "true" |
other | conversion fails |
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
bool toBool() const;
bool toBool(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as a bool
. This can be used to modify the value of the variant.
// PROTOTYPE
bool& asBool();
This method will convert the type of the variant to bool if necessary, see toBool for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant to an int
(32-bit signed), converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | false → "false", true → "true" |
numeric | unchanged |
String | string number → 32-bit integer |
other | conversion fails |
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
int toInt() const;
int toInt(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an int
. This can be used to modify the value of the variant.
// PROTOTYPE
int& asInt();
This method will convert the type of the variant to int
if necessary, see toInt for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant to unsigned
(unsigned int
, 32-bit unsigned), converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | false → 0 , true → 1 |
numeric | unchanged |
String | string number → 32-bit unsigned integer |
other | conversion fails |
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
unsigned toUInt() const;
unsigned toUInt(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an unsigned
. This can be used to modify the value of the variant.
// PROTOTYPE
unsigned& asUnsigned();
This method will convert the type of the variant to unsigned
if necessary, see toUInt for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant to an int64_t
(64-bit signed integer), converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | false → 0 , true → 1 |
numeric | unchanged |
String | string number → 64-bit signed integer |
other | conversion fails |
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
int64_t toInt64() const;
int64_t toInt64(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an int64_t
. This can be used to modify the value of the variant.
// PROTOTYPE
int64_t& asInt64();
This method will convert the type of the variant to int64_t
if necessary, see toInt64 for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant to uint64_t
(64-bit unsigned integer), converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | false → 0 , true → 1 |
numeric | unchanged |
String | string number → 64-bit unsigned integer |
other | conversion fails |
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
uint64_t toUInt64() const;
uint64_t toUInt64(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an uint64_t
. This can be used to modify the value of the variant.
// PROTOTYPE
uint64_t& asUInt64();
This method will convert the type of the variant to uint64_t
if necessary, see toUInt64 for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant to an double
(8 byte or 64-bit double precision floating point), converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | false → 0.0 , true → 1.0 |
numeric | unchanged |
String | string number → double floating point |
other | conversion fails |
If the value of the double
it within the bounds of a float
you can static cast from double
to float
. A float
(single precision, 4-byte or 32-bit) will be promoted to a double
automatically if necessary.
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
double toDouble() const;
double toDouble(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an double
. This can be used to modify the value of the variant.
// PROTOTYPE
double& asDouble();
This method will convert the type of the variant to double
if necessary, see toDouble for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant to an String
(an ASCII or UTF-8 string) and returns a copy of it, converting the type if necessary. The original value is left unchanged.
Source | Result |
---|---|
bool | false → "0", true → "1" |
numeric | converted to a string (decimal ASCII) |
String | unchanged |
other | conversion fails |
There are two overloads, one that takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type or can be converted.
// PROTOTYPES
String toString() const;
String toString(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an String
. This can be used to modify the value of the variant and to more efficiently read the string without having to copy it.
// PROTOTYPE
String& asString();
This method will convert the type of the variant to String
if necessary, see toString for more information. If the conversion is not possible, the method will cause a compilation error.
See value, as, and to for when to use the asXXX() vs. other accessors.
Since 6.2.0:
// PROTOTYPE
Buffer toBuffer() const;
Buffer toBuffer(bool& ok) const;
A Variant containing a Buffer
cannot be serialized to JSON or deserialized from JSON as JSON does not support binary values.
See value, as, and to for when to use the asXXX() vs. other accessors.
Since 6.2.0:
// PROTOTYPE
Buffer& asBuffer();
A Variant containing a Buffer
cannot be serialized to JSON or deserialized from JSON as JSON does not support binary values.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant as an array.
Source | Result |
---|---|
VariantArray | unchanged |
other | conversion fails |
This method takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type.
// PROTOTYPES
VariantArray toArray(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an VariantArray
. This can be used to modify the value of the variant and to more efficiently read the array without having to copy it.
// PROTOTYPE
VariantArray& asArray();
This method will not convert the type of variant. If the Variant is not already a VariantArray
then this method will assert.
See value, as, and to for when to use the asXXX() vs. other accessors.
Returns the value of the variant as an map.
Source | Result |
---|---|
VariantMap | unchanged |
other | conversion fails |
This method takes an ok
parameter passed by reference, which is filled in as
true
if the data is already this type.
// PROTOTYPES
VariantMap toMap(bool& ok) const;
See value, as, and to for when to use the toXXX() vs. other accessors.
Returns a reference to the value contained in this variant as an VariantMap
. This can be used to modify the value of the variant and to more efficiently read the map without having to copy it.
// PROTOTYPE
VariantMap& asMap();
This method will not convert the type of variant. If the Variant is not already a VariantMap
then this method will assert.
See value, as, and to for when to use the asXXX() vs. other accessors.
Serializes a VariantArray or VariantMap as JSON and returns it in a String
object.
A Variant containing a Buffer
cannot be serialized to JSON or deserialized from JSON as JSON does not support binary values.
// PROTOTYPE
String toJSON() const;
Given a JSON object as a c-string or JSONValue
, returns a Variant
object.
// PROTOTYPE
static Variant fromJSON(const char* json);
static Variant fromJSON(const JSONValue& val);
// EXAMPLE
const char *jsonStr = "{\"a\":123}";
Variant data = Variant::fromJson(jsonStr);
The VariantArray
is a Vector, essentially a dynamically-sized array of Variants, which are containers for arbitrary data. Think of this as a container to hold arbitrary data in Variant objects before converting to a JSON array before sending across the network.
// DEFINITION
typedef Vector<Variant> VariantArray;
The methods below are implemented in the Variant
class, so you can use them update a Variant
.
Appends an element to the end of a VariantArray
. This method is implemented in Variant
. Returns true if the operation succeeded.
// PROTOTYPES
bool append(Variant val);
Prepend (insert at the beginning) an element to a VariantArray
. This method is implemented in Variant
. Returns true if the operation succeeded.
// PROTOTYPES
bool prepend(Variant val);
Inserts an element with value val
at index index
(0-based) into a VariantArray
. This method is implemented in Variant
. Returns true if the operation succeeded.
// PROTOTYPES
bool insertAt(int index, Variant val);
Removes the element at index index
(0-based) into a VariantArray
. This method is implemented in Variant
.
// PROTOTYPES
void removeAt(int index);
Returns a copy of the element (by value, not by reference) at index index
(0-based) into a VariantArray
. This method is implemented in Variant
.
// PROTOTYPES
Variant at(int index) const;
The VariantMap
class is a Map of a String to a Variant. Think of this as a container to store key-value pair of arbitrary data. This is used to hold data before converting it to JSON for sending over the network.
// DEFINITION
typedef Map<String, Variant> VariantMap;
The methods below are implemented in the Variant
class, so you can use them update a Variant
.
Add or update an element with key key
in the map, setting the value to value
. This method is implemented in Variant
.
// PROTOTYPES
bool set(const char* key, Variant val);
bool set(const String& key, Variant val);
If the stored value of this Variant
is not a map, it is converted to a map in place prior to the operation.
Remove an key key
in the map. Returns true if this variant is a map and the key exists, otherwise returns false. This method is implemented in Variant
.
// PROTOTYPES
bool remove(const char* key);
bool remove(const String& key);
Get the value of an element in the map. This method is implemented in Variant
.
This method copies the value, which is inefficient for complex variant values. operator[] can be used to get a reference to the value instead of copying it.
If this variant is not a map, or the key does not exist, a null variant is returned.
// PROTOTYPES
Variant get(const char* key) const;
Variant get(const String& key) const;
Returns true if this variant is a map and contains key
. This method is implemented in Variant
.
// PROTOTYPES
bool has(const char* key) const;
bool has(const String& key) const;
Since 6.1.0:
The Map
C++ template holds key-value pairs. The template parameters determine the type of the key and value.
The VariantMap is used by Ledger to map String to a Variant in Ledger.
Allocates an empty Map object. As it is a template you will also need to include template parameters or use a predefined class like VariantMap.
// PROTOTYPE
Map();
Create a map from static initalization, typically to seed values from code.
// PROTOTYPE
Map(std::initializer_list<Entry> entries);
// EXAMPLE
Map<String, int> m1({ {"a", 123}, {"b", 456} });
Makes a new map that's a copy of an existing map. After copying, changes in the original map won't affect the copy.
// PROTOTYPE
Map(const Map& map);
Replace an existing key-value pair in the map, or create a new one if key
does not already exist.
Returns true if the operation succeeded or false, typically because there wasn't enough free memory to add the entry.
// PROTOTYPE
template<typename T>
bool set(const T& key, ValueT val);
Gets the value of an entry in the map from its key
. If the key does not exist, returns an empty value. See also the overload that allows you to specify the default value to be returned if the key does not exist.
This returns a copy of the value, so it's less efficient for complex values. See also operator[]
which returns a reference to the value instead of a copy, and find()
which returns an iterator for the value.
// PROTOTYPE
template<typename T>
ValueT get(const T& key) const;
Gets the value of an entry in the map from its key
. If the key does not exist, returns defaultVal
.
This returns a copy of the value, so it's less efficient for complex values. See also operator[]
which returns a reference to the value instead of a copy, and find()
which returns an iterator for the value.
// PROTOTYPE
template<typename T>
ValueT get(const T& key, const ValueT& defaultVal) const;
Returns true if the map has an entry with a key key
.
// PROTOTYPE
template<typename T>
bool has(const T& key) const;
Returns a Map::Iterator
or Map::ConstIterator
for an element with a key key
. The iterator has a value of end()
if the key is not found.
// PROTOTYPES
template<typename T>
Iterator find(const T& key);
template<typename T>
ConstIterator find(const T& key) const;
Returns a Map::Iterator
or Map::ConstIterator
for iterating the entries in a map.
// PROTOTYPES
Iterator begin();
ConstIterator begin() const;
// EXAMPLE
Map<String, int> m1({ {"a", 123}, {"b", 456} });
for(auto it = m1.begin(); it != m1.end(); it++) {
Log.info("key=%s value=%d", it->first.c_str(), it->second);
}
Returns a Map::Iterator
or Map::ConstIterator
for the entry after the last element of the map.
// PROTOTYPES
Iterator end();
ConstIterator end() const;
Removes the entry at the current iterator position and returns the value to use to continue iteration.
// PROTOTYPE
Iterator erase(ConstIterator pos);
Remove entry with key key
from the map. Returns true if the item existed and was removed, false if it did not exist.
// PROTOTYPE
template<typename T>
bool remove(const T& key);
Removes all entries from the map
// PROTOTYPE
void clear();
Returns the number of entries in the map.
// PROTOTYPE
int size() const;
Returns true if the map has no entries (size == 0).
// PROTOTYPE
ool isEmpty() const;
Gets a reference to an entry with key key
. This will create the entry if it does not exist. If there is insufficient memory to allocate a new entry, it will cause a SOS panic. Using set()
can provide more control over error conditions.
// PROTOTYPE
template<typename T>
ValueT& operator[](const T& key);
// EXAMPLE
Map<String, int> m1({ {"a", 123}, {"b", 456} });
m1["a"] = 999;
Since 0.6.1:
The Vector
C++ template is a dynamically-sized vector, essentially a dynamically-sized array, with additional convenience functions. It's similar to std::vector
but does not require linking std::vector into the user application, which can save on flash usage. Vector is used by Ledger.
As it a template, it can be used to store arbitrary C++ data types as well as standard built-in types. Some examples of types used in Device OS include:
Vector<WifiNetworkConfig>
Vector<NetworkInterfaceConfig>
Vector<SockAddr>
Vector<WifiScanResult>
Vector<BleCharacteristic>
Vector<BleService>
Vector<Asset>
Vector<String>
Vector<char>
Vector<char *>
Vector<void *>
Vector<uint8_t>
As it is a vector (not a linked list), it's efficient to randomly access elements in the list by their index.
Allocates a Vector object that is empty. It will be expanded as necessary. If you know the number of elements or the element contents, there are other constructors that can be used.
// PROTOTYPE
Vector();
Allocates a Vector object of size n elements. It can be expanded later if desired.
// PROTOTYPE
explicit Vector(int n);
Allocates a Vector object of size n
elements with each item in the vector set to value.
// PROTOTYPE
explicit Vector(int n, const T& value);
Allocates a Vector object from a std::initializer_list<T>
. This is often used to initialize a list of values from code, see the example.
// PROTOTYPE
Vector(std::initializer_list<T> values);
// EXAMPLE
Vector<int> v({123, 456, 789});
Allocates a new vector object that is a copy of an existing Vector.
If the original vector contained objects, for example Vector<String>
then a new object will be allocated, so the values will not be linked together after allocation.
If the original vector contained pointers such as Vector<void *>
, the pointers will be copied, which can lead to object lifetime confusion.
// PROTOTYPE
Vector(const Vector<T, AllocatorT>& vector);
Appends a value to a Vector, making the vector larger.
Returns true if the operation succeeded.
// PROTOTYPE
bool append(T value);
Appends n
copies of value
to a Vector, making the vector larger.
Returns true if the operation succeeded.
// PROTOTYPE
bool append(int n, const T& value);
Appends the n
items in the values
array to the vector. This is typically done if you have a C++ array of T
and want to append those items to your vector.
Returns true if the operation succeeded.
// PROTOTYPE
bool append(const T* values, int n);
Appends an existing vector to the end of this vector.
If the original vector contained objects, for example Vector<String>
then a new object will be allocated, so the values will not be linked together after appending.
If the original vector contained pointers such as Vector<void *>
, the pointers will be copied, which can lead to object lifetime confusion.
Returns true if the operation succeeded.
// PROTOTYPE
bool append(const Vector<T, AllocatorT>& vecto);
Prepend (insert at beginning) a value to a Vector, making the vector larger.
Returns true if the operation succeeded.
// PROTOTYPE
bool prepend(T value);
Prepend (insert at beginning) n
copies of value
to a Vector, making the vector larger.
Returns true if the operation succeeded.
// PROTOTYPE
bool prepend(int n, const T& value);
Prepend (insert at beginning) the n
items in the values
array to the vector. This is typically done if you have a C++ array of T
and want to prepend those items to your vector.
Returns true if the operation succeeded.
// PROTOTYPE
bool prepend(const T* values, int n);
Prepend (insert at beginning) an existing vector to the end of this vector.
If the original vector contained objects, for example Vector<String>
then a new object will be allocated, so the values will not be linked together after appending.
If the original vector contained pointers such as Vector<void *>
, the pointers will be copied, which can lead to object lifetime confusion.
Returns true if the operation succeeded.
// PROTOTYPE
bool prepend(const Vector<T, AllocatorT>& vecto);
Insert at location i
(0 = beginning) a value to a Vector, making the vector larger.
Returns true if the operation succeeded.
// PROTOTYPE
bool insert(int i, T value);
Insert at location i
(0 = beginning) n
copies of value
to a Vector, making the vector larger.
Returns true if the operation succeeded.
// PROTOTYPE
bool insert(int i, int n, const T& value);
Insert at location i
(0 = beginning) the n
items in the values
array to the vector. This is typically done if you have a C++ array of T
and want to insert those items to your vector.
Returns true if the operation succeeded.
// PROTOTYPE
bool insert(int i, const T* values, int n);
Insert at location i
(0 = beginning) an existing vector to the end of this vector.
If the original vector contained objects, for example Vector<String>
then a new object will be allocated, so the values will not be linked together after appending.
If the original vector contained pointers such as Vector<void *>
, the pointers will be copied, which can lead to object lifetime confusion.
Returns true if the operation succeeded.
// PROTOTYPE
bool insert(const Vector<T, AllocatorT>& vecto);
Removes n
elements (default" 1) at location i
(0 = beginning) from a vector.
If the original vector contained pointers such as Vector<void *>
, the pointers are not deleted or freed; you must maintain your own object lifecycle.
// PROTOTYPE
void removeAt(int i, int n = 1);
Removes the first element whose value is value
. This works for primitive types (int, etc.) as well as classes that support operator==
like String
.
If the value is a pointer (such as char *
) it will compare the pointer itself, not the thing being pointed to.
Returns true if the item existed and was removed.
// PROTOTYPE
bool removeOne(const T& value);
Removes all elements whose value is value
. This works for primitive types (int, etc.) as well as classes that support operator==
like String
.
If the value is a pointer (such as char *
) it will compare the pointer itself, not the thing being pointed to.
Returns true if the item existed and at least one was removed.
// PROTOTYPE
bool removeAll(const T& value);
Removes the first element of the vector and returns it. Only call this if the vector is not empty.
// PROTOTYPE
T takeFirst();
Removes the last element of the vector and returns it. Only call this if the vector is not empty.
// PROTOTYPE
T takeLast();
Removes the element of the vector at index i
and returns it. i
is zero-based (0 = same as takeFirst).
Make sure the item exists before calling.
// PROTOTYPE
T takeAt(int i);
Peek at the first item in the vector. Only call this if the vector is not empty.
// PROTOTYPES
T& first();
const T& first() const;
Peek at the last item in the vector. Only call this if the vector is not empty.
// PROTOTYPES
T& last();
const T& last() const;
Peek at item i
in the vector (zero-based). Only call this if the item exists.
// PROTOTYPES
T& at(int i);
const T& at(int i) const;
// EXAMPLE
Vector<int> v({123, 456, 789});
Log.info("%d", v.at(0));
Peek at item i
in the vector (zero-based) using operator[]
instead of at()
. Only call this if the item exists.
When used on the left side an expression, sets the value at that location. The array must already contain that element; you cannot increase the size of the array using operator[].
// PROTOTYPES
T& operator[](int i);
const T& operator[](int i) const;
// EXAMPLE
Vector<int> v({123, 456, 789});
Log.info("%d", v[0]);
v[0] = 999;
Find the index (zero-based) of an item whose value is value
starting at index i
(optional, if not specified from the start at index 0).
Returns -1 if the item does not exist.
// PROTOTYPE
int indexOf(const T& value, int i = 0) const;
Find the index (zero-based) of the last item whose value is value
in the vector.
Returns -1 if the item does not exist.
// PROTOTYPE
int lastIndexOf(const T& value) const;
Find the index (zero-based) of the last item whose value is value
in the vector starting at index i
.
Returns -1 if the item does not exist.
// PROTOTYPE
int lastIndexOf(const T& value, int i) const;
Returns true if the vector contains an item whose value is value
. Essentially the same as indexOf() >= 0
.
// PROTOTYPE
bool contains(const T& value) const;
Returns the size of the vector. 0 = empty. This operation is constant time regardless of the size of the vector.
// PROTOTYPE
int size() const;
Returns true if the vector is empty (size == 0).
// PROTOTYPE
bool isEmpty() const;
Removes all items in the vector, leaving it with a size of 0.
// PROTOTYPE
void clear();
Returns a Vector::Iterator
or Vector::ConstIterator
to iterate the vector. Since the vector provides efficient random access
to elements you can also access the items by index using at()
and an incrementing or decrementing loop instead of using an iterator.
// PROTOTYPES
Iterator begin();
ConstIterator begin() const;
// EXAMPLE
Vector<int> v({123, 456, 789});
for(auto it = v.begin(); it != v.end(); it++) {
Log.info("value %d", *it);
}
Returns a Vector::Iterator
or Vector::ConstIterator
of the end of the vector.
// PROTOTYPES
Iterator end();
ConstIterator end() const;
Inserts value
at the current position of iterator pos
, making the vector larger. Returns a new value
to continue iteration.
// PROTOTYPE
Iterator insert(ConstIterator pos, T value);
Removes the item at the current position of iterator pos
, making the vector smaller. Returns a new value
to continue iteration.
// PROTOTYPE
Iterator erase(ConstIterator pos);
NFC (Near-Field Communication) is typically used to communicate small amounts of data to a mobile app in very close range, within a few inches.
Particle Gen 3 devices (Argon, Boron, B-Series SoM, and TrackerSoM) only support emulating an NFC tag. They cannot locate or communicate with tags themselves, or support protocols such as for NFC payments.
A separate antenna is required. NFC uses the unlicensed 13.56 MHz band, and requires a special loop antenna for electromagnetic induction. On the Argon, Boron, and Xenon, the NFC antenna connects to a U.FL connector on the bottom of the board, directly underneath the USB connector. The Tracker One includes an NFC antenna within the sealed enclosure.
NFC is supported in Device OS 1.3.1 and later. NFC support was in beta test in Device OS 1.3.0. It is not available in earlier Device OS versions.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
The P2, Photon 2, and Gen 2 devices (Photon, P1, Electron, E-Series) do not support NFC.
You can run this device firmware and use a mobile app like NFC Reader to read the NFC data. The blue D7 LED will flash briefly when reading. Each time you read, the counter will increment.
#include "Particle.h"
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
SYSTEM_MODE(MANUAL);
static void nfcCallback(nfc_event_type_t type, nfc_event_t* event, void* context);
int counter = 0;
volatile bool updateCounter = true;
void setup (void) {
pinMode(D7, OUTPUT);
digitalWrite(D7, 0);
}
void loop(){
if (updateCounter) {
updateCounter = false;
char buf[64];
snprintf(buf, sizeof(buf), "testing counter=%d", ++counter);
NFC.setText(buf, "en");
NFC.on(nfcCallback);
Log.info("next read should show: %s", buf);
}
}
static void nfcCallback(nfc_event_type_t type, nfc_event_t* event, void* context) {
switch (type) {
case NFC_EVENT_FIELD_ON: {
digitalWrite(D7, 1);
break;
}
case NFC_EVENT_FIELD_OFF: {
digitalWrite(D7, 0);
break;
}
case NFC_EVENT_READ: {
updateCounter = true;
break;
}
default:
break;
}
}
Turn NFC on, optionally with a callback function.
// PROTOTYPE
int on(nfc_event_callback_t cb=nullptr);
The callback function has this prototype:
void nfcCallback(nfc_event_type_t type, nfc_event_t* event, void* context);
type
The type of event (described below)event
The internal event structure (not currently used)context
An optional context pointer set when the callback is registered (not currently used).The event types are:
NFC_EVENT_FIELD_ON
NFC tag has detected external NFC field and was selected by an NFC polling device.NFC_EVENT_FIELD_OFF
External NFC field has been removed.NFC_EVENT_READ
NFC polling device has read all tag data.NFC events may occur at interrupt service time. You should not, for example:
malloc
, free
, new
, delete
, strdup
, etc.).Particle.publish()
delay()
Serial.print()
, Log.info()
, etc.Turns NFC off.
// PROTOTYPE
int off();
Updates the NFC device, usually after changing the data using NFC.setCustomData()
, NFC.setText()
, etc..
// PROTOTYPE
int update();
Sets text to be passed to the NFC reader.
// PROTOTYPE
int setText(const char* text, const char* encoding)
text
A c-string (null-terminated) containing the text to sendencoding
The IANA language code. For example, "en" for English.Note that all of the set options are mutually exclusive. Calling NFC.setText()
will clear any NFC.setUri()
, NFC.setLaunchApp()
, etc..
The maximum size is 988 bytes for a single text record. Adding multiple records will add some additional overhead for each NDEF message, 2 - 4 bytes, plus the length of the data.
Sets a URI as the NFC data.
// PROTOTYPE
int setUri(const char* uri, NfcUriType uriType)
// EXAMPLE
// Open the web site https://www.particle.io:
NFC.setUri("particle.io", NfcUriType::NFC_URI_HTTPS_WWW);
The following NfcUriTypes are defined:
NfcUriType | Prefix |
---|---|
NFC_URI_NONE |
(no prefix) |
NFC_URI_HTTP_WWW |
"http://www." |
NFC_URI_HTTPS_WWW |
"https://www." |
NFC_URI_HTTP |
"http:" |
NFC_URI_HTTPS |
"https:" |
NFC_URI_TEL |
"tel:" |
NFC_URI_MAILTO |
"mailto:" |
NFC_URI_FTP_ANONYMOUS |
"ftp://anonymous:anonymous@" |
NFC_URI_FTP_FTP |
"ftp://ftp." |
NFC_URI_FTPS |
"ftps://" |
NFC_URI_SFTP |
"sftp://" |
NFC_URI_SMB |
"smb://" |
NFC_URI_NFS |
"nfs://" |
NFC_URI_FTP |
"ftp://" |
NFC_URI_DAV |
"dav://" |
NFC_URI_NEWS |
"news:" |
NFC_URI_TELNET |
"telnet://" |
NFC_URI_IMAP |
"imap:" |
NFC_URI_RTSP |
"rtsp://" |
NFC_URI_URN |
"urn:" |
NFC_URI_POP |
"pop:" |
NFC_URI_SIP |
"sip:" |
NFC_URI_SIPS |
"sips:" |
NFC_URI_TFTP |
"tftp:" |
NFC_URI_BTSPP |
"btspp://" |
NFC_URI_BTL2CAP |
"btl2cap://" |
NFC_URI_BTGOEP |
"btgoep://" |
NFC_URI_TCPOBEX |
"tcpobex://" |
NFC_URI_IRDAOBEX |
"irdaobex://" |
NFC_URI_FILE |
"file://" |
NFC_URI_URN_EPC_ID |
"urn:epc:id:" |
NFC_URI_URN_EPC_TAG |
"urn:epc:tag:" |
NFC_URI_URN_EPC_PAT |
"urn:epc:pat:" |
NFC_URI_URN_EPC_RAW |
"urn:epc:raw:" |
NFC_URI_URN_EPC |
"urn:epc:" |
NFC_URI_URN_NFC |
"urn:nfc:" |
Note that all of the set options are mutually exclusive. Calling NFC.setUri()
will clear any setText, setLaunchApp, etc..
The maximum size is 988 bytes for a single record. Adding multiple records will add some additional overhead for each NDEF message, 2 - 4 bytes, plus the length of the data.
On Android devices, it's possible to set an app (by its android package name) to launch when the tag is read.
// PROTOTYPE
int setLaunchApp(const char* androidPackageName);
Note that all of the set options are mutually exclusive. Calling NFC.setLaunchApp()
will clear any NFC.setText()
, NFC.setUri()
, etc..
This does not do anything on iOS. An NFC-aware iOS app can read the package name, but in general won't be able to do much with it.
It's possible to send any NFC-compliant data instead of one of the pre-defined types above.
// PROTOTYPE
int setCustomData(Record& record)
Note that all of the set options are mutually exclusive. Calling NFC.setCustomData()
will clear any NFC.setText()
, NFC.setUri()
, etc..
The maximum size is 988 bytes for a single record. Adding multiple records will add some additional overhead for each NDEF message, 2 - 4 bytes, plus the length of the data.
The NFC Record
class specifies the custom data to be sent via NFC. This section describes the methods you may need to use BLE.setCustomData()
.
// PROTOTYPE
void setTnf(Tnf tnf)
The valid values for Record::Tnf are:
TNF_EMPTY
(0x00)TNF_WELL_KNOWN
(0x01)TNF_MIME_MEDIA
(0x02)TNF_ABSOLUTE_URI
(0x03)TNF_EXTERNAL_TYPE
(0x04)TNF_UNKNOWN
(0x05)TNF_UNCHANGED
(0x06)TNF_RESERVED
(0x07)Set the type field in the NFC record.
// PROTOTYPE
size_t setType(const void* type, uint8_t numBytes);
The ID field is optional in NFC. If you are using the ID, call this method to set the value. It will automatically set the IL field in the header.
// PROTOTYPE
size_t setId(const void* id, uint8_t numBytes);
Appends to the NFC record payload.
// PROTOTYPE
size_t setPayload(const void* payload, size_t numBytes);
Returns the number of bytes added (numBytes
).
Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
Cellular devices (Boron, B-Series SoM, Tracker SoM, Electron, E-Series) do not support TCPServer. The cellular modem does not support it, and also the mobile carriers do not support it. You can only make outgoing TCP connections (TCPClient) on cellular devices.
// SYNTAX
TCPServer server = TCPServer(port);
// PROTOTYPE
TCPServer(uint16_t, network_interface_t nif=0);
Create a server that listens for incoming connections on the specified port.
Parameters: port
: the port to listen on (int
)
// EXAMPLE USAGE
// telnet defaults to port 23
TCPServer server = TCPServer(23);
TCPClient client;
void setup()
{
// start listening for clients
server.begin();
// Make sure your Serial Terminal app is closed before powering your device
Serial.begin(9600);
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
Log.info("localIP=%s", WiFi.localIP().toString().c_str());
Log.info("subnetMask=%s", WiFi.subnetMask().toString().c_str());
Log.info("gatewayIP=%s", WiFi.gatewayIP().toString().c_str());
Log.info("SSID=%s", WiFi.SSID().toString().c_str());
}
void loop()
{
if (client.connected()) {
// echo all available bytes back to the client
while (client.available()) {
server.write(client.read());
}
} else {
// if no client is yet connected, check for a new connection
client = server.available();
}
}
nif
is the network interface to bind to. This is typically omitted, in which case the primary network
inteface is used (cellular or Wi-Fi). You can, however pass Ethernet
, WiFi
, or Cellular
as this
parameter, which will bind to that specific interface. This is most commonly used when you use cellular
as your cloud connection, but listen for TCP connections on Ethernet from an isolated Ethernet LAN.
In Device OS 6.2.0 and later, nif
can be Tether
to listen for connection from another device
such as a Raspberry Pi connected by UART serial.
Tells the server to begin listening for incoming connections.
// SYNTAX
server.begin();
Gets a client that is connected to the server and has data available for reading. The connection persists when the returned client object goes out of scope; you can close it by calling client.stop()
.
available()
inherits from the Stream
utility class.
Write data to the last client that connected to a server. This data is sent as a byte or series of bytes. This function is blocking by default and may block the application thread indefinitely until all the data is sent.
This function also takes an optional argument timeout
, which allows the caller to specify the maximum amount of time the function may take. If timeout
value is specified, write operation may succeed partially and it's up to the caller to check the actual number of bytes written and schedule the next write()
call in order to send all the data out.
The application code may additionally check if an error occurred during the last write()
call by checking getWriteError()
return value. Any non-zero error code indicates and error during write operation.
// SYNTAX
server.write(val);
server.write(buf, len);
server.write(val, timeout);
server.write(buf, len, timeout);
Parameters:
val
: a value to send as a single byte (byte or char)buf
: an array to send as a series of bytes (byte or char)len
: the length of the buffertimeout
: timeout in milliseconds (0
- non-blocking mode)Returns: size_t
: the number of bytes written
Print data to the last client connected to a server. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3').
// SYNTAX
server.print(data);
server.print(data, BASE);
Parameters:
data
: the data to print (char, byte, int, long, or string)BASE
(optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16).Returns: size_t
: the number of bytes written
Print data, followed by a newline, to the last client connected to a server. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3').
// SYNTAX
server.println();
server.println(data);
server.println(data, BASE) ;
Parameters:
data
(optional): the data to print (char, byte, int, long, or string)BASE
(optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16).Get the error code of the most recent write()
operation.
Returns: int 0
when everything is ok, a non-zero error code in case of an error.
This value is updated every after every call to write()
or can be manually cleared by clearWriteError()
// SYNTAX
int err = server.getWriteError();
// EXAMPLE
TCPServer server;
// Write in non-blocking mode to the last client that connected to the server
int bytes = server.write(buf, len, 0);
int err = server.getWriteError();
if (err != 0) {
Log.trace("TCPServer::write() failed (error = %d), number of bytes written: %d", err, bytes);
}
Clears the error code of the most recent write()
operation setting it to 0
. This function is automatically called by write()
.
clearWriteError()
does not return anything.
(inherits from Stream
via Client
)
Creates a client which can connect to a specified internet IP address and port (defined in the client.connect()
function).
Warning:
In most cases, using TCPClient
is not recommended. If you want to export data off your device, we recommend using Particle.publish()
and webhooks.
TCPClient
does not itself support HTTP. HTTP requires a 3rd-party library.Particle.publish()
and webhooks instead of MQTT. Using MQTT without TLS/SSL is not secure. Using MQTT with TLS/SSL will use large amounts of cellular data if also using sleep modes, as the MQTT connection cannot be maintained during sleep and each handshake requires 2 to 5 kilobytes of data.
// SYNTAX
TCPClient client;
// PROTOTYPE
int connect(IPAddress ip, uint16_t port, network_interface_t nif=0);
int connect(const char *host, uint16_t port, network_interface_t nif=0);
// EXAMPLE USAGE
TCPClient client;
byte server[] = { 74, 125, 224, 72 }; // Google
void setup()
{
// Make sure your Serial Terminal app is closed before powering your device
Serial.begin(9600);
// Wait for a USB serial connection for up to 30 seconds
waitFor(Serial.isConnected, 30000);
Serial.println("connecting...");
if (client.connect(server, 80))
{
Serial.println("connected");
client.println("GET /search?q=unicorn HTTP/1.0");
client.println("Host: www.google.com");
client.println("Content-Length: 0");
client.println();
}
else
{
Serial.println("connection failed");
}
}
void loop()
{
if (client.available())
{
char c = client.read();
Serial.print(c);
}
if (!client.connected())
{
Serial.println();
Serial.println("disconnecting.");
client.stop();
for(;;);
}
}
When using TCP with debugging logs enabled, you might see something like this:
0000182000 [wiring] ERROR: recv error = 113
These errors are documented in the POSIX error codes list. From code, you can find this underlying error code value in the global variable errno
or using getWriteError()
.
nif
is the network interface to use. This is typically omitted, in which case the primary network
inteface is used (cellular or Wi-Fi). You can, however pass Ethernet
, WiFi
, or Cellular
as this
parameter, which will only use that interface to make the outbound TCP connection.
In Device OS 6.2.0 and later, nif
can be Tether
to make connections to a device
such as a Raspberry Pi connected by UART serial.
Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
On cellular devices, be careful interacting with web hosts with TCPClient
or libraries using TCPClient
. These can use a lot of data in a short period of time. To keep the data usage low, use Particle.publish
along with webhooks.
Direct TCP, UDP, and DNS do not consume Data Operations from your monthly or yearly quota. However, they do use cellular data and could cause you to exceed the monthly data limit for your account.
Whether or not the client is connected. Note that a client is considered connected if the connection has been closed but there is still unread data.
// SYNTAX
client.connected();
Returns true if the client is connected, false if not.
Returns true if the network socket is open and the underlying network is ready.
// SYNTAX
client.status();
This is different than connected() which returns true if the socket is closed but there is still unread buffered data, available() is non-zero.
Connects to a specified IP address and port. The return value indicates success or failure. Also supports DNS lookups when using a domain name.
// SYNTAX
client.connect();
client.connect(ip, port);
client.connect(hostname, port);
Parameters:
ip
: the IP address that the client will connect to (array of 4 bytes)hostname
: the host name the client will connect to (string, ex.:"particle.io")port
: the port that the client will connect to (int
)Returns true if the connection succeeds, false if not.
Write data to the server the client is connected to. This data is sent as a byte or series of bytes. This function is blocking by default and may block the application thread indefinitely until all the data is sent.
This function also takes an optional argument timeout
, which allows the caller to specify the maximum amount of time the function may take. If timeout
value is specified, write operation may succeed partially and it's up to the caller to check the actual number of bytes written and schedule the next write()
call in order to send all the data out.
The application code may additionally check if an error occurred during the last write()
call by checking getWriteError()
return value. Any non-zero error code indicates and error during write operation.
// SYNTAX
client.write(val);
client.write(buf, len);
client.write(val, timeout);
client.write(buf, len, timeout);
Parameters:
val
: a value to send as a single byte (byte or char)buf
: an array to send as a series of bytes (byte or char)len
: the length of the buffertimeout
: timeout in milliseconds (0
- non-blocking mode)Returns: size_t
: write()
returns the number of bytes written.
Print data to the server that a client is connected to. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3').
// SYNTAX
client.print(data);
client.print(data, BASE) ;
Parameters:
data
: the data to print (char, byte, int, long, or string)BASE
(optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16).Returns: byte
: print()
will return the number of bytes written, though reading that number is optional
Print data, followed by a carriage return and newline, to the server a client is connected to. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3').
// SYNTAX
client.println();
client.println(data);
client.println(data, BASE) ;
Parameters:
data
(optional): the data to print (char, byte, int, long, or string)BASE
(optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16).Returns the number of bytes available for reading (that is, the amount of data that has been written to the client by the server it is connected to).
// SYNTAX
client.available();
Returns the number of bytes available.
Read the next byte received from the server the client is connected to (after the last call to read()
).
// SYNTAX
client.read();
Returns the next byte (or character), or -1 if none is available.
or int read(uint8_t *buffer, size_t size)
reads all readily available bytes up to size
from the server the client is connected to into the provided buffer
.
// SYNTAX
bytesRead = client.read(buffer, length);
Returns the number of bytes (or characters) read into buffer
.
Waits until all outgoing data in buffer has been sent.
NOTE: That this function does nothing at present.
// SYNTAX
client.flush();
Since 0.4.5:
Retrieves the remote IPAddress
of a connected TCPClient
. When the TCPClient
is retrieved
from TCPServer.available()
(where the client is a remote client connecting to a local server) the
IPAddress
gives the remote address of the connecting client.
When TCPClient
was created directly via TCPClient.connect()
, then remoteIP
returns the remote server the client is connected to.
// EXAMPLE - TCPClient from TCPServer
TCPServer server(80);
// ...
void setup()
{
Serial.begin(9600);
server.begin(80);
}
void loop()
{
// check for a new client to our server
TCPClient client = server.available();
if (client.connected())
{
// we got a new client
// find where the client's remote address
IPAddress clientIP = client.remoteIP();
// print the address to Serial
Log.info("remoteAddr=%s", clientIP.toString().c_str());
}
}
// EXAMPLE - TCPClient.connect()
TCPClient client;
client.connect("www.google.com", 80);
if (client.connected())
{
IPAddress clientIP = client.remoteIP();
// IPAddress equals whatever www.google.com resolves to
}
Disconnect from the server.
// SYNTAX
client.stop();
Get the error code of the most recent write()
operation.
Returns: int 0
when everything is ok, a non-zero error code in case of an error.
This value is updated every after every call to #write--4 or can be manually cleared by clearWriteError()
.
// SYNTAX
int err = client.getWriteError();
// EXAMPLE
TCPClient client;
// Write in non-blocking mode
int bytes = client.write(buf, len, 0);
int err = client.getWriteError();
if (err != 0) {
Log.trace("TCPClient::write() failed (error = %d), number of bytes written: %d", err, bytes);
}
Clears the error code of the most recent write()
operation setting it to 0
. This function is automatically called by write()
.
clearWriteError()
does not return anything.
(inherits from Stream
and Printable
)
This class enables UDP messages to be sent and received.
// EXAMPLE USAGE
SerialLogHandler logHandler;
// UDP Port used for two way communication
unsigned int localPort = 8888;
// An UDP instance to let us send and receive packets over UDP
UDP Udp;
void setup() {
// start the UDP
Udp.begin(localPort);
// Print your device IP Address via serial
Serial.begin(9600);
Log.info("localIP=%s", WiFi.localIP().toString().c_str());
}
void loop() {
// Check if data has been received
if (Udp.parsePacket() > 0) {
// Read first char of data received
char c = Udp.read();
// Ignore other chars
while(Udp.available())
Udp.read();
// Store sender ip and port
IPAddress ipAddress = Udp.remoteIP();
int port = Udp.remotePort();
// Echo back data to sender
Udp.beginPacket(ipAddress, port);
Udp.write(c);
Udp.endPacket();
}
}
Note that UDP does not guarantee that messages are always delivered, or that
they are delivered in the order supplied. In cases where your application
requires a reliable connection, TCPClient
is a simpler alternative.
There are two primary ways of working with UDP - buffered operation and unbuffered operation.
buffered operation allows you to read and write packets in small pieces, since the system takes care of allocating the required buffer to hold the entire packet.
parsePacket
, then use available
and read
to retrieve the packet receivedsetBuffer
to set the maximum size of the packet (the default is 512 bytes), followed by
beginPacket
, then as many calls to write
/print
as necessary to build the packet contents, followed finally by end
to send the packet over the network.unbuffered operation allows you to read and write entire packets in a single operation - your application is responsible for allocating the buffer to contain the packet to be sent or received over the network.
receivePacket
with a buffer to hold the received packet.sendPacket
with the packet buffer to send, and the destination address.Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
On cellular devices, be careful interacting with web hosts with UDP
or libraries using UDP
. These can use a lot of data in a short period of time.
Direct TCP, UDP, and DNS do not consume Data Operations from your monthly or yearly quota. However, they do use cellular data and could cause you to exceed the monthly data limit for your account.
Initializes the UDP library and network settings.
// SYNTAX
Udp.begin(port);
// PROTOTYPE
uint8_t begin(uint16_t port, network_interface_t nif=0);
If using SYSTEM_THREAD(ENABLED)
, you'll need
to wait until the network is connected before calling Udp.begin()
. With Device OS 6.2.0 and later, system thread is always enabled.
If you are listening on a specific port, you need to call begin(port) again every time the network is disconnected and reconnects, as well.
const int LISTENING_PORT = 8080;
SYSTEM_THREAD(ENABLED);
UDP udp;
bool wasConnected = false;
void setup() {
}
void loop() {
// For Wi-Fi, use WiFi.ready()
if (Cellular.ready()) {
if (!wasConnected) {
udp.begin(LISTENING_PORT);
wasConnected = true;
}
}
else {
wasConnected = false;
}
}
nif
is the network interface to bind to. This is typically omitted, in which case the primary network
inteface is used (cellular or Wi-Fi). You can, however pass Ethernet
, WiFi
, or Cellular
as this
parameter, which will bind to that specific interface. This is most commonly used when you use cellular
as your cloud connection, but listen for UDP packets on Ethernet from an isolated Ethernet LAN.
In Device OS 6.2.0 and later, nif
can be Tether
to allow UDP to and from another device
such as a Raspberry Pi connected by UART serial.
Get the number of bytes (characters) available for reading from the buffer. This is data that's already arrived.
// SYNTAX
int count = Udp.available();
This function can only be successfully called after UDP.parsePacket()
.
available()
inherits from the Stream
utility class.
Returns the number of bytes available to read.
Starts a connection to write UDP data to the remote connection.
// SYNTAX
Udp.beginPacket(remoteIP, remotePort);
Parameters:
remoteIP
: the IP address of the remote connection (4 bytes)remotePort
: the port of the remote connection (int)It returns nothing.
Called after writing buffered UDP data using write()
or print()
. The buffered data is then sent to the
remote UDP peer.
// SYNTAX
Udp.endPacket();
Parameters: NONE
Writes UDP data to the buffer - no data is actually sent. Must be wrapped between beginPacket()
and endPacket()
. beginPacket()
initializes the packet of data, it is not sent until endPacket()
is called.
// SYNTAX
Udp.write(message);
Udp.write(buffer, size);
Parameters:
message
: the outgoing message (char)buffer
: an array to send as a series of bytes (byte or char)size
: the length of the bufferReturns:
byte
: returns the number of characters sent. This does not have to be read// PROTOTYPES
int receivePacket(uint8_t* buffer, size_t buf_size, system_tick_t timeout = 0)
int receivePacket(char* buffer, size_t buf_size, system_tick_t timeout = 0)
// SYNTAX
size = Udp.receivePacket(buffer, size);
// EXAMPLE USAGE - get a string without buffer copy
UDP Udp;
char message[128];
int port = 8888;
int rxError = 0;
Udp.begin (port);
int count = Udp.receivePacket((byte*)message, 127);
if (count >= 0 && count < 128) {
message[count] = 0;
rxError = 0;
} else if (count < -1) {
rxError = count;
// need to re-initialize on error
Udp.begin(port);
}
if (!rxError) {
Log.info(message);
}
Checks for the presence of a UDP packet and returns the size. Note that it is possible to receive a valid packet of zero bytes, this will still return the sender's address and port after the call to receivePacket().
Parameters:
buffer
: the buffer to hold any received bytes (uint8_t).buf_size
: the size of the buffer.timeout
: The timeout to wait for data in milliseconds, or 0 to not block in Device OS 2.0.0 and later. Prior to 2.0.0 this function did not block.Returns:
int
: on success the size (greater then or equal to zero) of a received UDP packet. On failure the internal error code.Checks for the presence of a UDP packet, and reports the size. parsePacket()
must be called before reading the buffer with UDP.read()
.
It's usually more efficient to use receivePacket()
instead of parsePacket()
and read()
.
// PROTOTYPE
int parsePacket(system_tick_t timeout = 0);
// SYNTAX
size = Udp.parsePacket();
Parameters:
timeout
: The timeout to wait for data in milliseconds, or 0 to not block in Device OS 2.0.0 and later. Prior to 2.0.0 this function did not blockReturns:
int
: the size of a received UDP packetReads UDP data from the specified buffer. If no arguments are given, it will return the next character in the buffer.
This function can only be successfully called after UDP.parsePacket()
.
// SYNTAX
count = Udp.read();
count = Udp.read(packetBuffer, MaxSize);
Parameters:
packetBuffer
: buffer to hold incoming packets (char)MaxSize
: maximum size of the buffer (int)Returns:
int
: returns the character in the buffer or -1 if no character is availableWaits until all outgoing data in buffer has been sent.
NOTE: That this function does nothing at present.
// SYNTAX
Udp.flush();
Disconnect from the server. Release any resource being used during the UDP session.
// SYNTAX
Udp.stop();
Parameters: NONE
Returns the IP address of sender of the packet parsed by Udp.parsePacket()
/Udp.receivePacket()
.
// SYNTAX
ip = Udp.remoteIP();
Parameters: NONE
Returns:
Udp.parsePacket()
/Udp.receivePacket()
.Returns the port from which the UDP packet was sent. The packet is the one most recently processed by Udp.parsePacket()
/Udp.receivePacket()
.
// SYNTAX
int port = Udp.remotePort();
Parameters: NONE
Returns:
int
: the port from which the packet parsed by Udp.parsePacket()
/Udp.receivePacket()
was sent.Since 0.4.5:
Initializes the buffer used by a UDP
instance for buffered reads/writes. The buffer
is used when your application calls beginPacket()
and parsePacket()
. If setBuffer()
isn't called,
the buffer size defaults to 512 bytes, and is allocated when buffered operation is initialized via beginPacket()
or parsePacket()
.
// SYNTAX
Udp.setBuffer(size); // dynamically allocated buffer
Udp.setBuffer(size, buffer); // application provided buffer
// EXAMPLE USAGE - dynamically allocated buffer
UDP Udp;
// uses a dynamically allocated buffer that is 1024 bytes in size
if (!Udp.setBuffer(1024))
{
// on no, couldn't allocate the buffer
}
else
{
// 'tis good!
}
// EXAMPLE USAGE - application-provided buffer
UDP Udp;
uint8_t appBuffer[800];
Udp.setBuffer(800, appBuffer);
Parameters:
unsigned int
: the size of the bufferpointer
: the buffer. If not provided, or NULL
the system will attempt to
allocate a buffer of the size requested.Returns:
true
when the buffer was successfully allocated, false
if there was insufficient memory. (For application-provided buffers
the function always returns true
.)Since 0.4.5:
Releases the buffer previously set by a call to setBuffer()
.
// SYNTAX
Udp.releaseBuffer();
This is typically required only when performing advanced memory management and the UDP instance is not scoped to the lifetime of the application.
Since 0.4.5:
Sends a packet, unbuffered, to a remote UDP peer.
// SYNTAX
Udp.sendPacket(buffer, bufferSize, remoteIP, remotePort);
// EXAMPLE USAGE
UDP Udp;
char buffer[] = "Particle powered";
IPAddress remoteIP(192, 168, 1, 100);
int port = 1337;
void setup() {
// Required for two way communication
Udp.begin(8888);
if (Udp.sendPacket(buffer, sizeof(buffer), remoteIP, port) < 0) {
Particle.publish("Error");
}
}
Parameters:
pointer
(buffer): the buffer of data to sendint
(bufferSize): the number of bytes of data to sendIPAddress
(remoteIP): the destination address of the remote peerint
(remotePort): the destination port of the remote peerReturns:
int
: The number of bytes written. Negative value on error.Since 0.4.5:
Join a multicast address for all UDP sockets which are on the same network interface as this one.
// SYNTAX
Udp.joinMulticast(IPAddress& ip);
// EXAMPLE USAGE
UDP Udp;
int remotePort = 1024;
IPAddress multicastAddress(224,0,0,0);
Udp.begin(remotePort);
Udp.joinMulticast(multicastAddress);
This will allow reception of multicast packets sent to the given address for UDP sockets
which have bound the port to which the multicast packet was sent.
Must be called only after begin()
so that the network interface is established.
Since 0.4.5:
Leaves a multicast group previously joined on a specific multicast address.
// SYNTAX
Udp.leaveMulticast(multicastAddress);
// EXAMPLE USAGE
UDP Udp;
IPAddress multicastAddress(224,0,0,0);
Udp.leaveMulticast(multicastAddress);
This object allows your device to control RC (hobby) servo motors. Servos have integrated gears and a shaft that can be precisely controlled. Standard servos allow the shaft to be positioned at various angles, usually between 0 and 180 degrees. Continuous rotation servos allow the rotation of the shaft to be set to various speeds.
This example uses pin D0, but D0 cannot be used for Servo on all devices.
// EXAMPLE CODE
Servo myservo; // create servo object to control a servo
// a maximum of eight servo objects can be created
int pos = 0; // variable to store the servo position
void setup()
{
myservo.attach(D0); // attaches the servo on the D0 pin to the servo object
// Only supported on pins that have PWM
}
void loop()
{
for(pos = 0; pos < 180; pos += 1) // goes from 0 degrees to 180 degrees
{ // in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for(pos = 180; pos>=1; pos-=1) // goes from 180 degrees to 0 degrees
{
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
}
NOTE: Unlike Arduino, you do not need to include Servo.h
; it is included automatically.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
Gen 4 devices including the M-SoM, P2, and Photon 2 share a single timer for both PWM (analogWrite)
and Servo. If you are using Servo, you must set the analogWrite frequency to 50 Hz, which matches
servo, or your servo will not function properly. For example: analogWrite(A2, 255, 50);
using the
optional third parameter to set the frequency.
Set up a servo on a particular pin. Note that, Servo can only be attached to pins with a timer.
Devices | Pins |
---|---|
Argon, Boron, Xenon | A0, A1, A2, A3, A4, A5, D2, D3, D4, D5, D6, D7, D8 |
Asset Tracker / Monitor One | A0, A1, A2, A3, A4, A5, A6, A7, RX, TX |
B-SoM, B5-SoM | A0, A1, A6, A7, D4, D5, D6, D7 |
E-SoM-X | A3, A4, A5, A6, B2, B3, C0, C4, C5, D0, D1, D2, RX, TX |
M-SoM | A0, A1, A5, A5, A6, D4, D5, D6, D7, MISO, MOSI, RX, TX |
Photon 2 / P2 | A2, A5, D1, S0, S1 |
Electron, Electron | A4, A5, B0, B1, B2, B3, C4, C5, D0, D1, D2, D3, RX, TX, WKP |
P1 | A4, A5, D0, D1, D2, D3, P1S6, P1S0, P1S1, RX, TX, WKP |
Photon | A4, A5, D0, D1, D2, D3, RX, TX, WKP |
// SYNTAX
servo.attach(pin)
Writes a value to the servo, controlling the shaft accordingly. On a standard servo, this will set the angle of the shaft (in degrees), moving the shaft to that orientation. On a continuous rotation servo, this will set the speed of the servo (with 0 being full-speed in one direction, 180 being full speed in the other, and a value near 90 being no movement).
// SYNTAX
servo.write(angle)
Writes a value in microseconds (uS) to the servo, controlling the shaft accordingly. On a standard servo, this will set the angle of the shaft. On standard servos a parameter value of 1000 is fully counter-clockwise, 2000 is fully clockwise, and 1500 is in the middle.
// SYNTAX
servo.writeMicroseconds(uS)
Note that some manufactures do not follow this standard very closely so that servos often respond to values between 700 and 2300. Feel free to increase these endpoints until the servo no longer continues to increase its range. Note however that attempting to drive a servo past its endpoints (often indicated by a growling sound) is a high-current state, and should be avoided.
Continuous-rotation servos will respond to the writeMicrosecond function in an analogous manner to the write function.
Read the current angle of the servo (the value passed to the last call to write()). Returns an integer from 0 to 180 degrees.
// SYNTAX
servo.read()
Check whether the Servo variable is attached to a pin. Returns a boolean.
// SYNTAX
servo.attached()
Detach the Servo variable from its pin.
// SYNTAX
servo.detach()
Since 2.0.0:
In Device OS 2.0.0 and later, the destructor for Servo will detach from the pin allowing it to be used as a regular GPIO again.
Sets a trim value that allows minute timing adjustments to correctly calibrate 90 as the stationary point.
// SYNTAX
// shortens the pulses sent to the servo
servo.setTrim(-3);
// a larger trim value
servo.setTrim(30);
// removes any previously configured trim
servo.setTrim(0);
This object allows the user to control the RGB LED on the front of the device.
// EXAMPLE CODE
// take control of the LED
RGB.control(true);
// red, green, blue, 0-255.
// the following sets the RGB LED to white:
RGB.color(255, 255, 255);
// wait one second
delay(1000);
// scales brightness of all three colors, 0-255.
// the following sets the RGB LED brightness to 25%:
RGB.brightness(64);
// wait one more second
delay(1000);
// resume normal operation
RGB.control(false);
User can take control of the RGB LED, or give control back to the system.
// take control of the RGB LED
RGB.control(true);
// resume normal operation
RGB.control(false);
Returns Boolean true
when the RGB LED is under user control, or false
when it is not.
// take control of the RGB LED
RGB.control(true);
// Print true or false depending on whether
// the RGB LED is currently under user control.
// In this case it prints "true".
Serial.println(RGB.controlled());
// resume normal operation
RGB.control(false);
Set the color of the RGB with three values, 0 to 255 (0 is off, 255 is maximum brightness for that color). User must take control of the RGB LED before calling this method.
// Set the RGB LED to red
RGB.color(255, 0, 0);
// Sets the RGB LED to cyan
RGB.color(0, 255, 255);
// Sets the RGB LED to white
RGB.color(255, 255, 255);
Scale the brightness value of all three RGB colors with one value, 0 to 255 (0 is 0%, 255 is 100%). This setting persists after RGB.control()
is set to false
, and will govern the overall brightness of the RGB LED under normal system operation. User must take control of the RGB LED before calling this method.
// Scale the RGB LED brightness to 25%
RGB.brightness(64);
// Scale the RGB LED brightness to 50%
RGB.brightness(128);
// Scale the RGB LED brightness to 100%
RGB.brightness(255);
Returns current brightness value.
// EXAMPLE
uint8_t value = RGB.brightness();
Specifies a function to call when the color of the RGB LED changes. It can be used to implement an external RGB LED.
// EXAMPLE USAGE
void ledChangeHandler(uint8_t r, uint8_t g, uint8_t b) {
// Duplicate the green color to an external LED
analogWrite(D0, g);
}
void setup()
{
pinMode(D0, OUTPUT);
RGB.onChange(ledChangeHandler);
}
onChange
can also call a method on an object.
// Automatically mirror the onboard RGB LED to an external RGB LED
// No additional code needed in setup() or loop()
class ExternalRGB {
public:
ExternalRGB(pin_t r, pin_t g, pin_t b) : pin_r(r), pin_g(g), pin_b(b) {
pinMode(pin_r, OUTPUT);
pinMode(pin_g, OUTPUT);
pinMode(pin_b, OUTPUT);
RGB.onChange(&ExternalRGB::handler, this);
}
void handler(uint8_t r, uint8_t g, uint8_t b) {
analogWrite(pin_r, 255 - r);
analogWrite(pin_g, 255 - g);
analogWrite(pin_b, 255 - b);
}
private:
pin_t pin_r;
pin_t pin_g;
pin_t pin_b;
};
// Connect an external RGB LED to D0, D1 and D2 (R, G, and B)
ExternalRGB myRGB(D0, D1, D2);
The onChange handler is called 1000 times per second so you should be careful to not do any lengthy computations or functions that take a long time to execute. Do not call functions like Log.info, Serial.print, or Particle.publish from the onChange handler. Instead, save the values from onChange in global variables and handle lengthy operations from loop if you need to do lengthy operations.
Since 0.6.1:
Allows a set of PWM pins to mirror the functionality of the on-board RGB LED.
// SYNTAX
// Common-cathode RGB LED connected to A4 (R), A5 (G), A7 (B)
RGB.mirrorTo(A4, A5, A7);
// Common-anode RGB LED connected to A4 (R), A5 (G), A7 (B)
RGB.mirrorTo(A4, A5, A7, true);
// Common-anode RGB LED connected to A4 (R), A5 (G), A7 (B)
// Mirroring is enabled in firmware _and_ bootloader
RGB.mirrorTo(A4, A5, A7, true, true);
// Enable RGB LED mirroring as soon as the device starts up
STARTUP(RGB.mirrorTo(A4, A5, A7));
Parameters:
pinr
: PWM-enabled pin number connected to red LED (see analogWrite()
for a list of PWM-capable pins)ping
: PWM-enabled pin number connected to green LED (see analogWrite()
for a list of PWM-capable pins)pinb
: PWM-enabled pin number connected to blue LED (see analogWrite()
for a list of PWM-capable pins)invert
(optional): true
if the connected RGB LED is common-anode, false
if common-cathode (default).bootloader
(optional): if true
, the RGB mirroring settings are saved in DCT and are used by the bootloader. If false
, any previously stored configuration is removed from the DCT and RGB mirroring only works while the firmware is running (default). Do not use the true
option for bootloader
from STARTUP()
.Since 0.6.1:
Disables RGB LED mirroring.
Parameters:
true
, RGB mirroring configuration stored in DCT is also cleared disabling RGB mirroring functionality in bootloader (default)Since 0.6.1:
This object allows applications to share control over the on-device RGB
LED with the Device OS in a non-exclusive way, making it possible for the system to use the LED for various important indications, such as cloud connection errors, even if an application already uses the LED for its own signaling. For this to work, an application needs to assign a priority to every application-specific LED indication (using instances of the LEDStatus
class), and the system will ensure that the LED only shows a highest priority indication at any moment of time.
The library also allows to set a custom theme for the system LED signaling. Refer to the Device Modes and LEDSignal Enum sections for information about default LED signaling patterns used by the system.
Note: Consider using this object instead of the RGB API for all application-specific LED signaling, unless a low-level control over the LED is required.
This class allows to define a LED status that represents an application-specific LED indication. Every LED status is described by a signaling pattern, speed and color parameters. Typically, applications use separate status instance for each application state that requires LED indication.
// EXAMPLE - defining and using a LED status
LEDStatus blinkRed(RGB_COLOR_RED, LED_PATTERN_BLINK, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
void setup() {
// Blink red for 3 seconds after connecting to the Cloud
blinkRed.setActive(true);
delay(3000);
blinkRed.setActive(false);
}
void loop() {
}
In the provided example, the application defines a single LED status (blinkRed
) and activates it for 3 seconds, causing the LED to start blinking in red color. Note that there is no need to toggle the LED on and off manually – this is done automatically by the system, according to the parameters passed to the status instance.
Constructs a status instance. Initially, a newly constructed status instance is set to inactive state and doesn't affect the LED until setActive() method is called by an application to activate this instance.
// SYNTAX
LEDStatus::LEDStatus(uint32_t color = RGB_COLOR_WHITE, LEDPattern pattern = LED_PATTERN_SOLID, LEDPriority priority = LED_PRIORITY_NORMAL); // 1
LEDStatus::LEDStatus(uint32_t color, LEDPattern pattern, LEDSpeed speed, LEDPriority priority = LED_PRIORITY_NORMAL); // 2
LEDStatus::LEDStatus(uint32_t color, LEDPattern pattern, uint16_t period, LEDPriority priority = LED_PRIORITY_NORMAL); // 3
LEDStatus::LEDStatus(LEDPattern pattern, LEDPriority priority = LED_PRIORITY_NORMAL); // 4
// EXAMPLE - constructing LEDStatus instance
// Solid green; normal priority (default)
LEDStatus status1(RGB_COLOR_GREEN);
// Blinking blue; normal priority (default)
LEDStatus status2(RGB_COLOR_BLUE, LED_PATTERN_BLINK);
// Fast blinking blue; normal priority (default)
LEDStatus status3(RGB_COLOR_BLUE, LED_PATTERN_BLINK, LED_SPEED_FAST);
// Breathing red with custom pattern period; important priority
LEDStatus status4(RGB_COLOR_RED, LED_PATTERN_FADE, 1000 /* 1 second */, LED_PRIORITY_IMPORTANT);
Parameters:
color
: RGB color (uint32_t
, default value is RGB_COLOR_WHITE
)pattern
: pattern type (LEDPattern
, default value is LED_PATTERN_SOLID
)speed
: pattern speed (LEDSpeed
, default value is LED_SPEED_NORMAL
)period
: pattern period in milliseconds (uint16_t
)priority
: status priority (LEDPriority
, default value is LED_PRIORITY_NORMAL
)Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM in Particle color scheme), the priority of breathing cyan is LED_PRIORITY_NORMAL
so to override it with your own custom color scheme you should use LED_PRIORITY_IMPORTANT
.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On Gen 2 devices (Photon, P1, Electron, E-Series), the priority of breathing cyan is LED_PRIORITY_BACKGROUND
. You can override it with LED_PRIORITY_NORMAL
or LED_PRIORITY_IMPORTANT
.
Sets status color.
// SYNTAX
void LEDStatus::setColor(uint32_t color);
uint32_t LEDStatus::color() const;
// EXAMPLE - setting and getting status color
LEDStatus status;
status.setColor(RGB_COLOR_BLUE);
uint32_t color = status.color(); // Returns 0x000000ff
Parameters:
color
: RGB color (uint32_t
)Returns status color (uint32_t
).
Sets pattern type.
// SYNTAX
void LEDStatus::setPattern(LEDPattern pattern);
LEDPattern LEDStatus::pattern() const;
// EXAMPLE - setting and getting pattern type
LEDStatus status;
status.setPattern(LED_PATTERN_BLINK);
LEDPattern pattern = status.pattern(); // Returns LED_PATTERN_BLINK
Parameters:
pattern
: pattern type (LEDPattern
)Returns pattern type (LEDPattern
).
Sets pattern speed. This method resets pattern period to a system-default value that depends on specified pattern speed and current pattern type set for this status instance.
// SYNTAX
void LEDStatus::setSpeed(LEDSpeed speed);
// EXAMPLE - setting pattern speed
LEDStatus status;
status.setSpeed(LED_SPEED_FAST);
Parameters:
speed
: pattern speed (LEDSpeed
)Sets pattern period. Pattern period specifies duration of a signaling pattern in milliseconds. For example, given the pattern type LED_PATTERN_BLINK
(blinking color) with period set to 1000 milliseconds, the system will toggle the LED on and off every 500 milliseconds.
// SYNTAX
void LEDStatus::setPeriod(uint16_t period);
uint16_t LEDStatus::period() const;
// EXAMPLE - setting and getting pattern period
LEDStatus status;
status.setPeriod(1000); // 1 second
uint16_t period = status.period(); // Returns 1000
Parameters:
period
: pattern period in milliseconds (uint16_t
)Returns pattern period in milliseconds (uint16_t
).
Sets status priority. Note that a newly assigned priority will take effect only after setActive()
method is called for the next time.
// SYNTAX
void LEDStatus::setPriority(LEDPriority priority);
LEDPriority LEDStatus::priority() const;
// EXAMPLE - setting and getting status priority
LEDStatus status;
status.setPriority(LED_PRIORITY_IMPORTANT);
LEDPriority priority = status.priority(); // Returns LED_PRIORITY_IMPORTANT
Parameters:
priority
: status priority (LEDPriority
)Returns status priority (LEDPriority
).
Turns the LED on.
// SYNTAX
void LEDStatus::on();
void LEDStatus::off();
void LEDStatus::toggle();
bool LEDStatus::isOn() const;
bool LEDStatus::isOff() const;
// EXAMPLE - turning the LED on and off
LEDStatus status;
status.off(); // Turns the LED off
bool on = status.isOn(); // Returns false
status.on(); // Turns the LED on
on = status.isOn(); // Returns true
status.toggle(); // Toggles the LED
on = status.isOn(); // Returns false
status.toggle();
on = status.isOn(); // Returns true
Turns the LED off.
Toggles the LED on or off.
Returns true
if the LED is turned on, or false
otherwise.
Returns true
if the LED turned off, or false
otherwise.
Activates or deactivates this status instance. The overloaded method that takes priority
argument assigns a new priority to this status instance before activating it.
// SYNTAX
void LEDStatus::setActive(bool active = true); // 1
void LEDStatus::setActive(LEDPriority priority); // 2
bool LEDStatus::isActive() const;
// EXAMPLE - activating and deactivating a status instance
LEDStatus status;
status.setActive(true); // Activates status
bool active = status.isActive(); // Returns true
status.setActive(false); // Deactivates status
active = status.isActive(); // Returns false
status.setActive(LED_PRIORITY_IMPORTANT); // Activates status with new priority
LEDPriority priority = status.priority(); // Returns LED_PRIORITY_IMPORTANT
active = status.isActive(); // Returns true
Parameters:
active
: whether the status should be activated (true
) or deactivated (false
). Default value is true
priority
: status priority (LEDPriority
)Returns true
if this status is active, or false
otherwise.
LEDStatus
class can be subclassed to implement a custom signaling pattern.
// EXAMPLE - implementing a custom signaling pattern
class CustomStatus: public LEDStatus {
public:
explicit CustomStatus(LEDPriority priority) :
LEDStatus(LED_PATTERN_CUSTOM, priority),
colorIndex(0),
colorTicks(0) {
}
protected:
virtual void update(system_tick_t ticks) override {
// Change status color every 300 milliseconds
colorTicks += ticks;
if (colorTicks > 300) {
if (++colorIndex == colorCount) {
colorIndex = 0;
}
setColor(colors[colorIndex]);
colorTicks = 0;
}
}
private:
size_t colorIndex;
system_tick_t colorTicks;
static const uint32_t colors[];
static const size_t colorCount;
};
const uint32_t CustomStatus::colors[] = {
RGB_COLOR_MAGENTA,
RGB_COLOR_BLUE,
RGB_COLOR_CYAN,
RGB_COLOR_GREEN,
RGB_COLOR_YELLOW
};
const size_t CustomStatus::colorCount =
sizeof(CustomStatus::colors) /
sizeof(CustomStatus::colors[0]);
CustomStatus customStatus(LED_PRIORITY_IMPORTANT);
void setup() {
// Activate custom status
customStatus.setActive(true);
}
void loop() {
}
In the provided example, CustomStatus
class implements a signaling pattern that alternates between some of the predefined colors.
Any class implementing a custom pattern needs to pass LED_PATTERN_CUSTOM
pattern type to the constructor of the base LEDStatus
class and reimplement its update()
method. Once an instance of such class is activated, the system starts to call its update()
method periodically in background, passing number of milliseconds passed since previous update in ticks
argument.
Note: The system may call update()
method within an ISR. Ensure provided implementation doesn't make any blocking calls, returns as quickly as possible, and, ideally, only updates internal timing and makes calls to setColor()
, setActive()
and other methods of the base LEDStatus
class.
This class allows to set a custom theme for the system LED signaling. Refer to the LEDSignal Enum section for the list of LED signals defined by the system.
// EXAMPLE - setting custom colors for network status indication
LEDSystemTheme theme;
theme.setColor(LED_SIGNAL_NETWORK_OFF, RGB_COLOR_GRAY);
theme.setColor(LED_SIGNAL_NETWORK_ON, RGB_COLOR_WHITE);
theme.setColor(LED_SIGNAL_NETWORK_CONNECTING, RGB_COLOR_YELLOW);
theme.setColor(LED_SIGNAL_NETWORK_DHCP, RGB_COLOR_YELLOW);
theme.setColor(LED_SIGNAL_NETWORK_CONNECTED, RGB_COLOR_YELLOW);
theme.apply(); // Apply theme settings
Constructs a theme instance and initializes it with current system settings.
// SYNTAX
LEDSystemTheme::LEDSystemTheme();
// EXAMPLE - constructing theme instance
LEDSystemTheme theme;
Sets signal color.
// SYNTAX
void LEDSystemTheme::setColor(LEDSignal signal, uint32_t color);
uint32_t LEDSystemTheme::color(LEDSignal signal) const;
// EXAMPLE - setting and getting signal color
LEDSystemTheme theme;
theme.setColor(LED_SIGNAL_NETWORK_ON, RGB_COLOR_BLUE);
uint32_t color = theme.color(LED_SIGNAL_NETWORK_ON); // Returns 0x000000ff
Parameters:
Returns signal color (uint32_t
).
Sets signal pattern.
// SYNTAX
void LEDSystemTheme::setPattern(LEDSignal signal, LEDPattern pattern);
LEDPattern LEDSystemTheme::pattern(LEDSignal signal) const;
// EXAMPLE - setting and getting signal pattern
LEDSystemTheme theme;
theme.setPattern(LED_SIGNAL_NETWORK_ON, LED_PATTERN_BLINK);
LEDPattern pattern = theme.pattern(LED_SIGNAL_NETWORK_ON); // Returns LED_PATTERN_BLINK
Parameters:
signal
: LED signal (LEDSignal
)pattern
: pattern type (LEDPattern
)Returns signal pattern (LEDPattern
).
Sets signal speed.
// SYNTAX
void LEDSystemTheme::setSpeed(LEDSignal signal, LEDSpeed speed);
// EXAMPLE - setting signal speed
LEDSystemTheme theme;
theme.setSpeed(LED_SIGNAL_NETWORK_ON, LED_SPEED_FAST);
Parameters:
Sets signal period.
// SYNTAX
void LEDSystemTheme::setPeriod(LEDSignal signal, uint16_t period);
uint16_t LEDSystemTheme::period(LEDSignal signal) const;
// EXAMPLE - setting and getting signal period
LEDSystemTheme theme;
theme.setPeriod(LED_SIGNAL_NETWORK_ON, 1000); // 1 second
uint16_t period = theme.period(); // Returns 1000
Parameters:
signal
: LED signal (LEDSignal
)period
: pattern period in milliseconds (uint16_t
)Returns signal period in milliseconds (uint16_t
).
Sets several signal parameters at once.
// SYNTAX
void LEDSystemTheme::setSignal(LEDSignal signal, uint32_t color); // 1
void LEDSystemTheme::setSignal(LEDSignal signal, uint32_t color, LEDPattern pattern, LEDSpeed speed = LED_SPEED_NORMAL); // 2
void LEDSystemTheme::setSignal(LEDSignal signal, uint32_t color, LEDPattern pattern, uint16_t period); // 3
// EXAMPLE - setting signal parameters
LEDSystemTheme theme;
theme.setSignal(LED_SIGNAL_NETWORK_ON, RGB_COLOR_BLUE, LED_PATTERN_BLINK, LED_SPEED_FAST);
Parameters:
signal
: LED signal (LEDSignal
)color
: RGB color (uint32_t
)pattern
: pattern type (LEDPattern
)speed
: pattern speed (LEDSpeed
)period
: pattern period in milliseconds (uint16_t
)Applies theme settings.
// SYNTAX
void LEDSystemTheme::apply(bool save = false);
// EXAMPLE - applying theme settings
LEDSystemTheme theme;
theme.setColor(LED_SIGNAL_NETWORK_ON, RGB_COLOR_BLUE);
theme.apply();
Parameters:
save
: whether theme settings should be saved to a persistent storage (default value is false
)Restores factory default theme.
// SYNTAX
static void LEDSystemTheme::restoreDefault();
// EXAMPLE - restoring factory default theme
LEDSystemTheme::restoreDefault();
This enum defines LED signals supported by the system:
Name | Description | Priority | Default Pattern |
---|---|---|---|
LED_SIGNAL_NETWORK_OFF | Network is off | Background | Breathing white |
LED_SIGNAL_NETWORK_ON | Network is on | Background | Breathing blue |
LED_SIGNAL_NETWORK_CONNECTING | Connecting to network | Normal | Blinking green |
LED_SIGNAL_NETWORK_DHCP | Getting network address | Normal | Fast blinking green |
LED_SIGNAL_NETWORK_CONNECTED | Connected to network | Background | Breathing green |
LED_SIGNAL_CLOUD_CONNECTING | Connecting to the Cloud | Normal | Blinking cyan |
LED_SIGNAL_CLOUD_HANDSHAKE | Performing handshake with the Cloud | Normal | Fast blinking cyan |
LED_SIGNAL_CLOUD_CONNECTED | Connected to the Cloud | Background | Breathing cyan |
LED_SIGNAL_SAFE_MODE | Connected to the Cloud in safe mode | Background | Breathing magenta |
LED_SIGNAL_LISTENING_MODE | Listening mode | Normal | Slow blinking blue |
LED_SIGNAL_DFU_MODE * | DFU mode | Critical | Blinking yellow |
LED_SIGNAL_POWER_OFF | Soft power down is pending | Critical | Solid gray |
Note: Signals marked with an asterisk (*) are implemented within the bootloader and currently don't support pattern type and speed customization due to flash memory constraints. This may be changed in future versions of the firmware.
This enum defines LED priorities supported by the system (from lowest to highest priority):
LED_PRIORITY_BACKGROUND
: long-lasting background indicationsLED_PRIORITY_NORMAL
: regular, typically short indicationsLED_PRIORITY_IMPORTANT
: important indicationsLED_PRIORITY_CRITICAL
: critically important indicationsInternally, the system uses the same set of priorities for its own LED signaling. In a situation when both an application and the system use same priority for their active indications, system indication takes precedence. Refer to the LEDSignal Enum section for information about system signal priorities.
This enum defines LED patterns supported by the system:
LED_PATTERN_SOLID
: solid colorLED_PATTERN_BLINK
: blinking colorLED_PATTERN_FADE
: breathing colorLED_PATTERN_CUSTOM
: custom patternThis enum defines system-default LED speed values:
LED_SPEED_SLOW
: slow speedLED_SPEED_NORMAL
: normal speedLED_SPEED_FAST
: fast speedRGB colors are represented by 32-bit integer values (uint32_t
) with the following layout in hex: 0x00RRGGBB
, where R
, G
and B
are bits of the red, green and blue color components respectively.
For convenience, the library defines constants for the following basic colors:
RGB_COLOR_BLUE
: blue (0x000000ff
)RGB_COLOR_GREEN
: green (0x0000ff00
)RGB_COLOR_CYAN
: cyan (0x0000ffff
)RGB_COLOR_RED
: red (0x00ff0000
)RGB_COLOR_MAGENTA
: magenta (0x00ff00ff
)RGB_COLOR_YELLOW
: yellow (0x00ffff00
)RGB_COLOR_WHITE
: white (0x00ffffff
)RGB_COLOR_GRAY
: gray (0x001f1f1f
)RGB_COLOR_ORANGE
: orange (0x00ff6000
)The device synchronizes time with the Particle Device Cloud during the handshake. From then, the time is continually updated on the device. This reduces the need for external libraries to manage dates and times.
Before the device gets online and for short intervals, you can use the
millis()
and micros()
functions.
Returns the number of milliseconds since the device began running the current program. This number will overflow (go back to zero), after approximately 49 days.
unsigned long time = millis();
// EXAMPLE USAGE
SerialLogHandler logHandler;
void setup()
{
}
void loop()
{
unsigned long time = millis();
//prints time since program started
Log.info("millis=%lu", time);
// wait a second so as not to send massive amounts of data
delay(1000);
}
Note: The return value for millis is an unsigned long, errors may be generated if a programmer tries to do math with other data types such as ints.
Instead of using millis()
, you can instead use System.millis()
that returns a 64-bit value that will not roll over to 0 during the life of the device.
Returns the number of microseconds since the device booted.
unsigned long time = micros();
// EXAMPLE USAGE
SerialLogHandler logHandler;
void setup()
{
Serial.begin(9600);
}
void loop()
{
unsigned long time = micros();
//prints time since program started
Log.info("micros=%lu", time);
// wait a second so as not to send massive amounts of data
delay(1000);
}
It overflows at the maximum 32-bit unsigned long value.
Pauses the program for the amount of time (in milliseconds) specified as parameter. (There are 1000 milliseconds in a second.)
// SYNTAX
delay(ms);
ms
is the number of milliseconds to pause (unsigned long)
// EXAMPLE USAGE
int ledPin = D1; // LED connected to digital pin D1
void setup()
{
pinMode(ledPin, OUTPUT); // sets the digital pin as output
}
void loop()
{
digitalWrite(ledPin, HIGH); // sets the LED on
delay(1000); // waits for a second
digitalWrite(ledPin, LOW); // sets the LED off
delay(1000); // waits for a second
}
NOTE: the parameter for millis is an unsigned long, errors may be generated if a programmer tries to do math with other data types such as ints.
Since 1.5.0:
You can also specify a value using chrono literals, for example: delay(2min)
for 2 minutes. However you should generally avoid long delays.
Pauses the program for the amount of time (in microseconds) specified as parameter. There are a thousand microseconds in a millisecond, and a million microseconds in a second.
// SYNTAX
delayMicroseconds(us);
us
is the number of microseconds to pause (unsigned int)
// EXAMPLE USAGE
int outPin = D1; // digital pin D1
void setup()
{
pinMode(outPin, OUTPUT); // sets the digital pin as output
}
void loop()
{
digitalWrite(outPin, HIGH); // sets the pin on
delayMicroseconds(50); // pauses for 50 microseconds
digitalWrite(outPin, LOW); // sets the pin off
delayMicroseconds(50); // pauses for 50 microseconds
}
Since 1.5.0:
You can also specify a value using chrono literals, for example: delayMicroseconds(2ms)
for 2 milliseconds, but you should generally avoid using long delay values with delayMicroseconds.
Retrieve the hour for the current or given time. Integer is returned without a leading zero.
// Print the hour for the current time
Serial.print(Time.hour());
// Print the hour for the given time, in this case: 4
Serial.print(Time.hour(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 0-23
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice.
Retrieve the hour in 12-hour format for the current or given time. Integer is returned without a leading zero.
// Print the hour in 12-hour format for the current time
Serial.print(Time.hourFormat12());
// Print the hour in 12-hour format for a given time, in this case: 3
Serial.print(Time.hourFormat12(1400684400));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 1-12
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice.
Returns true if the current or given time is AM.
// Print true or false depending on whether the current time is AM
Serial.print(Time.isAM());
// Print whether the given time is AM, in this case: true
Serial.print(Time.isAM(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Unsigned 8-bit integer: 0 = false, 1 = true
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice, potentially causing AM/PM to be calculated incorrectly.
Returns true if the current or given time is PM.
// Print true or false depending on whether the current time is PM
Serial.print(Time.isPM());
// Print whether the given time is PM, in this case: false
Serial.print(Time.isPM(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Unsigned 8-bit integer: 0 = false, 1 = true
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice, potentially causing AM/PM to be calculated incorrectly.
Retrieve the minute for the current or given time. Integer is returned without a leading zero.
// Print the minute for the current time
Serial.print(Time.minute());
// Print the minute for the given time, in this case: 51
Serial.print(Time.minute(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 0-59
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice.
Retrieve the seconds for the current or given time. Integer is returned without a leading zero.
// Print the second for the current time
Serial.print(Time.second());
// Print the second for the given time, in this case: 51
Serial.print(Time.second(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 0-59
Retrieve the day for the current or given time. Integer is returned without a leading zero.
// Print the day for the current time
Serial.print(Time.day());
// Print the day for the given time, in this case: 21
Serial.print(Time.day(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 1-31
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice, potentially causing an incorrect date.
Retrieve the weekday for the current or given time.
// Print the weekday number for the current time
Serial.print(Time.weekday());
// Print the weekday for the given time, in this case: 4
Serial.print(Time.weekday(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 1-7
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice, potentially causing an incorrect day of week.
Retrieve the month for the current or given time. Integer is returned without a leading zero.
// Print the month number for the current time
Serial.print(Time.month());
// Print the month for the given time, in this case: 5
Serial.print(Time.month(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer 1-12
If you have set a timezone using zone(), beginDST(), etc. the hour returned will be local time. You must still pass in UTC time, otherwise the time offset will be applied twice, potentially causing an incorrect date.
Retrieve the 4-digit year for the current or given time.
// Print the current year
Serial.print(Time.year());
// Print the year for the given time, in this case: 2014
Serial.print(Time.year(1400647897));
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Returns: Integer
// PROTOTYPE
time32_t now();
// Print the current Unix timestamp
Serial.print((int) Time.now()); // 1400647897
// Print using loggging
Log.info("time is: %d", (int) Time.now());
Retrieve the current time as seconds since January 1, 1970 (commonly known as "Unix time" or "epoch time"). This time is not affected by the timezone setting, it's coordinated universal time (UTC).
Returns: time32_t (Unix timestamp), coordinated universal time (UTC), int32_t
(signed 32-bit integer). Prior to Device OS 2.0.0, this was a standard C time_t
, however starting in Device OS 2.0.0, the standard C library changed the size of time_t to 64-bit to avoid rollover to 0 in 2038. For compatibility, Device OS still uses the 32-bit version.
Retrieve the current time in the configured timezone as seconds since January 1, 1970 (commonly known as "Unix time" or "epoch time"). This time is affected by the timezone setting.
Note that the functions in the Time
class expect times in UTC time, so the result from this should be used carefully. You should not pass Time.local() to Time.format(), for example.
Since 0.6.0
Local time is also affected by the Daylight Saving Time (DST) settings.
Returns: time32_t (Unix timestamp), local time, int32_t
(signed 32-bit integer). Prior to Device OS 2.0.0, this was a standard C time_t
, however starting in Device OS 2.0.0, the standard C library changed the size of time_t to 64-bit to avoid rollover to 0 in 2038. For compatibility, Device OS still uses the 32-bit version.
Set the time zone offset (+/-) from UTC. The device will remember this offset until reboot.
NOTE: This function does not observe daylight savings time.
// Set time zone to Eastern USA daylight saving time
Time.zone(-4);
Parameters: floating point offset from UTC in hours, from -12.0 to 14.0
Since 0.6.0:
Returns true if Daylight Saving Time (DST) is in effect.
// Print true or false depending on whether the DST in in effect
Serial.print(Time.isDST());
Returns: Unsigned 8-bit integer: 0 = false, 1 = true
This function only returns the current DST setting that you choose using beginDST() or endDST(). The setting does not automatically change based on the calendar date.
Since 0.6.0:
Retrieve the current Daylight Saving Time (DST) offset that is added to the current local time when Time.beginDST() has been called. The default is 1 hour.
// Get current DST offset
float offset = Time.getDSTOffset();
Returns: floating point DST offset in hours (default is +1.0 hours)
Since 0.6.0:
Set a custom Daylight Saving Time (DST) offset. The device will remember this offset until reboot.
// Set DST offset to 30 minutes
Time.setDSTOffset(0.5);
Parameters: floating point offset in hours, from 0.0 to 2.0
Since 0.6.0:
Start applying Daylight Saving Time (DST) offset to the current time.
You must call beginDST() at startup if you want use DST mode. The setting is not remembered and is not automatically changed based on the calendar.
Since 0.6.0:
Stop applying Daylight Saving Time (DST) offset to the current time.
You must call endDST() on the appropriate date to end DST mode. It is not calculated automatically.
Set the system time to the given timestamp.
NOTE: This will override the time set by the Particle Device Cloud. If the cloud connection drops, the reconnection handshake will set the time again
Also see: Particle.syncTime()
// Set the time to 2014-10-11 13:37:42
Time.setTime(1413034662);
Parameter: time_t (Unix timestamp), coordinated universal time (UTC)
Return string representation for the given time.
Serial.print(Time.timeStr()); // Wed May 21 01:08:47 2014
Returns: String
NB: In 0.3.4 and earlier, this function included a newline at the end of the returned string. This has been removed in 0.4.0.
Formats a time string using a configurable format.
// SYNTAX
Time.format(time, strFormat); // fully qualified (e.g. current time with custom format)
Time.format(strFormat); // current time with custom format
Time.format(time); // custom time with preset format
Time.format(); // current time with preset format
// EXAMPLE
time_t time = Time.now();
Time.format(time, TIME_FORMAT_DEFAULT); // Sat Jan 10 08:22:04 2004 , same as Time.timeStr()
Time.zone(-5.25); // setup a time zone, which is part of the ISO8601 format
Time.format(time, TIME_FORMAT_ISO8601_FULL); // 2004-01-10T08:22:04-05:15
The formats available are:
TIME_FORMAT_DEFAULT
TIME_FORMAT_ISO8601_FULL
Optional parameter: time_t (Unix timestamp), coordinated universal time (UTC), long integer
If you have set the time zone using Time.zone(), beginDST(), etc. the formatted time will be formatted in local time.
Note: The custom time provided to Time.format()
needs to be UTC based and not contain the time zone offset (as Time.local()
would), since the time zone correction is performed by the high level Time
methods internally.
Sets the format string that is the default value used by format()
.
Time.setFormat(TIME_FORMAT_ISO8601_FULL);
In more advanced cases, you can set the format to a static string that follows
the same syntax as the strftime()
function.
// custom formatting
Time.format(Time.now(), "Now it's %I:%M%p.");
// Now it's 03:21AM.
Retrieves the currently configured format string for time formatting with format()
.
Since 0.6.1:
// SYNTAX
Time.isValid();
Used to check if current time is valid. This function will return true
if:
Time.setTime()
Particle.syncTime()
Backup RAM (SRAM)
for cases when RTC retains the time.NOTE: When the device is running in AUTOMATIC
mode and threading is disabled this function will block if current time is not valid and there is an active connection to Particle Device Cloud. Once it synchronizes the time with Particle Device Cloud or the connection to Particle Device Cloud is lost, Time.isValid()
will return its current state. This function is also implicitly called by any Time
function that returns current time or date (e.g. Time.hour()
/Time.now()
/etc).
// Print true or false depending on whether current time is valid
Serial.print(Time.isValid());
SerialLogHandler logHandler;
void setup()
{
// Wait for time to be synchronized with Particle Device Cloud (requires active connection)
waitFor(Time.isValid, 60000);
}
void loop()
{
// Print current time
Log.info("current time: %s", Time.timeStr().c_str());
delay(1000);
}
For more information about real-time clocks on Particle devices, see Learn more about real-time clocks.
For more advanced date parsing, formatting, normalization and manipulation functions, use the C standard library time functions like mktime
. See the note about the standard library on the device and the description of the C standard library time functions.
Since 1.5.0:
A number of APIs have been modified to support chrono literals. For example, instead of having to use 2000
for 2 seconds in the delay(), you can use 2s
for 2 seconds.
// EXAMPLE
SerialLogHandler logHandler;
void setup() {
}
void loop() {
Log.info("testing");
delay(2s);
}
The available units are:
Literal | Unit |
---|---|
us | microseconds |
ms | milliseconds |
s | seconds |
min | minutes |
h | hours |
Individual APIs may have minimum unit limits. For example, delay() has a minimum unit of milliseconds, so you cannot specify a value in microseconds (us). If you attempt to do this, you will get a compile-time error:
../wiring/inc/spark_wiring_ticks.h:47:20: note: no known conversion for argument 1 from 'std::chrono::microseconds {aka std::chrono::duration<long long int, std::ratio<1ll, 1000000ll> >}' to 'std::chrono::milliseconds {aka std::chrono::duration<long long int, std::ratio<1ll, 1000ll> >}'
Some places where you can use them:
Interrupts are a way to write code that is run when an external event occurs. As a general rule, interrupt code should be very fast, and non-blocking. This means performing transfers, such as I2C, Serial, TCP should not be done as part of the interrupt handler. Rather, the interrupt handler can set a variable which instructs the main loop that the event has occurred.
Specifies a function to call when an external interrupt occurs. Replaces any previous function that was attached to the interrupt.
NOTE:
pinMode()
MUST be called prior to calling attachInterrupt() to set the desired mode for the interrupt pin (INPUT, INPUT_PULLUP or INPUT_PULLDOWN).
The attachInterrpt()
function is not interrupt-safe. Do not call it from within an interrupt service routine (ISR).
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
There are no limitations on attaching interrupts on Gen 4 devices, however there are limitations on pins that can be used for waking from HIBERNATE sleep mode by pin interrupt.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
All A and D pins (including TX, RX, and SPI) on Gen 3 devices can be used for interrupts, however you can only attach interrupts to 8 pins at the same time.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
External interrupts are supported on the following pins:
Photon
Not supported on the Photon (you can't use attachInterrupt on these pins):
No restrictions on the Photon (all of these can be used at the same time):
Shared on the Photon (only one pin for each bullet item can be used at the same time):
For example, you can use attachInterrupt on D1 or A4, but not both. Since they share an EXTI line, there is no way to tell which pin generated the interrupt.
But you can still attachInterrupt to D2, D3, and D4, as those are on different EXTI lines.
P1
Not supported on the P1 (you can't use attachInterrupt on these pins):
No restrictions on the P1 (all of these can be used at the same time):
Shared on the P1 (only one pin for each bullet item can be used at the same time):
Electron/E-Series
Not supported on the Electron/E series (you can't use attachInterrupt on these pins):
No restrictions on the Electron/E series (all of these can be used at the same time):
Shared on the Electron/E series (only one pin for each bullet item can be used at the same time):
Additional information on which pins can be used for interrupts is available on the pin information page.
// SYNTAX
attachInterrupt(pin, function, mode);
attachInterrupt(pin, function, mode, priority);
attachInterrupt(pin, function, mode, priority, subpriority);
Parameters:
pin
: the pin numberfunction
: the function to call when the interrupt occurs; this function must take no parameters and return nothing. This function is sometimes referred to as an interrupt service routine (ISR).mode
: defines when the interrupt should be triggered. Three constants are predefined as valid values:priority
(optional): the priority of this interrupt. Default priority is 13. Lower values increase the priority of the interrupt.subpriority
(optional): the subpriority of this interrupt. Default subpriority is 0.The function returns a boolean whether the ISR was successfully attached (true) or not (false).
// EXAMPLE USAGE
void blink(void);
int ledPin = D1;
volatile int state = LOW;
void setup()
{
pinMode(ledPin, OUTPUT);
pinMode(D2, INPUT_PULLUP);
attachInterrupt(D2, blink, CHANGE);
}
void loop()
{
digitalWrite(ledPin, state);
}
void blink()
{
state = !state;
}
You can attach a method in a C++ object as an interrupt handler.
class Robot {
public:
Robot() {
pinMode(D2, INPUT_PULLUP);
attachInterrupt(D2, &Robot::handler, this, CHANGE);
}
void handler() {
// do something on interrupt
}
};
Robot myRobot;
// nothing else needed in setup() or loop()
Using Interrupts: Interrupts are useful for making things happen automatically in microcontroller programs, and can help solve timing problems. Good tasks for using an interrupt may include reading a rotary encoder, or monitoring user input.
If you wanted to insure that a program always caught the pulses from a rotary encoder, so that it never misses a pulse, it would make it very tricky to write a program to do anything else, because the program would need to constantly poll the sensor lines for the encoder, in order to catch pulses when they occurred. Other sensors have a similar interface dynamic too, such as trying to read a sound sensor that is trying to catch a click, or an infrared slot sensor (photo-interrupter) trying to catch a coin drop. In all of these situations, using an interrupt can free the microcontroller to get some other work done while not missing the input.
About Interrupt Service Routines: ISRs are special kinds of functions that have some unique limitations most other functions do not have. An ISR cannot have any parameters, and they shouldn't return anything.
Generally, an ISR should be as short and fast as possible. If your sketch uses multiple ISRs, only one can run at a time, other interrupts will be executed after the current one finishes in an order that depends on the priority they have. millis()
relies on interrupts to count, so it will never increment inside an ISR. Since delay()
requires interrupts to work, it will not work if called inside an ISR. Using delayMicroseconds()
will work as normal.
Things you should not do from an ISR:
%f
(float) value.Turns off the given interrupt.
// SYNTAX
detachInterrupt(pin);
pin
is the pin number of the interrupt to disable.
The detachInterrpt()
function is not interrupt-safe. Do not call it from within an interrupt service routine (ISR).
Re-enables interrupts (after they've been disabled by noInterrupts()
). Interrupts allow certain important tasks to happen in the background and are enabled by default. Some functions will not work while interrupts are disabled, and incoming communication may be ignored. Interrupts can slightly disrupt the timing of code, however, and may be disabled for particularly critical sections of code.
// EXAMPLE USAGE
void setup() {}
void loop()
{
noInterrupts(); // disable interrupts
//
// put critical, time-sensitive code here
//
interrupts(); // enable interrupts
//
// other code here
//
}
interrupts()
neither accepts a parameter nor returns anything.
Disables interrupts (you can re-enable them with interrupts()
). Interrupts allow certain important tasks to happen in the background and are enabled by default. Some functions will not work while interrupts are disabled, and incoming communication may be ignored. Interrupts can slightly disrupt the timing of code, however, and may be disabled for particularly critical sections of code.
Particle.publish
, or Cellular calls, with interrupts disabled.String
and many standard library functions (like std::vector).// SYNTAX
noInterrupts();
noInterrupts()
neither accepts a parameter nor returns anything.
You must enable interrupts again as quickly as possible. Never return from setup(), loop(), from a function handler, variable handler, system event handler, etc. with interrupts disabled.
Since 0.4.7:
Software Timers provide a way to have timed actions in your program. FreeRTOS provides the ability to have up to 10 Software Timers at a time with a minimum resolution of 1 millisecond. It is common to use millis() based "timers" though exact timing is not always possible (due to other program delays). Software timers are maintained by FreeRTOS and provide a more reliable method for running timed actions using callback functions. Please note that Software Timers are "chained" and will be serviced sequentially when several timers trigger simultaneously, thus requiring special consideration when writing callback functions.
// EXAMPLE
SerialLogHandler logHandler;
Timer timer(1000, print_every_second);
void print_every_second()
{
static int count = 0;
Log.info("count=%d", count++);
}
void setup()
{
timer.start();
}
Timers may be started, stopped, reset within a user program or an ISR. They may also be "disposed", removing them from the (max. 10) active timer list.
The timer callback is similar to an interrupt - it shouldn't block. However, it is less restrictive than an interrupt. If the code does block, the system will not crash - the only consequence is that other software timers that should have triggered will be delayed until the blocking timer callback function returns.
Particle.publish
from a timer callback. Serial.print
and its variations from a timer callback as writing to Serial
is not thread safe. Use Log.info
instead.delay()
in a timer callback as it will delay other timers from running.Cellular.RSSI()
and Cellular.command()
.Software timers run with a smaller stack (1024 bytes vs. 6144 bytes). This can limit the functions you use from the callback function.
// SYNTAX
Timer timer(period, callback, one_shot)
period
is the period of the timer in milliseconds (unsigned int)callback
is the callback function which gets called when the timer expires.one_shot
(optional, since 0.4.9) when true
, the timer is fired once and then stopped automatically. The default is false
- a repeating timer.Software timers are currently implemented as a thin layer over FreeRTOS timers. The semantics of how they work is not clearly defined by FreeRTOS. For example, changing the period also resets it to zero, however this is not documented. Furthermore, future versions of Device OS could use a different timing system. If you require very precise control of how your timers behave in various edge cases, it's best to implement your own thread and handle it yourself. This will also allow you to change the stack size.
Since 0.4.9:
A class member function can be used as a callback using this syntax to create the timer:
Timer timer(period, callback, instance, one_shot)
period
is the period of the timer in milliseconds (unsigned int)callback
is the class member function which gets called when the timer expires.instance
the instance of the class to call the callback function on.one_shot
(optional, since 0.4.9) when true
, the timer is fired once and then stopped automatically. The default is false
- a repeating timer.// Class member function callback example
class CallbackClass
{
public:
void onTimeout();
}
CallbackClass callback;
Timer t(1000, &CallbackClass::onTimeout, callback);
Starts a stopped timer (a newly created timer is stopped). If start()
is called for a running timer, it will be reset.
start()
// EXAMPLE USAGE
timer.start(); // starts timer if stopped or resets it if started.
Stops a running timer.
stop()
// EXAMPLE USAGE
timer.stop(); // stops a running timer.
Changes the period of a previously created timer. It can be called to change the period of an running or stopped timer. Note that changing the period of a dormant timer will also start the timer.
changePeriod(newPeriod)
newPeriod
is the new timer period (unsigned int)
// EXAMPLE USAGE
timer.changePeriod(1000); // Reset period of timer to 1000ms.
Since 1.5.0:
You can also specify a value using chrono literals, for example: timer.changePeriod(2min)
for 2 minutes.
Resets a timer. If a timer is running, it will reset to "zero". If a timer is stopped, it will be started.
reset()
// EXAMPLE USAGE
timer.reset(); // reset timer if running, or start timer if stopped.
startFromISR()
stopFromISR()
resetFromISR()
changePeriodFromISR()
Start, stop and reset a timer or change a timer's period (as above) BUT from within an ISR. These functions MUST be called when doing timer operations within an ISR.
// EXAMPLE USAGE
timer.startFromISR(); // WITHIN an ISR, starts timer if stopped or resets it if started.
timer.stopFromISR(); // WITHIN an ISR,stops a running timer.
timer.resetFromISR(); // WITHIN an ISR, reset timer if running, or start timer if stopped.
timer.changePeriodFromISR(newPeriod); // WITHIN an ISR, change the timer period.
dispose()
Stop and remove a timer from the (max. 10) timer list, freeing a timer "slot" in the list.
// EXAMPLE USAGE
timer.dispose(); // stop and delete timer from timer list.
Since 0.5.0:
bool isActive()
Returns true
if the timer is in active state (pending), or false
otherwise.
// EXAMPLE USAGE
if (timer.isActive()) {
// ...
}
Since 5.3.0:
Starting with Device OS 5.3.0, Gen 3 devices based on the nRF52840 (Boron, B-Series SoM, Argon, Tracker SoM, E404X) and RTL872x (P2, Photon 2) can use the hardware watchdog built into the MCU. This is highly effective at resetting based on conditions that cause user or system firmware to freeze when in normal operating mode. It is only operational in normal operations mode, not DFU or safe mode.
Typically you start it from setup()
. Since it does not run during firmware updates, it's safe to use a relatively short timeout, however you probably don't want to set it lower than 30 seconds to prevent unintended resets.
The hardware watchdog is automatically stopped before entering safe mode or DFU mode so it will not affect upgrades of user firmware or Device OS, which may take longer than the typical watchdog timeout.
On all platforms, the default is for the watchdog to continue running during sleep mode.
On the Boron, B-Series SoM, Tracker, Argon, and E404X (nRF52840), the WatchdogCap::SLEEP_RUNNING
defaults to on, which is to say the watchdog continues to run while in sleep. It can, however, be explictly cleared so the watchdog will be paused while in sleep mode.
On the P2 and Photon 2 (RTL872x), the watchdog contiues to run in sleep mode. However, you could manually stop it before sleep mode.
For best compatibility across devices, we recommend that you set a watchdog timeout longer than your sleep period, plus an allowance for the time it takes to go to sleep, and wake up from sleep, which could add 30 seconds to a minute. This also provides extra safety in case the device does not wake up at the expected time, as the watchdog will reset the system in this case. This could happen if you have a bug in your logic for how long to sleep, for example.
// Getting capabiltiies
WatchdogInfo info;
Watchdog.getInfo(info);
// Get the capabilities that are always enabled
WatchdogCaps mandatoryCaps = info.mandatoryCapabilities();
// Get the capabilities that can be turned off
WatchdogCaps optionalCaps = info.capabilities();
The capabilities vary depending on the platform:
Capability | nRF52 | RTL872x | Details |
---|---|---|---|
WatchdogCap::RESET |
Mandatory | Default | Reset device on watchdog timeout expired |
WatchdogCap::NOTIFY |
Optional | Generate an interrupt on expired | |
WatchdogCap::NOTIFY_ONLY |
Optional | Generate an interrupt on expired without resetting the device | |
WatchdogCap::RECONFIGURABLE |
Optional | Can be re-configured after started | |
WatchdogCap::STOPPABLE |
Optional | Can be stopped after started | |
WatchdogCap::SLEEP_RUNNING |
Default | 1 | Watchdog runs while in sleep mode |
WatchdogCap::DEBUG_RUNNING |
Optional | Mandatory | Can be paused in debug mode |
Minimim Timeout | 1 | 1 | |
Maximum Timeout | 131071 s | 8190 s |
1On the RTL872x platform, the watchdog cannot be automatically stopped in sleep mode.
Watchdog.init(WatchdogConfiguration().timeout(30s));
Watchdog.start();
The minimum is 1 millisecond, but you should never set it that short since it will frequently fire as the thread scheduler works at 1 millisecond timeslices, and any delays caused by high priority threads or disabled interrupts will exceed that limit.
The maximium varies by platform:
You can only call init()
if the watchdog is not currently running. On the nRF52 platform (Boron, B-Series SoM, Argon, Tracker SoM) you cannot stop the watchdog, so you necessarily can only start it once until the device is reset. This also means you cannot change the watchdog time period on the nRF52 platform.
You typically start it when initializating during setup().
RTL872x platform (P2, Photon 2): You can stop the watchdog after starting it.
nRF52 platform (Boron, B-Series SoM, Argon, Tracker SoM): stop()
is not available due to hardware limitations.
For maximum compatibility across devices, you should design your watchdog configuration to avoid having to stop the watchdog.
You must call Watchdog.refresh()
more often than the timeout interval. You can call it on every loop()
.
If you have logic that blocks loop, you must also call it while blocking. Beware of using long delay()
if you are using the hardware watchdog.
Beware of setting a short watchdog timeout if you call Watchdog.refresh()
only from loop()
if you have lengthy operations that occur during setup()
. Since the watchdog is already running, it could fire before you actually are able to refresh the watchdog the first time.
// Call on every loop, or when blocking
Watchdog.refresh();
Watchdog.init(WatchdogConfiguration()
.capabilities(WatchdogCap::NOTIFY | WatchdogCap::DEBUG_RUNNING)
.timeout(5min));
Watchdog.start();
On nRF52840 (Boron, B-Series SoM, Argon, Tracker SoM, E404X) devices, you can optionally stop the watchdog from running during sleep by setting the capabilities.
The default is WatchdogCap::NOTIFY | WatchdogCap::SLEEP_RUNNING | WatchdogCap::DEBUG_RUNNING
so not setting WatchdogCap::SLEEP_RUNNING
will pause the watchdog while in sleep mode.
Since pausing the watchdog is not possible on the RTL872x platform (P2 and Photon 2), we recommend that you set your watchdog and sleep timeout parameters to avoid having to stop the watchdog on sleep. This also provides extra safety in case the device does not wake up at the expected time, as the watchdog will reset the system in this case. This could happen if you have a bug in your logic for how long to sleep, for example.
// As a global variable:
retained bool watchdogExpiredFlag = false;
// In setup():
Watchdog.onExpired([]() {
// This is called from an ISR, beware!
watchdogExpiredFlag = true;
});
It is possible to set an expired handler. Since the device is probably in an unstable state at that point, you are limited in what you can do from the expired handler. Additionally, it's called an interrupt context (ISR) so you cannot allocate memory, make Particle calls (like publish), cellular modem calls, etc.. About the only thing you can do safely is set a retained variable.
RTL872x platform (P2, Photon 2): If the onExpired()
callback is used, the device will not automatically reset.
nRF52 platform (Boron, B-Series SoM, Argon, Tracker SoM): The onExpired()
callback is available, and the device will always reset shortly after the callback is called.
Due to the limitations of onExpired()
and the difference between platforms, we recommed not using this feature.
Since 0.5.0:
A Watchdog Timer is designed to rescue your device should an unexpected problem prevent code from running. This could be the device locking or or freezing due to a bug in code, accessing a shared resource incorrectly, corrupting memory, and other causes.
Device OS includes a software-based watchdog, ApplicationWatchdog, that is based on a FreeRTOS thread. It theoretically can help when user application enters an infinite loop. However, it does not guard against the more problematic things like deadlock caused by accessing a mutex from multiple threads with thread swapping disabled, infinite loop with interrupts disabled, or an unpredictable hang caused by memory corruption. Only a hardware watchdog can handle those situations. In practice, the application watchdog is rarely effective.
Starting with Device OS 5.3.0, Gen 3 devices based on the nRF52840 (Boron, B-Series SoM, Argon, Tracker SoM, E404X) and RTL872x (P2, Photon 2) can use the hardware watchdog built into the MCU. This is highly effective at resetting based on conditions that cause user or system firmware to freeze when in normal operating mode. It is only operational in normal operations mode, not DFU or safe mode. Using this, instead of the application watchdog, is recommended.
The page on watchdog timers has information about external hardware watchdog timers, and hardware and software designs for the TPL5010 and AB1805.
// PROTOTYPES
ApplicationWatchdog(unsigned timeout_ms,
std::function<void(void)> fn,
unsigned stack_size=DEFAULT_STACK_SIZE);
ApplicationWatchdog(std::chrono::milliseconds ms,
std::function<void(void)> fn,
unsigned stack_size=DEFAULT_STACK_SIZE);
// EXAMPLE USAGE
// Global variable to hold the watchdog object pointer
ApplicationWatchdog *wd;
void watchdogHandler() {
// Do as little as possible in this function, preferably just
// calling System.reset().
// Do not attempt to Particle.publish(), use Cellular.command()
// or similar functions. You can save data to a retained variable
// here safetly so you know the watchdog triggered when you
// restart.
// In 2.0.0 and later, RESET_NO_WAIT prevents notifying the cloud of a pending reset
System.reset(RESET_NO_WAIT);
}
void setup() {
// Start watchdog. Reset the system after 60 seconds if
// the application is unresponsive.
wd = new ApplicationWatchdog(60000, watchdogHandler, 1536);
}
void loop() {
while (some_long_process_within_loop) {
ApplicationWatchdog::checkin(); // resets the AWDT count
}
}
// AWDT count reset automatically after loop() ends
A default stack_size
of 512 is used for the thread. stack_size
is an optional parameter. The stack can be made larger or smaller as needed. This is generally too small, and it's best to use a minimum of 1536 bytes. If not enough stack memory is allocated, the application will crash due to a Stack Overflow. The RGB LED will flash a red SOS pattern, followed by 13 blinks.
The application watchdog requires interrupts to be active in order to function. Enabling the hardware watchdog in combination with this is recommended, so that the system resets in the event that interrupts are not firing.
The Particle.process()
function calls ApplicationWatchdog::checkin()
internally, so you can also use that to service the application watchdog.
Your watchdog handler should have the prototype:
void myWatchdogHandler(void);
You should generally not try to do anything other than call System.reset()
or perhaps set some retained variables in your application watchdog callback. In particular:
Particle.publish()
or even Particle.disconnect()
.Cellular.command()
.Calling these functions will likely cause the system to deadlock and not reset.
The following is a recommended watchdog callback implementation. It sets a reset reason, and does not wait for the cloud connection to gracefully close, since the device is in a bad state and that will likely never complete.
void myWatchdogHandler(void) {
System.reset(RESET_REASON_USER_APPLICATION_WATCHDOG, RESET_NO_WAIT);
}
Note: waitFor
and waitUntil
do not tickle the application watchdog. If the condition you are waiting for is longer than the application watchdog timeout, the device will reset.
Since 1.5.0:
You can also specify a value using chrono literals, for example: wd = new ApplicationWatchdog(60s, System.reset)
for 60 seconds.
Note that in addition to functions outlined below all of the newlib math functions described at sourceware.org are also available for use by simply including the math.h header file thus:
#include "math.h"
Calculates the minimum of two numbers.
min(x, y)
x
is the first number, any data type
y
is the second number, any data type
The functions returns the smaller of the two numbers.
// EXAMPLE USAGE
sensVal = min(sensVal, 100); // assigns sensVal to the smaller of sensVal or 100
// ensuring that it never gets above 100.
NOTE: Perhaps counter-intuitively, max() is often used to constrain the lower end of a variable's range, while min() is used to constrain the upper end of the range.
WARNING: Because of the way the min() function is implemented, avoid using other functions inside the brackets, it may lead to incorrect results
min(a++, 100); // avoid this - yields incorrect results
a++;
min(a, 100); // use this instead - keep other math outside the function
Calculates the maximum of two numbers.
max(x, y)
x
is the first number, any data type
y
is the second number, any data type
The functions returns the larger of the two numbers.
// EXAMPLE USAGE
sensVal = max(senVal, 20); // assigns sensVal to the larger of sensVal or 20
// (effectively ensuring that it is at least 20)
NOTE: Perhaps counter-intuitively, max() is often used to constrain the lower end of a variable's range, while min() is used to constrain the upper end of the range.
WARNING: Because of the way the max() function is implemented, avoid using other functions inside the brackets, it may lead to incorrect results
max(a--, 0); // avoid this - yields incorrect results
a--; // use this instead -
max(a, 0); // keep other math outside the function
Computes the absolute value of a number.
abs(x);
where x
is the number
The function returns x
if x
is greater than or equal to 0
and returns -x
if x
is less than 0
.
WARNING: Because of the way the abs() function is implemented, avoid using other functions inside the brackets, it may lead to incorrect results.
abs(a++); // avoid this - yields incorrect results
a++; // use this instead -
abs(a); // keep other math outside the function
Constrains a number to be within a range.
constrain(x, a, b);
x
is the number to constrain, all data types
a
is the lower end of the range, all data types
b
is the upper end of the range, all data types
The function will return:
x
: if x is between a
and b
a
: if x
is less than a
b
: if x
is greater than b
// EXAMPLE USAGE
sensVal = constrain(sensVal, 10, 150);
// limits range of sensor values to between 10 and 150
// EXAMPLE USAGE
// Map an analog value to 8 bits (0 to 255)
void setup() {
pinMode(D1, OUTPUT);
}
void loop()
{
int val = analogRead(A0);
val = map(val, 0, 4095, 0, 255);
analogWrite(D1, val);
}
Re-maps a number from one range to another. That is, a value of fromLow would get mapped to toLow
, a value
of fromHigh
to toHigh
, values in-between to values in-between, etc.
map(value, fromLow, fromHigh, toLow, toHigh);
Does not constrain values to within the range, because out-of-range values are sometimes intended and useful. The constrain()
function may be used either before or after this function, if limits to the ranges are desired.
Note that the "lower bounds" of either range may be larger or smaller than the "upper bounds" so the map()
function may be used to reverse a range of numbers, for example
y = map(x, 1, 50, 50, 1);
The function also handles negative numbers well, so that this example
y = map(x, 1, 50, 50, -100);
is also valid and works well.
When called with integers, the map()
function uses integer math so will not generate fractions, when the math might indicate that it should do so. Fractional remainders are truncated, not rounded.
Parameters can either be integers or floating point numbers:
value
: the number to mapfromLow
: the lower bound of the value's current rangefromHigh
: the upper bound of the value's current rangetoLow
: the lower bound of the value's target rangetoHigh
: the upper bound of the value's target rangeThe function returns the mapped value, as integer or floating point depending on the arguments.
Appendix: For the mathematically inclined, here's the whole function
int map(int value, int fromStart, int fromEnd, int toStart, int toEnd)
{
if (fromEnd == fromStart) {
return value;
}
return (value - fromStart) * (toEnd - toStart) / (fromEnd - fromStart) + toStart;
}
Calculates the value of a number raised to a power. pow()
can be used to raise a number to a fractional power. This is useful for generating exponential mapping of values or curves.
pow(base, exponent);
base
is the number (float)
exponent
is the power to which the base is raised (float)
The function returns the result of the exponentiation (double)
Calculates the square root of a number.
sqrt(x)
x
is the number, any data type
The function returns the number's square root (double)
The firmware incorporates a pseudo-random number generator.
Retrieves the next random value, restricted to a given range.
random(max);
Parameters
max
- the upper limit of the random number to retrieve.Returns: a random value between 0 and up to, but not including max
.
int r = random(10);
// r is >= 0 and < 10
// The smallest value returned is 0
// The largest value returned is 9
NB: When max
is 0, the result is always 0.
random(min,max);
Parameters:
min
- the lower limit (inclusive) of the random number to retrieve.max
- the upper limit (exclusive) of the random number to retrieve.Returns: a random value from min
and up to, but not including max
.
int r = random(10, 100);
// r is >= 10 and < 100
// The smallest value returned is 10
// The largest value returned is 99
NB: If min
is greater or equal to max
, the result is always 0.
randomSeed(newSeed);
Parameters:
newSeed
- the new random seedThe pseudorandom numbers produced by the firmware are derived from a single value - the random seed.
The value of this seed fully determines the sequence of random numbers produced by successive
calls to random()
. Using the same seed on two separate runs will produce
the same sequence of random numbers, and in contrast, using different seeds
will produce a different sequence of random numbers.
On startup, the default random seed is set by the system to 1. Unless the seed is modified, the same sequence of random numbers would be produced each time the system starts.
Fortunately, when the device connects to the cloud, it receives a very randomized seed value, which is used as the random seed. So you can be sure the random numbers produced will be different each time your program is run.
Disable random seed from the cloud
When the device receives a new random seed from the cloud, it's passed to this function:
void random_seed_from_cloud(unsigned int seed);
The system implementation of this function calls randomSeed()
to set
the new seed value. If you don't wish to use random seed values from the cloud,
you can take control of the random seeds set by adding this code to your app:
void random_seed_from_cloud(unsigned int seed) {
// don't do anything with this. Continue with existing seed.
}
In the example, the seed is simply ignored, so the system will continue using whatever seed was previously set. In this case, the random seed will not be set from the cloud, and setting the seed is left to up you.
// PROTOTYPE
uint32_t HAL_RNG_GetRandomNumber(void);
Gets a cryptographic random number (32-bit) from the hardware random number generator.
Note: The hardware random number generator has a limit to the number of random values it can produce in a given timeframe. If there aren't enough random numbers available at this time, this function may be block until there are. If you need a large number of random values you can use this function to seed a pseudo-random number generator (PRNG).
Note: Only available on Gen 2 (Photon, P1, Electron, E-Series), and Gen 3 (Argon, Boron, B-Series SoM, Tracker SoM). Not available on the Spark Core.
EEPROM emulation allows small amounts of data to be stored and persisted even across reset, power down, and user and system firmware flash operations.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM, and E404X) the EEPROM emulation is stored as a file on the flash file system. Since the data is spread across a large number of flash sectors, flash erase-write cycle limits should not be an issue in general.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
The P2 and Photon 2 also uses a file a file on the flash file system for EEPROM emulation.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On Gen 2 devices (Photon, P1, Electron, and E-Series except the E404X) EEPROM emulation allocates a region of the device's built-in Flash memory to act as EEPROM. Unlike "true" EEPROM, flash doesn't suffer from write "wear" with each write to each individual address. Instead, the page suffers wear when it is filled.
Each write containing changed values will add more data to the page until it is full, causing a page erase. When writing unchanged data, there is no flash wear, but there is a penalty in CPU cycles. Try not write to EEPROM every loop() iteration to avoid unnecessary CPU cycle penalties.
Backup RAM may be a better storage solution for quickly changing values, see Backup RAM (SRAM)).
The EEPROM functions can be used to store small amounts of data in Flash that will persist even after the device resets after a deep sleep or is powered off.
Returns the total number of bytes available in the emulated EEPROM.
// SYNTAX
size_t length = EEPROM.length();
This function will write an object to the EEPROM. You can write single values like int
and
float
or group multiple values together using struct
to ensure that all values of the struct are
updated together.
// SYNTAX
EEPROM.put(int address, object)
address
is the start address (int) of the EEPROM locations to write. It must be a value between 0
and EEPROM.length()-1
object
is the object data to write. The number of bytes to write is automatically determined from
the type of object.
// EXAMPLE USAGE
// Write a value (2 bytes in this case) to the EEPROM address
int addr = 10;
uint16_t value = 12345;
EEPROM.put(addr, value);
// Write an object to the EEPROM address
addr = 20;
struct MyObject {
uint8_t version;
float field1;
uint16_t field2;
char name[10];
};
MyObject myObj = { 0, 12.34f, 25, "Test!" };
EEPROM.put(addr, myObj);
The object data is first compared to the data written in the EEPROM to avoid writing values that haven't changed.
If the device loses power before the write finishes, the partially written data will be ignored.
If you write several objects to EEPROM, make sure they don't overlap: the address of the second object must be larger than the address of the first object plus the size of the first object. You can leave empty room between objects in case you need to make the first object bigger later.
This function will retrieve an object from the EEPROM. Use the same type of object you used in the
put
call.
// SYNTAX
EEPROM.get(int address, object)
address
is the start address (int) of the EEPROM locations to read. It must be a value between 0
and EEPROM.length()-1
object
is the object data that would be read. The number of bytes read is automatically determined
from the type of object.
// EXAMPLE USAGE
// Read a value (2 bytes in this case) from EEPROM addres
int addr = 10;
uint16_t value;
EEPROM.get(addr, value);
if(value == 0xFFFF) {
// EEPROM was empty -> initialize value
value = 25;
}
// Read an object from the EEPROM addres
addr = 20;
struct MyObject {
uint8_t version;
float field1;
uint16_t field2;
char name[10];
};
MyObject myObj;
EEPROM.get(addr, myObj);
if(myObj.version != 0) {
// EEPROM was empty -> initialize myObj
MyObject defaultObj = { 0, 12.34f, 25, "Test!" };
myObj = defaultObj;
}
The default value of bytes in the EEPROM is 255 (hexadecimal 0xFF) so reading an object on a new device will return an object filled with 0xFF. One trick to deal with default data is to include a version field that you can check to see if there was valid data written in the EEPROM.
Read a single byte of data from the emulated EEPROM.
// SYNTAX
uint8_t value = EEPROM.read(int address);
address
is the address (int) of the EEPROM location to read
// EXAMPLE USAGE
// Read the value of the second byte of EEPROM
int addr = 1;
uint8_t value = EEPROM.read(addr);
When reading more than 1 byte, prefer get()
over multiple read()
since it's faster.
Write a single byte of data to the emulated EEPROM.
// SYNTAX
write(int address, uint8_t value);
address
is the address (int) of the EEPROM location to write to
value
is the byte data (uint8_t) to write
// EXAMPLE USAGE
// Write a byte value to the second byte of EEPROM
int addr = 1;
uint8_t val = 0x45;
EEPROM.write(addr, val);
When writing more than 1 byte, prefer put()
over multiple write()
since it's faster and it ensures
consistent data even when power is lost while writing.
The object data is first compared to the data written in the EEPROM to avoid writing values that haven't changed.
Erase all the EEPROM so that all reads will return 255 (hexadecimal 0xFF).
// EXAMPLE USAGE
// Reset all EEPROM locations to 0xFF
EEPROM.clear();
On Gen 2 devices, calling this function pauses processor execution (including code running in interrupts) for 800ms since no instructions can be fetched from Flash while the Flash controller is busy erasing both EEPROM pages.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Pending erase functions are only used on Gen 2 devices (Photon, P1, Electron, and E-Series, except the E404X).
Automatic page erase is the default behavior. This section describes optional functions the application can call to manually control page erase for advanced use cases.
After enough data has been written to fill the first page, the EEPROM emulation will write new data to a second page. The first page must be erased before being written again.
Erasing a page of Flash pauses processor execution (including code running in interrupts) for 500ms since no instructions can be fetched from Flash while the Flash controller is busy erasing the EEPROM page. This could cause issues in applications that use EEPROM but rely on precise interrupt timing.
hasPendingErase()
lets the application developer check if a full EEPROM page needs to be erased.
When the application determines it is safe to pause processor execution to erase EEPROM it calls
performPendingErase()
. You can call this at boot, or when your device is idle if you expect it to
run without rebooting for a long time.
// EXAMPLE USAGE
void setup() {
// Erase full EEPROM page at boot when necessary
if(EEPROM.hasPendingErase()) {
EEPROM.performPendingErase();
}
}
To estimate how often page erases will be necessary in your application, assume that it takes
2*EEPROM.length()
byte writes to fill a page (it will usually be more because not all bytes will always be
updated with different values).
If the application never calls performPendingErase()
then the pending page erase will be performed
when data is written using put()
or write()
and both pages are full. So calling
performPendingErase()
is optional and provided to avoid the uncertainty of a potential processor
pause any time put()
or write()
is called.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
A 3068 bytes section of backup RAM is provided for storing values that are maintained across system reset and hibernate sleep mode. Unlike EEPROM emulation, the backup RAM can be accessed at the same speed as regular RAM and does not have any wear limitations.
On Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM), retained memory is only initialized in Device OS 1.5.0 and later. In prior versions, retained memory would always be uninitialized on first power-up.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
The P2 and Photon 2 have limited support for retained memory in Device OS 5.3.1 and later.
Retained memory is preserved on RTL872x devices in the following cases:
Case | Saved |
---|---|
When entering sleep modes | 5.3.1 and later |
OTA firmware updates | 5.3.1 and later |
System.backupRamSync() |
5.3.1 and later |
System.reset() |
Not saved |
Reset button or reset pin | Not saved |
Every 10 seconds | 5.3.1 to 5.8.0 only |
Calling System.backupRamSync()
will manually save the contents of retained memory to a dedicated flash page on the RTL872x processor and will be restored after the device is reset. You should avoid saving the data extremely frequently as it is slower than RAM and will cause flash wear and is relatively slow to execute.
Prior to Device OS 5.3.1, retained memory is not supported on RTL872x devices. The flash file system can be used, or you can use an external chip such as an I2C or SPI FRAM.
Retained memory is 3068 bytes.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
The STM32F2xx features 4KB of backup RAM (3068 bytes for Device OS version v0.6.0 and later) of which is available to the user. Unlike the regular RAM memory, the backup RAM is retained so long as power is provided to VIN or to VBAT. In particular this means that the data in backup RAM is retained when:
Note that if neither VIN or VBAT is powered then the contents of the backup RAM will be lost; for data to be retained, the device needs a power source. For persistent storage of data through a total power loss, please use the EEPROM.
Power Conditions and how they relate to Backup RAM initialization and data retention:
Power Down Method | Power Up Method | When VIN Powered | When VBAT Powered | SRAM Initialized | SRAM Retained |
---|---|---|---|---|---|
Power removed on VIN and VBAT | Power applied on VIN | - | No[1] | Yes | No |
Power removed on VIN and VBAT | Power applied on VIN | - | Yes | Yes | No |
Power removed on VIN | Power applied on VIN | - | Yes | No | Yes |
System.sleep(SLEEP_MODE_DEEP) | Rising edge on WKP pin, or Hard Reset | Yes | Yes/No | No | Yes |
System.sleep(SLEEP_MODE_DEEP,10) | RTC alarm after 10 seconds | Yes | Yes/No | No | Yes |
System.reset() | Boot after software reset | Yes | Yes/No | No | Yes |
Hard reset | Boot after hard reset | Yes | Yes/No | No | Yes |
[1] Note: If VBAT is floating when powering up for the first time, SRAM remains uninitialized. When using this feature for Backup RAM, it is recommended to have VBAT connected to a 3V3 or a known good power source on system first boot. When using this feature for Extra RAM, it is recommended to jumper VBAT to GND on the Photon, P1, and Electron to ensure it always initializes on system first boot. On the Electron, VBAT is tied to 3V3 and you must not connect it to GND.
With regular RAM, data is stored in RAM by declaring variables.
// regular variables stored in RAM
float lastTemperature;
int numberOfPresses;
int numberOfTriesRemaining = 10;
This tells the system to store these values in RAM so they can be changed. The system takes care of giving them initial values. Before they are set, they will have the initial value 0 if an initial value isn't specified.
Variables stored in backup RAM follow a similar scheme but use an additional keyword retained
:
// retained variables stored in backup RAM
retained float lastTemperature;
retained int numberOfPresses;
retained int numberOfTriesRemaining = 10;
A retained
variable is similar to a regular variable, with some key differences:
retained
variables are initialized
when the device is first powered on (with VIN, from being powered off with VIN and VBAT completely removed).
When the device is powered on, the system takes care of setting these variables to their initial values.
lastTemperature
and numberOfPresses
would be initialized to 0, while numberOfTriesRemaining
would be initialized to 10.retained
variables can be updated freely just as with regular RAM variables and operate
just as fast as regular RAM variables.
Here's some typical use cases for retained
variables:
Finally, if you don't need the persistence of retained
variables, you
can consider them simply as extra RAM to use.
// EXAMPLE USAGE
SerialLogHandler logHandler;
retained int value = 10;
void setup() {
System.enableFeature(FEATURE_RETAINED_MEMORY);
}
void loop() {
Log.info("value before=%s", value);
value = 20;
Log.info("value after=%s", value);
delay(100); // Give the serial TX buffer a chance to empty
System.sleep(SLEEP_MODE_DEEP, 10);
// Or try a software reset
// System.reset();
}
/* OUTPUT
*
* 10
* 20
* DEEP SLEEP for 10 seconds
* 20 (value is retained as 20)
* 20
*
*/
You cannot store dynamically allocated object in retained memory. Some examples of things that cannot be used:
String
variablesstd::list
, std::vector
, std:map
, etc.new
or malloc
You can store string data by allocating a fixed char
array in retained variable, but you cannot retain a String
.
Be sure to validate the data before using it. The data is reinitialized to default values on cold boot, however there are cases where the data could be invalid. For example, if the device is reset during the process of writing data.
Storing the data in a struct and using a CRC or hash is a good defensive technique. It can also gracefully handle adding new retained variables, or changing the size of retained variables.
When doing a user firmware update, retained memory is typically preserved, but in addition to explicit changes in the data your retain, the compiler may also relocate retained data unexpectedly. Using additional sanity checks can help prevent using invalid data.
On Gen 3 devices (B-Series SoM, Boron, Argon, Tracker SoM, Tracker One, E404X), retained memory is always enabled, regagrdless of the feature flag.
On the P2, Photon 2, and Tracker M, retained memory is not available.
On Gen 2 devices (E-Series, Photon, P1), backup RAM is disabled by default, since it does require some maintenance power which may not be desired on some low-powered projects. Backup RAM consumes roughly 5uA or less on VIN and 9uA or less on VBAT.
Backup RAM is enabled with this code in setup():
void setup()
{
System.enableFeature(FEATURE_RETAINED_MEMORY));
}
There are a number of persistent settings on devices that are not erased in most situations:
These settings include:
void setup_the_fundulating_conbobulator()
{
pinMode(D3, OUTPUT);
digitalWrite(D3, HIGH);
}
// The STARTUP call is placed outside of any other function
// What goes inside is any valid code that can be executed. Here, we use a function call.
// Using a single function is preferable to having several `STARTUP()` calls.
STARTUP( setup_the_fundulating_conbobulator() );
Typically an application will have its initialization code in the setup()
function.
Using SYSTEM_THREAD(ENABLED)
reduces the amount of time before setup is called to milliseconds and is the recommended method of calling code early. With Device OS 6.2.0 and later, system thread is always enabled.
The STARTUP()
function instructs the system to execute the code even earlier, however there are limitations:
The code referenced by STARTUP()
is executed very early in the startup sequence, so it's best suited
to initializing digital I/O and peripherals. Networking setup code should still be placed in setup()
.
There is one notable exception - WiFi.selectAntenna()
should be called from STARTUP()
to select the default antenna before the Wi-Fi connection is made on the Photon and P1.
Note that when startup code performs digital I/O, there will still be a period of at least few hundred milliseconds
where the I/O pins are in their default power-on state, namely INPUT
. Circuits should be designed with this
in mind, using pullup/pulldown resistors as appropriate. For Gen 2 devices, see note below about JTAG/SWD as well.
Other acceptable calls to make from STARTUP include:
System.setPowerConfiguration()
System.enableFeature()
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Some acceptable calls to make from STARTUP()
on Gen 2 devices (Photon, P1, Electron, E-Series) include:
cellular_credentials_set()
WiFi.selectAntenna()
Mouse.begin()
Keyboard.begin()
On Gen 2 devices, beware when using pins D3, D5, D6, and D7 as OUTPUT controlling external devices on Gen 2 devices. After reset, these pins will be briefly taken over for JTAG/SWD, before being restored to the default high-impedance INPUT state during boot.
The brief change in state (especially when connected to a MOSFET that can be triggered by the pull-up or pull-down) may cause issues when using these pins in certain circuits. Using STARTUP will not prevent this!
When preparing software for your product, you must include product version at the top of the firmware source code. This must also match the version specified when uploading the firmware as product firmware.
// EXAMPLE
PRODUCT_VERSION(1); // increment each time you upload to the console
Devices that are marked as development devices do not need a valid PRODUCT_VERSION
.
With Device OS 4.0 and later, you only need the PRODUCT_VERSION
and not the PRODUCT_ID
.
In Device OS 4.0 and later, the PRODUCT_ID
macro is no longer used. If you attempt to use it, you will get an error during compilation targeting 4.0.0 and later.
The PRODUCT_ID macro must be removed from your firmware source code. The same compiled firmware binary may be used in multiple products that share the same platform and functionality.
You must also pre-add all Device IDs that will be using your product when using Device OS 4.0 and later. There is no support for quarantine mode or auto-approve modes. The only way to add a device to a product is to do it using the console or cloud API before the device first connects to the cloud.
You still need to include PRODUCT_VERSION
.
When preparing software for versions of Device OS before 4.0 it is essential to include your product ID and version at the top of the firmware source code.
// EXAMPLE 3.x and earlier
PRODUCT_ID(94); // replace by your product ID
PRODUCT_VERSION(1); // increment each time you upload to the console
You can find more details about the product ID and how to get yours in the Console guide.
In Device OS 1.5.3 and later, but before 4.0, you can also use a wildcard product ID. In order to take advantage of this feature you must pre-add the device IDs to your product as you cannot use quarantine with a wildcard product ID. Then use:
// EXAMPLE 1.5.3 to 3.x with wildcard product ID
PRODUCT_ID(PLATFORM_ID);
PRODUCT_VERSION(1); // increment each time you upload to the console
This will allow the device to join the product it has been added to without hardcoding the product ID into the device firmware. This is used with the Tracker SoM to join the product it is assigned to with the factory firmware and not have to recompile and flash custom firmware.
The behavior with 4.0 and later is similar to using a wildcard product ID, but you simply omit the PRODUCT_ID
.
If you want to make your product firmware compile both before and after 4.0, you could use something like:
#ifndef SYSTEM_VERSION_v400ALPHA1
// Include this only for versions prior to 4.0.0-alpha.1
PRODUCT_ID(PLATFORM_ID)
#endif
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, RISING);
SystemSleepResult result = System.sleep(config);
Since 1.5.0:
System.sleep()
can be used to dramatically improve the battery life of a Particle-powered project.
For introduction to sleep and the various sleep modes and trade-offs, see Learn more about sleep modes.
The SystemSleepConfiguration
class configures all of the sleep parameters and eliminates the previous numerous and confusing overloads of the System.sleep()
function. You pass this object to System.sleep()
.
For earlier versions of Device OS you can use the classic API.
The Tracker One, Tracker SoM, and Monitor One have an additional layer of sleep functionality. You can find out more in the Tracker Sleep Tutorial and TrackerSleep API Reference. You generally should avoid directly using this sleep API on the Tracker as it will not put the tracker-specific peripherals like the GNSS, IMU, CAN, and RTC to sleep properly, as these functions are implemented in the Tracker Edge or Monitor Edge sleep functionality, not Device OS.
The are are three sleep modes:
STOP | ULTRA_LOW_POWER | HIBERNATE | |
---|---|---|---|
Relative power consumption | Low | Lower | Lowest |
Relative wake options | Most | Some | Fewest |
Execution continues with variables intact | ✓ | ✓ |
To help decide which mode you should use, see Learn more about sleep modes.
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(WKP, RISING)
.duration(15min);
System.sleep(config);
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.network(NETWORK_INTERFACE_CELLULAR)
.flag(SystemSleepFlag::WAIT_CLOUD)
.duration(15min);
The SystemSleepMode::STOP
mode is the same as the classic stop sleep mode (pin or pin + time).
Wake Mode | Gen 2 | Gen 3 | Gen 4 |
---|---|---|---|
GPIO | ✓ | ✓ | ✓ |
Time (RTC) | ✓ | ✓ | ✓ |
Analog | ✓ | ✓ | |
Serial | ✓ | ✓ | |
BLE | ✓ | ||
Cellular | ✓ | ✓ | ✓ |
Wi-Fi | ✓ |
Typical power consumption in STOP sleep mode, based on the wakeup source:
Device | GPIO | RTC | Analog | Serial | BLE | Network |
---|---|---|---|---|---|---|
P2 | 579 uA | 572 uA | n/a | n/a | n/a | n/a |
M404 | 547 uA | 515 uA | n/a | n/a | n/a | n/a |
M524 | 421 uA | 415 uA | n/a | n/a | n/a | n/a |
T523 Eval | 872 uA | 873 uA | 852 uA | 840 uA | 919 uA | 21.5 mA |
T402 Eval | 807 uA | 835 uA | 831 uA | 798 uA | 858 uA | 17.2 mA |
Boron 2G/3G | 631 uA | 607 uA | 585 uA | 606 uA | 907 uA | 15.6 mA |
Boron LTE | 575 uA | 584 uA | 577 uA | 587 uA | 885 uA | 12.1 mA |
B402 SoM | 555 uA | 556 uA | 557 uA | 556 uA | 631 uA | 9.7 mA |
B523 SoM | 538 uA | 537 uA | 537 uA | 537 uA | 604 uA | 23.1 mA |
Argon | 396 uA | 398 uA | 398 uA | 397 uA | 441 uA | 22.2 mA |
Electron | 2.40 mA | 2.53 mA | 6.03 mA | 13.1 mA | n/a | 28.1 mA |
Photon | 2.75 mA | 2.82 mA | 7.56 mA | 18.2 mA | n/a | n/a |
SLEEP_NETWORK_STANDBY
.System.sleep()
command with all local and global variables intact.Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
On cellular devices, wake-on network can be enabled in STOP mode. This is recommended for any sleep duration of less than 10 minutes as it keeps the modem active while in sleep mode.
You should avoid powering off and on the cellular modem in periods of less than 10 minutes. Since the cellular modem needs to reconnect to the cellular network on wake, your mobile carrier may ban your SIM card from the network for aggressive reconnection if you reconnect more than approximately 6 times per hour.
The M-SoM does not support wake on network for cellular or Wi-Fi networks.
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
.gpio(D2, FALLING);
System.sleep(config);
Since 2.0.0:
The SystemSleepMode::ULTRA_LOW_POWER
mode is similar to STOP mode however internal peripherals such as GPIO, UART, ADC, and DAC are turned off. Like STOP mode, the RTC continues to run but since many more peripherals are disabled, the current used is closer to HIBERNATE. It is available in Device OS 2.0.0 and later.
Wake Mode | Gen 2 | Gen 3 | Gen 4 |
---|---|---|---|
GPIO | ✓ | ✓ | ✓ |
Time (RTC) | ✓ | ✓ | ✓ |
Analog | ✓ | ||
Serial | ✓ | ||
BLE | ✓ | ||
Cellular | ✓ | ✓ | |
Wi-Fi | ✓ |
Typical power consumption in ultra-low power (ULP) sleep mode, based on the wakeup source:
Device | GPIO | RTC | Analog | Serial | BLE | Network |
---|---|---|---|---|---|---|
P2 | 579 uA | 572 uA | n/a | n/a | n/a | n/a |
M404 | 547 uA | 515 uA | n/a | n/a | n/a | n/a |
M524 | 421 uA | 415 uA | n/a | n/a | n/a | n/a |
T523 Eval | 139 uA | 139 uA | 140 uA | 564 uA | 214 uA | 21.7 mA |
T402 Eval | 114 uA | 114 uA | 117 uA | 530 uA | 186 uA | 16.9 mA |
Boron 2G/3G | 171 uA | 174 uA | 178 uA | 610 uA | 494 uA | 16.4 mA |
Boron LTE | 127 uA | 128 uA | 130 uA | 584 uA | 442 uA | 14.2 mA |
B402 SoM | 48 uA | 47 uA | 48 uA | 557 uA | 130 uA | 9.5 mA |
B523 SoM | 54 uA | 55 uA | 56 uA | 537 uA | 139 uA | 22.8 mA |
Argon | 82 uA | 81 uA | 82 uA | 520 uA | 141 uA | 21.3 mA |
Electron | 2.42 mA | 2.55 mA | n/a | n/a | n/a | n/a |
Photon | 2.76 mA | 2.83 mA | n/a | n/a | n/a | n/a |
In this mode:
SLEEP_NETWORK_STANDBY
.System.sleep()
command with all local and global variables intact.Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
On Gen 3 cellular devices, wake-on network can be enabled in ultra-low power mode. This is recommended for any sleep duration of less than 10 minutes as it keeps the modem active while in sleep mode.
You should avoid powering off and on the cellular modem in periods of less than 10 minutes. Since the cellular modem needs to reconnect to the cellular network on wake, your mobile carrier may ban your SIM card from the network for aggressive reconnection if you reconnect more than approximately 6 times per hour.
The M-SoM does not support wake on network for cellular or Wi-Fi networks.
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
.gpio(WKP, RISING);
System.sleep(config);
The SystemSleepMode::HIBERNATE
mode is the similar to the classic SLEEP_MODE_DEEP
. It is the lowest power mode, however there are limited ways you can wake:
Wake Mode | Gen 2 | Gen 3 | Gen 4 |
---|---|---|---|
GPIO | WKP RISING Only | ✓ | WKP |
Time (RTC) | ✓ | 1 | ✓ |
Analog | ✓ |
1Tracker SoM can wake from RTC in HIBERNATE mode. Other Gen 3 devices cannot.
Typical power consumption in hibernate sleep mode, based on the wakeup source:
Device | GPIO | RTC |
---|---|---|
P2 | 114 uA | 115 uA |
M404 | 44 uA | 43 uA |
M524 | 26 uA | 27 uA |
T523 Eval | 103 uA | 95 uA |
T402 Eval | 103 uA | 95 uA |
Boron 2G/3G | 146 uA | n/a |
Boron LTE | 106 uA | n/a |
B402 SoM | 26 uA | n/a |
B523 SoM | 30 uA | n/a |
Argon | 65 uA | n/a |
Electron | 114 uA | 114 uA |
Photon | 114 uA | 114 uA |
In this mode on wake, device is reset, running setup() again.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On the Argon, Boron, B-Series SoM, and E404X you can only wake by pin, not by time, in HIBERNATE mode.
On the Tracker SoM, Tracker One, and Monitor One, you can wake by time from HIBERNATE mode using the hardware RTC (AM1805).
You can wake from HIBERNATE (SLEEP_MODE_DEEP) on any GPIO pin, on RISING, FALLING, or CHANGE, not just WKP/D8 with Device OS 2.0.0 and later on Gen 3 devices.
Since the difference in current consumption is so small between HIBERNATE and ULTRA_LOW_POWER, using ULTRA_LOW_POWER is a good alternative if you wish to wake based on time on Gen 3 devices. The difference is 106 uA vs. 127 uA on the Boron LTE, for example.
GPIO are kept on; OUTPUT pins retain their HIGH or LOW voltage level during sleep.
On Gen 3 devices the RTC does not run in HIBERNATE sleep and clock time and date will not be valid until the next cloud connection.
Gen 3 devices (Argon, Boron, B-Series SoM, E404X, Tracker SoM, Tracker One), can wake on analog voltage comparison.
P2 Devices (RTL872x):
The P2 does not support holding a GPIO in OUTPUT
mode when in HIBERNATE
mode. The pin will go into high impedance mode.
The P2 can only wake from HIBERNATE
mode on the pins listed below, RISING
, FALLING
, or CHANGE
.
On the P2, pins S4, S5, and S6 do not support pull-up or pull-down in HIBERNATE sleep mode. Use an external pull resistor if this is required.
On the P2, when waking from HIBERNATE
the RTC will be reset and clock time and date will not be valid until the next cloud connection.
The P2 does not support wake on analog, BLE, or UART serial.
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
30 | D10 / WKP | D10 GPIO, Serial 3 CTS, WKP. (Was WKP/A7 on P1.) | Pin can wake from HIBERNATE sleep | PA[15] |
33 | S6 / D21 | S6 GPIO, I2S WS. (Was P1S6/TESTMODE on P1.) | No internal pull up or pull down in HIBERNATE sleep mode. | PB[31] |
40 | S0 / D15 | S0 GPIO, PWM, SPI MOSI, Serial3 TX, I2S MCLK. (Was P1S0 on P1.) | Pin can wake from HIBERNATE sleep | PA[12] |
41 | S1 / D16 | S1 GPIO, PWM, SPI MISO, Serial3 RX. (Was P1S1 on P1.) | Pin can wake from HIBERNATE sleep | PA[13] |
42 | S2 / D17 | S2 GPIO, SPI SCK, Serial3 RTS. (Was P1S2 on P1.) | Pin can wake from HIBERNATE sleep | PA[14] |
45 | D2 | D2 GPIO, Serial2 RTS, SPI1 MOSI | Pin can wake from HIBERNATE sleep | PA[16] |
47 | S4 / D19 | S4 GPIO, I2S RX. (Was P1S4 on P1.) | No internal pull up or pull down in HIBERNATE sleep mode. | PA[0] |
51 | D3 | D3 GPIO, Serial2 CTS, SPI1 MISO | Pin can wake from HIBERNATE sleep | PA[17] |
52 | D4 | D4 GPIO, Serial2 TX, SPI1 SCK | Pin can wake from HIBERNATE sleep | PA[18] |
53 | D5 | D5 GPIO, Serial2 RX, SPI1 SS | Pin can wake from HIBERNATE sleep | PA[19] |
Photon 2 Devices (RTL872x):
The Photon 2es do not support holding a GPIO in OUTPUT
mode when in HIBERNATE
mode. The pin will go into high impedance mode.
The Photon 2 can only wake from HIBERNATE
mode on the pins listed below, RISING
, FALLING
, or CHANGE
.
On the Photon 2, pin D10 is in the same position as the Argon/Feather pin D8.
On the Photon 2, pin S4 is in the position of A4 on the Argon and other Feather devices. It does not support pull-up or pull-down in HIBERNATE sleep mode. Use an external pull resistor if this is required.
On the Photon 2, when waking from HIBERNATE
the RTC will be reset and clock time and date will not be valid until the next cloud connection.
The Photon 2 does not support wake on analog, BLE, or UART serial.
Pin Name | Description | Interface | MCU |
---|---|---|---|
D2 | D2 GPIO, Serial2 RTS, SPI1 MOSI | Pin can wake from HIBERNATE sleep | PA[16] |
D3 | D3 GPIO, Serial2 CTS, SPI1 MISO | Pin can wake from HIBERNATE sleep | PA[17] |
D4 | D4 GPIO, Serial2 TX, SPI1 SCK | Pin can wake from HIBERNATE sleep | PA[18] |
D5 | D5 GPIO, Serial2 RX, SPI1 SS | Pin can wake from HIBERNATE sleep | PA[19] |
D10 / WKP | D10 GPIO. Serial3 CTS, WKP. Was D8/WKP on Gen 3. | Pin can wake from HIBERNATE sleep | PA[15] |
MISO / D16 | D16 GPIO, S1 GPIO, PWM, SPI MISO, Serial3 RX. | Pin can wake from HIBERNATE sleep | PA[13] |
MOSI / D15 | D15 GPIO, S0 GPIO, PWM, SPI MOSI, Serial3 TX | Pin can wake from HIBERNATE sleep | PA[12] |
S4 / D19 | S4 GPIO, Was A4 on Gen 3. | No internal pull up or pull down in HIBERNATE sleep mode. | PA[0] |
SCK / D17 | SPI SCK, D13 GPIO, S3 GPIO, Serial3 RTS | Pin can wake from HIBERNATE sleep | PA[14] |
M-SoM Devices (RTL872x):
The M-SoM does not support holding a GPIO in OUTPUT
mode when in HIBERNATE
mode. The pin will go into high impedance mode.
The M-SoM can only wake from HIBERNATE
mode on the pins listed below, RISING
, FALLING
, or CHANGE
.
On the M-SoM, pin D21 does not support pull-up or pull-down in HIBERNATE sleep mode. Use an external pull resistor if this is required.
On the M-SoM, when waking from HIBERNATE
the RTC will be reset and clock time and date will not be valid until the next cloud connection.
The M-SoM does not support wake on analog, BLE, or UART serial.
Pin | Pin Name | Description | Interface | MCU |
---|---|---|---|---|
17 | D21 | D21 GPIO, I2S RX | No internal pull up or pull down in HIBERNATE sleep mode. | PA[0] |
36 | TX / D9 | Serial TX, PWM, GPIO, SPI1 MOSI, I2S MCLK | Pin can wake from HIBERNATE sleep | PA[12] |
38 | RX / D10 | Serial RX, PWM, GPIO, SPI1 MISO | Pin can wake from HIBERNATE sleep | PA[13] |
40 | D3 | D3 GPIO, Serial1 CTS flow control (optional), SPI1 SS | Pin can wake from HIBERNATE sleep | PA[15] |
42 | D2 | D2 GPIO, Serial RTS flow control (optional), SPI1 SCK | Pin can wake from HIBERNATE sleep | PA[14] |
47 | A7 / WKP | A7 Analog In, WKP, GPIO D28 | Pin can wake from HIBERNATE sleep | PA[20] |
48 | D8 | D8 GPIO, SPI SS | Pin can wake from HIBERNATE sleep | PA[19] |
50 | MISO / D11 | D11 GPIO, PWM, SPI MISO | Pin can wake from HIBERNATE sleep | PA[17] |
52 | MOSI / D12 | D12 GPIO, PWM, SPI MOSI | Pin can wake from HIBERNATE sleep | PA[16] |
54 | SCK / D13 | D13 GPIO, SPI SCK | Pin can wake from HIBERNATE sleep | PA[18] |
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Photon, P1, Electron, and E-Series you can only wake on time or WKP
RISING
in HIBERNATE
mode.
GPIO are put into high impedance state before sleep. However, you can use pinMode(INPUT)
to disconnect output pins on Gen 2 devices so the same code can be used for both Gen 2 and Gen 3 with HIBERNATE mode.
Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
// PROTOTYPES
SystemSleepConfiguration& duration(system_tick_t ms)
SystemSleepConfiguration& duration(std::chrono::milliseconds ms)
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
.gpio(WKP, RISING)
.duration(15min);
Specifies the sleep duration in milliseconds. Note that this is different than the classic API, which was in seconds.
You can also specify a value using chrono literals, for example: .duration(15min)
for 15 minutes.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
On the P2, Photon 2, and M-SoM, even though the parameter can be in milliseconds, the resolution is only in seconds, and the minimum sleep time is 1000 milliseconds. The maximum sleep duration is 546 minutes (approximately 9.1 hours).
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On the Argon, Boron, B-Series SoM you cannot wake from HIBERNATE mode by time because the nRF52 RTC does not run in HIBERNATE mode. You can only wake by pin. The maximum wake by time duration is approximately 6213 days in STOP and ULP mode.
On the Tracker SoM, even though it has an nRF52 processor, you can wake from HIBERNATE by time as it uses the AM1805 external watchdog/RTC to implement this feature.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
On the Photon, P1, Electron, and E-Series even though the parameter can be in milliseconds, the resolution is only in seconds, and the minimum sleep time is 1000 milliseconds.
Cellular Devices (B-Series SoM, Tracker SoM, Tracker One, Boron, E404X, E-Series, and Electron):
On cellular devices, if you turn off the cellular modem, you should not wake with a period of less than 10 minutes on average. Your mobile carrier may ban your SIM card from the network for aggressive reconnection if you reconnect more than approximately 6 times per hour. You can wake your device frequently if you do not reconnect to cellular every time. For example, you can wake, sample a sensor and save the value, then go to sleep and only connect to cellular and upload the data every 10 minutes. Or you can use cellular standby so cellular stays connected through sleep cycles and then you can sleep for short durations.
// PROTOTYPE
SystemSleepConfiguration& gpio(pin_t pin, InterruptMode mode)
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
.gpio(WKP, RISING);
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, RISING);
.gpio(D3, FALLING);
Specifies wake on pin. The mode is:
You can use .gpio()
multiple times to wake on any of multiple pins, with the limitations below.
If you are using RISING
mode, then an internal pull-down, equivalent to INPUT_PULLDOWN
is added to the pin before sleep. This is approximately 13K on Gen 3 devices and 40K on Gen 2 devices. It varies depending on the pin on the P2 and Photon 2. Never use a RISING
interrupt on an open-collector output that uses an external pull-up resistor, as the competing pull resistors can prevent the signal from ever going high.
If you are using FALLING
mode, then an internal pull-up, equivalent to INPUT_PULLUP
is added to the pin before sleep. This is approximately 13K on Gen 3 devices and 40K on Gen 2 devices. It varies depending on the pin on the P2 and Photon 2.
If you are using CHANGE
mode, then pull is removed prior to sleep, even if you explictly added it using pinMode()
and INPUT_PULLUP
or INPUT_PULLDOWN
. Make sure the pin is not left floating when using CHANGE
.
This pull can be an issue if you are connecting the wake pin to a voltage divider! Using CHANGE
mode does not add pull and can be used to work around this. There are pins on the P2 and Photon 2 that have 2.1K internal pull resistors that can easily overpower your voltage divider resistors.
System.sleep(SystemSleepConfiguration().mode(SystemSleepMode::STOP).gpio(GPS_INT, FALLING));
Since 3.0.0:
On the Tracker SoM, you can pass GPIO connected to the IO Expander directly to the GPIO sleep option in Device OS 3.0.0 and later.
Name | Description | Location |
---|---|---|
LOW_BAT_UC | Fuel Gauge Interrupt | IOEX 0.0 |
GPS_INT | u-blox GNSS interrupt | IOEX 0.7 |
WIFI_INT | ESP32 interrupt | IOEX 0.4 |
P2 and Photon 2 Devices (RTL872x):
HIBERNATE
mode on certain pins, RISING
, FALLING
, or CHANGE
. See hibernate sleep mode for more information.
M-SoM Devices (RTL872x):
HIBERNATE
mode on certain pins, RISING
, FALLING
, or CHANGE
. See hibernate sleep mode for more information.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
On Gen 3 devices the location of the WKP
pin varies, and it may make more sense to just use the actual pin name. You do not need to use WKP
to wake from HIBERNATE
on Gen 3 devices, and you can wake on either RISING, FALLING or CHANGE.
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
attachInterrupt
.SLEEP_MODE_DEEP
in the classic API defaults to allowing wake by WKP
rising. This is no longer automatic and you should specify it explicitly as in the example here if you want this behavior by adding .gpio(WKP, RISING)
.
// PROTOTYPE
SystemSleepConfiguration& flag(particle::EnumFlags<SystemSleepFlag> f)
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.network(NETWORK_INTERFACE_CELLULAR)
.flag(SystemSleepFlag::WAIT_CLOUD)
.duration(2min);
The only supported flag is:
SystemSleepFlag::WAIT_CLOUD
This will make sure all cloud messages have been acknowledged before going to sleep. Another way to accomplish this is to use graceful disconnect mode.
// PROTOTYPE
SystemSleepConfiguration& network(network_interface_t netif, EnumFlags<SystemSleepNetworkFlag> flags = SystemSleepNetworkFlag::NONE)
// EXAMPLE 1
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.duration(15min)
.network(NETWORK_INTERFACE_CELLULAR);
// EXAMPLE 2
SystemSleepConfiguration config;
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
.duration(15min)
.network(NETWORK_INTERFACE_CELLULAR, SystemSleepNetworkFlag::INACTIVE_STANDBY);
This option not only allows wake from network activity, but also keeps the network connected, making resume from sleep significantly faster. This is a superset of the SLEEP_NETWORK_STANDBY
feature. This should also be used with cellular devices with sleep periods of less than 10 minutes to prevent your SIM from being banned for aggressively reconnecting to the cellular network.
Network Wake Support | Gen 2 Wi-Fi | Gen 2 Cellular | Gen 3 (any) | Gen 4 (any) |
---|---|---|---|---|
Wake from STOP sleep | ✓ | ✓ | ||
Wake from ULTRA_LOW_POWER sleep | ✓ | |||
Wake from HIBERNATE sleep |
The first example configures the cellular modem to both stay awake and for the network to be a wake source. If incoming data, from a function call, variable request, subscribed event, or OTA request arrives, the device will wake from sleep mode.
The second example adds the SystemSleepNetworkFlag::INACTIVE_STANDBY
flag which keeps the cellular modem powered, but does not wake the MCU for received data. This is most similar to SLEEP_NETWORK_STANDBY
.
Note: You must not sleep longer than the keep-alive value, which by default is 23 minutes in order to wake on data received by cellular. The reason is that if data is not transmitted by the device before the keep-alive expires, the mobile network will remove the channel back to the device, so it can no longer receive data from the cloud. Fortunately in network sleep mode you can wake, transmit data, and go back to sleep in a very short period of time, under 2 seconds, to keep the connection alive without using significanly more battery power.
If you are waking on network activity, be sure to wait for Particle.connected()
to be true before entering sleep mode. If your device has not completely connected to the cloud, it will not be possible to wake from sleep by network activity.
If you use NETWORK_INTERFACE_CELLULAR
without INACTIVE_STANDBY
, then data from the cloud to the device (function, variable, subscribe, OTA) will wake the device from sleep. However if you sleep for less than the keep-alive length, you can wake up with zero additional overhead. This is offers the fastest wake time with the least data usage.
If you use INACTIVE_STANDBY
, the modem is kept powered, but the cloud is disconnected. This eliminates the need to go through a reconnection process to the cellular tower (blinking green) and prevents problems with aggressive reconnection. The device will not wake from sleep on functions, variables, or OTA. However, it also will cause the cloud to disconnect. The device will be marked offline in the console, and will go through a cloud session resumption on wake. This will result in the normal session negotiation and device vitals events at wake that are normally part of the blinking cyan phase.
For more information on using network sleep modes, see Learn more about sleep modes.
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
Wake on network is not supported on the P2, Photon 2, M-SoM.
// PROTOTYPE
SystemSleepConfiguration& analog(pin_t pin, uint16_t voltage, AnalogInterruptMode trig)
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.analog(A2, 1500, AnalogInterruptMode::BELOW);
Wake on an analog voltage compared to a reference value specified in millivolts. Can only be used on analog pins. Voltage is a maximum of 3.3V (3300 mV).
The AnalogInterruptMode
is one of:
voltage
.voltage
.volage
in either direction.Analog Wake Support | Gen 2 | Gen 3 | Gen 4 |
---|---|---|---|
Wake from STOP sleep | ✓ | ✓ | |
Wake from ULTRA_LOW_POWER sleep | ✓ | ||
Wake from HIBERNATE sleep |
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
Wake on analog is not supported on the P2, Photon 2, or M-SoM.
// PROTOTYPE
SystemSleepConfiguration& usart(const USARTSerial& serial)
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.usart(Serial1);
Wake from a hardware UART (USART). This can only be done with a hardware serial port; you cannot wake from the USB virtual serial port (Serial).
Note: Keeping the USART active in ultra-low power mode significanly increases the current used while sleeping.
USART Wake Support | Gen 2 | Gen 3 | Gen 4 |
---|---|---|---|
Wake from STOP sleep | ✓ | ✓ | |
Wake from ULTRA_LOW_POWER sleep | ✓ | ||
Wake from HIBERNATE sleep |
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
Wake on serial is not supported on the P2, Photon 2, or M-SoM.
// PROTOTYPE
SystemSleepConfiguration& ble()
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.duration(15min)
.ble();
Wake on Bluetooth LE data (BLE). Only available on Gen 3 platforms (Argon, Boron, B-Series SoM, and Tracker SoM).
In addition to Wake on BLE, this keeps the BLE subsystem activated so the nRF52 MCU can wake up briefly to:
This brief wake-up only services the radio. User firmware and Device OS do not resume execution if waking only to service the radio. If the radio receives incoming data or connection attempt packets, then the MCU completely wakes up in order to handle those events.
BLE Wake Support | Gen 2 | Gen 3 | Gen 4 |
---|---|---|---|
Wake from STOP sleep | ✓ | ||
Wake from ULTRA_LOW_POWER sleep | ✓ | ||
Wake from HIBERNATE sleep |
Gen 4 Devices (RTL872x) (P2, Photon 2, and M-SoM):
Wake on BLE is not supported on the P2, Photon 2, or M-SoM.
Since 1.5.0:
The SystemSleepResult
class is a superset of the older SleepResult
class and contains additional information when using System.sleep()
with the newer API.
// PROTOTYPE
SystemSleepWakeupReason wakeupReason() const;
// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, FALLING)
.duration(15min);
SystemSleepResult result = System.sleep(config);
if (result.wakeupReason() == SystemSleepWakeupReason::BY_GPIO) {
// Waken by pin
pin_t whichPin = result.wakeupPin();
}
Returns the reason for wake. Constants include:
Constant | Purpose |
---|---|
SystemSleepWakeupReason::UNKNOWN |
Unknown reason |
SystemSleepWakeupReason::BY_GPIO |
GPIO pin |
SystemSleepWakeupReason::BY_RTC |
Time-based |
SystemSleepWakeupReason::BY_LPCOMP |
Analog value |
SystemSleepWakeupReason::BY_USART |
Serial |
SystemSleepWakeupReason::BY_CAN |
CAN bus |
SystemSleepWakeupReason::BY_BLE |
BLE |
SystemSleepWakeupReason::BY_NETWORK |
Network (cellular or Wi-Fi) |
// PROTOTYPE
pin_t wakeupPin() const;
If wakeupReason()
is SystemSleepWakeupReason::BY_GPIO
returns which pin caused the wake. See example under wakeupReason()
, above.
// PROTOTYPE
system_error_t error() const;
If there was an error, returns the system error code. 0 is no error.
// PROTOTYPES
SleepResult toSleepResult();
operator SleepResult();
Returns the previous style of SleepResult
. There is also an operator to automatically convert to a SleepResult
.
In most sleep modes, GPIO outputs retain their HIGH or LOW GPIO output states. The exception is HIBERNATE on Gen 2 devices, where outputs go into a high-impedance state during sleep.
This can result in unexpected current usage, depending on your design. You should pinMode(INPUT)
to disconnect the GPIO if you do not want the OUTPUT driven during sleep mode to get the lower. While this is not necessary if you are using Gen 2 HIBERNATE mode, it does not hurt to do so, allowing the same code to be used for both Gen 2 and Gen 3.
Make sure the external device can handle the pin being disconnected. This may require an external pull-up or pull-down, or you can just drive the pin always at the expense of slightly increased power usage.
Sleep mode | Gen 2 | Gen 3 | P2/Photon 2 |
---|---|---|---|
STOP | Preserved | Preserved | Preserved |
ULTRA_LOW_POWER | Preserved | Preserved | Preserved |
HIBERNATE | High-Z | Preserved | Preserved |
This API is the previous API for sleep and is less flexible. You should use the newer sleep APIs with Device OS 1.5.0 and later.
Using the current sleep APIs is recommended on all cellular devices. The legacy sleep API does not wait for the cellular modem to power off before going into sleep mode, which may leave the modem on or in a partially on state. The newer sleep APIs correct this. It will not be fixed in the legacy sleep APIs.
System.sleep()
can be used to dramatically improve the battery life of a Particle-powered project. There are several variations of System.sleep()
based on which arguments are passed.
Gen 3 Devices (nRF52) (B-Series SoM, Tracker SoM, Tracker One, Boron, Argon, and E404X):
Gen 3 devices (Argon, Boron, B-Series SoM, Tracker SoM) only support sleep modes in 0.9.0 and later.
On the Argon, Boron, and Xenon, WKP is pin D8.
On the B-Series SoM, WKP is pin A7 in Device OS 1.3.1 and later. In prior versions, it was D8.
On the Tracker SoM WKP is pin A7/D7.
System.sleep(SLEEP_MODE_DEEP)
can be used to put the entire device into a deep sleep mode, sometimes referred to as "standby sleep mode." It is not possible to specify a wake time in seconds using SLEEP_MODE_DEEP
on Gen 3 devices, however you can use timed wake with ULTRA_LOW_POWER
sleep mode.
// SYNTAX
System.sleep(SLEEP_MODE_DEEP);
// EXAMPLE USAGE
// Put the device into deep sleep until wakened by D8.
System.sleep(SLEEP_MODE_DEEP);
// The device LED will shut off during deep sleep
On the Boron, B-Series SoM, and Tracker SoM it is not useful to combine SLEEP_MODE_DEEP
and SLEEP_NETWORK_STANDBY
as the modem will remain on, but also be reset when the device resets, eliminating any advantage of using SLEEP_NETWORK_STANDBY
.
The Gen 3 devices (Argon, Boron, Xenon) can only wake from SLEEP_MODE_DEEP by a high level on D8. It's not possible to exit SLEEP_MODE_DEEP based on time because the clock does not run in standby sleep mode on the nRF52. It's possible wake from HIBERNATE mode using other pins; you should use the newer sleep API that supports HIBERNATE if possible.
Also, the real-time-clock (Time class) will not be set when waking up from SLEEP_MODE_DEEP or HIBERNATE. It will get set on after the first cloud connection, but initially it will not be set.
System.sleep(SLEEP_MODE_SOFTPOWEROFF)
is just like SLEEP_MODE_DEEP
, with the added benefit that it also sleeps the Fuel Gauge. This is the only way to achieve the lowest quiescent current on the device, apart from sleeping the Fuel Gauge before calling SLEEP_MODE_DEEP
.
// SYNTAX
System.sleep(SLEEP_MODE_SOFTPOWEROFF);
Gen 2 Devices (STM32) (E-Series, Electron, Photon, and P2; does not include E404X):
Gen 2 devices (Photon, P1, Electron, E-Series):
System.sleep(SLEEP_MODE_DEEP, long seconds)
can be used to put the entire device into a deep sleep mode, sometimes referred to as "standby sleep mode."
// SYNTAX
System.sleep(SLEEP_MODE_DEEP, long seconds);
// EXAMPLE USAGE
// Put the device into deep sleep for 60 seconds
System.sleep(SLEEP_MODE_DEEP, 60);
// The device LED will shut off during deep sleep
// Since 0.8.0
// Put the device into deep sleep for 60 seconds and disable WKP pin
System.sleep(SLEEP_MODE_DEEP, 60, SLEEP_DISABLE_WKP_PIN);
// The device LED will shut off during deep sleep
// The device will not wake up if a rising edge signal is applied to WKP
Note: Be sure WKP is LOW before going into SLEEP_MODE_DEEP with a time interval! If WKP is high, even if it falls and rises again the device will not wake up. Additionally, the time limit will not wake the device either, and the device will stay in sleep mode until reset or power cycled.
Since 1.5.0:
You can also specify a value using chrono literals, for example: System.sleep(SLEEP_MODE_DEEP, 2min)
for 2 minutes.
The device will automatically wake up after the specified number of seconds or by applying a rising edge signal to the WKP pin.
Since 0.8.0:
Wake up by WKP pin may be disabled by passing SLEEP_DISABLE_WKP_PIN
option to System.sleep()
: System.sleep(SLEEP_MODE_DEEP, long seconds, SLEEP_DISABLE_WKP_PIN)
.
System.sleep(SLEEP_MODE_SOFTPOWEROFF, long seconds)
is just like SLEEP_MODE_DEEP
, with the added benefit that it also sleeps the Fuel Gauge. This is the only way to achieve the lowest quiescent current on the device, apart from sleeping the Fuel Gauge before calling SLEEP_MODE_DEEP
. This is also the same net result as used in the user-activated Soft Power Down feature when you double-tap the Mode button and the Electron powers down.
// SYNTAX
System.sleep(SLEEP_MODE_SOFTPOWEROFF, long seconds);
In this particular mode, the device shuts down the network subsystem and puts the microcontroller in a standby mode.
When the device awakens from deep sleep, it will reset and run all user code from the beginning with no values being maintained in memory from before the deep sleep.
The standby mode is used to achieve the lowest power consumption. After entering standby mode, the RAM and register contents are lost except for retained memory.
For cellular devices, reconnecting to cellular after SLEEP_MODE_DEEP
will generally use more power than using SLEEP_NETWORK_STANDBY
for periods less than 15 minutes. You should definitely avoid using SLEEP_MODE_DEEP
on cellular devices for periods less than 10 minutes. Your SIM can be blocked by your mobile carrier for aggressive reconnection if you reconnect to cellular very frequently.
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode)
can be used to put the entire device into a stop mode with wakeup on interrupt. In this particular mode, the device shuts down the network and puts the microcontroller in a stop mode with configurable wakeup pin and edge triggered interrupt. When the specific interrupt arrives, the device awakens from stop mode.
The device will not reset before going into stop mode so all the application variables are preserved after waking up from this mode. This mode achieves the lowest power consumption while retaining the contents of RAM and registers.
// SYNTAX
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode);
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode, SLEEP_NETWORK_STANDBY);
// EXAMPLE USAGE
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 pin
System.sleep(D1,RISING);
// The device LED will shut off during sleep
The Electron and Boron maintain the cellular connection for the duration of the sleep when SLEEP_NETWORK_STANDBY
is given as the last parameter value. On wakeup, the device is able to reconnect to the cloud much quicker, at the expense of increased power consumption during sleep. Roughly speaking, for sleep periods of less than 15 minutes, SLEEP_NETWORK_STANDBY
uses less power.
For sleep periods of less than 10 minutes you must use SLEEP_NETWORK_STANDBY
. Your SIM can be blocked by your mobile carrier for aggressive reconnection if you reconnect to cellular very frequently. Using SLEEP_NETWORK_STANDBY
keeps the connection up and prevents your SIM from being blocked.
Parameters:
wakeUpPin
: the wakeup pin number. supports external interrupts on the following pins:attachInterrupt
applyedgeTriggerMode
: defines when the interrupt should be triggered. Three constants are predefined as valid values:SLEEP_NETWORK_STANDBY
: optional - keeps the cellular modem in a standby state while the device is sleeping..The device will automatically reconnect to the cloud if the cloud was connected when sleep was entered. If disconnected prior to sleep, it will stay disconnected on wake.
Since 0.8.0:
// SYNTAX
System.sleep(std::initializer_list<pin_t> wakeUpPins, InterruptMode edgeTriggerMode);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, InterruptMode edgeTriggerMode);
System.sleep(std::initializer_list<pin_t> wakeUpPins, std::initializer_list<InterruptMode> edgeTriggerModes);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, const InterruptMode* edgeTriggerModes, size_t edgeTriggerModesCount);
System.sleep(std::initializer_list<pin_t> wakeUpPins, InterruptMode edgeTriggerMode, SLEEP_NETWORK_STANDBY);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, InterruptMode edgeTriggerMode, SLEEP_NETWORK_STANDBY);
System.sleep(std::initializer_list<pin_t> wakeUpPins, std::initializer_list<InterruptMode> edgeTriggerModes, SLEEP_NETWORK_STANDBY);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, const InterruptMode* edgeTriggerModes, size_t edgeTriggerModesCount, SLEEP_NETWORK_STANDBY);
// EXAMPLE USAGE
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and A4 pins
// Specify the pins in-place (using std::initializer_list)
System.sleep({D1, A4}, RISING);
// The device LED will shut off during sleep
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and FALLING edge interrupt on A4 pin
// Specify the pins and edge trigger mode in-place (using std::initializer_list)
System.sleep({D1, A4}, {RISING, FALLING});
// The device LED will shut off during sleep
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and A4 pins
// Specify the pins in an array
pin_t wakeUpPins[2] = {D1, A4};
System.sleep(wakeUpPins, 2, RISING);
// The device LED will shut off during sleep
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and FALLING edge interrupt on A4 pin
// Specify the pins and edge trigger modes in an array
pin_t wakeUpPins[2] = {D1, A4};
InterruptMode edgeTriggerModes[2] = {RISING, FALLING};
System.sleep(wakeUpPins, 2, edgeTriggerModes, 2);
// The device LED will shut off during sleep
Multiple wakeup pins may be specified for this mode.
Parameters:
wakeUpPins
: a list of wakeup pins:
std::initializer_list<pin_t>
: e.g. {D1, D2, D3}
pin_t
array. The length of the array needs to be provided in wakeUpPinsCount
argumentattachInterrupt
applywakeUpPinsCount
: the length of the list of wakeup pins provided in wakeUpPins
argument. This argument should only be specified if wakeUpPins
is an array of pins and not an std::initializer_list
.
edgeTriggerMode
: defines when the interrupt should be triggered. Three constants are predefined as valid values:edgeTriggerModes
: defines when the interrupt should be triggered on a specific pin from wakeUpPins
list:std::initializer_list<InterruptMode>
: e.g. {RISING, FALLING, CHANGE}
InterruptMode
array. The length of the array needs to be provided in edgeTriggerModesCount
argumentedgeTriggerModesCount
: the length of the edge trigger modes provided in edgeTriggerModes
argument. This argument should only be specified if edgeTriggerModes
is an array of modes and not an std::initializer_list
.SLEEP_NETWORK_STANDBY
: optional - keeps the cellular modem in a standby state while the device is sleeping..// SYNTAX
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode, long seconds);
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode, SLEEP_NETWORK_STANDBY, long seconds);
// EXAMPLE USAGE
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 pin or wakeup after 60 seconds whichever comes first
System.sleep(D1,RISING,60);
// The device LED will shut off during sleep
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode, long seconds)
can be used to put the entire device into a stop mode with wakeup on interrupt or wakeup after specified seconds. In this particular mode, the device shuts network subsystem and puts the microcontroller in a stop mode with configurable wakeup pin and edge triggered interrupt or wakeup after the specified seconds. When the specific interrupt arrives or upon reaching the configured timeout, the device awakens from stop mode. The device will not reset before going into stop mode so all the application variables are preserved after waking up from this mode. The voltage regulator is put in low-power mode. This mode achieves the lowest power consumption while retaining the contents of RAM and registers.
Parameters:
wakeUpPin
: the wakeup pin number. supports external interrupts on the following pins:attachInterrupt
applyedgeTriggerMode
: defines when the interrupt should be triggered. Three constants are predefined as valid values:seconds
: wakeup after the specified number of seconds (0 = no alarm is set). On Gen 3 devices, the maximum sleep time is approximately 24 days.SLEEP_NETWORK_STANDBY
: optional - keeps the cellular modem in a standby state while the device is sleeping..Since 1.5.0:
You can also specify a value using chrono literals, for example: System.sleep(D1, RISING, 2min)
for 2 minutes.
Since 0.8.0:
// SYNTAX
System.sleep(std::initializer_list<pin_t> wakeUpPins, InterruptMode edgeTriggerMode, long seconds);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, InterruptMode edgeTriggerMode, long seconds);
System.sleep(std::initializer_list<pin_t> wakeUpPins, std::initializer_list<InterruptMode> edgeTriggerModes, long seconds);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, const InterruptMode* edgeTriggerModes, size_t edgeTriggerModesCount, long seconds);
System.sleep(std::initializer_list<pin_t> wakeUpPins, InterruptMode edgeTriggerMode, SLEEP_NETWORK_STANDBY, long seconds);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, InterruptMode edgeTriggerMode, SLEEP_NETWORK_STANDBY, long seconds);
System.sleep(std::initializer_list<pin_t> wakeUpPins, std::initializer_list<InterruptMode> edgeTriggerModes, SLEEP_NETWORK_STANDBY, long seconds);
System.sleep(const pin_t* wakeUpPins, size_t wakeUpPinsCount, const InterruptMode* edgeTriggerModes, size_t edgeTriggerModesCount, SLEEP_NETWORK_STANDBY, long seconds);
// EXAMPLE USAGE
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and A4 pins or wakeup after 60 seconds whichever comes first
// Specify the pins in-place (using std::initializer_list)
System.sleep({D1, A4}, RISING, 60);
// The device LED will shut off during sleep
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and FALLING edge interrupt on A4 pin or wakeup after 60 seconds whichever comes first
// Specify the pins and edge trigger mode in-place (using std::initializer_list)
System.sleep({D1, A4}, {RISING, FALLING}, 60);
// The device LED will shut off during sleep
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and A4 pins or wakeup after 60 seconds whichever comes first
// Specify the pins in an array
pin_t wakeUpPins[2] = {D1, A4};
System.sleep(wakeUpPins, 2, RISING, 60);
// The device LED will shut off during sleep
// Put the device into stop mode with wakeup using RISING edge interrupt on D1 and FALLING edge interrupt on A4 pin or wakeup after 60 seconds whichever comes first
// Specify the pins and edge trigger modes in an array
pin_t wakeUpPins[2] = {D1, A4};
InterruptMode edgeTriggerModes[2] = {RISING, FALLING};
System.sleep(wakeUpPins, 2, edgeTriggerModes, 2, 60);
// The device LED will shut off during sleep
Multiple wakeup pins may be specified for this mode.
Parameters:
wakeUpPins
: a list of wakeup pins:std::initializer_list<pin_t>
: e.g. {D1, D2, D3}
pin_t
array. The length of the array needs to be provided in wakeUpPinsCount
argumentattachInterrupt
applywakeUpPinsCount
: the length of the list of wakeup pins provided in wakeUpPins
argument. This argument should only be specified if wakeUpPins
is an array of pins and not an std::initializer_list
.edgeTriggerMode
: defines when the interrupt should be triggered. Three constants are predefined as valid values:edgeTriggerModes
: defines when the interrupt should be triggered on a specific pin from wakeUpPins
list:std::initializer_list<InterruptMode>
: e.g. {RISING, FALLING, CHANGE}
InterruptMode
array. The length of the array needs to be provided in edgeTriggerModesCount
argumentedgeTriggerModesCount
: the length of the edge trigger modes provided in edgeTriggerModes
argument. This argument should only be specified if edgeTriggerModes
is an array of modes and not an std::initializer_list
.seconds
: wakeup after the specified number of seconds (0 = no alarm is set). On Gen 3 devices, the maximum sleep time is approximately 24 days.SLEEP_NETWORK_STANDBY
: optional - keeps the cellular modem in a standby state while the device is sleeping..Since 0.4.5. The state of the network and Cloud connections is restored when the system wakes up from sleep. So if the device was connected to the cloud before sleeping, then the cloud connection is automatically resumed on waking up.
Since 0.5.0 In automatic modes, the sleep()
function doesn't return until the cloud connection has been established. This means that application code can use the cloud connection as soon as sleep()
returns. In previous versions, it was necessary to call Particle.process()
to have the cloud reconnected by the system in the background.
Since 0.8.0 All System.sleep()
variants return an instance of SleepResult
class that can be queried on the result of System.sleep()
execution.
Since 0.8.0 An application may check the information about the latest sleep by using System.sleepResult()
or additional accessor methods:
System.wakeUpReason()
System.wokenUpByPin()
System.wokenUpByRtc()
System.wakeUpPin()
System.sleepError()
System.sleep(long seconds)
does NOT stop the execution of application code (non-blocking call). Application code will continue running while the network module is in this mode.
This mode is not recommended; it is better to manually control the network connection using SYSTEM_MODE(SEMI_AUTOMATIC) instead.
// SYNTAX
System.sleep(long seconds);
// EXAMPLE USAGE
// Put the Wi-Fi module in standby (low power) for 5 seconds
System.sleep(5);
// The device LED will breathe white during sleep
Since 1.5.0:
You can also specify a value using chrono literals, for example: System.sleep(2min)
for 2 minutes.
Some common sleep commands:
SLEEP_MODE_DEEP
wake by WKP
:// CLASSIC
System.sleep(SLEEP_MODE_DEEP);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
.gpio(WKP, RISING);
SystemSleepResult result = System.sleep(config);
SLEEP_MODE_DEEP
wake by WKP
or time:// CLASSIC
System.sleep(SLEEP_MODE_DEEP, 60);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
.gpio(WKP, RISING)
.duration(60s);
SystemSleepResult result = System.sleep(config);
SLEEP_MODE_DEEP
wake by time only (disable WKP):// CLASSIC
System.sleep(SLEEP_MODE_DEEP, 60, SLEEP_DISABLE_WKP_PIN);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
.duration(60s);
SystemSleepResult result = System.sleep(config);
// CLASSIC
System.sleep(D2, RISING);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, RISING);
SystemSleepResult result = System.sleep(config);
// CLASSIC
System.sleep(D2, FALLING, 30);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, FALLING)
.duration(30s);
SystemSleepResult result = System.sleep(config);
// CLASSIC
System.sleep({D2, D3}, RISING);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, RISING);
.gpio(D3, RISING);
SystemSleepResult result = System.sleep(config);
// CLASSIC
System.sleep(D2, RISING, SLEEP_NETWORK_STANDBY);
// NEW
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(D2, RISING)
.duration(30s)
.network(NETWORK_INTERFACE_CELLULAR, SystemSleepNetworkFlag::INACTIVE_STANDBY);
SystemSleepResult result = System.sleep(config);
Since 0.8.0:
This class allows to query the information about the most recent System.sleep()
. It is only recommended for use in Device OS 0.8.0 - 1.4.4. There is a newer, more flexible class SystemSleepResult
in 1.5.0 and later.
// SYNTAX
SleepResult result = System.sleepResult();
int reason = result.reason();
Get the wake up reason.
// EXAMPLE
SleepResult result = System.sleepResult();
switch (result.reason()) {
case WAKEUP_REASON_NONE: {
Log.info("did not wake up from sleep");
break;
}
case WAKEUP_REASON_PIN: {
Log.info("was woken up by a pin");
break;
}
case WAKEUP_REASON_RTC: {
Log.info("was woken up by the RTC (after a specified number of seconds)");
break;
}
case WAKEUP_REASON_PIN_OR_RTC: {
Log.info("was woken up by either a pin or the RTC (after a specified number of seconds)");
break;
}
}
Returns a code describing a reason the device woke up from sleep. The following reasons are defined:
WAKEUP_REASON_NONE
: did not wake up from sleepWAKEUP_REASON_PIN
: was woken up by an edge signal to a pinWAKEUP_REASON_RTC
: was woken up by the RTC (after a specified number of seconds)WAKEUP_REASON_PIN_OR_RTC
: was woken up either by an edge signal to a pin or by the RTC (after a specified number of seconds)// SYNTAX
SleepResult result = System.sleepResult();
bool r = result.wokenUpByPin();
// EXAMPLE
SleepResult result = System.sleepResult();
if (result.wokenUpByPin()) {
Log.info("was woken up by a pin");
}
Returns true
when the device was woken up by a pin.
Returns true
when the device was woken up by the RTC (after a specified number of seconds).
// SYNTAX
SleepResult result = System.sleepResult();
bool r = result.wokenUpByRtc();
// EXAMPLE
SleepResult result = System.sleepResult();
if (result.wokenUpByRtc()) {
Log.info("was woken up by the RTC (after a specified number of seconds)");
}
An alias to wokenUpByRtc()
.
// SYNTAX
SleepResult result = System.sleepResult();
pin_t pin = result.pin();
// EXAMPLE
SleepResult result = System.sleepResult();
pin_t pin = result.pin();
if (result.wokenUpByPin()) {
Log.info("was woken up by the pin number %d", pin);
}
Returns: the number of the pin that woke the device.
Get the error code of the latest sleep.
// SYNTAX
SleepResult result = System.sleepResult();
int err = result.error();
Returns: SYSTEM_ERROR_NONE (0)
when there was no error during latest sleep or a non-zero error code.
Since 0.8.0:
// SYNTAX
SleepResult result = System.sleepResult();
Retrieves the information about the latest sleep.
Returns: an instance of SleepResult
class.
Since 0.8.0:
// SYNTAX
int reason = System.wakeUpReason();
See SleepResult
documentation.
Since 0.8.0:
// SYNTAX
bool result = System.wokenUpByPin();
See SleepResult
documentation.
Since 0.8.0
// SYNTAX
bool result = System.wokenUpByRtc();
See SleepResult
documentation.
Since 0.8.0:
// SYNTAX
pin_t pin = System.wakeUpPin();
See SleepResult
documentation.
Since 0.8.0:
// SYNTAX
int err = System.sleepError();
See SleepResult
documentation.
Since 0.4.9:
System events are messages sent by the system and received by application code. They inform the application about changes in the system, such as when the system has entered setup mode (listening mode, blinking dark blue), or when an Over-the-Air (OTA) update starts, or when the system is about to reset.
System events are received by the application by registering a handler. The handler has this general format:
void handler(system_event_t event, int data, void* moredata);
Unused parameters can be removed from right to left, giving these additional function signatures:
void handler(system_event_t event, int data);
void handler(system_event_t event);
void handler();
Here's an example of an application that listens for reset
events so that the application is notified the device is about to reset. The application publishes a reset message to the cloud and turns off connected equipment before returning from the handler, allowing the device to reset.
void reset_handler()
{
// turn off the crankenspitzen
digitalWrite(D6, LOW);
// tell the world what we are doing
Particle.publish("reset", "going down for reboot NOW!");
}
void setup()
{
// register the reset handler
System.on(reset, reset_handler);
}
The button handlers may run as an interrupt service routine (ISR). There are limitations on what calls you can make from
an ISR, see attachInterrupt for more information. Logging calls such as Log.info()
do not work from the button handler as it's called from an interrupt service routine (ISR) and logging cannot be done from an interrupt context.
Some event types provide additional information. For example the button_click
event provides a parameter with the number of button clicks:
void button_clicked(system_event_t event, int param)
{
int times = system_button_clicks(param);
Log.info("button was clicked %d times", times);
}
It's possible to subscribe to multiple events with the same handler in cases where you want the same handler to be notified for all the events. For example:
void handle_all_the_events(system_event_t event, int param)
{
Log.info("got event %d with value %d", (int)event, param);
}
void setup()
{
// listen for Wi-Fi Listen events and Firmware Update events
System.on(wifi_listen+firmware_update, handle_all_the_events);
}
To subscribe to all events, there is the placeholder all_events
:
void setup()
{
// listen for network events and firmware update events
System.on(all_events, handle_all_the_events);
}
These are the system events produced by the system, their numeric value (what you will see when printing the system event to Serial) and details of how to handle the parameter value. The version of firmware these events became available is noted in the first column below.
Setup mode is also referred to as listening mode (blinking dark blue).
Since | Event Name | ID | Description | Parameter |
---|---|---|---|---|
setup_begin | 2 | signals the device has entered setup mode | not used | |
setup_update | 4 | periodic event signaling the device is still in setup mode. | milliseconds since setup mode was started | |
setup_end | 8 | signals setup mode was exited | time in ms since setup mode was started | |
network_credentials | 16 | network credentials were changed | network_credentials_added or network_credentials_cleared |
|
0.6.1 | network_status | 32 | network connection status | see below |
0.6.1 | cloud_status | 64 | cloud connection status | see below |
button_status | 128 | button pressed or released | the duration in ms the button was pressed: 0 when pressed, >0 on release. | |
firmware_update | 256 | firmware update status | see below | |
firmware_update_pending | 512 | notifies the application that a firmware update is available. This event is sent even when updates are disabled, giving the application chance to re-enable firmware updates with System.enableUpdates() |
not used | |
reset_pending | 1024 | notifies the application that the system would like to reset. This event is sent even when resets are disabled, giving the application chance to re-enable resets with System.enableReset() |
not used | |
reset | 2048 | notifies that the system will reset once the application has completed handling this event | not used | |
button_click | 4096 | event sent each time SETUP/MODE button is clicked. | int clicks = system_button_clicks(param); retrieves the number of clicks so far. |
|
button_final_click | 8192 | sent after a run of one or more clicks not followed by additional clicks. Unlike the button_click event, the button_final_click event is sent once, at the end of a series of clicks. |
int clicks = system_button_clicks(param); retrieves the number of times the button was pushed. |
|
0.6.1 | time_changed | 16384 | device time changed | time_changed_manually or time_changed_sync |
0.6.1 | low_battery | 32768 | generated when low battery condition is detected. | not used |
0.8.0 | out_of_memory | 1<<18 | event generated when a request for memory could not be satisfied | the amount in bytes of memory that could not be allocated |
3.3.0 | ble_prov_mode | 1<<19 | BLE provisioning mode status |
network_credentials_added
(1)network_credentials_cleared
(2)network_status_powering_off
(2)network_status_off
(3)network_status_powering_on
(4)network_status_on
(5)network_status_connecting
(6)network_status_connected
(7)network_status_disconnecting
(10)network_status_disconnected
(11)cloud_status_disconnected
(0)cloud_status_connecting
(1)cloud_status_handshake
(2)cloud_status_session_resume
(3)cloud_status_connected
(8)cloud_status_disconnecting
(9)firmware_update_begin
(0)firmware_update_complete
(1)firmware_update_progress
(2)firmware_update_failed
(-1)time_changed_manually
(0)time_changed_sync
(1)ble_prov_mode_connected
(0)ble_prov_mode_disconnected
(1)ble_prov_mode_handshake_failed
(2)ble_prov_mode_handshake_done
(3)System modes help you control how the device manages the connection with the cloud.
By default, the device connects to the Cloud and processes messages automatically. However there are many cases where a user will want to take control over that connection. There are three available system modes: AUTOMATIC
, SEMI_AUTOMATIC
, and MANUAL
. These modes describe how connectivity is handled.
These system modes describe how connectivity is handled and when user code is run.
System modes must be called before the setup() function. By default, the device is always in AUTOMATIC
mode.
The automatic mode (threading disabled) of connectivity (with threading disabled) provides the default behavior of the device, which is that:
SYSTEM_MODE(AUTOMATIC);
void setup() {
// This won't be called until the device is connected to the cloud
}
void loop() {
// Neither will this
}
Particle.process()
.Particle.process()
is also called during any delay() of at least 1 second.Particle.process()
manually.SYSTEM_MODE(AUTOMATIC)
does not need to be called, because it is the default state; however the user can invoke this method to make the mode explicit.SYSTEM_MODE(AUTOMATIC);
SYSTEM_THREAD(ENABLED);
void setup() {
// This is called even before being cloud connected
}
void loop() {
// This is too
}
When also using SYSTEM_THREAD(ENABLED)
, the following are true even in AUTOMATIC
mode:
Using SYSTEM_THREAD(ENABLED)
is recommended. With Device OS 6.2.0 and later, system thread is always enabled. For additional information, see non-threaded system mode.
The semi-automatic mode will not attempt to connect the device to the Cloud automatically, but once you connect it automatically handles reconnection. One common use case for this is checking the battery charge before connecting to cellular on the Boron, B-Series SoM, Electron, and E-Series.
This code checks the battery level, and if low, goes back into sleep mode instead of trying to connect with a low battery, which is likely to fail.
The combination of SEMI_AUTOMATIC
and threading enabled is a recommended combination; it's what
the Tracker Edge firmware uses.
SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
void setup() {
float batterySoc = System.batteryCharge();
if (batterySoc < 5.0) {
// Battery is very low, go back to sleep immediately
SystemSleepConfiguration config;
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
.duration(30min);
System.sleep(config);
return;
}
// Do normal things here
Particle.connect();
}
void loop() {
}
There are many hidden pitfalls when using MANUAL
system mode with cloud connectivity, and it may interact unexpectedly with threading.
The only recommended use case for MANUAL
mode is when you have no cloud connectivity at all, such as when you are using the device with no
cloud connectivity at all. For example, if you are only using BLE or NFC.
SYSTEM_MODE(MANUAL);
void setup() {
// This will run automatically
}
void loop() {
if (buttonIsPressed()) {
Particle.connect();
}
if (Particle.connected()) {
doOtherStuff();
}
Particle.process();
}
When using manual mode:
Particle.connect()
, the device will attempt to begin the connection process.Particle.connected()
== true
), the user must call Particle.process()
regularly to handle incoming messages and keep the connection alive. The more frequently Particle.process()
is called, the more responsive the device will be to incoming messages.