How to create a library

I have been downloading and installing libraries , my question is how does one goes about creating a library.
Let's say a library that blinks an LED , you just need to put the time delay.

Also , when I open libraries I see some files with c programming syntaxes.
Pls can someone demystify libraries in Arduino for me

Your post was MOVED to its current location as it is more suitable

A delay would be about the last thing you would want to put into a library

The difference between C and C++ can be subtle

Can you explain what libraries are and how to go about creating one.
Also what skill /knowledge should I have to create a library

A library is simply a means of implementing a common functionality (like a driver for a sensor, or a set of mathematical or logical functions) in a separate set of source files, such that they can be simply integrated into new projects.

They can be implemented in many different ways, as classes or a simple collection of functions.

Pls what are classes
Functions are callable group of code but classes, I don't know

Classes are collections of code and data.
Understanding then is fundamental to C++.

So I do need to take tutorials on classes in c++

How about writing a library that allows you blink an LED ,
When you install the library in the Arduino sketch ,you just need to put the required delay to make the led blink

Yes, I think that would be a good place to start.

I found this tutorial to be a useful introduction to writing Arduino libraries:

https://docs.arduino.cc/hacking/software/LibraryTutorial

The libraries can be loaded into a sketch where they can be viewed, where you can see how others created such and such library.

When you are first starting out developing a library you can start with a sketch. Since you want your library to blink an LED, let's start with the BlinkWithoutDelay example. You can find the full source under File->Examples->02.Digital->BlinkWithoutDelay so I am removing most of the comments for compactness.

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000; 

void setup() {
  pinMode(ledPin, OUTPUT);
}
void loop()
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

The next step is to move the guts of the library behavior into a function. This sketch does exactly the same thing as before.

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;

void setup()
{
  pinMode(ledPin, OUTPUT);
}

void blink(unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

void loop()
{
  blink(1000);
}

Now comes the 'library' split. You start but creating a new tab. At the right end of the bar just above the sketch is a little triangle in a square. Click on that and select "New Tab". name the new tab "BlinkLib.cpp". Now cut the 'blink' function out of the main tab and paste it into the new tab.

If you try to Verify your sketch now you will find some errors. The first is:

BlinkLib.cpp:3:33: error: 'millis' was not declared in this scope
   unsigned long currentMillis = millis();
                                 ^~~~~~

This is because, unlike a '.ino' file '.cpp' files don't get
#include <Arduino.h>
automatically inserted at compile time. Add that line to the top of BlinkLib.cpp and do another Verify. The next error is:

BlinkLib.cpp:6:23: error: 'previousMillis' was not declared in this scope
   if (currentMillis - previousMillis >= interval)
                       ^~~~~~~~~~~~~~

This is because that declaration didn't get moved to the library. In this case, only the library needs it so move that declaration to the top of BlinkLib.cpp. While you're at it, move 'ledState' and 'interval', too. Then try another Verify.

Now you get the error:

sketch_jan05a:13:3: error: 'blink' was not declared in this scope
   blink(1000);
   ^~~~~

That's because the main sketch no longer contains a declaration for the blink() function. This is a good time to create another tab that shares the library declarations with the sketch that uses the library. Create a new tab named BlinkLib.h (h for 'header').
In that new tab, put the line:
void blink(unsigned long);
Then in the main sketch, add the line:
#include "BlinkLib.h"
This will allow the main sketch to see a declaration of "blink". The compiler will know what to do.

That leaves you with the next error:

BlinkLib.cpp:21:18: error: 'ledPin' was not declared in this scope
     digitalWrite(ledPin, ledState);
                  ^~~~~~

We used 'ledPin' in setup() so we left the declaration there, but now BlinkLib.cpp needs the declaration. We need a way for setup() to set the pin mode so we create another function in BlinkLib.cpp:

void setPin(int pin)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);
}

Put the declaration into BlinkLib.h:
void setPin(int);
Move the declaration of 'ledPin' into BlinkLib.cpp. Now you can have setup() call setPin().

Alltogether you have:
Main Sketch:

#include "BlinkLib.h"

void setup()
{
  setPin(LED_BUILTIN);
}

void loop()
{
  blink(1000);
}

Library header:

void blink(unsigned long);
void setPin(int);

Library source:

#include <Arduino.h>
#include "BlinkLib.h"

int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000;
int ledPin =  LED_BUILTIN;

void setPin(int pin)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);
}

void blink(unsigned long)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}
4 Likes

Now that you have BlinkLib.h and BlinkLib.cpp you can move them into libraries/BlinkLib and your sketch will still compile. You can then move your sketch to libraries/BlinkLib/examples/test/test.ino and it will show up in the IDE under: File->Examples->BlinkLib->test

1 Like

One problem with the library is that you can only blink one LED because the library only has one set of global variables. To fix that, we 'encapsulate' the functionality of the library into an 'object'. You can then make multiple 'instances' of the object, each with its own variables.

Let's create an object class named 'Blinker'. This goes into BlinkLib.h in place of the shared function prototypes. Note that now BlinkLib.h needs some definitions from Arduino.h ('byte', 'LOW', 'pinMode'...) so we include Arduino.h at the top.

#include <Arduino.h>

class Blinker
{
  public:
    Blinker(const byte pin) : pin(pin) 
    {
      ledState = LOW; 
      previousMillis = 0;
    }

    void begin()
    {
      pinMode(pin, OUTPUT);
      previousMillis = millis();
    }

    void blink(const unsigned long interval);

  private:
    const byte pin;
    int ledState;
    unsigned long previousMillis;
};

The "setPin()" functionality can be done in the 'constructor' so we specify the pin when we create each instance. For a global instance (defined outside a function) it is not safe to call Arduino functions in the constructor so we add a "begin()" method to be called to do any Arduino calls needed for setup.

The BlinkLib.cpp file now contains definitions for whatever instance functions are not defined in the class declaration.

#include "BlinkLib.h"

void Blinker::blink(const unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(pin, ledState);
  }
}

The test sketch doesn't have to change much. It sets the pin in the declaration of the instance, calls .begin() for each instance in setup(), and calls the .blink() method repeatedly in loop(), just like the old blink() function but there can be more of them.

#include "BlinkLib.h"

Blinker blinker(LED_BUILTIN);  // Blink the built-in LED
Blinker blinker2(9);  // Blink the LED on Pin 9

void setup()
{
  blinker.begin();
  blinker2.begin();
}

void loop()
{
  blinker.blink(1000);
  blinker2.blink(750);
}
3 Likes

Let us write non-object codes first and then object-based codes to turn-ON Air Pump-1 of Fig-1 when the TS-1’s temperature goes above 30.50 degC. The Air Pump-1 will turn-OFF when temperature goes below 30.50 degC. TS-1 (Temperature Sensor-1) is embedded within the motor winding of Air Pump-1 to monitor over-heating. In the course of performing this task, we will come across the techniques of creating Library.


Figure-1:

1(a) Let us create a "non-object version" sketch to operate one-pump/on-sensor setup as per diagram of Fig-1. (Note that I have avoided saying "non-class version" as class is a keyword which we use to create user-defined data type" from which we create objects on which we apply methods to do some works like pump-on/pump-off.)

(b) Upload the following non-object based sketch (LD1 is used to simulate relay-K1) into UNO.

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup()
{
  Serial.begin(9600);
  sensors.begin();
}
void loop()
{ 
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0);
  Serial.print("Temperature for the device 1 (index 0) is: ");
  Serial.println(tempC, 2);
  if(tempC >= 30.50)  //test temperature
  {
    digitalWrite(2, HIGH);  //Air Pump-1/LD1 is ON
  }
  else
  {
    digitalWrite(2, LOW);  //Air Pump-1/LD1 is OFF
  }
  delay(1000);
}

(c) Rub the temp sensor by fingers and check that LD1 is ON when temperature goes belwo 30.50 degC (test temperature).

(2) Convert the sketch of Step-(1)(b) using objects.
(a) Create a "user-defined data type (UDT)" named AirPump using class keyword. The "user-defined data type" or simply "data type" refers to a conglomerate structure that contains variables (data) and functions/methods to process those data.)

class AirPump
{
   //------------------------
};

We use “class keyword” to declare a User Defined Data Type (UDT) named AirPump.

(b) From the data type named AirPump, let us create an object named pump1. The “pump1" refers to Air Pump-1 (Fig-1) which turns ON and turns OFF based on temperature supplied by TS-1 of Fig-1.

AirPump  pump1; 

(c) Let us name a method as pumpON() that we wish to apply on the object named pump1 to turn on Air Pump-1 of Fig-1 when temperature (supplied by TS-1) goes below 24.50 degC (test purpose).

(d) Lat us name another method as pumpOFF() that we wish to apply on the object named pump1 to turn off Air Pump-1 of Fig-1 when temperature (supplied by TS-1) goes above 24.50 degC (test value).

(e) We may now populate the the empty structure of Step-4 as follows:

class AirPump    //class is a keyword; we user it to get UDT (User Defined Data Type) = AirPump
{
     private:
     int   relay1Pin;                       //this is DPin-2 of Fig-1
     
     public:
     AirPump(int DPin):relay1Pin(DPin){}   //constructor that always comes with data type
     void   varSetup();                                        //function to intialize variables
     void   pumpON();                                 //function/method to turn on Air Pump-1
     void   pumpOFF();                               //function/methd to turn off Air Pump-1 
};

The “class" keyword helps to declare data structure with private, public, and protected keywords in which only the functions under “public" keyword can access the variables under “private" keyword.

(f) Let us define the functions of the structure of Step-(2)(e).
(i) varSetup()

void AirPump::varSetup()  //:: = Scope Operator
{
     pinMode(relay1Pin, OUTPUT);       //direction of DPin-2 is output 
}

(ii) pumpON()

void AirPump::pumpON()
{
      digitalWrite(relay1Pin, HIGH);     //Air Pump-1/LD1 is ON
}

(iii) pumpOFF()

void AirPump::pumpOFF()
{
      digitalWrite(relay1Pin, LOW);       //Air Pump-1 id OFF
}

(3) Create a Header File using Notepad application and save as AirPump.h. This header file will contain the following declarations/structure.

class AirPump	//class = keyword ; AirPump = User-defined data type (UDT) or className or classTag
{
     private:
     int   relay1Pin;                       //this is DPin-2 of Fig-1
     
     public:
     AirPump(int DPin):relay1Pin(DPin){}   //constructor that always comes with data type
     void   varSetup();                                        //function to intialize variables
     void   pumpON();                                 //function/method to turn on Air Pump-1
     void   pumpOFF();                               //function/methd to turn off Air Pump-1 
};

(4) Create a C++ File using Notepad application and save as AirPump.cpp. This file will contain the following lines.

#include<AirPump.h>
#include<Arduino.h>

void AirPump::varSetup()
{
  pinMode(relay1Pin, OUTPUT);      //direction set
}

void AirPump::pumpON()
{
  digitalWrite(relay1Pin, HIGH);
}
void AirPump::pumpOFF()
{
  digitalWrite(relay1Pin, LOW);
}

(5) Make a ZIF File for AirPump.h and AirPump.cpp. It will be saved as AirPump.zip.

(6) Open Arduino IDE and create the following sketch/source code and save as AirPump. This is the final sketch. Include the AirPump.zip file in the IDE and sketch.

#include <AirPump.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);//-----------------
DallasTemperature sensors(&oneWire);

AirPump pump1(2);       
//we create object named pump1 from AirPump constructor DPin-2 = relay1Pin;

void setup()
{
  Serial.begin(9600);
  pump1.varSetup();  //pin direction set for relay1Pin object pump1(2) is called upon with DPin-2
  //-------------
  sensors.begin();        //DS18B20 sensor is enabled
}

void loop()
{
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0);
  Serial.println(tempC, 2);
  if (tempC >= 30.50)
  {
    pump1.pumpON();		//Air Pump-1 is ON
  }
  else
  {
    pump1.pumpOFF();      //Air Pump-1 is OFF
  }
  delay(1000);			//test interval
}

(7) Upload the sketch of Step-6 into UNO.

(8) Check that the Serial Monitor shows room temperature.

(9) Rub temperature sensor by fingers and check that LD1 becomes ON/OFF accordingly.

2 Likes

Thanks a lot. Do you have a YouTube channel or a course where I can learn more.

The write up seems resourceful but I can barely understand what you were saying

Like anything, you will get better with practice. I tried to explain each step and why it was needed. Is there any particular step you don't understand?

I took some time to merge the explanation into one long message and added a little detail. Here it is again:

When you are first starting out developing a library you can start with a simple demonstration sketch that shows what you want the library to do. Let's make a library that blinks an LED at a specified rate. We can start with something like File->Examples->02.Digital->BlinkWithoutDelay (I am removing most of the comments for compactness.)

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000; 

void setup() 
{
  pinMode(ledPin, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();
  
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

The next step is to move the part you want the library to do into one or more functions. This sketch will do exactly the same thing as before but with very little in setup() and loop() and most code in a function:

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;

void setup()
{
  pinMode(ledPin, OUTPUT);
}

void blink(unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

void loop()
{
  blink(1000);
}

Now comes the 'library' split. You start by creating a new tab. At the right end of the bar just above the sketch is a little triangle in a square. Click on that and select "New Tab". Name the new tab "BlinkLib.cpp". Now cut the 'blink' function out of the main tab and paste it into the new tab.

If you try to Verify your sketch now you will find some errors. The first is:

BlinkLib.cpp:3:33: error: 'millis' was not declared in this scope
   unsigned long currentMillis = millis();
                                 ^~~~~~

This is because, unlike the '.ino' files, the '.cpp' files don't automatically get
#include <Arduino.h>
inserted at compile time. Add that line to the top of BlinkLib.cpp and do another Verify.

The next error is:

BlinkLib.cpp:6:23: error: 'previousMillis' was not declared in this scope
   if (currentMillis - previousMillis >= interval)
                       ^~~~~~~~~~~~~~

This is because that global variable declaration didn't get moved to the library. In this case, only the library needs it so move the declaration to the top of BlinkLib.cpp. While you're at it, move 'ledState' and 'interval', too. Then try another Verify.

Now you get the error:

sketch_jan05a:13:3: error: 'blink' was not declared in this scope
   blink(1000);
   ^~~~~

That's because the main sketch no longer contains a declaration for the blink() function. This is a good time to create another tab to share declarations between the library and the sketch. Create a new tab named BlinkLib.h (h for 'header'). In that new tab, put the line:
void blink(unsigned long);
Then in the main sketch, add the line:
#include "BlinkLib.h"
This will allow the main sketch to see a declaration of "blink()".

That leads you to the next error:

BlinkLib.cpp:21:18: error: 'ledPin' was not declared in this scope
     digitalWrite(ledPin, ledState);
                  ^~~~~~

We used 'ledPin' in setup() so we left the declaration there, but now BlinkLib.cpp needs the declaration. We need a way for setup() to set the pin mode so we create another function in BlinkLib.cpp:

void setPin(int pin)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);
}

Put the declaration of setPin() into BlinkLib.h so the sketch can use it:
void setPin(int);
Move the declaration of 'ledPin' into BlinkLib.cpp. Now you can have setup() call setPin().

All-together you have:
Main Sketch:

#include "BlinkLib.h"

void setup()
{
  setPin(LED_BUILTIN);
}

void loop()
{
  blink(1000);
}

Library header:

void blink(unsigned long);
void setPin(int);

Library source:

#include <Arduino.h>
#include "BlinkLib.h"

int ledState = LOW;
unsigned long previousMillis = 0;
int ledPin =  LED_BUILTIN;

void setPin(int pin)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);
}

void blink(unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

Your library is complete! Now you can move the files out of the sketch directory and into a library folder. In your sketchbook directory (see Preferences if you don't know where that is) find (or create) a subdirectory names 'libraries'. That is where all your third-party (not built-in) libraries are kept. In the 'libraries' directory, create a directory named BlinkLib and move BlinkLib.h and BlinkLib.cpp to there.

Now you can delete those two tabs from your sketch and the sketch will still Verify! The compiler will find the BlinkLib directory and make those files part of the Verify process.

Now you can turn your sketch into a library example. Move the .ino file to libraries/BlinkLib/examples/test/test.ino. It will show up in the IDE under: File->Examples->BlinkLib->test

Ah, but there is one small problem. Now you want to blink multiple LEDs at different rates! There is only one "ledPin" variable so if you call "setPin()" a second time, the first pin number is replaced by the second pin number, and blink() will only blink the second pin. Also, the "ledState" and "previousMillis" globals are shared.

To fix that, we are going to 'encapsulate' the functionality of the library into an 'object class'. You can then make multiple 'instances' of the class, each with its own variables.

Let's create an object class named 'Blinker'. This goes into BlinkLib.h in place of the shared function prototypes.

class Blinker
{
  public:
    Blinker(const byte pin);  // "Constructor" called when the object is defined
    
    void blink(const unsigned long interval);

  private:
    const byte ledPin;
    int ledState;
    unsigned long previousMillis;
};

Now we can change the test sketch to blink two LEDs:

#include "BlinkLib.h"

Blinker blinker(LED_BUILTIN);  // Blink the built-in LED
Blinker blinker2(9);  // Blink the LED on Pin 9

void setup()
{
}

void loop()
{
  blinker.blink(1000);
  blinker2.blink(750);
}

We can try to Verify the test sketch but we have not defined the functions ("member functions" or "methods") declared in the class yet. We get undefined reference errors for Blinker::Blinker() (the constructor) and Blinker::blink() (the function that does stuff!). The second one is easy to fix because we just have to change the declaration of 'blink' from:
void blink(unsigned long interval)
to
void Blinker::blink(unsigned long interval)
That tells the compiler that we are not defining just a function, but a function that is a member ("member function" or "method") of the object class "Blinker". Nothing else about 'blink()' has to be changed because the blink() "method" is perfectly happy to use the "member variables" of its class in place of the old globals. We can remove the globals and the setPin() function from BlinkLib.cpp because they are no longer used:

#include <Arduino.h>
#include "BlinkLib.h"

void Blinker::blink(unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

Now that "Blinker::blink()" is taken care of, we need to define Blinker::Blinker(). This is the "constructor": the function automatically called when an object of this class (an "instance") is created ("instantiated"). It is a good place to do some initialization. We can put this function in BlinkLib.cpp:

Blinker::Blinker(const byte pin)
{
  ledPin = pin;
  ledState = LOW; 
  previousMillis = 0;
  
  pinMode(ledPin, OUTPUT);
  previousMillis = millis();
}

The only problem is, "ledPin" is declared with the keyword 'const' so it can't be assigned a value inside the constructor. It has to be given a value BEFORE the body of the constructor so we have to use some special syntax:

Blinker::Blinker(const byte pin) : ledPin(pin)
{
  ledState = LOW; 
  previousMillis = 0;
  
  pinMode(ledPin, OUTPUT);
  previousMillis = millis();
}

Yay! The sketch compiles without any errors or warnings!

Except for one thing. A thing the compiler can't warn you about because it doesn't know. The Arduino library isn't initialized until just before your setup() function is called. That is AFTER all of the global variables are initialized. That includes calling the constructor of every global object instance. In other words, your object class can't safely call any Arduino functions (like "pinMode()" or "millis()") in the constructor! You may have wondered why some Arduino library objects have a 'begin()' method that you have to call from setup(). That's why.

To make our object safe to use we add a 'begin()' method (the name is just an Arduino convention) and call the Arduino functions from there.

void Blinker::begin()
{
  pinMode(ledPin, OUTPUT);
  previousMillis = millis();
}

That goes in BlinkLib.cpp and we call it in setup().

We have to add the "void begin();" declaration to the class (in BlinkLib.h) and we're ready to go!

All-together you have:
Main Sketch:

#include "BlinkLib.h"

Blinker blinker(LED_BUILTIN);  // Blink the built-in LED
Blinker blinker2(9);  // Blink the LED on Pin 9

void setup()
{
  blinker.begin();
  blinker2.begin();
}

void loop()
{
  blinker.blink(1000);
  blinker2.blink(750);
}

Library header:

class Blinker
{
  public:
    Blinker(const byte pin);  // "Constructor" called when the object is defined
    void begin(); // A function to call to do initialization

    void blink(const unsigned long interval);

  private:
    const byte ledPin;
    int ledState;
    unsigned long previousMillis;
};

Library source:

#include <Arduino.h>
#include "BlinkLib.h"

Blinker::Blinker(const byte pin) : ledPin(pin)
{
  ledState = LOW;
  previousMillis = 0;
}

void Blinker::begin()
{
  pinMode(ledPin, OUTPUT);
  previousMillis = millis();
}

void Blinker::blink(unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}
1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.