Академический Документы
Профессиональный Документы
Культура Документы
Final
Copyright 2002-2004 by Ember Corporation All rights reserved. The information in this document is subject to change without notice. The statements, configurations, technical data, and recommendations in this document are believed to be accurate and reliable but are presented without express or implied warranty. Users must take full responsibility for their applications of any products specified in this document. The information in this document is the property of Ember Corporation. Title, ownership, and all rights in copyrights, patents, trademarks, trade secrets and other intellectual property rights in the Ember Proprietary Products and any copy, portion, or modification thereof, shall not transfer to Purchaser or its customers and shall remain in Ember and its licensors. No source code rights are granted to Purchaser or its customers with respect to all Ember Application Software. Purchaser agrees not to copy, modify, alter, translate, decompile, disassemble, or reverse engineer the Ember Hardware (including without limitation any embedded software) or attempt to disable any security devices or codes incorporated in the Ember Hardware. Purchaser shall not alter, remove, or obscure any printed or displayed legal notices contained on or in the Ember Hardware. EmberNet, Ember Enabled, Ember, and the Ember logo are trademarks of Ember Corporation. All other trademarks are the property of their respective holders.
Contents
Chapter 1
Chapter 2
120-0065-000A Final
Page 3
Binding ................................................................................................................................ 22 Message Delivery .............................................................................................................. 23 Transmitting, Receiving, and Tracking Beacons .............................................................. 24 Data Addressing and Delivery in Leaf Nodes ........................................................................ 25 Addressing .......................................................................................................................... 25 Binding ................................................................................................................................ 25 Delivery of Broadcast, Unicast Messages ....................................................................... 25 Receiving and Tracking Beacons ..................................................................................... 25
Chapter 3
Chapter 4
120-0065-000A Final
Page 4
Wait for Callback .............................................................................................................. 42 Receiving Messages ................................................................................................................. 42 Create Binding ................................................................................................................... 42 Incoming Message Handler ............................................................................................. 42 Sending and Receiving Beacons ............................................................................................. 45 Sending Beacons ............................................................................................................... 45 Receiving Beacons ............................................................................................................. 46 Tracking Beacons ............................................................................................................... 46 Sleeping and Waking .............................................................................................................. 46 Going to Sleep ................................................................................................................... 46 Waking Up ......................................................................................................................... 46 Callbacks ................................................................................................................................... 47
Chapter 5
120-0065-000A Final
Page 5
Chapter 6
120-0065-000A Final
Page 6
Purpose
This document describes how to design and implement a project using Ember software. In addition, it also contains introductory information on radio propagation and embedded mesh networking topologies and details on networking that are important for understanding how the system functions and making the correct design decisions. We recommend that you read this document from beginning to end, because later chapters rely on the information in previous chapters. You can then refer to specific chapters as necessary during your development process.
Audience
This document is intended for project managers and for embedded software engineers who are responsible for building a successful embedded mesh networking solution using Ember's radio, networking stack, and tools. This document assumes that the reader has a solid understanding of embedded systems design and programming in the C language. Experience with networking and radio frequency systems is useful but not expected.
Getting Help
If you have any questions about how to developing Ember applications, please contact your Ember account representative: United States 343 Congress Street Boston, MA 02210 Telephone: +1 617-951-0200 Fax:+1 617-951-0999 Email: support@ember.com
120-0065-000A Final
Page 7
Documentation Conventions Europe Unit 29 Science Park, Milton Road Cambridge CB4 0DW, UK Telephone: +44 (0) 1223 423322 Fax: +44 (0) 1223 423390 Email: support@ember.com The Ember website contains information about the full range of Ember products and services and allows you to sign up for the support section of the site: http://www.ember.com
Documentation Conventions
Notation
Italics UPPERCASE Right-angle bracket (>) Courier
Meaning
Identifies on-screen software menu options. Identifies a keyboard key. Delimits a series of software menu options to be clicked. Identifies software code and, in body text, variables.
Example
Refresh Screen ENTER Open > Save void Main(String[] argv) the buffer variable
120-0065-000A Final
Page 8
CHAPTER 1
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 How Radios Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Electromagnetic Waves. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Radio Systems: Transmission, Reception, and Propagation . . . . . . . . . 10 Medium Access Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 How Embedded Mesh Networks Work . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Network Location and Association . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Route Discovery. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Message Delivery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Types of Embedded Mesh Networking Topologies . . . . . . . . . . . . . . . . . . 12 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Overview
Embedded networking is the solution for low-cost, low-power devices that need to communicate among themselves and with the outside world. The successful embedded networking solution must be easy to install, provide robust communication, be capable of supporting battery-operated nodes for more than a year, and have a low enough cost to be added to an already low-cost manufactured device, such as a thermostat. Meeting all of these requirements in one solution is not easy, and many past attempts have fallen short of the mark. The simplest and least-expensive approach is point-to-point wireless, but successful deployment often requires a time-consuming and expensive radio site-survey. More expensive radios can mitigate this problem, but this limits the market to higher cost, AC-powered applications. Embedded mesh networking allows inexpensive radios to relay information through each other, providing a robust communications channel and allowing deployment without a site-survey. Battery-operated nodes in such networks can last up to several years, depending on the application design. As the radio, networking software, and application framework become more and more integrated, these solutions will become even more power-efficient and even lower in cost. To meet these needs, Ember offers an industrial-strength embedded mesh networking algorithm combined with high-performance radios. Our whole-system approach allows us to offer customers a single point of contact for their embedded networking system design, implementation,
120-0065-000A Final
Page 9
and deployment needs, and also allows us to continue to develop the state of the art in all areas, to offer the best solution possible into the future.
Electromagnetic Waves
A radio wave is an electromagnetic (EM) wave that travels at the speed of light from its source in all directions simultaneously. The wave has both an electrical wave component (E) and a magnetic wave component (M) that are perpendicular both to each other and the direction of travel. For our purposes, EM waves can be described by two main properties: the frequency of the wave and its intensity, or power. The frequency of a wave is measured in hertz (cycles per second) and indicates the amount of time required for the wave to return to its starting position. The frequency of a wave is inversely proportional to its wavelength, a feature that is important for understanding antenna design and placement. The power of a wave is measured in milliwatts (mW) or decibels per milliwatt (dBm) and indicates how much energy it contains. An EM wave that you are familiar with is visible light. Different frequencies of visible light produce different colors, and different powers of visible light are clearly perceived as differences in intensity or brightness. Radio waves function in much the same way, although they use a much lower frequency that is not perceived by the human eye. The power of a wave diminishes as its distance from the source increases, but it is safe to assume that in most embedded networking situations, the waves frequency will remain the same.
120-0065-000A Final
Page 10
Route Discovery
The network is responsible for discovering and maintaining available routes so that the application does not need to know anything about the underlying routes to deliver a message to a destination node. The way this is handled varies among networks and routing mechanisms. The two main approaches are on-demand route discovery and active route discovery. Active route discovery attempts to keep certain routes up to date at all times. This consumes additional network overhead but means that the
120-0065-000A Final
Page 11
routes are available immediately when a node wishes to send data. On-demand route discovery creates less overhead network traffic but may incur a delay when a route has changed because of shifting RF conditions or network rearrangements.
Message Delivery
Once one or more viable routes have been discovered, the network must deliver the message using one or more of these routes. The information about the routes can be next-hop, where either the message or the node knows what the next hop should be, or it can be distributed, where the nexthop is decided dynamically depending on which nodes hear the message. Next-hop delivery can be made very bandwidth efficient, since only the node elected to be the next hop will re-transmit the message. Distributed delivery, however, will recover from intermittent failures much more quickly, since the next hop is chosen dynamically. AODV (ad hoc, on-demand, distance-vector routing) is one common type of a next-hop routing system, whereas Ember's GRAdient routing is a distributed delivery system. More information about distributed delivery can be found in Chapter 2, Introduction to Embedded Networking.
120-0065-000A Final
Page 12
Further Reading
Full mesh network: All nodes relay messages and can be in communication with one another. Traffic is distributed between several senders and receivers. Nodes can share information with one another, allowing an application designer to create a system of distributed intelligence. This topology is not very deterministic. There are no obvious bottlenecks and information is easy to share, but it is harder to troubleshoot and predict. Hybrid network: This topology combines the star and mesh strategies. Several star networks exist, but their hubs can communicate as a mesh network. A hybrid network allows for longer distance communication than a star topology and more capability for hierarchical design that a mesh topology. The choice of a topology should be based on the network design and individual devices and the expected data flow within the system. Leaf or reduced function nodes (that is, non-routing nodes) are used in many of these topologies to reduce cost and power requirements.
Further Reading
For in-depth information on the topics covered in this chapter, we recommend the following resources: IEEE 802.15.4 specification http://electronics.howstuffworks.com/radio.htm
120-0065-000A Final
Page 13
CHAPTER 2
EmberNet Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Understanding the EmberNet Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Understanding the HAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Understanding the Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Further Reading About the EmberNet Stack. . . . . . . . . . . . . . . . . . . . . 17 Network Association, Discovery, and Routing. . . . . . . . . . . . . . . . . . . . . . . 17 Network Formation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Discovery and Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Data Addressing and Delivery in Full-Function Devices. . . . . . . . . . . . . . . . 22 Addressing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Message Delivery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Transmitting, Receiving, and Tracking Beacons . . . . . . . . . . . . . . . . . . 24 Data Addressing and Delivery in Leaf Nodes . . . . . . . . . . . . . . . . . . . . . . . 25 Addressing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Delivery of Broadcast, Unicast Messages . . . . . . . . . . . . . . . . . . . . . . 25 Receiving and Tracking Beacons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
EmberNet Components
EmberNet's software consists of three main components: the stack, the HAL, and tools, both embedded and PC-based (Figure 2-1).
120-0065-000A Final
Page 14
EmberNet Components
The full-functionality stack provides complete mesh networking behavior, using Embers GRAdient routing algorithm. The application interacts with a comprehensive message transport layer that provides targeted broadcast messages (which Ember calls multicast messages) and end-to-end acknowledged unicast messages. The stack controls and automates route discovery and message delivery. For a detailed description of the EmberNet messaging API, see the EmberNet Stack Interface Guide. The full-functionality stack can also be configured to send one-hop beacons, either periodically at regular intervals or as unrelated messages. Beacons can be used to coordinate network activity and to announce network resource availability to other full-function devices and to leaf node devices. The full-functionality stack also provides a simple memory management system via data structures called message buffers, which the stack uses as temporary storage for incoming messages, outgoing messages, and messages being routed for other nodes. The application can also use these message buffers for its own dynamic information, if that is useful. Full-function devices form the routing backbone of an embedded mesh network. To do this, they perform some additional under-the-hood network maintenance, such as keeping track of nearby neighbors and the relative radio link quality heard from those neighbors. This imposes two restrictions on the use of full-function devices (which are not imposed on leaf nodes): They must be on at all times when the network is functioning, and they should not move quickly relative to one another. In general, a full-function device is not a suitable choice for a battery-powered node. It cannot always sleep immediately because its routing responsibilities are not deterministic, and it cannot wake instantaneously because it requires time (a small but significant amount when battery life is a concern) to gather local network information before it can send and receive messages. The reduced-functionality stack (that is, the leaf node stack) provides a significantly reduced feature set that enables the entire program to fit on a much smaller (16k flash) processor. The reduced feature set provides only point-to-point and point-to-multipoint data transmission within a single hop, and it does not route messages for other nodes. Using reduced-function nodes creates a EmberNet Application Development Guide 120-0065-000A Final Page 15
EmberNet Components
hybrid mesh topology (Figure 1-1). It also removes features (such as message buffers) that add to the RAM and FLASH overhead but are not strictly necessary for a smaller device. For a complete API description, see the Leaf Node Stack Interface Guide. Because leaf nodes have no relaying or routing responsibilities, they can be put to sleep quickly for long periods of time and woken up quickly, which makes them an effective choice for batterypowered nodes. Their transient nature in the network also makes them suitable for mobile applications.
Ember Studio
Ember Studio provides basic network monitoring and debug information to assist in the development of embedded applications. Ember Studio can be used with Ember development boards to provide an easy-to-use network for development and testing of an embedded application prior to deployment on customer specific hardware. For a full description of Ember Studios functionality and use, see the Ember Studio Users Guide.
Bootloader
The Ember over-the-air bootloader allows the system to upgrade its own software, either via an RS-232 serial link or over a single-hop radio connection. This can be used to speed the development process and also to make units field-upgradeable, which is often an important concern when deploying long-term installations. Because of the small flash size and limited microprocessor supported bootload area, bootloader support is not available for nodes implemented using the Leaf Node stack.
Rangetest
Embers rangetest application provides low-level control of the radio and can be used to verify proper functionality after manufacturing, characterize radio performance, set manufacturing parameters (that is, tokens), and control the radio properly for the certification process required by
120-0065-000A Final
Page 16
many countries. For information on using the rangetest application, see Application Note 5001, Bringing Up Custom Nodes.
120-0065-000A Final
Page 17
Figure 2-2: Node 2 has a message for node 6, but before it can send the message, it must discover node 6 in the network.
120-0065-000A Final
Page 18
Figure 2-5: All nodes now all have the cost for sending to node 2.
120-0065-000A Final
Page 19
Figure 2-7: Nodes now update with the cost to send to node 6.
120-0065-000A Final
Page 20
Figure 2-8: Node 2 can now send a message to node 6 with node 5 acting as a relay.
120-0065-000A Final
Page 21
Addressing
Every Ember node has a 64-bit IEEE address that is assigned at manufacturing time and never changes. Because these addresses are allocated by the IEEE, no two devices should ever have the same 64-bit address. This allows the networking system to uniquely address a particular device. Ember supplies IEEE 64-bit addresses with each chip to developers who need them. Companies may also choose to acquire their own block of addresses from IEEE. To avoid sending 64-bit addresses around the network with every message, which would consume significant bandwidth, the network negotiates a 16-bit address that is unique within the network. In EmberNet this 16-bit address is dynamically allocated, and conflicts are resolved internally to the networking software. Because the 16-bit address may change during device operation in the network, this address is not exposed to the application layer. All application-level addressing uses the 64-bit IEEE address or a multicast group address. EmberNet devices can also subscribe to any number of multicast groups. A multicast group has a 16-bit identifier and allows messages to be sent to multiple devices simultaneously (see Message Delivery on page 23). In addition to the main address (either a 64-bit IEEE address or a multicast group), messages are sent to a specific endpoint and interface pair. The endpoint/interface combination specifies an additional destination within the targeted device and is delivered as a parameter to the device's incoming message handler. Readers familiar with UDP or TCP can think of this as analogous to a UDP or TCP port. Endpoints and interfaces are configured at compile-time and are explained in more detail in the implementation section.
Binding
Each device maintains a binding table with a configurable number of entries. Each entry is either a multicast binding or a unicast binding. A multicast binding allows the device to hear multicast messages sent to the specified multicast identifier. It also allows the device to send messages to other nodes that also provide a binding to that multicast identifier. A unicast binding allows the device to send messages to the IEEE address specified in the binding.
Note: A device will receive unicast messages to its address even if no binding is present.
As of version 3.3 of the EmberNet stack, a special binding is supported to send messages to a local leaf node. Except for the binding type, these bindings are identical to unicast bindings. A binding table is used to identify required information about a device in the network with which the existing device will exchange messages. Each EmberNet binding is bidirectional in that messages can be sent and received. The binding table provides the mechanism for virtually joining discrete elements such as a wall switch and three overhead lights, so that any message created for a change of state at the switch is then sent to the appropriate lights.
120-0065-000A Final
Page 22
Message Delivery
For this discussion, the word message will refer to a single packet that is sent over the radio. Different transmission types have different maximum sizes. For example, a multicast message can be larger than a unicast message. When more data needs to be sent, it will need to be sent in multiple messages. All messages must be put into a message buffer, which is requested by the application from the stack. Once the message is constructed in or copied into a message buffer, it can be sent. Messages are always sent to a particular binding table entry. Messages sent to a unicast binding table entry must be sent using emberSendDatagram() or emberSendSequenced(), and messages sent to a multicast binding must be sent using emberSendMulticast() or emberSendLimitedMulticast(). The appropriate function will return EMBER_SUCCESS to indicate that no errors were detected in the form of the message or the destination binding and that the message has been accepted for delivery by the stack. It does not necessarily indicate that the message has reached its destination, or even that it has been sent.
Sending a Datagram
A datagram is a sent without any coordination between the sender and the recipient. For each message that receives an EMBER_SUCCESS result code from emberSendDatagram(), the callback emberMessageSent() will be called to indicate success or failure of the transmission. An EMBER_SUCCESS call to emberMessageSent() indicates that the message was received by the destination node and that the destination node was able to send an acknowledgement to the sender.
Note: A call to emberMessageSent() with an unsuccessful result code does not guarantee
that the recipient did not receive the message. It only guarantees that the acknowledgement did not reach the sender before the timeout period. If the acknowledgement is not received within 1.2 seconds, the datagram will be retried. Each datagram will be retried three times before a failure is indicated. Note that due to this retry mechanism, duplicate messages may occasionally be received by the destination node, and messages that are sent simultaneously (without first waiting for emberMessageSent() to be called back) may arrive in an order different from the one in which they were sent. Applications must accommodate this behavior or use sequenced messages.
120-0065-000A Final
Page 23
Sequenced messages may be sent after the emberConnectionStatusHandler() is called, indicating that the requested connection has been opened. A connection is always bidirectional, meaning that only one side needs to request that it be opened. As with datagrams, emberMessageSent() is always called back after each successful submission. If a message fails after the transport level retries, the connection is considered closed, and the emberConnectionStatusHandler() is called to indicate this new state.
120-0065-000A Final
Page 24
Binding
To save memory and code size, the leaf node does not have a binding table. Instead, it specifies the recipients 64-bit IEEE address directly when requesting message transmission.
Note: If the leaf nodes in a system are configured to transmit messages in response to receiving a
beacon, this can result in an unintended synchronization of messages when all leaf nodes in the range of a single beacon attempt to respond simultaneously. This may result in an undesirably high number of collisions unless the application provides a random backoff from the beacon prior to transmitting a message.
120-0065-000A Final
Page 25
CHAPTER 3
Capturing Application Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Environmental Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Device Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Traffic Requirements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Taking Data off the Network. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 Designing an EmberNet Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 General Design Recommendations. . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Identify Full-Function and Leaf Node Devices. . . . . . . . . . . . . . . . . . . . 29 Define Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Determine Device Binding Requirements . . . . . . . . . . . . . . . . . . . . . . . 30 Choose a Network Discovery Method . . . . . . . . . . . . . . . . . . . . . . . . . 31 Determine Endpoints and Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Define Binding Table Interactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Create the Means to Relay Messages to and from Leaf Nodes . . . . . 32 Evaluate the Design Against the Requirements . . . . . . . . . . . . . . . . . . . 32 Integrate Your Solution with EmberNet . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Environmental Requirements
The easiest requirements to articulate are the following environmental ones: Location: Are the nodes primarily indoors or outdoors? Physical environment: What are the likely physical obstacles? Pay specific attention to metal items and, when using 2.4GHz, items containing much water. Dont forget the device packaging and any mounting hardware. The occasional small metal or water-bearing object is
120-0065-000A Final
Page 26
not likely to pose a problem, but clusters of such objects or large instances of them may. Dont forget the device packaging and any mounting hardware. Expected range: For the expected location and physical environment, what range between devices is necessary? Can relay nodes be added if needed? The device layout needs to support enough nodes in proximity to each other to create a mesh (each node should have at least two neighbors). Node access once installed: Does the design incorporate field-upgrading of nodes? What upgrading method will be used (for example, over the air or via Ethernet)? Are any of the nodes inaccessible for the chosen upgrading method?
Device Requirements
Device requirements should be familiar to designers accustomed to standard embedded design. Because the stack and the application will be deeply integrated, it is important to pay attention to both what the application needs and what the application leaves available for the stack. Interrupt latencies: What are the minimum interrupt latencies required by your program, and what is the maximum duration of critical sections (that is, sections where interrupts are disabled) in your application? MCU utilization: Other than interrupts, what is the anticipated percentage of MCU time required by your application? A rough estimate is fine, because the amount of MCU time required by the stack is dependent on the traffic requirements. Primarily, this is a checkbox item to help guarantee that there is some processor time available for the network. MCU memory utilization: What is the estimated FLASH and RAM usage of your application? This is likely to be of most concern for complicated applications and for applications that are using the leaf node stack with a smaller, less expensive microprocessor such as the ATmega16L. Also worth considering are EEPROM requirements. Although the stack uses very little, it does consume some. MCU peripherals: What internal peripherals will your application make use of? Pay special attention to timers, as the stack does make use of timers in various ways. What MCU pins will need to be used to control external peripherals? The timers, pins, and most other peripherals used by the stack can be modified within the HAL. A few, like the SPI port, are used exclusively by the stack. Consider the UART and other peripherals that are likely to generate a large number of interrupts; these will affect the interrupt latencies above. Mobility: Are any of the nodes mobile? How quickly will they be moving? How quickly must they repair routes that have been changed by their movement? How quickly must they discover a new network upon entering its coverage area? Battery operation: Do any of the nodes need to be battery operated? What is the capacity (mA*H or equivalent) and peak current of the batteries under consideration? How long must they last before requiring recharging or replacement of batteries? Packaging: What is the anticipated packaging of the device, and what external access is available for debug? Although these factors primarily affect RF design (especially antenna design and placement), the embedded software developer should keep them in mind because they may affect the available choices for MCU peripherals. In addition, building devices without at least test points for providing debug output makes application troubleshooting difficult.
Traffic Requirements
Each deployment of an application will be slightly different, making it impossible to predict exact layouts, traffic patterns, and reliability requirements. Nevertheless, because the assumptions made in this stage will influence many later design decisions, it is important to get the best understanding
120-0065-000A Final
Page 27
possible. Usually this means making assumptions about standard operating environments, device placement, and usage patterns. We recommend that you document these assumptions so that you can later assess their consistency with real-world field trials and deployment. The primary result of analyzing the traffic requirements is a map of the traffic flow. Layout: What are the anticipated physical layouts of the devices in the network? This may be impossible to fully capture, but choose the likely configurations (and perhaps one or two boundary cases) and use them for the design. Does the layout provide for formation of a mesh network, or are there many single paths for failure? What can be done to eliminate single-path connections? Traffic patterns: What devices are sending data to which other devices? Try to recognize general patterns. For example, is most data flowing to one or more central collection points, or is it locally concentrated? Or a combination of the two? Range: Within the anticipated traffic patterns, what is the maximum range required (without a repeater node) between any two devices? Bandwidth: How much data is being sent, and at what rate? Pay attention to how bandwidth requirements increase as multiple nodes send along the same pathway. Put this information together with traffic patterns to determine the likely high-bandwidth pathways. For a first pass, it is sufficient to concentrate on high-bandwidth paths. Latency: What are the required latency times for messages? As with bandwidth, assess this against the traffic patterns. For a first pass, it is sufficient to concentrate on the messages that require the swiftest transmission (that is, those with the lowest latency requirements). Reliability: A realistic embedded design accounts for anticipated failure modes, their acceptable rate of occurrence, and acceptable recovery mechanisms. When designing an embedded mesh network design, the case of the failure of one or more crucial routes must be considered. Such failures persist long enough to result in the stacks informing the application that delivery is not possible. By contrast, failure of a single route is much less serious because the mesh network automatically detects and corrects for the failure. A crucial route may fail because of the failure of critical nodes in the delivery path, obstacles, or persistent interference. Appropriate recovery mechanisms are application-specific. In some applications (for example, long-term, low-data-rate sensor networks) transmission latency is not important, and messages can be cached for a very long time. In other applications, a fast higher-level failure notice or alert is important. Reliability requirements can be effectively captured by accounting for two component requirements: What is the maximum number of acceptable missed messages per relevant unit of time, and how long can messages be cached by the sending application for later delivery before they are considered lost?
by the gateway node memory requirements. This approach is recommended for any networks larger than thirty-two nodes.
Even if you are familiar with designing network applications, we recommend you read this section because certain aspects of the design process are unique to EmberNet.
120-0065-000A Final
Page 29
Define Messages
For each type of node identified, determine the types of messages that it will send and receive. Then, for each type of message, specify maximum payload size, latency requirements, the type of node that will receive the message, and whether it is a unicast or a multicast message. Try to eliminate unnecessary multicast messages because they use a lot of network bandwidth. But in some cases, such as discovery of services, they are unavoidable. Make sure that no message payloads are larger than the maximum acceptable size for that type of message. Unicast messages must be 93 bytes or fewer, multicast messages must be 100 bytes or fewer, and aggregation messages must be 77 bytes or fewer. Larger messages must be broken into fragments by the application. To calculate approximate latencies, add 10ms per hop in a lightly loaded network and up to 100ms per hop in a heavily loaded network. Compare against your required minimum latencies.
120-0065-000A Final
Page 30
Note that unicast bindings require the 64-bit IEEE address, which, to save bandwidth, is not included by default in messages. Update payload lengths for messages that must contain the 64-bit address of the sender. Consider how the application will perform out of the box, and make sure that any required startup tasks are understood and documented.
Is there a method for establishing the appropriate endpoint and interface for all message types within the network? After initial installation, can the method of endpoints and interfaces chosen be used for as new devices are added or devices removed.
120-0065-000A Final
Page 32
CHAPTER 4
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Basic Template: Full-Function and Leaf Node Devices . . . . . . . . . . . . . . . . 34 Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Initialization Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Running the Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Sending Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Create Bindings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Open a Connection. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Creating the Message. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Creating Message Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Submit Message to the EmberNet Stack . . . . . . . . . . . . . . . . . . . . . . . 42 Wait for Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Receiving Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Create Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Incoming Message Handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Sending and Receiving Beacons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Sending Beacons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Receiving Beacons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Tracking Beacons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Sleeping and Waking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Going to Sleep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Waking Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Overview
This chapter explains the steps required to implement a simple project and shows proper use of the building blocks required to make more complicated projects. Users interested in more detailed examples should see Chapter 5, Sample Sensor Applications and browse the sample code available in the stack distribution and on the Ember support web site (http://support.ember.com). EmberNet Application Development Guide 120-0065-000A Final Page 33
For more information on configuring and using the IAR compiler, see Chapter 6, Developing Applications.
Header Files
Ember uses several standard header files to define the types, data structures, and static variables used by the stack. Detailed descriptions of these files can be found in the EmberNet Stack Interface Guide. For EmberNet functionality, the following header files are required: PLATFORM_HEADER: A constant defined by compiler flags, this header file provides low-level compiler- and microprocessor-specific directives. It must be included in all source files that use the EmberNet API. stack/include/ember.h (or stack/include/ember-leaf.h for leaf nodes): Defines the EmberNet API. stack/include/error.h: Defines return values for EmberNet APIs. stack/include/ember-static-configuration.h: Must be included by exactly one source file per application. Source files that are involved in controlling the MCU-level and board level hardware should include the following header files: stack/include/hal.h: Defines high-level HAL APIs. hal/board/board.h: Defines board-specific control APIs and configurations. hal/micro/micro.h: Defines microprocessor-specific control APIs and configurations. Other header files commonly used are: stack/include/packet-buffer.h: Packet buffers are used only by the full-functionality stack and are explored in Sending Messages on page 38 and Receiving Messages on page 42. app/util/serial/serial.h: APIs for control of the UART driver. Sample of Header Files from app\simple\main #include PLATFORM_HEADER // Micro- and compiler-specific //typedefs and macros // Public Ember Stack APIs
#include "stack/include/ember.h"
#include "stack/include/packet-buffer.h" // Stack/Linked buffer //access APIs #include "stack/include/error.h" #include "stack/include/hal.h" #include "hal/board/board.h" #include "hal/micro/micro.h" // EmberNet stack status codes // Common HAL APIs that the stack // also uses // Board-specific HAL APIs // Micro- specific HAL APIs // Serial utility APIs
#include "app/util/serial/serial.h"
120-0065-000A Final
Page 34
Variables
Including stack/include/ember-static-configuration.h includes the default values of many variables. Changes to the default variable value must be done using macros defined in ember-static-config.h. In addition, full-function devices must include the following application-specific static variables: int8u emberEndpointCount: Defines the number of endpoints used by the application. This is used by the EmberNet stack to access the emberEndpoints variable. If it is not set correctly, stack behavior is undefined. This variable must be between 0 and 31, inclusive. EmberEndpoint emberEndpoints[]: This variable allows the stack to access userdefined endpoints. This array must be at least one element long (even if no endpoints are defined). Sample of Variables in app\simple\main // Declare adjustable memory allocations required by the stack // See the header for macros which can be used to adjust the size // of the allocations for specific applications // Ex: // #define EMBER_PACKET_BUFFER_COUNT 12 #include "stack/include/ember-static-configuration.h" // A variable to track the current status of the stack EmberStatus stackStatus = EMBER_NETWORK_DOWN; // Endpoints used by this application, and referenced by the stack int8u emberEndpointCount = 0; EmberEndpoint emberEndpoints[1];
Utility Initialization
Certain Ember-provided utilities, such as the ADC library, the UART/serial library, and the stack debugging output, must be initialized separately. Consult the EmberNet Stack Interface Guide for information on these utilities.
Stack-Level Initialization
The EmberNet stack has a two-step initialization process. First, emberInit() must be called to set up the right variables and prepare the stack for operation. Then, emberAssociate() must be called to configure the radio channel, PAN ID, and other network parameters. Once this is complete, the stack is ready to run.
120-0065-000A Final
Page 35
Sample of Initialization and Main Event Loop from app\simple\main int main(void){ EmberResetType reset; EmberStatus status; EmberNetworkParameters networkParams; // Determine the cause of the reset (powerup, etc.). reset = microGetResetInfo(); microBoot(); boardBoot(); // Initialize the micro and its peripherals //(timers, watchdog, ...) // Initialize the board and its peripherals //(LEDs, buttons, ...) // Safe to enable interrupts at this point // Use serial port 0 for debug output
INTERRUPTS_ON(); emberDebugInit(0);
// Initialize the Ember Stack. status = emberInit(reset); if (status != EMBER_SUCCESS) { // Report status here, if possible. if (status == EMBER_UNINITIALIZED_EUI64 || status == EMBER_INVALID_EUI64) { // Run to allow over-the-air configuration. while (TRUE) { __watchdog_reset(); emberTick(); } } else { assert(FALSE); } } // Bring the network up. networkParams.panId = 0; networkParams.enableRelay = TRUE; networkParams.radioTxPower = -1; networkParams.radioChannel = 0; status = emberAssociate(&networkParams); EmberNet Application Development Guide 120-0065-000A Final Page 36
if (status != EMBER_SUCCESS) { // Report status here, if possible. assert(FALSE); } // Main event loop of the application while(TRUE) { __watchdog_reset(); emberTick(); // Periodically reset the watchdog // Allow the stack to run
if(stackStatus == EMBER_NETWORK_UP) { // OK to use the stack to send messages, etc. } heartBeat(); // Visually indicate normal operation // Needed for buffered serial, //which debug uses #ifdef DEBUG_ON emberSerialBufferTick(); #endif } }
Note: Do not call any EmberNet stack APIs if the stack is not up.
120-0065-000A Final
Page 37
Sending Messages
Sending Messages
Once the hardware and stack are initialized, messages can be sent and received. A message can be created at any time (see Allocating a Message Buffer on page 39), but it cannot be sent until the destination is known. The destination is identified by use of the binding table. A binding must be created (see Create Bindings on page 38) for the message to be sent. If a series of messages is to be sent and the sequence is to be maintained, then a connection between the source and destination is opened (see Open a Connection on page 39). Once a binding and message have been created, the message can be submitted to the Ember stack for delivery (see Submit Message to the EmberNet Stack on page 42) and status can be checked using the callback (see Wait for Callback on page 42).
Create Bindings
Note: This section applies to full-function devices only.
To send a message, a node must first create the proper binding table entry. The binding table entry can target a single destination node (for a unicast) or multiple destination nodes (for a multicast). The stack uses the binding table, but the table entries are managed entirely by the application. The number of bindings available is limited by the amount of RAM and EEPROM on the processor and can be adjusted in ember-static-configuration.h. Very large network applications should considering using RNAP to offload some of the memory requirements onto a larger host processor. The application must create bindings when it needs them and, if more bindings are required than there is space in the table, it must delete old bindings and reclaim the space they occupied. The application can also check to see if a binding is currently involved in sending or receiving a message by using emberBindingIsActive(). Bindings cannot be deleted while they are active. For the exact structure of binding table entries and function calls for managing binding table entries, see the EmberNet Stack Interface Guide. Sample Binding Table Entry Creation from app\sensor\sensor-ffd // setup multicast binding void setMulticastBinding(void) { EmberBindingTableEntry entry; EmberStatus status; entry.type = EMBER_MULTICAST_BINDING; entry.local = emberMakeTerminal(ENDPOINT, 1); entry.remote = emberMakeTerminal(ENDPOINT, 1); entry.identifier[0] = HIGH_BYTE(MULTICAST_ID); entry.identifier[1] = LOW_BYTE(MULTICAST_ID); status = emberSetBinding(MULTICAST_BINDING_INDEX, &entry);
120-0065-000A Final
Page 38
Sending Messages
Open a Connection
Note: This section applies one to full-function devices and the sending of sequenced unicast
messages. If you are using sequenced messages, you must first use emberOpenConnection() to open a connection to the correct binding. Then you must wait for the handler function, emberConnectionStatusHandler(), to inform you that the connection is ready for use.
120-0065-000A Final
Page 39
Sending Messages
// get a random piece of data data = firmGetRandom(); emberDebugPrintf("sensor has data ready: 0x%2x \r\n", data); // the data - msg type, my address and data globalBuffer[0] = MSG_DATA; MEMCOPY(&(globalBuffer[1]), emberGetEui64(), 8); for (i=0; i<(SEND_DATA_SIZE/2); i++) { globalBuffer[9+(i*2)] = HIGH_BYTE(data); globalBuffer[10+(i*2)] = LOW_BYTE(data); } // copy the data into a packet buffer buffer = emberFillLinkedBuffers(globalBuffer, 9 + SEND_DATA_SIZE); // check to make sure a buffer is available if (buffer == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR [data], OUT OF BUFFERS\r\n"); return; } // send the message status = emberSendDatagram(SINK_BINDING_INDEX, buffer); // done with the packet buffer emberReleaseMessageBuffer(buffer); // print a status message emberSerialPrintf(APP_SERIAL, "TX [DATA] status: 0x%x data: 0x%2x\r\n", status, data); }
120-0065-000A Final
Page 40
Sending Messages
When this message is received by a full-function device, the first byte in the received message buffer is the first byte of data, not the number of bytes. The length of the message is determined via the message buffer APIs. Sample of Creating and Sending a Message from app\sensor\sensor-leaf // The sensor-leaf fabricates data and sends it out to the sink void sendLeafData(void) { int16u data; EmberStatus status; int8u localTerminal = 9; int8u remoteTerminal = 9; // length is msg type (1 byte), eui (8 bytes), and data // (length byte does not count in length) int8u length = SEND_DATA_SIZE + 9; int8u i; // get a random piece of data data = firmGetRandom(); //emberDebugPrintf("sensor-leaf has data ready: 0x%2x \r\n", data); // the data - length, msg type, my address and data globalBuffer[0] = length; globalBuffer[1] = MSG_LEAF_DATA; MEMCOPY(&(globalBuffer[2]), emberGetEui64(), 8); for (i=0; i<(SEND_DATA_SIZE/2); i++) { globalBuffer[10+(i*2)] = HIGH_BYTE(data); globalBuffer[11+(i*2)] = LOW_BYTE(data); } // send the message status = emberSendDatagram(sinkEui64, localTerminal, remoteTerminal, globalBuffer); // print a status message emberSerialWaitSend(APP_SERIAL); emberSerialPrintf(APP_SERIAL, "TX [leaf DATA] status: 0x%x data: 0x%2x\r\n", status, data); emberSerialWaitSend(APP_SERIAL); }
120-0065-000A Final
Page 41
Receiving Messages
Receiving Messages
A device receives any messages that are sent using its address as the destination. The message is handed up to the application with a status of EMBER_NULL_BINDING in the following cases: When a binding table entry does not exist for the source of the message. When the binding table only has the 64-bit address and the message is sent using the 16-bit address.
Create Binding
Note: This section applies to full-function devices and multicast messages only.
For full-function devices to receive a multicast message, they must create a binding to the appropriate multicast group. This is done in the same way as creating and binding to send to the multicast group. There is no need to create a separate binding. However, the same binding can be used for sending and receiving.
120-0065-000A Final
Page 42
Receiving Messages
Leaf node devices will receive the incoming message as an array with the first byte indicating the number of bytes to follow. Full-function devices will receive a callback for each matching binding in the binding table. The function parameters will be populated as per the API documentation. The message buffer may be modified, but will stay modified for any subsequent calls to emberIncomingMessageHandler() on other matching bindings. The message buffer must not be freed by the application. The binding index will contain either the binding match or EMBER_NULL_BINDING if no binding matched an incoming unicast message. Note that if no binding matches, it does necessarily follow that no binding to the sending node is present in the table. Bindings are associated with the 64-bit address. Messages are sent using a 16-bit short address. By calling emberNoteSendersBinding() on a received message, the binding table will be updated with the short address. The short address can sometimes change during network operation. In this event, the binding table entry may not match, and the old binding should be deleted and a new binding created. Sample Code for Incoming Message Handler from app\simple\sensor-ffd void emberIncomingMessageHandler(EmberIncomingMessageType type, int8u localTerminal, int8u bindingTableIndex, EmberMessageBuffer message){ // Called with an incoming message int8u* data; int8u length; data = emberMessageBufferContents(message); length = emberMessageBufferLength(message); // make sure this is a valid packet of sensor/sink app // it must have a type, then 64 bit address (9 bytes minimum) if (length < 9) { emberSerialPrintf(APP_SERIAL, "RX [bad packet] of length %x\r\n", length); return; } // handle the incoming message switch (data[0]) { case MSG_SINK_ADVERTISE: emberSerialPrintf(APP_SERIAL, "RX [sink advertise] from: "); printEUI64(APP_SERIAL, &data[1]); if ((mainSinkFound == FALSE) && (waitingForSinkReadyMessage == FALSE) && (waitingToRespond == FALSE)) { waitingToRespond = TRUE;
120-0065-000A Final
Page 43
Receiving Messages
emberSerialPrintf(APP_SERIAL, "; processing message\r\n"); MEMCOPY(&(dataBuffer[0]), data, 9); // wait for 1 - 5 seconds before responding respondTimer = ((firmGetRandom()) % 20) + 5; timeToWaitForSinkReadyMessage = TIME_TO_WAIT_FOR_SINK_READY; emberSerialPrintf(APP_SERIAL, "EVENT waiting %x ticks before reponding\r\n", respondTimer); } else { emberSerialPrintf(APP_SERIAL, "; ignoring\r\n"); } break; case MSG_SENSOR_SELECT_SINK: emberSerialPrintf(APP_SERIAL, "RX [sensor select sink] from: "); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "; this is an ERROR]\r\n"); break; case MSG_SINK_READY: emberSerialPrintf(APP_SERIAL, "RX [sink ready] from: "); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "; will start sending data\r\n"); waitingForSinkReadyMessage = FALSE; mainSinkFound = TRUE; sendDataCountdown = SEND_DATA_RATE; startSensorBeacons(); break; case MSG_DATA: emberSerialPrintf(APP_SERIAL, "RX [DATA] from: "); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "; this is an ERROR\r\n"); break; case MSG_MULTICAST_HELLO: emberSerialPrintf(APP_SERIAL, "RX [multicast hello] from: "); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "\r\n"); break; case MSG_LEAF_JOIN:
120-0065-000A Final
Page 44
emberSerialPrintf(APP_SERIAL, "RX [leaf join] from: "); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "; processing message\r\n"); emberSerialWaitSend(APP_SERIAL); handleLeafJoin(data); break; case MSG_LEAF_DATA: // we just heard from this binding table index ticksSinceLastHeard[bindingTableIndex] = 0; emberSerialPrintf(APP_SERIAL, "RX [leaf DATA] from: "); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "; len 0x%x / data 0x%x%x\r\n", length, data[9], data[10]); forwardLeafDataToSink(message); break; default: emberSerialPrintf(APP_SERIAL, "RX [unknown (%x)] from: ", data[0]); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "; ignoring\r\n"); } }
120-0065-000A Final
Page 45
Receiving Beacons
Beacons are received in the incomingBeaconHandler() callback. Two parameters are passed to the application in the callback: An application-specific beacon payload and the EUI64 address of the node transmitting the beacon. On the leaf node, if the payload is not NULL, the first byte is the length of the payload. Bindings are not used for receiving beacons.
Tracking Beacons
Beacon tracking is used to notify the application when the stack has stopped receiving beacons from a beaconing node. The emberBeaconTrack() API call enables or disables beacon tracking. When beacon tracking is enabled and the stack has not heard a beacon in four consecutive beacon periods, the EMBER_MAC_BEACON_LOSS status is passed to the application in the emberStackStatusHandler() callback. Beacon tracking is automatically disabled when the beacon loss status is passed up to the application. See the EmberNet Stack Interface Guide documentation on emberBeaconTrack()for details on how to enable or disable beacon tracking. The EMBER_MAC_BEACON_LOSS status is a one-time event and has no effect on the EMBER_NETWORK_UP status.
Going to Sleep
To put the EmberNet stack to sleep, call emberSleep(). When emberSleep() returns TRUE, the network stack is no longer operating and the radio has been put into idle mode. Because it takes time for the stack to complete processing, the microprocessor should not be put to sleep until emberSleep() returns TRUE. Once the radio is asleep, the microprocessor must be put to sleep. Ember provides utility functions in the appropriate HAL micro.c file to do this, but the application must ensure that the microprocessor has some mechanism configured to wake up again. Be sure to disable the watchdog timer before sleeping, to avoid a watchdog reset immediately upon wakeup.
Waking Up
Once the microprocessor has waked up, it should reinitialize any peripherals it was using and then call emberWakeUp() to reinitialize the stack and wake up the radio. The emberWakeUp() call should only be used once. Sample of Sleep and Wake Code from app\sensor\sensor-leaf // manage the sleeping and waking up of the leaf if (isAwake == TRUE) { // *** leaf is awake *** EmberNet Application Development Guide 120-0065-000A Final Page 46
Callbacks
awakeTimer = awakeTimer - 1; if (awakeTimer < 1) { // delay going to sleep if in the middle of a protocol exchange if ((waitingToRespond == TRUE) || (waitingForSinkAllowJoin == TRUE)) { awakeTimer = 1; } else { // put the node to sleep status = emberSleep(); emberSerialPrintf(APP_SERIAL, "EVENT: leaf node going to sleep, status 0x%x\r\n", status); isAwake = FALSE; sleepTimer = LEAF_SLEEP_TIME; } } } else { // *** leaf is asleep *** sleepTimer = sleepTimer - 1; if (sleepTimer < 1) { emberWakeUp(); emberSerialPrintf(APP_SERIAL, "EVENT: leaf node waking up\r\n"); isAwake = TRUE; awakeTimer = LEAF_AWAKE_TIME; } } } }
Callbacks
EmberNet requires a number of callbacks to be implemented so that the stack can inform the application of events and status changes. The current list of callbacks for full-function devices (as of version 3.3 of the EmberNet stack) is: emberEndpointFoundHandler() emberMessageSent() emberIncomingMessageHandler() emberConnectionStatusHandler() emberApplicationBufferWasFreed() emberRemoteSetBindingHandler()
120-0065-000A Final
Page 47
Callbacks
emberStackStatusHandler() emberIncomingBeaconHandler() (optional) Details on these callbacks can be found in the EmberNet Stack Interface Guide.
120-0065-000A Final
Page 48
CHAPTER 5
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 How the Sample Sensor Application Works . . . . . . . . . . . . . . . . . . . . . . . . 50 Writing the Main Application Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Initializing the Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Associating to the Network . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Initializing Application Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Writing the Event Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Establishing Interfaces and Endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Declaring Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Declaring Endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 Binding Devices Together. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Commissioning the Network . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Sending and Receiving Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Sending Multicasts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Receiving Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Setting the Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Sending Datagrams. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Leaf Node Sample Sensor Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Design of the Leaf Node Sensor Sample Application . . . . . . . . . . . . . 62 Leaf Node Development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Leaf Node Initialization and Startup. . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Leaf Node Network Acquisition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Routing from a Leaf Node Device. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Routing to a Leaf Node Device. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Beacons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Battery Life . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
120-0065-000A Final
Page 49
Overview
Overview
This chapter draws on the general design and implementation topics covered earlier to demonstrate the subset of EmberNet stack calls needed for a basic sensor data collection application, using the Developer Kit sample sensor applications to illustrate the discussion. This type of application involves many key stack API abstractions and calls, and thereby the topics covered in this chapter will be of use to developers working on all types of applications. Following discussion of the general sensor network, the chapter covers modification of the sensor application to support leaf node devices. The sample sensor application is a simplified version of typical industrial control or monitoring applications in which multiple sensors periodically report data to one or more data collection points, called data sinks. The application follows the usual practice of requiring that data sinks periodically announce their presence and that sensors respond to the announcements. (This is preferred to having the sensors announce their presence, because there are many more sensors than sinks.) The discussion of the sample application assumes a single sink, but it can easily be generalized to more sinks. The code examples used in this chapter are from the Developer Kit sensor sample application. This application is contained in three files: common.h, sensor.c, and sink.c. The leaf node sensor application can be found in sensor-ffd.c and sensor-leaf.c. See the source code for additional comments and code features not discussed here. The following discussion frequently references Ember HAL functions, structures, and data types. For information about these constructs, see the Hardware Abstraction Layer Interface Guide.
Figure 5-1: Program flow and message types in the sample sensor application Sink Node Sink advertise Multicast Unicast Set binding to sink Sensor Node
120-0065-000A Final
Page 50
120-0065-000A Final
Page 51
120-0065-000A Final
Page 52
They will all have an ad hoc interface, which will be used on a symmetric endpoint. This means that the protocol will be the same for every node that implements the interface. Protocols sometimes involve different client and server messages; these are designated by selecting the corresponding endpoints. Applications in which many data sources report to a data aggregator should specify that the interface resides on an aggregation endpoint. Each message implements a different protocol, as declared by the specification field in each structure. Because the interface is of the ad hoc type, the value for this field is arbitrary and uncoordinated. Note that there is a risk of a potential ID conflict with other ad hoc interfaces. Note that the structures are declared PGM, which means that they are stored in program memory and so cannot be changed at runtime.
Declaring Endpoints
Endpoints are declared with the EmberEndpointDescription structure. An endpoint abstractly represents an embedded device connected to the Ember radio. For example, one radio could have an on/off switch attached. This would be one device, represented by one endpoint. If we also added a dimmer switch, it would be a good idea to represent it as a different endpoint. The following excerpt represents the distributed sensor node in the sensor data collection example. // interface IDs #define COMMON_IFACE 1 #define SENSOR_IFACE 2 static EmberEndpointDescription PGM sensorEndpoint = { {COMMON_IFACE, &commonInterfaceId}, {SENSOR_IFACE, &sensorInterfaceId} }; This structure tells the stack that there is a sensor connected to the radio. Devices sometimes accept messages in multiple protocols, so the endpoint definition lists the interfaces the device supports. Because devices do not usually change their communication characteristics, the endpoint definition is not something that changes on the fly, and thus the structure can be stored in flash to preserve RAM. The stack needs a list of the endpoints that this radio will support: // emberEndpointCount and emberEndpoints MUST be defined by // the application. This is how EmberNet stack accesses the // application-defined endpoints and interfaces. int8u emberEndpointCount = 1; EmberEndpoint emberEndpoints[] = { {ENDPOINT, &sensorEndpoint} }; // Interface IDs #define COMMON_IFACE 1 #define SENSOR_IFACE 2 EmberNet Application Development Guide 120-0065-000A Final Page 54
#define SINK_IFACE
// define the actual endpoint (stored in PGM memory) static EmberEndpointDescription PGM sensorEndpoint = { {COMMON_IFACE, &commonInterfaceId}, {SENSOR_IFACE, &sensorInterfaceId} }; static EmberEndpointDescription PGM sinkEndpoint = { {COMMON_IFACE, &commonInterfaceId}, {SINK_IFACE, &sinkInterfaceId} } An endpoint can support multiple interfaces. The combination of endpoint and interface is called a terminal. This combination can be thought of as a service provided or subscribed to. In the preceding lighting example, one of the interfaces we may establish in the on/off switch is the actual on/off toggle itself. We might also want to add a control interface to this endpoint, so that we can tell the switch how often to report a given datum for a node (for example, battery life). The stack can support different node configurations, and the application can detect endpoint configuration dynamically. For example, suppose your product is available to the end consumer in three configurations. The first configuration contains devices A and B, the second contains devices A and C, and the third contains only device A. If you want to use the same application image in all versions, include endpoint descriptions for devices A, B, and C in flash. At power-up, the application detects which devices are connected to the radio, sets emberEndpointCount to either 2 or 1, and populates the emberEndpoints[] array only with the endpoints found. The variables emberEndpointCount and emberEndpoints[] must be declared in every application, because the stack uses them. They are left to the application because the amount of RAM is usually limited and the size of emberEndpoints[] depends on the application.
Note: While most of the index and ID values are arbitrary in their valid ranges, endpoint 0 is
reserved. emberEndpointCount is the number of endpoints defined by the user application. It does not include endpoint 0 in its count.
120-0065-000A Final
Page 55
entry.type = EMBER_MULTICAST_BINDING; entry.local = emberMakeTerminal(ENDPOINT, 1); entry.remote = emberMakeTerminal(ENDPOINT, 1); entry.identifier[0] = HIGH_BYTE(MULTICAST_ID); entry.identifier[1] = LOW_BYTE(MULTICAST_ID); status = emberSetBinding(MULTICAST_BINDING_INDEX, &entry); emberSerialPrintf(APP_SERIAL, "EVENT: setting multicast binding, status is 0x%x\r\n", status); } As the name indicates, the EmberBindingTableEntry type field indicates when the entry: Is not used. Is used to exchange messages with a single recipient (unicasts). Is used to exchange multicasts. The identifier field allows two bytes for multicasts and eight bytes for unicasts, to distinguish the association corresponding to this binding from any others. In the case of a multicast, the stack uses the identifier field to filter messages, so both the sender and receiver must use the same values. The local terminal identifies which interface on what endpoint is set up for these messages when they are inbound, and we set the field here in case we want to check for multiple controllers in the network. Unlike the datagrams discussed below, multicast messages do not get sent to a specific remote terminal, so the remote field is not used.
// the data - msg type, and sink address globalBuffer[0] = MSG_SINK_ADVERTISE; MEMCOPY(&(globalBuffer[1]), emberGetEui64(), 8); // copy the data into a stack buffer buffer = emberFillLinkedBuffers((int8u*) globalBuffer, 9); // check to make sure a buffer is available if (buffer == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR [sink advertise], OUT OF BUFFERS\r\n"); return; } // send the message status = emberSendMulticast(MULTICAST_BINDING_INDEX, buffer); // done with the stack buffer emberReleaseMessageBuffer(buffer); } This creates a message containing the 64-bit identifier. Sensor nodes that set a binding table entry with the same multicast identifier field will be able to receive this message. As you will see later in this chapter, these sensors use the controller EUI to create a binding. Because the sending node does not keep a list of the recipients of multicast messages, there is no acknowledgement mechanism for multicasts. Consequently, the user application does not receive any indication of whether they were delivered. Note that emberFillStackBuffer() allocates a message buffer from the stack buffer pool. This function is responsible for releasing buffers it allocates.
Receiving Messages
Once the sensor hears the sink advertisement, it sets a binding and sends a unicast message to the sink, which then sets a corresponding binding entry. The user application receives messages via the emberIncomingMessageHandler() callback, which the stack requires in every application. The following code handles all incoming messages because the same code is running both the sensor nodes and the sink nodes. void emberIncomingMessageHandler(EmberIncomingMessageType type, int8u localTerminal, int8u bindingTableIndex, EmberMessageBuffer message){ // called with an incoming message int8u* data; int8u length; data = emberMessageBufferContents(message); length = emberMessageBufferLength(message); // make sure this is a valid packet of sensor/sink app EmberNet Application Development Guide 120-0065-000A Final Page 57
// it must have a type, then 64-bit address (9 bytes minimum) if (length < 9) { emberSerialPrintf(APP_SERIAL, "RX [bad packet] of length %x\r\n", length); return; } emberSerialPrintf(APP_SERIAL, "RX from: ["); printEUI64(APP_SERIAL, &data[1]); emberSerialPrintf(APP_SERIAL, "] "); // handle the incoming message switch (data[0]) { case MSG_SINK_ADVERTISE: if (mainSinkFound == FALSE) { emberSerialPrintf(APP_SERIAL, "[sink advertise]\r\n"); handleSinkAdvertise(data); } else { emberSerialPrintf(APP_SERIAL, "[sink advertise], ignoring\r\n"); } break; The handler above is the callback for incoming messages. In the full sample code this handler contains more message types, as they have been defined by the application. The code sorts messages based on these types. In more complex systems, nodes may receive datagrams in multiple protocols for the various devices they support. In these cases, messages should be sorted further by localTerminal.
MEMCOPY(entry.identifier, &(data[1]), 8); status = emberSetReplyBinding(SINK_BINDING_INDEX, &entry); if (status == EMBER_SUCCESS) { emberSerialPrintf(APP_SERIAL, "EVENT: sink set binding to sensor ["); printEUI64(APP_SERIAL, &(data[1])); emberSerialPrintf(APP_SERIAL, "]\r\n"); // send a message picking this sink // the data globalBuffer[0] = MSG_SENSOR_SELECT_SINK; MEMCOPY(&(globalBuffer[1]), emberGetEui64(), 8); // copy the data into a stack buffer buffer = emberFillLinkedBuffers((int8u*)globalBuffer, 9); // check to make sure a buffer is available if (buffer == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR can't send [sensor select sink], OUT OF BUFFERS\r\n"); return; } } // send the message status = emberSendDatagram(SINK_BINDING_INDEX, buffer); // done with the stack buffer emberReleaseMessageBuffer(buffer); }
Sending Datagrams
To send and receive datagrams, the stack requires an appropriate binding. The code example sets such a binding based on the EUI ID sent in the sink advertisement. We first verify that we do not already have binding for exchanging unicasts with the sink. To check for this, call emberGetBinding() and check the type field to ensure that it is an EMBER_UNUSED_BINDING. Because emberGetBinding() provides a copy of the binding rather than returning a pointer, this code provides storage for an EmberBindingTableEntry structure. The stack operates this way so that it can store some bindings in nonvolatile memory and others in RAM, depending upon the binding index used. The microprocessor on the Developer Kit RF communication module handles references to these memory types differently, so returning a pointer is inconvenient for the user application. Do not use emberBindingIsActive() to check the validity of a binding. This function checks something different: It determines whether the binding can safely be changed by checking for in-flight messages and active connections. If neither exist for the binding in question, a perfectly valid binding entry will return FALSE when the active check is made.
120-0065-000A Final
Page 59
// The sensor fabricates data and sends it out to the sink. void sendData(void) { int16u data; EmberStatus status; EmberMessageBuffer buffer = 0; int8u i; // get a random piece of data data = firmGetRandom(); emberDebugPrintf("sensor has data ready: 0x%2x \r\n", data); // if we have a bind index for a sink node then // send it our data if (mainSinkFound == TRUE) { // the data - msg type, my address, and data globalBuffer[0] = MSG_DATA; MEMCOPY(&(globalBuffer[1]), emberGetEui64(), 8); for (i=0; i<(SEND_DATA_SIZE/2); i++) { globalBuffer[9+(i*2)] = HIGH_BYTE(data); globalBuffer[10+(i*2)] = LOW_BYTE(data); } // copy the data into a stack buffer buffer = emberFillLinkedBuffers(globalBuffer, 9 + SEND_DATA_SIZE); // check to make sure a buffer is available if (buffer == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR [data], OUT OF BUFFERS\r\n"); return; } // send the message status = emberSendDatagram(SINK_BINDING_INDEX, buffer); // done with the stack buffer emberReleaseMessageBuffer(buffer); // print a status message printEUI64(APP_SERIAL, emberGetEui64()); emberSerialPrintf(APP_SERIAL, "TX [DATA] status: 0x%x data: 0x%2x\r\n", status, data); } }
120-0065-000A Final
Page 60
The code runs on the sensor and sends the datagram, beginning by checking the binding to ensure that it has been set. Note again that we retrieve the binding table entry and check the type field rather than use emberBindingEntryIsActive(). The call to emberFillLinkedBuffers() allocates a buffer and fills it with the sensor data. After sending the datagram, it is the applications responsibility to release the buffer. In most applications it is important to verify that the datagram was delivered successfully. In this example, the sensor can reuse memory once a report is successfully delivered. To determine the fate of a sent message, we need to fill in the callback function emberMessageSent(). // Called when a message has completed transmission -// status indicates whether or not the message was // successfully transmitted. void emberMessageSent(int8u bindingTableIndex, EmberMessageBuffer message, EmberStatus status){ // print an error if a message can't be sent int8u* data; int8u length; data = emberMessageBufferContents(message); length = emberMessageBufferLength(message); if (status != EMBER_SUCCESS) { emberSerialPrintf(APP_SERIAL, "Error 0x%x: can't send message ", status); if (length > 0) { emberSerialPrintf(APP_SERIAL, "0x%x ", data[0]); if (length > 8) { emberSerialPrintf(APP_SERIAL, " to:"); printEUI64(APP_SERIAL, &(data[1])); } } } emberSerialPrintf(APP_SERIAL, "\r\n"); } This code examines the status associated with the datagram sent in the message. Acknowledged datagrams are reported as EMBER_SUCCESS. Messages not acknowledged within the timeout interval are lost. In this example, we print an error. Other applications, such as those with moving or power-cycled nodes, might deal with failed messages differently.
120-0065-000A Final
Page 61
Note: Customers upgrading from EmberNet stack version 3.2 should be aware that although a
3.2 device can communicate with a 3.3 full-function device, the changes required to support leaf nodes mean that 3.3 leaf node devices and 3.2 devices cannot communicate with each other. Version 3.3 full-function devices are compatible with version 3.2 fullfunction devices and can communicate with them. For information about specific functions available to leaf node devices and full-function devices, see the Leaf Node Interface Guide and the EmberNet Stack Interface Guide, respectively.
Sink SA SSS SR D
Sensor
SA = Sink advertise (multicast), len=9 [MSG_SINK_ADV, eui(8)] SSS = Sensor select sink (datagram), len=9 [MSG_SENSOR_SELECT_SINK, eui(8)] SR = Sink ready (datagram), len=9 [MSG_SINK_READY, eui(8)] D = Data (datagram), len=variable [MSG_DATA, eui(8), data(2-84)]
120-0065-000A Final
Page 62
By contrast, in the leaf node version of the sensor-sink application, the messaging model looks like this:
Sink SB LJ SAJ LD
Sensor-to-Leaf Node
SB = Sink beacon (beacon), len=3 [MSG_SINK_BEACON, BEACON_SINK_KEY(2)] LJ = Leaf join (link datagram), len=9 [MSG_SENSOR_ALLOW_JOIN, eui(8)] SAJ = Sink allow join (link datagram) LD = Leaf data In a wider network that includes FFDs other than the sink device, the following messaging model would be used. It mixes features from the original sensor-sink application and the leaf node sample application:
Sink SA SSS SR
Sensor-to-FFD
SB LJ RAJ LD FD
SA = Sink advertise (multicast), len=9 [MSG_SINK_ADV, eui(8)] SSS = Sensor select sink (datagram), len=9 [MSG_SENSOR_SELECT_SINK, eui(8)] SR = Sink ready (datagram), len=9 [MSG_SINK_READY, eui(8)] SB = Sensor beacon, len=3 [MSG_SENSOR_BEACON, BEACON_SENSOR_KEY(2)] LJ = Leaf join (link datagram), len=9 [MSG_SENSOR_ALLOW_JOIN, eui(8)] RAJ = Sensor allow join LD = Leaf data FD = Forwarded datagram
120-0065-000A Final
Page 63
120-0065-000A Final
Page 64
120-0065-000A Final
Page 65
globalBuffer[1] = HIGH_BYTE(BEACON_SENSOR_KEY); globalBuffer[2] = LOW_BYTE(BEACON_SENSOR_KEY); // copy the data into a packet buffer payload = emberFillLinkedBuffers((int8u*) globalBuffer, 3); // check to make sure a buffer is available if (payload == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR [sensor beacon], OUT OF BUFFERS\r\n"); return; } // beacon Interval of 10 is roughly 16 seconds; status = emberTransmitBeacons(payload, beaconInterval, activeTime); // check status if (status != EMBER_SUCCESS) { emberSerialPrintf(APP_SERIAL, "TX ERROR [sensor beacon], status 0x%x\r\n", status); } else { emberSerialPrintf(APP_SERIAL, "EVENT: sensor has started sending beacons, status 0x%x\r\n", status); } emberSetOutgoingBeaconNotifyCounter(TRUE, 1); } The following excerpt from sensor_leaf.c shows the establishment of the leaf association with either a sensor or the sink. A sensor prefers to associate with the sink, but if a sink has not been found it will associate with a beaconing FFD sensor. // is this a beacon from a sink? if ((data[0] == MSG_SINK_BEACON) && (data[1] == HIGH_BYTE(BEACON_SINK_KEY)) && (data[2] == LOW_BYTE(BEACON_SINK_KEY))){ // if we don't have a sink then use this one if ((sinkFound == FALSE) && (waitingForSinkAllowJoin == FALSE) &&(waitingToRespond == FALSE)) { // if we have an ffd-sensor, ditch it ffdSensorFound = FALSE; sinkFound = TRUE; // we are acting on this beacon actingOnBeacon = TRUE; } else { emberSerialPrintf(APP_SERIAL, "; ignoring\r\n"); EmberNet Application Development Guide 120-0065-000A Final Page 66
return; } } // is this a beacon from a sensor? if ((data[0] == MSG_SENSOR_BEACON) && (data[1] == HIGH_BYTE(BEACON_SENSOR_KEY)) && (data[2] == LOW_BYTE(BEACON_SENSOR_KEY))){ // if we don't have a sink then use this one if ((sinkFound == FALSE) &&(waitingForSinkAllowJoin == FALSE) && (waitingToRespond == FALSE) && (ffdSensorFound == FALSE)) { // if we have an ffd-sensor, ditch it ffdSensorFound = TRUE; // we are acting on this beacon actingOnBeacon = TRUE; } else { emberSerialPrintf(APP_SERIAL, "; ignoring\r\n"); return; } } The leaf node device uses the following code to send the join message to the FFD from which it heard the beacon. This join request establishes who the leaf node device communicates with in the network. // this sends a leaf join to the selected device (either sink // or ffd-sensor) void sendLeafJoin() { EmberStatus status; int8u localTerminal = 9; int8u remoteTerminal = 9; // first byte is length: msg type(1) + eui(8) = 9 globalBuffer[0] = 9; globalBuffer[1] = MSG_LEAF_JOIN; // the data MEMCOPY(&(globalBuffer[2]), emberGetEui64(), 8); // send the message status = emberSendDatagram(sinkEui64, localTerminal, remoteTerminal, globalBuffer); }
120-0065-000A Final
Page 67
The FFD uses the following code to establish a binding with a leaf node from which it has received a join request. void handleLeafJoin(int8u* data) { EmberBindingTableEntry entry; EmberStatus status; EmberMessageBuffer buffer = 0; int8u freeBindingLocation = 0; int8u duplicate = 0; // We set the binding to the leaf so that we can send a message // to the leaf indicating that it has successfully joined // the network. // No binding would be necessary for applications that // do not send messages to their leaf nodes. // Find a free binding location to put this binding freeBindingLocation = findFreeBindingLocation(); if (freeBindingLocation == EMBER_NULL_BINDING) { emberSerialPrintf(APP_SERIAL, "WARNING: no more free bindings for this sensor-ffd-node\r\n"); return; } // check for a duplicate message, if duplicate then ignore duplicate = checkForBindingDuplicates(&(data[1])); if (duplicate == TRUE) { emberSerialPrintf(APP_SERIAL, "receive duplicate message from "); printEUI64(APP_SERIAL, &(data[1])); emberSerialPrintf(APP_SERIAL, ", ignoring\r\n"); return; } // add a binding to freeBindingLocation entry.type = EMBER_LINK_BINDING; entry.local = emberMakeTerminal(ENDPOINT, 1); entry.remote = emberMakeTerminal(ENDPOINT, 1); MEMCOPY(entry.identifier, &(data[1]), 8); status = emberSetReplyBinding(freeBindingLocation, &entry); ticksSinceLastHeard[freeBindingLocation] = 0; emberSerialPrintf(APP_SERIAL, "EVENT: sensor-ffd set binding %x to node [",freeBindingLocation ); printEUI64(APP_SERIAL, &(data[1])); emberSerialPrintf(APP_SERIAL, "]\r\n");
120-0065-000A Final
Page 68
if (status != EMBER_SUCCESS) { emberSerialPrintf(APP_SERIAL, "TX ERROR [sensor allow join], binding failed, status 0x%s\r\n", status); return; } // send a message indicating success globalBuffer[0] = MSG_SENSOR_ALLOW_JOIN; MEMCOPY(&(globalBuffer[1]), emberGetEui64(), 8); // copy the data into a packet buffer buffer = emberFillLinkedBuffers((int8u*)globalBuffer, 9); // check to make sure a buffer is available if (buffer == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR [sensor allow join], OUT OF BUFFERS\r\n"); status = emberDeleteBinding(freeBindingLocation); emberSerialPrintf(APP_SERIAL, "EVENT deleting binding 0x%x, status 0x%x\r\n", freeBindingLocation, status); return; } // send the message status = emberSendDatagram(freeBindingLocation, buffer); // done with the packet buffer emberReleaseMessageBuffer(buffer); At this point, beacons have been initiated on an FFD, the leaf node device has heard the beacon and sent a join request, and the FFD has used this join to ask if can set a binding to the leaf node device.
120-0065-000A Final
Page 69
emberSerialPrintf(APP_SERIAL, "; processing message\r\n"); emberSerialWaitSend(APP_SERIAL); MEMCOPY(&(sinkEui64[0]), &(sender[0]), 8); // start tracking the beacon status = emberBeaconTrack(TRUE); beacon, status 0x%x\r\n", status); // wait for 0.1 - 3 seconds before responding respondTimer = ((firmGetRandom()) % 15) + 1; timeToWaitForSinkReadyMessage = TIME_TO_WAIT_FOR_SINK_READY; emberSerialPrintf(APP_SERIAL, "EVENT: waiting %x ticks before reponding\r\n", respondTimer); emberSerialWaitSend(APP_SERIAL); return; } ... if (readyToSendData == TRUE) { sendDataCountdown = sendDataCountdown - 1; if (sendDataCountdown < 1) { // delay sending data if we are asleep if (isAwake == FALSE) { sendDataCountdown = 1; } else { sendDataCountdown = SEND_DATA_RATE; sendLeafData(); } } } // The sensor-leaf fabricates data and sends it out to the sink void sendLeafData(void) { int16u data; EmberStatus status; int8u localTerminal = 9; int8u remoteTerminal = 9; // length is msg type (1 byte), eui (8 bytes), and data // (length byte does not count in length) int8u length = SEND_DATA_SIZE + 9; int8u i; ...
120-0065-000A Final
Page 70
// the data - length, msg type, my address and data globalBuffer[0] = length; globalBuffer[1] = MSG_LEAF_DATA; MEMCOPY(&(globalBuffer[2]), emberGetEui64(), 8); for (i=0; i<(SEND_DATA_SIZE/2); i++) { globalBuffer[10+(i*2)] = HIGH_BYTE(data); globalBuffer[11+(i*2)] = LOW_BYTE(data); } // send the message status = emberSendDatagram(sinkEui64, localTerminal, remoteTerminal, globalBuffer); The user application on the FFD can use the EmberNet stack API to route the message to its final destination. In some applications the FFD may already know the final destination. For example, in a sensor network the FFD will already know about the gateway device and will be able to route the packet accordingly. In more complicated applications, the leaf node device may need to specify the final destination within its own packet. Then the FFD can interpret the destination and relay accordingly. For example, in networks that place a premium on reliability, leaf node devices may broadcast messages to all known eligible FFDs, specifying a unique identifier in the payload that allows the destination to avoid processing duplicates. In networks that place a premium on battery life, leaf node devices may instead first try to transmit to a known FFD and ignore several failures before spending extra processor and radio time to rediscover eligible FFDs. The following excerpt from sensor_ffd.c handles the incoming data from a leaf node device sensor and sends it to an associated sink. // forwards a datagram received from a leaf to the sink that this // sensor-ffd is currently hooked up to void forwardLeafDataToSink(EmberMessageBuffer leafMessage) { EmberStatus status; EmberMessageBuffer buffer = 0; int8u* data; int8u length; // check for the case where the sink is gone but the leaf hasn't // noticed that this sensor-ffd is no longer beaconing if (mainSinkFound == FALSE) { emberSerialPrintf(APP_SERIAL, "ERROR: can't forward leaf data; no longer have a sink!\r\n"); return; } // pull out the data from the message data = emberMessageBufferContents(leafMessage); EmberNet Application Development Guide 120-0065-000A Final Page 71
length = emberMessageBufferLength(leafMessage); // the data - msg type, my address and data globalBuffer[0] = MSG_PROXY_DATA; // don't copy over the msg type from the leaf MEMCOPY(&(globalBuffer[1]), &(data[1]), length); // copy the data into a packet buffer buffer = emberFillLinkedBuffers(globalBuffer, length); // check to make sure a buffer is available if (buffer == EMBER_NULL_MESSAGE_BUFFER) { emberSerialPrintf(APP_SERIAL, "TX ERROR [proxy data], OUT OF BUFFERS\r\n"); return; } // send the message status = emberSendDatagram(SINK_BINDING_INDEX, buffer); // done with the packet buffer emberReleaseMessageBuffer(buffer);
120-0065-000A Final
Page 72
Beacons
The design requirements of your project will decide whether you should use beacons and how to set them up. FFDs can send beacons, and both FFDs and leaf node devices can receive and track them. See Sending and Receiving Beacons on page 45 if you intend to use beacons in your application. As described above, beacons can be used to synchronize and advertise a network. When they are used for advertisement, a leaf node device can look for these to know if the network is on, or in the area.
Battery Life
The key parameters determining battery life are availability, minimum necessary on-time, and, for networks with mobile nodes, discovery time. If extending battery life is important for your application, the leaf node device should sleep as much as possible. In dynamic networks, discovery time is established by the amount of time a mobile device is within range of the network. For example, if the device will be within range for only a few minutes, the time available for discovery will be short. A requirement for fast discovery times means that leaf node device sleep-wake cycles must be short, which will reduce battery life. In fixed networks, network discovery time does not affect battery life because leaf node devices only perform discovery when they associate to a particular FFD, a process that takes only a few minutes. A leaf node devices sleep-wake cycle determines its responsiveness to a network message. Some leaf node devices do not need a sleep-wake cycle (for example, a light switch leaf node device that only wakes and sends a message when the switch is changed). A leaf node device that only wakes on actuation will have maximum battery life, but the network can never communicate with it. Typically, such a device periodically wakes and communicates with its FFD as a heartbeat, to validate that the device is present and the battery power is adequate. Alternatively, some leaf node devices are required to receive data from the network at regular intervals. For example, a leaf node device that is a thermostat may not only send data to its FFD but also display the control state of the local HVAC unit. The application for such a leaf node device should select an update rate that is suitable for the responsiveness of this display but balances with the battery life of the device. If the device is always on, any change of state will be displayed very quickly, but the battery life will be short. If the developer determines that a four-second display update rate is acceptable, the leaf node device can have a four-second wake/sleep cycle.
120-0065-000A Final
Page 73
CHAPTER 6
Developing Applications
Development Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Developing Full-Function Device Applications . . . . . . . . . . . . . . . . . . . 74 Developing Leaf Node Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Building Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 About IAR Embedded Workbench . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Creating an IAR Workspace File from the Application Template File . 75 Familiarizing Yourself with the IAR Embedded Workbench . . . . . . . . . 76 Building with the Debug Version of the EmberNet Stack . . . . . . . . . . . 76 Building with the Release Version of the EmberNet Stack . . . . . . . . . . 77 Building for the Atmega64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Building with the EmberNet Leaf Stack. . . . . . . . . . . . . . . . . . . . . . . . . 77 Debugging Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Adding Debug Features to an Application . . . . . . . . . . . . . . . . . . . . . . 78 Viewing Application Debug Messages. . . . . . . . . . . . . . . . . . . . . . . . . 78 Viewing Debug Messages Via the Serial Port . . . . . . . . . . . . . . . . . . . 80 Using the Ember Studio Backchannel Debug Tools . . . . . . . . . . . . . . . 80 Uploading Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Choosing the Correct Upload Method for an Application . . . . . . . . . 81 Uploading Via Ember Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Uploading Via the Ember Development Board Serial Line Connection81
Development Overview
Developing Full-Function Device Applications
Ember provides several tools to make the development of full-function devices easy: the bootloader, the Development Kit hardware, and a debug version of the EmberNet stack. We recommend that you first develop your application using the Development Kit development boards and the debug version of the EmberNet stack. This will allow you to rapidly detect and understand unexpected or incorrect behavior. If your development boards are connected using the
120-0065-000A Final
Page 74
Building Applications
Ethernet backchannel, you will also be able to simultaneously update all modules with new software, further reducing development time. When you have custom hardware available, you can test it using the Ember Rangetest application and then, if necessary, adapt the Ember HAL to your custom hardware. You can test your HAL by using a simple sample application, to reduce the complexity and possible errors. Once you are satisfied with the behavior and performance of your application on the development boards, you can move your software to your custom hardware by changing the HAL from the Ember development board HAL to the pre-tested HAL that your hardware uses.
Building Applications
About IAR Embedded Workbench
Ember supports only the IAR Embedded Workbench for application building. The release notes for your Developer Kit will specify the version of IAR to use. Please refer to the IAR documentation for information on installing and using the program.
120-0065-000A Final
Page 75
Building Applications
2.
3.
4.
2. 3.
4. 5.
6. 7. 8. 9.
Debugging Applications
10. Select Project > Add Files. Select the file or files you wish to include in the group and click Open. 11. Select the top level of the tree, and then right-click this item and select Options, or select Project > Options from the menu. The Project Options dialog opens. 12. Select the XLINK category from the list at the left of the window. A tabbed window displays. 13. In the Output tab, in the Output File box, make sure that Override default is checked, and type a name for the output file. The output filename must have the .lbin postfix. 14. Click OK. The Project Options dialog closes. 15. Select Project > Make. The IAR Embedded Workbench will try to build your application. If the build is successful, the output file that you specified is generated (with the .lbin extension). If the build fails, the Messages window displays error messages. 16. When the output file is built, add this file to the file group you created for your application files (similarly to how you added the group in step 9). Including this file as part of a group ensures that the post-processing tool that converts the .lbin file into a .bin file (etrim.exe) runs whenever your .lbin file is rebuilt. Only .bin files can be uploaded to Developer Kit hardware.
Debugging Applications
Debugging applications in a live network environment is challenging. Traditional debugging methods such as setting breakpoints and examining variables are of little use, because the act of applying them itself changes network behavior. In effect, you end up debugging a network that is different from the network that the application sets up. The Developer Kit provides a flexible and powerful set of debugging tools that sidestep these difficulties: A debug build of the EmberNet stack that supports an Ethernet debugging backchannel as well as providing debugging data. A set of debug functions in the EmberNet stack. A real-time debugger inside Ember Studio that displays debug messages (the Debug Reader). The ability to debug via serial connection to a single node. The Ember Studio backchannel debugging tools. EmberNet Application Development Guide 120-0065-000A Final Page 77
Debugging Applications
When debugging is complete, you can build the final application with the release build of the EmberNet stack, which lacks debug features and thus produces smaller, faster code. Ember provides only a release build of the Leaf Node stack.
120-0065-000A Final
Page 78
Debugging Applications
To use the Debug Reader, nodes must be set up in a backchannel network. In this type of network the emulator/debug module is configured to intercept application debug messages from a nodes serial port and route them to the Ethernet. Ember Studio aggregates the Ethernet messages and displays them in the Debug Reader. For information on setting up a backchannel network, see Chapter 4 of the Ember Developer Kit Users Guide.
120-0065-000A Final
Page 79
Debugging Applications
120-0065-000A Final
Page 80
Uploading Applications
Uploading Applications
Choosing the Correct Upload Method for an Application
Table 6-2 lists the methods available for uploading different types of applications.
Upload Method/s
Ember Studio or Ember development board serial line connection, to upload to a device that is mounted on an Ember development board. Direct serial connection to a custom device, to upload an EmberNet application to the device for the first time.
Leaf node application (.hex) Bootloader application (.hex) EmberNet stack tokens (.ebin) Leaf Node stack tokens (.ehex)
Uploading Applications
Locate the serial configuration array headers on the board (see Figure 6-1). Write down the existing configuration of the headers so that you can restore it when you are done. Configure the headers as shown below. This configuration allows the RF communication module to receive data over a DB-9 connection. (For more information on this specific configuration and on configuring the headers in general, see the Ember Developer Kit Carrier Board Technical Specification.) J27 J28 J29 J25 J26
4. 5.
Connect carrier board serial port 1 (see Figure 6-1) to a computer serial port. Open a terminal emulator and set its com port settings to 19200, N, 8, 1, no flow control. The bootloader menu displays: 3219 8 MHz EM2420 1.program upload (.bin image) 3.run program image 7.stack and application token defaults (.ebin image) 8.application token defaults (.ebin image) 6.Enter the option number for the type of file you are uploading. The terminal will echo begin upload. Transfer the .ebin file using the XModem protocol.
6.
Note: If the upload terminates with an error, repeat the upload procedure until it terminates
successfully. 7. 8. Optional: Use option 3 to run the application on the node. Restore the serial configuration array headers to their original configuration.
120-0065-000A Final
Page 82
Uploading Applications
3.
4.
Enter the com port and browse to the image file to upload. Click Bootload. Ember Studio displays the progress of the upload in the status bar. The upload is done when the node beeps and the status bar says Successful. You can close the window at that point.
Note: If the upload terminates with an error, repeat the upload procedure until it terminates
successfully. 5. Optional: Restore the serial configuration array headers to their original configuration.
120-0065-000A Final
Page 83
Uploading Applications
120-0065-000A Final
Page 84
Uploading Applications
Serial Port 0
Serial Port 1
Buzzer
RF Communication Module
White triangle
Emulator/Debug Module EM4 RF Communication Module LEDs EM5 EM3 Serial Configuration Array Headers 10/100 Controller Chip Emulator/Debug Module LEDs
EM3: RF Comm. Module Reset Button EM4: Bootloader Button EM5: Emulator/Debug Module Reset Button
RJ-45 Connector
Power Jack
120-0065-000A Final
Page 85
APPENDIX A
A .ebin file is a binary image of EEPROM data. EmberNet stack tokens must be compiled into this type of file before they can be uploaded to a node. To create a .ebin file, you must use IAR Embedded Workbench (EW) version 3.10D. To create a .ebin file: 1. Either create an additional build target (called a configuration in IAR Embedded Workbench) and add it to the project that you are using to build the application or import a project from a previous version. Select Edit Configurations from the Project menu. Click New in the window that opens. The New Configuration window displays.
2.
3.
In the Name field, enter a title for the configuration. In the Based on configuration drop-down list, select the release configuration used to produce your program image. Select Release in the Factory settings field. Click OK to add the configuration and OK again in the Edit Configurations window. The workspace view returns. Select the configuration that you just created from the drop-down list at the top of the workspace view. The active configuration changes to the new one, with all nodes (files and groups) included in the build.
4. 5.
120-0065-000A Final
Page 86
Appendix A: Creating a .ebin File 6. To omit files or groups that you dont want to include, such as debug files, right-click them and select Options from the context menu to display the Options window. Select Exclude from build and click OK. Right-click on the top-most node in the project and select Options from the context menu. The Options window displays. Select the XLINK category, then click the Output tab. Make sure that Override default is checked and that the filename below ends in the .ebin extension, not the .bin extension. You should not need to change any other settings in this tab. In the XLINK category, select the Extra Options tab. Change the command line option -y(CODE) to -y(XDATA). The -w6 argument is optional. Select the Custom Build category and make sure that all fields are blank. (No additional processing is needed to produce a suitable EEPROM image once the .ebin file has been linked.)
7.
8. 9.
10. Select Make from the Project menu, or press F7. IAR Embedded Workbench builds the .ebin file in the same directory as the .lbin and .bin files that were created during the building of the program image.
120-0065-000A Final
Page 87