Raspberry Pi: Control WS2812B (NeoPixels) With Bluetooth LE

Raspberry Pi: Control WS2812B (NeoPixels) With Bluetooth LE

by ACROBOTIC Industries

Overview

Time to complete: 10–15min; Level of difficulty: Beginner

In this tutorial we’ll install and use the Bluetooth Linux stack Bluez to read and write data voer Bluetooth LE to and from a nearby device. We’ll run the Node.js program Bleno to simulate a peripheral device that will receive and transmit data from and to the Pi!

List of Materials

  • 1 x Raspberry Pi 3 Model B
  • 1 x 5V USB Power Supply
  • 1 x 8GB MicroSD Card
  • 1 x USB Bluetooth 4.0 Adapter
  • 1 x RFduino Essentials Kit
  • 1 x 8mm PL9823-F8 (NeoPixels) RGB LED
  • 1 x Premium Jumper Wires Male/Male
  • 1 x MicroSD USB Adapter (Optional)

Overview of Bluetooth Low Energy

Bluetooth Low Energy (aka BLE/Bluetooth 4.0/Bluetooth Smart) is the most recent incarnation of Bluetooth technology developed by the Bluetooth SIG (the organization that maintains the specification). This communication protocol is designed for applications where data needs to be transferred in small amounts at relatively low speed while consuming low amounts of power (e.g., heart rate monitor, step counter, wireless keyboard). This latest version of the protocol is not compatible with its predecessor (Bluetooth classic), as an upside, long gone are the days where pairing devices was necessary!

The goal of this tutorial is to demonstrate how you can setup your Raspberry Pi to control RGB LEDs from a Bluetooth Low Energy (BLE) device nearby. Specifically, we'll be using the RFduino microcontroller, which has a built-in BLE module. We'll easily program the microcontroller to listen to messages sent by the Pi and control the LED color accordingly.

Let's get started!


System Overview

In order to control an RGB LED wirelessly we'll be using a few different hardware and software components that we'll outline here.

System overview for BLE-controlled RGB LED with Raspberry Pi and RFduino

Raspberry Pi

We'll be making use of a Raspberry Pi, a single-board computer running the Raspbian distribution of the Linux Operating System. Because the Pi doesn't have a native BLE interface, we'll connect a USB Bluetooth LE adapter to it. A script that we'll write using the Python programming language will let us send commands BLE via the USB adapter. The commands sent over BLE will be received by a separate microcontroller via its own BLE interface (RFduino).

The list of components that will be used on this side of things is:

  • Raspberry Pi (hardware)
  • USB BLE Adapter (hardware)
  • Raspbian (software; Operating System)
  • Bluez Bluetooth stack for Linux (software; libraries, tools)
  • Python Script (software; user code)
RFduino

To control an RGB LED we'll use the RFduino microcontroller connected to it on a solderless breadboard. The tiny-sized RFduino can be programmed over USB from any computer capable of running the Arduino IDE. In this case we'll use a Mac laptop to write code in C++ using version 1.6.4 of the Arduino IDE. We'll use the same IDE to upload the code to the RFduino. Once the code is running on the RFduino we'll go back to the Pi and run the Python script to change the color of the RGB LED.

The list of components that will be used on this side of things is:

  • RFduino (hardware)
  • RGB LED (hardware)
  • Arduino IDE (software; program)
  • C++ firmare (software; user code)

Configuring your Raspberry Pi to use Bluetooth LE

Currently, Bluetooth Low Energy (BLE) is not well supported by the standard Raspberry Pi distributions, thus some additional work is required to get it working. We've put together a comprehensive guid on how to get started in a previous tutorial, check it out!


Configuring the RFduino in the Arduino IDE

After configuring the Raspberry Pi to use BLE, we're ready to load a program to our RFduino. But, first we need to prepare our computer to recognize the RFduino boards.

At the time of this writing the following instructions have been tested in the latest version of the Arduino IDE (1.6.5). With your RFduino disconnected from the computer where you'll be using the Arduino IDE to program the RFduino.

Configuring Arduino IDE preferences for using RFduino

  • Navigate to Tools → Board → Board Manager and scroll down to RFduino Boards by RFduino. Click and select install the RFduino library.

Arduino IDE boards manager during RFduino install

  • Double check that "RFduino" is one of the entries now under Tools → Board

Arduino IDE Boards List during RFduino installation

  • Connect the RFduino DIP Module to the RFduino USB Shield by matching the Pinouts. Then proceed to connect it to the computer's USB port.
  • Restart the Arduino IDE and the connected RFduino should be available under Tools → Port otherwise you may need to install the FTDI drivers for your Operating System.

Having trouble? Consult the comprehensive Quick Start guide by the RFduino creators.

Now that we're able to program the RFduino, we can write the code that we'll upload to it!


Firmware for the RFduino: Receiving Messages

The code for this step can be found in our RFduino Demos repository (https://github.com/acrobotic/Ai_Demos_RFduino/) inside the receiving_messages folder.  The goal for the receiving_messages.ino program is to allow the RFduino to receive data from a BLE-capable device.  To understand a little better how this occurs, the program shows how data sent from the Raspberry Pi over BLE is received by the RFduino.  With the RFduino still connected to our computers, we can go ahead and upload this program to it.

As with every program written in the Arduino IDE, we want to define the setup() and loop() functions.  The first thing we set up is the data to be advertised, in this case "My BLE LED".  Then, we set the RFduino to idle so that it remains in a low-power state while a message is received.

void setup() {
  // this is the data we want to appear in the advertisement
  RFduinoBLE.advertisementData = "My BLE LED";
  // Start the BLE stack
  RFduinoBLE.begin();
}

void loop() {
  // Switch to lower power mode
  RFduino_ULPDelay(INFINITE);
}

Then, we want to make use of 3 functions provided by the RFduino BLE stack that will allow us to handle the different events we need.  In particular, we have one that gets called when a device connects to the RFduino (RFduinoBLE_onConnect()), another for when data is received (RFduinoBLE_onReceive()), and one other for when a device that was connected closes the connection with the RFduino (RFduinoBLE_onDisconnect()).  There are quite a few functions that comprise the RFduino BLE stack, so have a look at the documentation for additional details!

The use of the three functions is straight forward, so for gaining some familiarity with them, let's make them print a debug message by calling the print() method os the Serial obeject:

void RFduinoBLE_onConnect() {
  // Debug message printed to Serial interface
  Serial.println("RFduino connected");
}

void RFduinoBLE_onDisconnect() { 
  // Debug message printed to Serial interface
  Serial.println("RFduino disconnected");
}

void RFduinoBLE_onReceive(char *data, int len) {
  // Debug message printed to Serial interface
  Serial.println("Data received: ");
  for(int i=0;i<len;i++)
    Serial.print(data[i]);
    Serial.println();
    Serial.println(data);
}

Now that we've examined the code, we're ready to upload it to our RFduino. To do this, connect the RFduino DIP Module and the RFduino USB Shield. Then proceed to connect the connected boards to the USB port of your computer.

After uploading this simple program to the RFduino, we can test the communication from/to the Raspberry Pi!


Writing Data Interactively with Gatttool (Raspberry Pi)

With the Raspberry Pi and RFduino ready to go, we can go ahead and open a Terminal Window on the Pi and make sure that the RFduino is powered.

The first thing we do is to scan for nearby BLE devices by entering the command:

sudo hcitool lescan

We'll see that the output will contain an entry for our RFduino, in our case it reads:

DC:E2:F4:0B:3A:03 My BLE LED

Where the first set of alphanumeric (hex) characters corresponds to the MAC address of our RFduino, and the string "My BLE LED" is the device name we assigned to it. Now that we have the MAC address, we can use it to write data to the RFduino.

For this step we want to open a Serial Monitor window on the computer where the RFduino is connected. This will allow us to see the debug statements we programmed. Next, we go back to the Terminal Window on the Pi and enter:

sudo gatttool -b DC:E2:F4:0B:3A:03 -t random -I

Note: remember to change the Mac address for your own, and that the last letter of the command is a capital "i".

As seen in our previous tutorial, this allows us to send/receive commands interactively. The next steps are to issue the commands to connect, and write data to the RFduino while keeping an eye on the Serial Monitor:

[DC:E2:F4:0B:3A:03][LE]> connect

Note: After entering the command "connect" you should see a "Connection successful" message on the Terminal Window and a "RFduino connected" on the Serial Monitor.

Next, we can send some data to the characteristic handle 0x0011 where the RFduino listens to incoming messages:

[DC:E2:F4:0B:3A:03][LE]> char-write-req 0x0011 5B48454C4C4F5D

Sending message over BLE from a Raspberry Pi using gatttool

If we look at the Serial Monitor we can see how the messages are received:

Serial Monitor output of BLE test message received by RFduino

This shows that the character array data has a length equal to the number of bytes we send. In our case, the first byte is "5B" whose decimal equivalent is "91" and corresponds to the character "[". Our second byte is "48" whose decimal equivalent is "72" and corresponds to the character "H".

Knowing this, we can now start sending commands to control the brightness and color of an RGB LED!


Building and Testing an RGB LED Circuit with RFduino

We are now ready to wire a very simple circuit using our RGB LED. Unlike regular RGB LEDs where each pin is physically connected to either the positive or negative terminal of a Red, Green, and LED, these PL9823-F8 (aka NeoPixels) are 'smart' LEDs that contain additional circuitry inside allowing them to receive and send data using a custom 1-wire communication protocol. For this reason we wire it a little differently than to what you may be accustomed, so double-check the wiring diagram!

Wiring digram for RGB LED connected to an RFduino

First, make sure to disconnect the RFduino Module and Shield from the USB port of the computer! Then proceed to follow the wiring diagram. Once its done, go ahead and connect it to the computer running the Arduino IDE and ensure that everything is in order as described in the previous step.

Note: once you apply power to the RFduino you might see the RGB LED turn on to a blue-ish color; this is ok!

Wiring of RGB LED connected to an RFduino

Second, we need to download a library that will allow us to use these LEDs with the RFduino board. We've written our own specifically for the RFduino, which is based on the wonderful NeoPixels library written by Adafruit.

The library is available at https://github.com/acrobotic/Ai_RFDlib_WS2812/ and if you're not familiar with Git/Github, simply click download on the bottom right side of the page.

Download RFduino library for controlling the RGB LED

Then, you can simply use the Arduino IDE to import the downloaded zipped library by navigating to Sketch → Include Library → Add .ZIP Library... and selecting the recently downloaded file Ai_RFDlib_WS2812-master.zip. Once this is done go open the rainbow example by navigating to File → Examples → Ai_RFDlib_WS2812-master → rainbow, and upload it to the RFduino.

Feel free to tweak the rainbow program to experiment with the RGB LED a bit. In the next step we'll be loading the code that will not only receive the messages from the Raspberry Pi, but also set the brightness of the LED!


Writing BLE Data Directly from the Terminal (Raspberry Pi)

With the circuit built and tested we're ready to start sending RGB LED brightness/color data from the Raspberry Pi. The first thing to do is ensure that our RFduino is ready to receive data to control the RGB LED.  For this we connect the RFduino to our computer and upload the program ble_led_control.ino using the Arduino IDE.

Once the code is loaded to the RFduino, we can go back to our Raspberry Pi and use the command gatttool that we were using before. However, instead of using the interactive mode, we'll enter complete commands directly. For instance, in order to turn on the Red and Blue LEDs to maximum brightness we use the command:

sudo gatttool -b DC:E2:F4:0B:3A:03 -t random --char-write-req -a 0x0011 -n FF00FF

We're familiar with the first part of the command (sudo gatttool -b DC:E2:F4:0B:3A:03 -t random) as this is what we were using before, however we've gotten rid of the -I switch, which is what started the interactive prompt. Instead, we are writing the --char-write-req directly using the same handle as before 0x0011. The only thing that has changed is the last value.

Blue and Red LEDs turned on from the Raspberry Pi over BLE

On a previous step we saw how the data is sent in bytes, which made it a little tricky when we needed to send character values as we needed to send their HEX representation instead. In this case, we simply want to send 3 values ranging from 0 to 255 that will be used by the RFduino to set the brightness of each (i.e., Red, Green, Blue) LED.

Fortunately, we can use a single byte to represent each value, for example, to send a 0 we'd use the byte 0x00, for 10 we'd use 0x0A, for 255 we'd use 0xFF, and so on.  Thus, to send the RGB data so that the Red and the Blue LEDs are set to maximum brightness, we simply send the 3 bytes FF, 00, and FF. This particular combination should result in a purple/pink color displayed by the RGB LED!

In our final step we'll describe a simple way of making our Raspberry Pi change the colors programmatically using Python!


Writing BLE Data Programmatically in Python (Raspberry Pi)

In this last step we'll write a script in Python to use the program gatttool programmatically. That means that we'll structure our script in a way that will allow us to use gatttool without having to repeatedly enter commands in the Terminal.

The script is fairly straight-forward, and can be expanded by you to display different animations of the RGB LED. With the RFduino powered and running the same firmware as in the previous step, we go to our Raspberry Pi and open a Text Editor (e.g., LeafpadNano).

We'll use it to create our script. We start by entering our function definitions:

MAC = "DC:E2:F4:0B:3A:03"
CHAR = 0x0011

def initialize():
    commands.getoutput('hciconfig hci0 down')
    commands.getoutput('hciconfig hci0 up')
    print commands.getoutput('hciconfig -a')
    commands.getoutput('killall hcitool')

The initialize()function takes care of configuring the BLE interface. Although we did this already in a previous step, it's not bad to ensure it's available. This function will print the output of the command hciconfig to the Terminal Window's standard output in which the script runs.

def sendColor(c):
    command = 'sudo gatttool -t random -b {0} --char-write-req -a {1} -n {2}'.format(MAC, CHAR, c)
    p = subprocess.Popen(command.split(), shell=False)
    print p.communicate()[0]

The sendColor function takes a 3-byte string and sends it to the device with mac address MAC to the characteristic handle CHAR. You'll need to replace these values with your own values at the beginning of the script.

if __name__ == '__main__':
    initialize()
    sendColor('FF0000')
    time.sleep(1)
    sendColor('00FF00')
    time.sleep(1)
    sendColor('0000FF')
    time.sleep(1)
    sendColor('000000')

Lastly, when the script is ran it will run the initialize() function once, and then set the color to full-brightness Red, wait 1 second, full-brightness Green, wait 1 second, full-brightness Blue, wait 1 second, and then send a 'no color' (OFF) command. If everything is in place, the RFduino should show the following behavior:

Leave a comment

  • Please note, comments must be approved before they are published

$1 Days
$2 Hours
$3 Minutes
$4 Second
{ "en":{ "general": { "field": { "required": "Required", "actions": "Actions", "top_btn": "Top" }, "accessibility": { "skip_to_content": "Skip to content", "close_modal": "Close (esc)" }, "meta": { "tags": "Tagged \"[[ tags ]]\"", "page": "Page [[ page ]]" }, "404": { "title": "404 Page Not Found", "subtext": "The page you requested does not exist.", "link": "Continue shopping" }, "pagination": { "previous": "Previous", "next": "Next", "current_page": "Page [[ current ]] of [[ total ]]" }, "password_page": { "opening_soon": "Opening Soon", "login_form_heading": "Enter store using password", "login_form_password_label": "Password", "login_form_password_placeholder": "Your password", "login_form_submit": "Enter", "signup_form_email_label": "Email", "signup_form_success": "We will send you an email right before we open!", "admin_link_html": "Are you the store owner? Log in here<\/a>", "password_link": "Enter using password", "powered_by_shopify_html": "This shop will be powered by [[ shopify ]]" }, "social": { "share_on_facebook": "Share", "share_on_twitter": "Tweet", "share_on_pinterest": "Pin it", "alt_text": { "share_on_facebook": "Share on Facebook", "share_on_twitter": "Tweet on Twitter", "share_on_pinterest": "Pin on Pinterest" } }, "search": { "no_results_html": "Your search for \"[[ terms ]]\" did not yield any results.", "results_with_count": { "one": "[[ count ]] result for \"[[ terms ]]\"", "other": "[[ count ]] results for \"[[ terms ]]\"" }, "title": "Search our site", "placeholder": "Search", "submit": "Submit", "close": "Close search" }, "newsletter_form": { "newsletter_email": "Join our mailing list", "email_placeholder": "Email address", "confirmation": "Thanks for subscribing", "submit": "Subscribe", "show_me_text": "Do not show me again" }, "filters": { "show_more": "Show More", "show_less": "Show Less" }, "breadcrumbs": { "home": "Home", "create_account": "Create account", "account": "Account", "addresses": "Addresses" }, "item": { "remove": "Remove Item" } }, "sections": { "header": { "top_header_login": "Login", "top_header_register": "Register", "top_header_wishlist": "Wish list", "register_dropdown": "No account? Create one here", "forgot": "Forgot password", "all_collection": "All Collections", "world_wide_delivery": "Worldwide delivery", "shipping_text": "Free UK Delivery on orders over £ 100", "hot_line": "Hot line" }, "menu": { "mobile_menu_tab": "Menu", "mobile_account_tab": "Account", "mobile_settings_tab": "Settings" }, "slideshow": { "next_slide": "Next slide", "previous_slide": "Previous slide", "pause_slideshow": "Pause slideshow", "play_slideshow": "Play slideshow", "play_video": "Play video", "close_video": "Close video" }, "map": { "get_directions": "Get directions", "address_error": "Error looking up that address", "address_no_results": "No results for that address", "address_query_limit_html": "You have exceeded the Google API usage limit. Consider upgrading to a Premium Plan<\/a>.", "auth_error_html": "There was a problem authenticating your Google Maps account. Create and enable the JavaScript API<\/a> and Geocoding API<\/a> permissions of your app." } }, "blogs": { "article": { "view_all": "View all", "all_topics": "All topics", "by_author": "by [[ author ]]", "posted_in": "Posted in", "read_more": "Read more", "back_to_blog": "Back to [[ title ]]" }, "comments": { "title": "Leave a comment", "name": "Name", "email": "Email", "message": "Message", "post": "Post comment", "moderated": "Please note, comments must be approved before they are published", "success_moderated": "Your comment was posted successfully. We will publish it in a little while, as our blog is moderated.", "success": "Your comment was posted successfully! Thank you!", "comments_with_count": { "one": "[[ count ]] comment", "other": "[[ count ]] comments" } } }, "cart": { "general": { "title": "Your cart", "note": "Add a note to your order", "remove": "Remove", "subtotal": "Subtotal", "savings": "You're saving", "shipping_at_checkout": "Shipping & taxes calculated at checkout", "update": "Update", "checkout": "Process Check out", "empty": "Your cart is currently empty.", "cookies_required": "Enable cookies to use the shopping cart", "edit": "Edit", "cancel": "Cancel", "continue_shopping": "Continue shopping", "recently_added_item": "Recently added item(s)", "remove_item": "Remove This Item", "view_and_edit_cart": "View and edit cart", "clear": "Clear cart", "empty_page_title": "Shopping Cart is Empty", "here": "here", "empty_continue_html": "Click here to continue shopping.", "processing": "Processing...", "items_count_label" : "[[ count ]] item(s) in your cart", "ok" : "Ok" }, "label": { "product": "Product", "price": "Price", "quantity": "Quantity", "total": "Total", "total_item": "Total item", "sub_total_top": "Cart Subtotal" } }, "collections": { "general": { "view_all": "View all", "clear_all": "Clear All", "no_matches": "Sorry, there are no products in this collection", "items_with_count": { "one": "[[ count ]] product", "other": "[[ count ]] products" }, "load_more": "Load More", "sidebar_btn": "Filter by" }, "sorting": { "title": "Sort by", "manual": "Featured", "best_selling": "Best Selling", "title_ascending": "Alphabetically, A-Z", "title_descending": "Alphabetically, Z-A", "price_ascending": "Price, low to high", "price_descending": "Price, high to low", "created_descending": "Date, new to old", "created_ascending": "Date, old to new" }, "filters": { "title_tags": "Filter", "all_tags": "All products", "categories": "categories", "title": "Filter", "color": "Color", "size": "Size", "brand": "Brand", "price": "Price", "green": "Green", "blue": "Blue", "red": "Red", "pink": "Pink", "black": "Black", "purple": "Purple", "white": "White", "orange": "Orange" }, "product_item": { "quick_shop": "Quick View", "compare": "Compare", "wishlist": "Add to Wishlist" } }, "contact": { "form": { "name": "Name", "email": "Email", "phone": "Phone Number", "message": "Message", "submit": "Submit", "post_success": "Thanks for contacting us. We'll get back to you as soon as possible.", "address": "Address", "telephone": "Telephone", "title": "Write us", "required": "Required" } }, "customer": { "account": { "title": "My Account", "details": "Account Details", "view_addresses": "View Addresses", "return": "Return to Account Details" }, "activate_account": { "title": "Activate Account", "subtext": "Create your password to activate your account.", "password": "Password", "password_confirm": "Confirm Password", "submit": "Activate Account", "cancel": "Decline Invitation" }, "addresses": { "title": "Your Addresses", "default": "Default", "add_new": "Add a New Address", "edit_address": "Edit address", "first_name": "First Name", "last_name": "Last Name", "company": "Company", "address1": "Address1", "address2": "Address2", "city": "City", "country": "Country", "province": "Province", "zip": "Postal\/Zip Code", "phone": "Phone", "set_default": "Set as default address", "add": "Add Address", "update": "Update Address", "cancel": "Cancel", "edit": "Edit", "delete": "Delete", "delete_confirm": "Are you sure you wish to delete this address?" }, "login": { "title": "Login", "desc": "If you have an account, sign in with your email address.", "email": "Email", "password": "Password", "forgot_password": "Forgot your password?", "sign_in": "Sign In", "guest_title": "Continue as a guest", "guest_continue": "Continue" }, "orders": { "title": "Order History", "order_number": "Order", "date": "Date", "payment_status": "Payment Status", "fulfillment_status": "Fulfillment Status", "total": "Total", "none": "You haven't placed any orders yet." }, "order": { "title": "Order [[ name ]]", "date": "Placed on [[ date ]]", "cancelled": "Order Cancelled on [[ date ]]", "cancelled_reason": "Reason: [[ reason ]]", "billing_address": "Billing Address", "payment_status": "Payment Status", "shipping_address": "Shipping Address", "fulfillment_status": "Fulfillment Status", "discount": "Discount", "shipping": "Shipping", "tax": "Tax", "product": "Product", "sku": "SKU", "price": "Price", "quantity": "Quantity", "total": "Total", "fulfilled_at": "Fulfilled [[ date ]]", "subtotal": "Subtotal" }, "recover_password": { "title": "Reset your password", "email": "Email", "submit": "Submit", "cancel": "Cancel", "subtext": "We will send you an email to reset your password.", "success": "We've sent you an email with a link to update your password." }, "reset_password": { "title": "Reset account password", "subtext": "Enter a new password for [[ email ]]", "password": "Password", "password_confirm": "Confirm Password", "submit": "Reset Password" }, "register": { "title": "Create Account", "first_name": "First Name", "last_name": "Last Name", "email": "Email", "password": "Password", "submit": "Create", "desc": "Creating an account is easy. Just fill in the form below." } }, "homepage": { "onboarding": { "product_title": "Your product's name", "product_description": "This area is used to describe your product’s details. Tell customers about the look, feel, and style of your product. Add details on color, materials used, sizing, and where it was made.", "collection_title": "Your collection's name", "blog_title": "Your post's title", "blog_excerpt": "Your store hasn’t published any blog posts yet. A blog can be used to talk about new product launches, tips, or other news you want to share with your customers. You can check out Shopify’s ecommerce blog for inspiration and advice for your own store and blog.", "blog_author": "Author name", "no_content": "This section doesn’t currently include any content. Add content to this section using the sidebar." } }, "layout": { "navigation": { "search": "Search", "toggle": "expand\/collapse", "expand": "expand", "collapse": "collapse", "all_categories": "All Categories" }, "cart": { "title": "Cart", "items_count": { "one": "item", "other": "items" } }, "customer": { "account": "Account", "log_out": "Log out", "logout": "Log out", "log_in": "Log in", "create_account": "Create account", "sign_up": "Sign up", "wishlist": "Wishlist" }, "footer": { "social_platform": "[[ name ]] on [[ platform ]]" }, "list_page": { "grid": "Grid", "list": "List" } }, "products": { "product": { "regular_price": "Regular price", "sold_out": "Sold out", "unavailable": "Unavailable", "on_sale": "Sale", "quantity": "Quantity", "add_to_cart": "Add to cart", "back_to_collection": "Back to [[ title ]]", "related_title": "Related Products", "qty_increase": "Increase", "qty_decrease": "Decrease", "deal_days": "Days", "deal_hours": "Hours", "deal_minutes": "Minutes", "deal_second": "Second", "select_option": "Select Option", "add_to_wishlist": "Add to Wishlist", "add_to_review": "Add to review", "compare_success_msg": "[[ product_title ]] has added to comparing box successful", "compare_exist_msg": "[[ product_title ]] is exist in comparing box", "compare_cart_msg": "[[ product_title ]] has added to shopping cart", "compare_remove_msg": "[[ product_title ]] has removed from comparing box", "compare_remove_msg": "[[ product_title ]] has removed from comparing box", "comparing_box": "Comparing box", "compare_no_items": "There is no items in comparing box", "wishlist_success_msg": "[[ product_title ]] has added to wishlist successful", "wishlist_exist_msg": "[[ product_title ]] is exist in wishlist", "wishlist_cart_msg": "[[ product_title ]] has added to shopping cart", "wishlist_box": "Wishlist", "wishlist_remove_msg": "[[ product_title ]] has removed from wishlist", "wislist_no_items": "There is no items in wishlist", "upsell_cart_msg": "\"[[ product_title ]]\" has added to shopping cart", "upsell_block_title": "Frequently bought with \"[[ product_title ]]\"", "upsell_cart_qty": "[[ count ]] item(s)", "upsell_product_page_title": "You may also like these products", "upsell_checkout_btn": "Checkout", "share": "Share product", "share_on_facebook": "Share on Facebook", "share_on_twitter": "Share on Twitter", "share_on_pinterest": "Share on Pinterest", "share_on_google": "Share on Google+", "share_on_linkedin": "Share on LinkedIn", "availability": "Availability", "in_stock": "In Stock", "out_of_stock": "Out of stock", "quick_overview": "Quick Overview", "details": "Details", "reviews": "Reviews", "first_review": "Be the first review", "tags": "Product Tags", "size_chart": "Size Chart", "options": "Options", "vendor": "Vendor", "features": "Features", "sale_left_text": "[[ sales ]] SOLD. HURRY! ONLY A FEW LEFT!", "checkout_text": "Secured and trusted checkout with" }, "upsell": { "recommend_text": "Someone purchased a", "minute_ago": "minutes ago" } }, "gift_cards": { "issued": { "title_html": "Here's your [[ value ]] gift card for [[ shop ]]!", "subtext": "Your gift card", "disabled": "Disabled", "expired": "Expired on [[ expiry ]]", "active": "Expires on [[ expiry ]]", "redeem_html": "Use this code at checkout to redeem your [[ value ]] gift card", "shop_link": "Start shopping", "print": "Print this gift card", "remaining_html": "[[ balance ]] left", "add_to_apple_wallet": "Add to Apple Wallet" } }, "date_formats": { "month_day_year": "%B %d, %Y" } } }