当前位置:首页 >> 互联网 >>

Getting Started with the Internet of Things


Getting started with the internet of things
What is the internet of things? It’s countless embedded computers, sensors, and actuators all connected online. If you have basic programming skills, you can use these powerful little devices to create a variety of useful systems-such as devices that react to real-world events and take action. This hands-on guide shows you to start building your own fun and fascinating projects. Learn to program embedded devices using the .NET Micro Framework and the Netduino Plus board. Then connect your devices to the Internet with Pachube, a cloud platform for sharing real-time sensor data. All you need is a Netduino Plus, a USB cable, a couple of sensors, an Ethernet connection of the Internet-and your imagination. ? Develop programs with simple outputs (actuators) and inputs (sensors) ? Learn about the Internet of things and the Web of things ? Build client programs that push sensors reading from a device to a web service ? Creat server programs that allow you to control a device over the WEB ? Get example programs that you can compile with Visual Studio on Windows or Mono on Mac OSX and Linux

Preface
One of the most fascinating trends today is the emergence of low-cost Microcontrollers that are sufficiently powerful to connect to the Internet. They are the key to the Internet of Things, where all kinds of devices become the Internet’s interface to the physical world. Traditionally, programming such tiny embedded devices required completely different platforms and tools than those most programmers were used to. Fortunately, some microcontrollers are now capable of supporting modern software platforms like .NET, or at least useful subsets of .NET. This allows you to use the same programming language (C#) and the same development environment (Visual Studio) when creating programs for small embedded devices, smartphones, PCs, enterprise servers, and even cloud services. So what should you know in order to get started? This book gives one possible answer to this question. It is a Getting Startedbook, so it is neither an extensive collection of recipes, nor a reference manual, nor a textbook that compares different approaches, use cases, etc. Instead, its approach is “less is more,” helping you to start writing Internet of Things applications with minimal hassle.

The Platforms
The .NET Micro Framework(NETMF) provides Internet connectivity, is simple and open source, has hardware available from several vendors, and benefits from the huge .NET ecosystem and

available know-how. Also, you can choose between Visual Studio on Windows, and the open source Mono toolchain on Linux and Mac OS X. Netduino Plus is an inexpensive NETMF board from Secret Labs. This board makes Ethernet networking available with a price tag of less than $60. It has the following characteristics: ? A 48 MHz Atmel SAM7 microcontroller with 128 KB RAM and 512 KB Flash memory ? USB, Ethernet, and 20 digital I/O pins (six of which can be configured optionally for analog input) ? Micro SD card support ? Onboard LED and pushbutton ? Form factor of the Arduino (http://www.arduino.cc/); many Arduino shields(add-on boards) can be used ? .NET Micro Framework preprogrammed into Flash memory ? All software and hardware is open source

Part I, Introduction
The first part tells you how to set up the development environment and write and run a “Hello World” program. It shows how to write to output ports (for triggering so-called actuators such as LED lights or motors) and how to read from input ports (for sensors). It then introduces the most essential concepts of the Internet of Things: HTTP and the division of labor between clients and servers. In the Internet of Things, devices are programmed as clients if you want them to push sensor data to some service; they are programmed as servers if you want to enable remote control of the device over the Web.

Thanks to the unrelenting progress of the semiconductor industry, all the digital parts of a computer can be put onto a single chip, called a microcontroller. A 32-bit microcontroller chip costing less than $10 may have more than twice as much memory as the original 8-bit Apple II computer with its 48 KB of RAM, and may run 100 times faster. Because of such inexpensive hardware and easy-to-use development platforms, it is now possible for hobbyists to create systems that interact with the physical world in every conceivable way. For example, a sensor can measure the humidity in a flowerpot, and a computer-controlled valve (actuator) lets water pass into the pot when the humidity drops too low. Moreover, since the hardware allows the use of standard Internet protocols, monitoring and controlling can be done over the Internet. Various Internet services can be used for storing data, visualizing it, sharing it with other people, etc. For example, to learn about seasonal effects on humidity, you can store measurements of your flowerpot’s humidity over the course of a year. In Part I, I will show you how to set up the development environment so that you can start playing with simple sensors and actuators. Then I will lay the groundwork for Parts II and III, which show how you can program devices as clients that send requests to various services, or as servers that handle requests from clients, e.g., from web browsers.

1. Hello World
To familiarize you with the development environment, your first program should be a simple HelloWorld. Because the Netduino Plus board does not have a display, use the USB connection between board and development PC to write the string HelloWorld to the development environment’s Output window running on the PC. The USB connection is used to deploy and debug your programs, and in the Hello World example, it allows you to send the HelloWorld string to your development PC.

1) Setting Up the Development Environment
Before writing your first program for the .NET Micro Framework, you need to install a few tools and libraries, including: ? Microsoft Visual Studio 2010 or later. The free Visual Studio Express version is sufficient. Full commercial versions can also be used, of course. For my descriptions and screenshots, I will use Visual Studio Express. ? Microsoft .NET Micro Framework 4.1 SDK or later. available at http://www.netduino.com/downloads/MicroFrameworkSDK.msi. ? Your development board’s SDK and drivers. The SDK and drivers for the Netduino Plus can be downloaded from http://www.netduino.com/downloads/. ? The client-side Gsiot.PachubeClient library and the server-side Gsiot.Serverlibrary, which are used in some of this book’s examples. They can be downloaded from http://www.gsiot.info/download/.

2) HelloWorld
Example 1-1. HelloWorld program
using Microsoft.SPOT; public class HelloWorld { public static void Main() { Debug.Print("Hello World"); } }

The HelloWorld program (Example 1-1) contains a class HelloWorld with a parameterless static method Main. The keywords public static void specify the type of the method; in this case, it’s public(is visible to other classes), static(doesn’t need an instance of the HelloWorld class to execute the method), and void(doesn’t return a value). Also, because the parentheses are empty, Main() doesn’t expect you to pass it any arguments (objects or variables that would be referred to within the method). In fact, you won’t call Main() on your own; NETMF does it for you. When the Netduino Plus reboots or is powered on, it looks for the Main() method and runs it as the entry point of your program. This program writes the string Hello World to a debug console, e.g., the Output window of Visual Studio. NETMF provides a Debug class in the Microsoft.SPOT namespace. Debug’s Print method writes text output directly to the development environment via the same transport(connection) used for deploying software to the device and for debugging. On the Netduino Plus board, it is a USB transport. Other development boards may use a serial transport (RS-232) or an Ethernet transport.

3) Building the Program in Visual Studio
Assuming you have already installed the .NET Micro Framework SDK and the Netduino SDK, there are a few steps you must follow before you can type in the HelloWorld program: ? Start Visual Studio. ? Click on File?New Project…. ? Select Micro Framework in the Installed Templates pane, select Netduino Plus Application in the middle pane, and type HelloWorld in the Name field at the bottom (see Figure 1-2). Then click OK. ? In the Solution Explorer on the right side, double-click on Program.cs. A tab with the title Program.cs will open, containing some boilerplate program text. ? Replace the text with the HelloWorldprogram from Example 1-1. ? Select Debug?Build Solution to build the solution.

At the bottom-left corner of Visual Studio, it should now say “Build succeeded”.

4) Deploying to the Device
Once you have built the example, you can deploy it to your hardware. First, you need to make sure that the deployment properties are set as shown in Figure 1-3.

To do this, perform the following steps: ? In the Solution Explorer, right-click on the HelloWorld project (just below the text “Solution ‘HelloWorld’ (1 project)”), then select Properties in the menu. The tab shown in Figure 1-3 will open. ? On the left side, click on the .NET Micro Framework tab, which results in the dialog box shown in Figure 1-4.

Make sure that the properties are set up as follows: ? Configuration: Active (Debug) ? Platform: Active (Any CPU) ? Transport: USB ? Device: select your Netduino from the drop-down list. ? Generate native stubs for internal methods: unchecked ? If the Device list box says <none>, you need to plug in your Netduino Plus. The first time you plug it in, the driver should be installed automatically. Its name should appear when you click on the Device list box. ? To open the Output window, which will show debug output, use the keyboard shortcut Ctrl-W, followed by O. ? Next, select Debug?Start Debugging, and the HelloWorld program will be sent to your board, loaded by the .NET Micro Framework, after which the Main method is executed. The program then terminates immediately. You can see the debug output in Visual Studio. Now you have successfully deployed your first program to a real device! It is certainly not an Internet of Things application yet, as it does not involve any communication over the Internet. Nor is it an embedded application, as it doesn’t use any of the typical embedded inputs or

outputs (which we will look at in the following chapters).

2. Writing to Actuators
You can now write your first truly embedded program. In a time-honored tradition, this program, BlinkingLed, which is the embedded equivalent of HelloWorld, makes an LED blink.

In Figure 2-1, the large box indicates a Netduino Plus, which has a blue LED—labeled LED on the board—that can be controlled from an application program. This LED is connected to a general-purpose input/output (GPIO) pin of the microcontroller. Most microcontrollers have a number of such GPIO pins, each of which can be configured as digital input or digital output. A digital output might be connected to an LED, as in our example; a digital input might be connected to a switch or button.

1) BlinkingLed
Example 2-1. BlinkingLed
using System.Threading; using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware.NetduinoPlus; public class BlinkingLed { public static void Main() { var ledPort = new OutputPort(Pins.ONBOARD_LED, false); while (true) { ledPort.Write(true); // turn on LED Thread.Sleep(500); // wait 500 ms

ledPort.Write(false); // turn off LED Thread.Sleep(500); // wait 500 ms } } }

The BlinkingLed program, shown in Example 2-1, contains a simple endless loop that switches the LED on, waits for half a second, switches the LED off again, waits for another half a second, and then starts all over. The calls to the Sleep method in the Thread class make the program pause for (at least) a given number of milliseconds. In the .NET Micro Framework, using Thread.Sleepis the best practice for waiting, as it allows the hardware to go into a lower power state to conserve energy.

2) C# Namespaces
In C#, related classes are bundled together into so-called namespaces. In the BlinkingLed program, the namespace Microsoft.SPOT.Hardware provides the class OutputPort. Its full name is Microsoft.SPOT.Hardware.OutputPort. Of course, you could spell out the full name of the class every time you use it, but for the sake of readability and convenience, it is often preferable to use a using directive. If you specify the directive using Microsoft.SPOT.Hardware; (as I did in BlinkingLed) at the beginning of your program, you can use the short name OutputPort, rather than the full name. I will use short names in this book; please see the tables in Appendix B to find the appropriate namespace for each class used in these examples.

3) Running the Program
To run the program, create a new Netduino Plus Application project in Visual Studio, and replace the contents of Program.cs with the code given in Example 2-1. Next, build it and deploy it to your Netduino Plus, as described in the section “Deploying to the Device” in Chapter 1.

4) Digital Outputs
In the .NET Micro Framework, using a physical pin as output is represented by an output port object, which is an instance of the class OutputPort. An output port provides the method Write that takes the target state of the output pin as a Boolean (true or false) parameter. Using such an output port, called ledPort in Example 2-1, the LED can be switched on by writing the value true, and switched off by writing the value false. When I defined the output port ledPort, I specified the microcontroller pin that is connected to the LED. In this case, I want to use the built-in (onboard) LED.

Pins are represented by the type Cpu.Pin, but you don’t specify the number of the pin you want to use. Instead, manufacturers provide constants for the pins on their boards. On a Netduino Plus, you must specify Pins.ONBOARD_LED for the onboard LED’s pin. In this book, we are mainly interested in the constants shown in Table 2-1, where I also include some input ports to be used in later chapters. These pins are defined in the namespace SecretLabs.NETMF.Hardware.NetduinoPlus, which is provided as part of the Netduino SDK. When you type in Pins., Visual Studio conveniently pulls up a list of all the available pins on a Netduino Plus. Table 2-1. Pin assignment of Netduino board (excerpt) Connected hardware Onboard LED (blue) Onboard switch Pins D0 through D13 Pins A0 through A5 Pin usage Digital output Digital input Digital input or digital output Analog input Constant Pins.ONBOARD_LED Pins.ONBOARD_SW1 Pins.GPIO_PIN_D0 Pins.GPIO_PIN_A0

The second parameter of the OutputPort constructor shown in Example 2-1 indicates whether the LED should initially be switched on or off. In our case, false indicates that it should be off at the beginning. A pin may be used with at most one output (or input) port at the same time—i.e., creating a port object reserves this pin. Attempts at reserving a pin multiple times will lead to an exception, which is a software event that is triggered by an error condition. Unless you create handlers that catch and resolve exceptions, they will typically cause your Netduino Plus program to halt.

3. Reading from Sensors

The first example in this chapter, LightSwitch, not only writes to output ports, it also reads from input ports. The switch input is used to control the LED output, as shown in Figure 3-1.

While the switch (actually a push button on the Netduino Plus board) is closed, the LED stays lit; otherwise, it is dark.

1) LightSwitch
Example 3-1. LightSwitch
using System.Threading; using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware.NetduinoPlus; public class LightSwitch { public static void Main() { var switchPort = new InputPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled); var ledPort = new OutputPort(Pins.ONBOARD_LED, false); while (true) { bool isClosed = switchPort.Read(); if (isClosed) { ledPort.Write(true); } else { ledPort.Write(false); } Thread.Sleep(100); // 100 milliseconds } } }

The program LightSwitch(Example 3-1) reads the current switch state periodically and copies it to the LED. This is done frequently enough that a user does not detect a delay when she opens or closes the switch. Delays of 1/10th of a second or less are undetectable by humans; therefore, the loop is executed every 100 milliseconds. To build the program, create a new Netduino Plus project, name it LightSwitch, and replace the contents of Program.cs with the code in Example 3-1. Next, build the project and deploy it to your Netduino Plus, as described in the section “Deploying to the Device” in Chapter 1.

2) Digital Inputs
For reading the switch state, create object switchPort of type InputPort for the pin to which your board’s switch is connected (in this case, I use the ONBOARD_SW1 constant to refer to the pin

that’s wired to the Netduino’s built-in switch). When an input port is created, you have to pass two parameters in addition to the pin number: bool glitchFilter and Port.ResistorMode resistor. Parameter glitchFilter determines whether button presses are debounced—i.e., whether intermittent mechanical contacts are suppressed. In LightSwitch, it doesn’t really matter whether a value is read that is “wrong” temporarily; therefore, I pass false. This would be different if the application did something critical whenever the button was pressed, like launching rockets. In such a situation, you wouldn’t want one keypress to launch an entire salvo of rockets, simply because the button jumps up and down a bit before it settles down.

To understand the resistor parameter, we need to look at the hardware of the board. The microcontroller’s input pin ONBOARD_SW1 is connected to power(PWR)—i.e., to the supply voltage on the one hand—and via switch SW1to ground(GND), or to zero voltage. Without resistance between power and ground, it would be unclear what the input pin would see when the switch is closed (Figure 3-2). Power? Ground? Something in between? Moreover, the current would become infinite when the switch is closed—in other words, you would get a short circuit that might destroy the board. These are the reasons why a resistor R must be supplied. It limits the current, prevents a short circuit, and defines whether ONBOARD_SW1 detects a high or a low voltage. On the Netduino Plus board, this pull-up resistoris placed between ONBOARD_SW1 and power. Figure 3-3 shows an excerpt of board schematics that illustrates the situations with switch SW1 open (left) and closed (right). Because the Netduino Plus board already provides a pull-up resistor for ONBOARD_SW1, the microcontroller pin doesn’t need to provide additional resistance of its own. Therefore, the value Port.ResistorMode.Disabled is passed as a parameter to the input port constructor. If the switch is open—i.e., the button is released—the supply voltage causes the pin to “see” a high voltage (Figure 3-3, left). If the switch is closed—i.e., the button is pressed—the voltage below the resistor is sucked down to ground, causing the pin to “see” a zero voltage (Figure 3-3, right).

3) VoltageReader

Reading digital inputs for buttons, switches, and the like is fine, but sometimes you may want to read analog inputs as well. The VoltageReader in Figure 3-4 shows how this can be done. The complete code is given in Example 3-2. It polls a potentiometer every three seconds and prints the raw value and the corresponding voltage value to the debug output. Example 3-2. VoltageReader

using System.Threading; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware; using SecretLabs.NETMF.Hardware.NetduinoPlus; public class VoltageReader { public static void Main() { const double maxVoltage = 3.3; const int maxAdcValue = 1023; var voltagePort = new AnalogInput(Pins.GPIO_PIN_A1); var lowPort = new OutputPort(Pins.GPIO_PIN_A0, false); var highPort = new OutputPort(Pins.GPIO_PIN_A2, true); while (true) { int rawValue = voltagePort.Read(); double value = (rawValue * maxVoltage) / maxAdcValue; Debug.Print(rawValue + " " + value.ToString("f")); Thread.Sleep(3000); // 3 seconds } } }

To run the program, first connect a potentiometer to your Netduino Plus, as shown in Figure 3-5.

Next, create a new Netduino Plus project, name it VoltageReader, and replace the contents of Program.cs with the code in Example 3-2. Then, build the project and deploy it to your Netduino Plus, as described in the section “Deploying to the Device” in Chapter 1. To view the output, choose Debug?Windows?Output. Every three seconds you’ll see a new value displayed in the window.

4) Analog Inputs

A typical analog sensor translates some physical phenomenon, such as temperature, into a voltage level. The analog/digital converter(ADC) built into the microcontroller of the Netduino Plus can measure this voltage and turn it into an integer number. For an ADC with 10-bit resolution, like the one in the Netduino Plus, the numbers range from 0 (for 0.0 Volt) to 1023 (for 3.3 Volt). These are the 1,024 values that can be represented with 10 bits (210 values). An ADC supporting only 8 bits would yield the 256 numbers between 0 and 255 (28 values); an ADC supporting 12 bits would yield the 4,096 numbers between 0 and 4095 (212 values). A Netduino Plus provides six analog inputs on one of the blue connectors. They are labeled Analog In, 0to 5. If you have a suitable potentiometer, you can stick it into the Netduino Plus connector such that one of the outer most leads (no matter which one) connects to A0, the other outermost lead connects to A2, and the middle lead connects to A1. See Figure 3-5 for an image of this scenario. Because the pins on our potentiometer lie so closely together, it is convenient to plug them directly into the row of analog pins on the Netduino. However, we will not be configuring all the connected pins to be analog inputs. Recall that the analog pins on the Netduino can be used either for general-purpose digital I/O or for analog input. In our case, we will configure the pins at the two ends (A0and A2) to be digital outputs supplying 3.3V on one pin and 0.0V on the other. Only the middle pin (A1) will be configured to be an analog input. Figure 3-6 shows a schematic diagram for this arrangement of components.

The symbol for a potentiometer looks similar to a resistor because it is indeed a kind of variable resistor. Depending on how you turn the potentiometer’s knob, the resistances between pins A0 and A1 on the one hand, and between pins A1 and A2 on the other hand, will change. As a result, the voltage seen by A1 will change, all the way from 0.0 Volt to 3.3 Volt. A potentiometer can therefore be regarded as a variable voltage divider, as shown in Figure 3-7.

With your potentiometer attached to the Netduino Plus, you have hands-on experience with an analog sensor. This is a good basis for learning about more advanced sensors later on. After all, most analog sensors produce varying voltages that the Netduino measures at one of the analog inputs, representing them as an unsigned integer value. Let’s take another look at part of Example 3-2:
const double maxVoltage = 3.3; const int maxAdcValue = 1023; var voltagePort = new AnalogInput(Pins.GPIO_PIN_A1); var lowPort = new OutputPort(Pins.GPIO_PIN_A0, false); var highPort = new OutputPort(Pins.GPIO_PIN_A2, true);

From the microcontroller’s ADC resolution (adcResolution), which is 10 bit, the maximum value of the input port is 1023. The analog input port for pin A1 is an instance of class AnalogInput. Pins A0 and A2 are used as digital outputs here, forcing one of them to low (false) and the other to high (true). The Netduino Plus allows the use of pins A0 to A5 as either analog inputs, or as digital inputs or outputs (i.e., as GPIOs). This trick lets you use one pin as voltage (high corresponds to 3.3 Volt) and one as ground (0.0 Volt). Reading an analog input port is accomplished with this line:
int rawValue = voltagePort.Read();

This yields a value between 0 and 1023. Scaling it to between 0.0 and 3.3 Volt is done in the following way.
double value = (rawValue * maxVoltage) / maxAdcValue;

We multiply the value we read (rawValue) by the maximum voltage (3.3) and divide it by the maximum value possible (1023)

Part II, Device as HTTP Client
The second part focuses on examples that send HTTP requests to some services—e.g., to push new sensor measurements to the Pachube service (http://www.pachube.com) for storage and presentation. In this part, we will see how devices can be programmed as HTTP clients, accessing services on the Internet. The main focus will be on Pachube, a service created specifically for Internet of Things applications. Your device(s) can send measurements to Pachube for storage and for later access via web browsers or other programs. The .NET Micro Framework provides mainly two application programming interfaces (APIs) for implementing HTTP clients: the high-level HttpWebRequestAPI (in namespace System.Net) and the low-level SocketAPI (in namespace System). You will learn how to work with either one, depending on your application needs and available hardware resources.

4. The Internet of Things
Now that you have seen how to work with simple sensors and actuators, it is time to take the next step toward an Internet of Things application. In this chapter, I will briefly introduce the Internet of Things, and the related Web of Things. The Internet of Things is a global network of computers, sensors, and actuators connected through Internet protocols. A most basic example is a PC that communicates over the Internet with a small device, where the device has a sensor attached (e.g., a temperature sensor), as shown in Figure 4-1.

The TCP/IP protocol is the key Internet protocol for such communication scenarios.

It enables the transfer of byte streams between two computers in either direction. For example, using the TCP/IP protocol, the device in Figure 4-1 may periodically deliver temperature measurements to a program running on the PC.

1) HTTP
While it is possible to run any kind of proprietary protocol on top of TCP/IP, there are a few popular and widely supported standard protocols. If you use a standard protocol to deliver your sensor data, you’ll be able to work with many more devices and applications than if you developed your own proprietary protocol. The most important standard protocol by far is the Hypertext Transfer Protocol (HTTP), the protocol of the World Wide Web. HTTP describes how a client interacts with a server, by sending request messages and receiving response messages over TCP/IP, as diagrammed in Figure 4-2.

Web browsers are the most popular HTTP clients, but you can easily write your own clients—and your own servers. If you use a web browser to access a device, the device has the role of a web server, providing a web service over the Internet. A server contains resources, which can be anything of interest, e.g., a document (typically an HTML web page), the most current measurement of a sensor, or the configuration of a device. When you design a web service, you need to decide which resources it should expose to the world. HTTP uses Uniform Resource Identifiers(URIs) to tell the server which resource the client wants to read, write, create, or delete. You know URIs from web browsing; they look something like these:
http://www.example.com/index.html http://www.example.com/temperatures http://www.example.com/temperatures/actual http://www.example.com:50000/temperatures/actual http://www.example.com/temperatures?alarm=none http://www.example.com/temperatures?alarm=high http://www.example.com/temperatures?alarm=low http://www.example.com/valve/target

A URI indicates the scheme (e.g., http), the host (e.g., www.example.com), optionally the port (e.g., 50000), and the path (e.g., /temperatures/actual) to the resource owned and managed by this host, as shown in Figure 4-3.

Optionally, a URI may also contain a query (e.g., alarm=high) after a ? character that follows the path. For the HTTP protocol, port 80 is used by default unless another port is chosen explicitly, perhaps for testing purposes. The path is called request URI in HTTP; it denotes the target resource of an HTTP request. There are several kinds of HTTP requests that a client can send, but the most popular are GET for reading a resource, PUT for writing to a resource, POST for creating a resource, and DELETE for deleting a resource. Web browsers mostly issue GET requests, which make up the vast majority of HTTP requests. In a Web of Things application, a GET request to a URI, such as:
http://www.example.com/temperatures/actual

may return the most recent measurement of a temperature sensor, while a PUT to a URI, such as:
http://www.example.com/valve/target

may change the setting of an actuator—in this case, a valve. POST requests add sub-resources to a resource, which is similar to putting a file into a directory. For example, a POST of a measurement to the following resource:
http://www.example.com/temperatures

may create a new resource:
http://www.example.com/temperatures(42135)

A DELETE request removes a resource—e.g., it may remove the /temperatures resource:
http://www.example.com/temperatures

from the server. (Of course, this would not physically remove the temperature sensor from the hardware.) PUT requests, POST requests, and GET responses carry representations of the addressed resource. The best-known representation is the Hypertext Markup Language, better known as HTML. A web browser is an HTTP client that knows how to render HTML pages on the screen. There are other popular representations: PDF, JPEG, XML-based data formats, etc. A web service may support one or several representations for a single resource. For example, a temperature measurement may be represented in a plaintext representation, like this:

23.5 deg

or in an XML representation, like this:
<sample> <value>23.5</value> <unit>deg</unit> </sample>

Some representations are standardized, like HTML, but you may also define your own representations, like those above. Some representations are self-contained documents; others support links to other resources. You know the hypertext links from HTML, which use URIs to address other resources. By clicking on a link, you cause the browser to send a GET request to obtain a representation of that resource. This request is sent to the host contained in the link’s URI. Let’s look at a complete example of an HTTP request/response interaction (Figure 4-4): ? This diagram shows a GET request, as it may be sent by a web browser or your own client program. The client requests a representation of the resource’s “actual temperature as measured by the temperature sensor,” whose URI consists of the host www.example.comand the request URI /temperatures/actual. ? The service at host www.example.com receives the request, measures the temperature, and returns a response message. In this example, the response indicates success (200 OK) and a plain-text representation that is 8 bytes long. The representation is 23.5 deg.

Even the most complex web interactions consist of such message exchanges. The Web includes several hundred million clients and several hundred thousand servers with their resources, and it produces a torrent of messages that carry resource representations. The technical term for this architecture is representational state transfer, or REST. The focus of Getting Started with the Internet of Things is to show how REST and common web standards can be used as the preferred way of creating Internet of Things applications.

Such applications are sometimes called Web of Things applications, to emphasize the use of web standards on top of the basic Internet protocols. The Web of Things consists of RESTful web services that measure or manipulate physical properties. Thus, the term Web of Things focuses on the application layer and the real-world “things” that are measured or manipulated. The term Internet of Things focuses on the underlying network layers and the technical means for measuring and manipulating the physical environment—i.e., sensors and actuators.

2) Push Versus Pull
Thereare four basic ways in which your device may communicate with another computer on the Web: ? Device is the client, pushing data to a server ? Device is the client, pulling data from a server ? Device is the server, providing data to clients ? Device is the server, accepting data from clients These patterns can be visualized as shown in Figure 4-5.

A black arrow indicates the direction of a request message and a dotted arrow indicates the direction in which data flows, i.e., in which direction a resource representation is sent. In monitoring applications, a device produces data, i.e., measurements from its attached sensors. For such applications, the interaction patterns 1 and 3 are suitable: data flows from the device to another computer; the device is either client (1) or server (3). In control applications, a device consumes data, i.e., commands from a web browser or other client. For such applications, the interaction patterns 2 and 4 are suitable: data flows to the device from another computer; the device is either client (2) or server (4).

In Part II, I will focus on the device as client (i.e., on scenarios 1 and 2). Since in general, a device cannot know in advance when you want to send it a command (e.g., to set up an actuator or to reconfigure a sensor), it makes sense to support devices as servers as well. Therefore, I will discuss scenarios 3 and 4 in Part III. I believe that the potential of the Internet of Things will only be realized if devices can become clients, servers, or both.

5. Pachube

Imagine that your Netduino Plus uses a sensor to take measurements periodically. After each measurement, the Netduino Plus immediately sends the sample to a server for storage and later retrieval. This server effectively provides a feed resource to which you publish your data samples. You may already know the concept of feeds from RSS feed readers. A feed entry can be anything of interest, from political news to blog entries to measurements, as in the case of your Netduino Plus. In a way, a feed that contains measurements can be thought of as a news source about the physical world. For such an example, you need a suitable web service to which your device can send its

measurements. Conveniently, there’s a free service, Pachube, which does exactly this. It provides web-based interfaces for storing and for accessing feeds, as shown in Figure 5-1. To use Pachube, you need a free account and a feed to which you can send your own data. Follow these steps to create both the account and a first feed: ? Sign up for a free account at http://www.pachube.com/signup. ? On the “my settings” page (http://www.pachube.com/users/<your account name>/settings), you will find the private master API key that you will need later on in your Pachube client programs ? Set up your first feed at http://www.pachube.com/feeds/new. ? For the Feed type, click on “manual”. ? For the Feed title, type in a suitable name, such as “My first feed”. ? For the Feed tags, you could type in “gsiot” so that other readers of this book can find it. ? For the Exposure, click on “indoor”. ? For the Disposition, click on “fixed”. ? For the Domain, click on “physical”. ? You may enter other information if you want, such as a location name and the location itself (click on the Google map to define the location). If you choose to provide a location, I suggest you pick a well-known public point of interest near you rather than your actual home address. ? Note the ID of this feed. It is part of the web page URI (circled in Figure 5-2).

?

Click on “+ Add a new data stream”.

Enter “voltage” as the ID, enter“Volt” in the Units field, and enter V in the Symbol field. In Type, select“derived SI”, which means that this is a unit derived from some other physical units that are considered more basic. ? Click on “+ Add a new data stream” again. Enter “number” as the ID and leave all other properties as they are. ? Click on Save Feed. ? Given your Pachube feed ID, look at the feed’s home page by typing in its URI. For example, for the feed 256, use the URI http://www.pachube.com/feeds/256. Pachube supports a number of URIs for accessing a given feed or data stream. Table 5-1 shows the most important URIs, using the feed ID 256 and the data stream ID 0 as examples. Table 5-1. Most important URIs for accessing Pachube feeds Pachube URI http://www.pachube.com/feeds/256 http://api.pachube.com/v2/feeds/256. json Description HTML home page of feed 256. JSON (http://www.json.org) representation of feed 256, providing maximum, minimum, and current measurement values, plus some metadata that describes the feed. It is also possible to request the data in XML or CSV formats by using the .xml or .csv suffixes respectively, instead of .json. History of measurements in data stream 0 of feed 256, represented as comma-separated values. Can be imported directly into a spreadsheet. All measurements of the last 24 hours are given, in 15-minute intervals. You can vary the arguments to adjust the time period and the minimum interval between the points. Same data as in the above example, but represented as a diagram.

http://api.pachube.com/v2/feeds/256/ datastreams/0.csv?duration=24hours& interval=900

http://api.pachube.com/v2/feeds/256/ datastreams/0.png?duration=24hours& interval=900

JSON JSON, which stands for JavaScript Object Notation, is a textual format for representing arbitrary data. In this respect, it is similar to the often-used XML representation. JSON is popular for web applications since its text is simpler and usually less verbose than equivalent XML text. While JSON is part of the JavaScript language, it is supported by libraries for practically all programming languages today, and has thereby gained “a life of its own.” Here is an example of JSON text:

{ "recorded_at" : "20110323T13:29:37Z", "max_value" : 25.5, "min_value" : 0.0, "value" : 1.6 }

In Chapter 6, you will learn how to send data to your Pachube feed from a program that runs on your Netduino Plus.

6. Hello Pachube
In this chapter, I will show a basic HTTP client, HelloPachube, that pushes samples to Pachube, as shown in Figure 6-1.

HelloPachuberuns on the Netduino Plus and sends measurements to the Pachube web service by issuing HTTP PUT requests. The user, through his web browser, sends HTTP GET requests to Pachube to retrieve feed entries. The data flow originates in the device, goes up to Pachube, and continues from there to the user.

1) Setting Up the Network Configuration
Before you can run such a client, you need to make sure that your Netduino Plus board has access to the Internet—i.e., it can send request messages to any server visible on the Internet. I assume that your Netduino Plus is connected to the Internet via a router and a cable or DSL modem (Figure 6-2).

This means that you have a local area network to which both the board and your development PC are connected. During development and debugging, the PC and Netduino Plus are directly connected via a USB cable as well.

? Internet Addresses
A router typically implements the Dynamic Host Configuration Protocol(DHCP). This protocol allows your development PC, your Netduino Plus, and other devices to automatically obtain Internet addresses (e.g., 192.168.0.3 for the PC, and 192.168.0.4 for the Netduino Plus). The Internet protocols rely on Internet addresses for routing messages between clients and servers. If your Netduino Plus obtains its Internet address automatically via DHCP, it typically gets an Internet address in one of these reserved address ranges:
192.168.xxx.xxx 172.16.xxx.xxx 10.xxx.xxx.xxx

where xxx lies between 0 and 255. Public Internet servers never use these reserved addresses. They are unique only within a given local area network, not worldwide like other Internet addresses. For example, there are thousands of computers with the private address 192.168.1.100. This is not a problem as long as your device is only a client, but it can be a problem for devices used as servers, as we will see in Part III.

To implement such a multiplexing of Internet addresses, a router has to perform network address translation (NAT). This hides the private Internet addresses from the Internet by making it appear as though all Internet traffic from the board or from the development PC originated from the router. This provides a certain degree of security because a program on the Internet cannot directly address—and therefore try to connect to—a device hidden behind the router. In addition, it reduces the number of Internet addresses that must be visible globally, which is important because the common four-byte IPv4 Internet addresses will basically be used up by the time this book comes out. A client program can directly use an Internet address to connect to a server on the Internet—e.g., the address 173.203.98.29 to connect to a Pachube server. Since such Internet addresses are not very convenient, you can alternatively use a domain name for addressing a host. In the above example, the domain name is pachube.com. Domain names are registered with the Internet’s domain name system(DNS). The domain name system allows for looking up domain names, much in the same way as a phone book is used for looking up names (except instead of finding phone numbers, the domain name system returns Internet addresses). A domain name lookup is simply another request over the Internet, e.g., to a DNS server of your Internet service provider. ? The MFDeploy Tool Before you can use your Netduino Plus on the network, you need to check its network settings and configure it if necessary. In particular, you should make sure that DHCP is switched on and that the correct MAC address of the board is set. The MAC address is a unique six-byte identifier, typically written like this:
3c8a4a000007

To check or modify the network configuration, use the tool MFDeploy, which is provided as part of the Microsoft .NET Micro Framework SDK. To find it, click Start?All Programs?Microsoft .Net Micro Framework 4.1?Tools and run MFDeploy.exe. Another way to find it is to look in the directory:
C:\Program Files\Microsoft .NET Micro Framework\v4.1\Tools\MFDeploy.exe

Now, perform the following steps: ? Start MFDeploy.exe. The dialog box .NET Micro Framework Deployment Tool opens. ? In the leftmost Device list box, change the selection from Serial to USB. ? Plug your Netduino Plus USB cable into your development PC. In the rightmost Device list box, the name NetduinoPlus_NetduinoPlus should appear. ? Click on the Ping button to make sure the device responds. As result, the large text box should now show “Pinging… TinyCLR”. ? In the Target menu, select Configuration?Network. The Network Configuration dialog box opens. ? If it isn't checked already, click on the DHCP checkbox to enable automatic configuration of

?

? ?

most network parameters. If it isn’t configured yet, enter your board’s MAC address. This is the only parameter you need to provide. You can leave the DNS Primary Address and the DNS Secondary Address at 0.0.0.0, as shown in Figure 6-3. Click the Update button. Reboot your Netduino Plus. It should now automatically obtain the missing network parameters from your router. To make sure that the Netduino Plus reboots, I usually perform a complete power-off/power-on cycle by briefly unplugging and reinserting the USB cable from the PC. After such a power cycle, you have five seconds to deploy a new program; otherwise, the most recently deployed program is restarted automatically.

To check whether the configuration works correctly, run the Hello Pachube client program described next.

2) HelloPachube
Nowthat your Netduino Plus is ready to access the Internet, we can look at a first version of a

Pachube client. Its source code is given in Example 6-1 Example 6-1. HelloPachube
using System; using System.Threading; using Gsiot.PachubeClient; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware; using SecretLabs.NETMF.Hardware.NetduinoPlus; public class HelloPachube { public static void Main() { const string apiKey = "your Pachube API key"; const string feedId = "your Pachube feed id"; const int samplingPeriod = 20000; // 20 seconds const double maxVoltage = 3.3; const int maxAdcValue = 1023; var voltagePort = new AnalogInput(Pins.GPIO_PIN_A1); var lowPort = new OutputPort(Pins.GPIO_PIN_A0, false); var highPort = new OutputPort(Pins.GPIO_PIN_A2, true); while (true) { WaitUntilNextPeriod(samplingPeriod); int rawValue = voltagePort.Read(); double value = (rawValue * maxVoltage) / maxAdcValue; string sample = "voltage," + value.ToString("f"); Debug.Print("new message: " + sample); PachubeClient.Send(apiKey, feedId, sample); } } static void WaitUntilNextPeriod(int period) { long now = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; var offset = (int)(now % period); int delay = period offset; Debug.Print("sleep for " + delay + " ms\r\n"); Thread.Sleep(delay); } }

To run the program, follow these steps: ? Make sure that your Netduino Plus is connected to your Ethernet router and that it is correctly configured for network access (see the previous section).

? ?

? ?

If you haven’t done so already, download the Visual Studio project Gsiot.PachubeClient from http://www.gsiot.info/download/, unzip it into the Visual Studio 2010\Projects\directory. Create a new Visual Studio project (using the Netduino Plus template) and name it HelloPachube. Replace the contents of Program.cs with the code from Example 6-1. You must replace the strings for api Key and feed Id so they match your Pachube API key and feed ID.

Right-click on References in the Solution Explorer. Select Add?New Reference. In the Add Reference dialog box, click on the Browse tab. In the directory hierarchy, go up two steps to directory Project. In the directory Gsiot.PachubeClient, open the subdirectory Gsiot.PachubeClient (yes, the same name again). In this directory, open the bin subdirectory. From there, open the Release subdirectory. In this subdirectory, select the Gsiot.PachubeClient.dll file. Click the OK button. You have now added the assembly Projects\Gsiot.PachubeClient\Gsiot.PachubeClient\bin\Release\Gsiot.PachubeClient.dll. Now you’re ready to test it: build the project and deploy it to your Netduino Plus, as described in the section “Deploying to the Device” in Chapter 1.

? Viewing the Results
After HelloPachube has started, you’ll see something like the following in Visual Studio’s Output window:
sleep for 19069 ms The thread ‘<No Name>’ (0x3) has exited with code 0 (0x0). new message: voltage,0.06 time: 01/01/2009 02:16:40 memory available: 20136 Status code: 200 sleep for 19371 ms The thread ‘<No Name>’ (0x4) has exited with code 0 (0x0). new message: voltage,0.06 time: 01/01/2009 02:17:00 memory available: 20136 Status code: 200 sleep for 19210 ms The thread ‘<No Name>’ (0x5) has exited with code 0 (0x0). new message: voltage,1.52 time: 01/01/2009 02:17:20 memory available: 20136 Status code: 200 sleep for 19369 ms The thread ‘<No Name>’ (0x6) has exited with code 0 (0x0).…

Because a Netduino Plus has no battery-backed real-time clock, its clock is started a new whenever you reboot the device. Upon rebooting, the initial time is the start of January 1, 2009. Twenty seconds pass between two consecutive samples; roughly 19 of them are spent sleeping. You can see that the samples were successfully sent to Pachube because the returned status code is 200, which is the OK status code of HTTP. To verify that the samples have indeed arrived at Pachube, type the following URI into your web browser, replacing your Pachube feed id with your feed ID:
http://www.pachube.com/feeds/your Pachube feed id

You should now see that the status of your feed is marked as currently: live. This means that the most recent sample is not older than 15 minutes; otherwise, the status currently: frozen would be shown. To see a graphical representation of the most recent samples, view the feed’s web page, look at the graph there, and click the label “last hour”.

? How It Works
The initialization of the HelloPachube Main method starts with two Pachube-related constants: your Pachube API key (apiKey) and the ID of the feed to which you want to publish your samples (feedId). After that, there are a few other constants and variables that are set: ? Specifying how often to send updates First comes the timing-related constant sampling Period. The goal for the example is to sample and publish a new observation at regular intervals, namely once every sampling Period, which is given in milliseconds. To publish a sample, send a web request and wait for its response. The time for such a complete round-trip consists of the time it takes for the request to travel to the server, for the server to create a response, and for the response to travel back to the client. ? Setting up the voltage reader The voltagePort object and related variables and constants are set up, as you saw in Chapter 3. They are used for reading voltage values from an attached potentiometer. After the variables and constants are initialized, a whileloop controls what happens from then on. This main loop will run until you turn off the Netduino Plus. The main loop does basically three things: ? Sleeps until the next sample is due, using the helper method WaitUntilNextPeriod, which I will discuss in the next section. ? Creates the sample by reading the voltage port. ? Sends the value to Pachube using PachubeClient.Send. This method takes the Pachube API key, your feed ID, plus the sample data, and sends them to Pachube in a suitable PUT request message. It then receives the response message and prints the response’s status code to the debug console. To use the Gsiot.PachubeClient for sending requests to Pachube in a “fire and forget” manner, you don’t need to know more than this.

However, if you want to know how the library actually works, how you could modify it, or how you could create a similar library, you need to understand more about how to send HTTP request messages and receive HTTP response messages. This is the topic of Chapter 7.

? The WaitUntilNextPeriod Method
In this example, samples should be taken at highly regular intervals. To do this, you can use the WaitUntilNextPeriod helper method, which you can reuse in similar programs later on. The following text explains the method in some detail. You can skip the explanation if you just want to go ahead and use the method. After each sample is sent, the program needs to sleep until the next period starts. How can this delay be calculated with precision when we don’t know in advance exactly how long it will take to send a request and receive its response? This example starts a new period every 20 seconds. (Free Pachube accounts don’t allow updates more often than every 12 seconds.) Assume the following: ? You last took a reading at 09:32:40. ? After the time it took you to send a message and receive the response, it is now 09:32:46. ? You want to send the next message (and start a new period) at 09:33:00. The delay then can be calculated as the difference between the length of the period (20 seconds) and the offset, where the offset indicates how far you are into the current period. The offset is calculated as the current time (i.e., now) modulo the period. In the example shown in Figure 6-4, the offset is six seconds; therefore, the delay is 14 seconds.

The property DateTime.Now.Ticks gives the current time in ticks, which in .NET is a time at a resolution of 100 nanoseconds. Dividing ticks by 10,000 (TimeSpan.TicksPerMillisecond) yields the same time in milliseconds, albeit less precisely.

This requires a 64-bit longinteger type. To calculate the modulus, use the %operator of C#. Because the result of a modulus operation is always smaller than the operand, in this case period, it can be safely cast to a 32-bit integer using the (int) cast. WaitUntilNextPeriodensures that sampling starts at highly regular intervals. It is robust even in cases where an iteration takes longer than its period allows for. This might occur if something unexpected happens, such as an exception that takes an inordinately long time to be sent to the debugger. This may result in one or several periods being skipped—but the next one starts at a correct period boundary anyway.

3) What Netduino Said to Pachube
To see that there is no magic involved in HTTP requests, let’s look at the data actually transferred to the Pachube server during a request:
PUT /v2/feeds/fid.csv HTTP/1.1\r\n Host: api.pachube.com\r\n XPachubeApiKey: your Pachube API key is here\r\n ContentType: text/csv\r\n ContentLength: 12\r\n \r\n voltage,1.52

This is the text sent over the Internet to Pachube! At least that’s what is sent if the measured voltage is 1.52. An HTTP request consists of one request line, followed by a number of header lines, followed by an empty line, and optionally followed by a message body(i.e., the message’s content). The request line starts with the HTTP method: PUT, GET, etc. After a blank, the request URI indicates the resource to be accessed. After another blank, the HTTP version is given, which is usually version 1.1 these days. The request line is terminated by a carriage-return byte followed by a newline byte. HTTP defines a number of headers, both for requests and responses. For requests, the Hostheader is particularly important because it defines to which computer the request is sent—in this case, to api.pachube.com. If you take this host and the request URI in the request line (here, it’s /v2/feeds/fid.csv), you can construct the absolute URI of the resource accessed by this PUT request:
http://api.pachube.com/v2/feeds/fid.csv

Unlike the URIs that we have seen in Chapter 5, which have been URIs for consumers of Pachube feeds, this is a URI for producers that send measurements to Pachube. Different applications may use very different sets of headers. For our purposes, the most important headers are Host (for requests only) and Content-Lengthand ContentType (for both requests and responses). Applications may define their own headers, like the XPachubeApiKey above. The order of HTTP headers is not significant, as every possible ordering is correct.

The message body consists of exactly the 12 bytes voltage, 1.52here, has no terminating characters, and is separated from the last header by an empty line.

4) What Pachube Said to Netduino
An HTTP response from Pachube may look like this:
HTTP/1.1 200 OK\r\n Server: nginx/0.7.65\r\n Date: Mon, 07 Feb 2011 13:36:55 GMT\r\n ContentType: text/plain; charset=utf8\r\n Connection: keep-alive\r\n SetCookie: _pachube_app_session=BAh7BjoPc2Vzc2lvbl9…;\r\n CacheControl: maxage=0\r\n ContentLength: 1\r\n Age: 0\r\n Vary: AcceptEncoding\r\n

In this response, the first line, known as the status line, is the most important. HTTP defines a number of status codes; status code 200 means that the request was handled successfully. (The most important status codes are given in Chapter 10.) The status code is located between the HTTP version and a plain-text version of the status code. The text version of the status code is optional—you neither need to generate nor interpret it. It is merely a convenience for human readers of HTTP interactions. Responses may contain many headers, as you can see from this example. Fortunately, you can usually ignore almost all of them. Nevertheless, let’s take a look at the headers in the response: ? Server: Indicates the web server software that Pachube uses. ? Date: Indicates the time when Pachube has sent the response. ? Content-Type: text/plain; charset=utf8 Indicates the format of the Pachube response. In this case, it is plain text encoded in UTF8 (the most common encoding of Unicode characters) ? Connection: keep-alive Is a relic from HTTP 1.0 (an early version of the HTTP specification). Originally, a new TCP/IP connection was opened for every request and then closed after the request. Because opening a connection incurs a considerable overhead, it is better to keep a connection open if requests are sent to the same server every couple of seconds. The keepalive value was added to indicate this desire. It is not relevant anymore because most servers and clients today support HTTP 1.1, where connections are kept alive by default. However, if for any reason a client or a server wants to close a connection after a message exchange, it can signal this to the other party by including the Connection: closeheader.

?

?

? ?

?

Set-Cookie Indicates a cookie (some text that the server sends a client to store, and which the client will send to the server in future requests) with a session identifier. You can ignore cookies because they are not needed for our examples. Cache-Control: max-age=0 Is intended for managing caches between client and server. It indicates that this response must not be cached. Content-Length: 1 Indicates that the response message body consists of one byte. Age: 0 Is an estimate (in seconds) of the time it has taken to produce and transmit the response. It is a header produced by some intermediary cache between server and client. You can ignore it. Vary: Accept-Encoding Tells the client that it may send an AcceptEncoding header along with GET requests, in order to ask for different representations of the resource. As we have seen in Chapter 5, Pachube supports several formats for samples: csv, json, png, etc. However, you won’t need the AcceptEncoding header in the examples of this book. Instead, you can pass the desired format as part of the URI, e.g.,
http://api.pachube.com/v2/feeds/256.csv.

The message body, after the last CR LF(empty line), consists of exactly one blank character. It seems a bit strange that it is not completely empty in the case of Pachube, but you can usually ignore the message body of a PUT response anyway. HTTP requests and responses are not complicated. Any device capable of supporting TCP/IP is able to send data to Pachube or to similar services.


赞助商链接
相关文章:
Get started:希拉里参选双语全文
A lot of things. 我已准备好做很多事。很多事。 It’s spring, so we’re starting to get the gardens ready and my tomatoes are legendary here in my...
Getting Started with TOEFL 托福考试解读
Getting Started with TOEFL 托福考试解读_英语学习_外语学习_教育专区。Getting...The PBT is less costly to take and does not require use of the Internet...
Getting Started with Delphi 3
procedure TMap.Set_FullExtent(Value: Rectangle); begin ControlInterface.FullExtent := Value; end; Getting help The map control is one of the 35 ...
iphone-GettingStarted
Getting Started with iOS Development Developing for...However, the best thing to do is follow the ...PVRTC Instead of DXT Texture Compression PVRTC ...
Getting Started with Rails
Getting Started with Rails_IT/计算机_专业资料。...the internet for learning Ruby, including: Rails ...every little thing through endless configuration ...
Getting Started with CAA RADE V5 Products翻译中文
Getting Started with CAA RADE V5 Products 翻译中文〔原文为英文,将之翻译了一下。 〕 本文解释如何定制 Visual Studio 2005〔说明需要用到这一软件,现在已经...
Getting Started with Pony教程
Getting Started with Pony教程_电脑基础知识_IT/计算机_专业资料。Pony教程 ...the entire (and not very large) set of classes and functions necessary ...
getting_started_with_cloud_computing_amazon_ec2_on_...
getting_started_with_cloud_computing_amazon_ec2_on_red_hat_e 微软、邮件...Amazon 简便存储服务 (Amazon S3) Amazon S3是面向 Internet 的存储。其在设计...
Getting Started with the TIVA C Series TM4C123G Lau...
Getting Started with the TIVA C Series TM4C123G Launchpad_信息与通信_工程科技_专业资料。TIVA C Series launchpad1. 前言这个研讨会的目的是让个人有 C...
Get started with PowerPoint 2003
Get started with PowerPoint 2003 OBJECTIVES Define..., video projector, or even via the Internet. ...When you need to change a part of your ...
更多相关标签: