You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@edgent.apache.org by dl...@apache.org on 2017/12/15 16:41:41 UTC

[03/10] incubator-edgent-website git commit: add The Power of Edgent

add The Power of Edgent


Project: http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/commit/2dc722d9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/tree/2dc722d9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/diff/2dc722d9

Branch: refs/heads/master
Commit: 2dc722d98bd0745abc981b0953748bd13660de7f
Parents: e538ee2
Author: Dale LaBossiere <dl...@us.ibm.com>
Authored: Sun Nov 5 23:32:57 2017 -0500
Committer: Dale LaBossiere <dl...@us.ibm.com>
Committed: Sun Nov 5 23:32:57 2017 -0500

----------------------------------------------------------------------
 site/_data/mydoc/mydoc_sidebar.yml          |   7 +
 site/docs/edgent-getting-started-samples.md |   1 +
 site/docs/edgent-getting-started.md         |  20 +-
 site/docs/faq.md                            |  11 +-
 site/docs/power-of-edgent.md                | 406 +++++++++++++++++++++++
 5 files changed, 432 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/blob/2dc722d9/site/_data/mydoc/mydoc_sidebar.yml
----------------------------------------------------------------------
diff --git a/site/_data/mydoc/mydoc_sidebar.yml b/site/_data/mydoc/mydoc_sidebar.yml
index 7d95700..8874d2c 100644
--- a/site/_data/mydoc/mydoc_sidebar.yml
+++ b/site/_data/mydoc/mydoc_sidebar.yml
@@ -65,6 +65,13 @@ entries:
       product: all
       version: all
       output: web, pdf
+    - title: The Power of Edgent
+      url: /docs/power-of-edgent.html
+      audience: writers, designers
+      platform: all
+      product: all
+      version: all
+      output: web, pdf
     - title: FAQ
       url: /docs/faq.html
       audience: writers, designers

http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/blob/2dc722d9/site/docs/edgent-getting-started-samples.md
----------------------------------------------------------------------
diff --git a/site/docs/edgent-getting-started-samples.md b/site/docs/edgent-getting-started-samples.md
index bb5c337..d14ed72 100644
--- a/site/docs/edgent-getting-started-samples.md
+++ b/site/docs/edgent-getting-started-samples.md
@@ -10,6 +10,7 @@ run them from either the command line or from an IDE.
 
 Then, see the Additional Resources section at the end of this page!
 
+See [The Power of Edgent](power-of-edgent) to help you quickly start to get a sense of Edgent's capabilities.
 If you want more of an introduction to Edgent and tutorial first, see the [Getting Started Guide](edgent-getting-started).
 
 

http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/blob/2dc722d9/site/docs/edgent-getting-started.md
----------------------------------------------------------------------
diff --git a/site/docs/edgent-getting-started.md b/site/docs/edgent-getting-started.md
index 614f0f9..1a06ddb 100644
--- a/site/docs/edgent-getting-started.md
+++ b/site/docs/edgent-getting-started.md
@@ -13,6 +13,8 @@ Edgent accellerates your development of applications to push data analytics and
 
 For example, if your device takes temperature readings from a sensor 1,000 times per second, it is more efficient to process the data locally and send only interesting or unexpected results over the network.
 
+See [The Power of Edgent](power-of-edgent) to help you quickly start to get a sense of Edgent's capabilities.
+
 Releases of Edgent prior to 1.2.0 distributed Edgent and the samples differently than today.  See [Old Getting Started](old-edgent-getting-started) if you are trying to use an older version.
 
 There's a lot of information available to get started with Edgent and no "best order" for navigating it.  See the navigation sidebar on the left hand side of this page.  In particular it is recommended that you review and visit the various items under _Get Started_.
@@ -109,19 +111,19 @@ Let's review each line.
 
 ### Specifying a provider
 
-Your first step when you write an Edgent application is to create a _Provider_.  In this case we're using a [`DirectProvider`]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/providers/direct/DirectProvider.html):
+Your first step when you write an Edgent application is to create a _Provider_.  In this case we're using a [DirectProvider]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/providers/direct/DirectProvider.html):
 
 ```java
 DirectProvider dp = new DirectProvider();
 ```
 
-A `Provider` is an object that contains information on how and where your Edgent application will run. A `DirectProvider` is a type of Provider that runs your application directly within the current virtual machine when its [`submit()`]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/providers/direct/DirectProvider.html#submit-org.apache.{{ site.data.project.unix_name }}.topology.Topology-) method is called.
+A `Provider` is an object that contains information on how and where your Edgent application will run. A `DirectProvider` is a type of Provider that runs your application directly within the current virtual machine when its [submit()]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/providers/direct/DirectProvider.html#submit-org.apache.{{ site.data.project.unix_name }}.topology.Topology-) method is called.
 
-The [`IotProvider`]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/providers/iot/IotProvider.html) is an alternative that offers very useful and powerful capabilities.
+The [IotProvider]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/providers/iot/IotProvider.html) is an alternative that offers very useful and powerful capabilities.
 
 ### Creating a topology
 
-The Provider is used to create a [`Topology`]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/Topology.html) instance:
+The Provider is used to create a [Topology]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/Topology.html) instance:
 
 ```java
 Topology topology = dp.newTopology();
@@ -136,7 +138,7 @@ Our application then composes the topology's progessing graph.
 
 ### Creating a source `TStream`
 
-In the `TempSensorApplication` class above, we have exactly one data source: the `TempSensor` object. We define the source stream by calling [`topology.poll()`]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/Topology.html#poll-org.apache.{{ site.data.project.unix_name }}.function.Supplier-long-java.util.concurrent.TimeUnit-), which takes both a [`Supplier`]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/function/Supplier.html) function and a time parameter to indicate how frequently readings should be taken. In our case, we read from the sensor every millisecond:
+In the `TempSensorApplication` class above, we have exactly one data source: the `TempSensor` object. We define the source stream by calling [topology.poll()]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/Topology.html#poll-org.apache.{{ site.data.project.unix_name }}.function.Supplier-long-java.util.concurrent.TimeUnit-), which takes both a [Supplier]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/function/Supplier.html) function and a time parameter to indicate how frequently readings should be taken. In our case, we read from the sensor every millisecond:
 
 ```java
 TStream<Double> tempReadings = topology.poll(sensor, 1, TimeUnit.MILLISECONDS);
@@ -144,11 +146,11 @@ TStream<Double> tempReadings = topology.poll(sensor, 1, TimeUnit.MILLISECONDS);
 
 Calling `topology.poll()` to define a source stream creates a `TStream<Double>` instance (because `TempSensor.get()` returns a `Double`), which represents the series of readings taken from the temperature sensor.
 
-A streaming application can run indefinitely, so the [`TStream`]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/TStream.html) might see an arbitrarily large number of readings pass through it. Because a `TStream` represents the flow of your data, it supports a number of operations which allow you to modify your data.
+A streaming application can run indefinitely, so the [TStream]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/TStream.html) might see an arbitrarily large number of readings pass through it. Because a `TStream` represents the flow of your data, it supports a number of operations which allow you to modify your data.
 
 ### Filtering a `TStream`
 
-In our example, we want to filter the stream of temperature readings, and remove any "uninteresting" or expected readings&mdash;specifically readings which are above 50 degrees and below 80 degrees. To do this, we call the `TStream`'s [`filter`]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/TStream.html#filter-org.apache.{{ site.data.project.unix_name }}.function.Predicate-) method and pass in a function that returns *true* if the data is interesting and *false* if the data is uninteresting:
+In our example, we want to filter the stream of temperature readings, and remove any "uninteresting" or expected readings&mdash;specifically readings which are above 50 degrees and below 80 degrees. To do this, we call the `TStream`'s [filter]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/TStream.html#filter-org.apache.{{ site.data.project.unix_name }}.function.Predicate-) method and pass in a function that returns *true* if the data is interesting and *false* if the data is uninteresting:
 
 ```java
 TStream<Double> filteredReadings = tempReadings.filter(reading -> reading < 50 || reading > 80);
@@ -158,7 +160,7 @@ As you can see, the function that is passed to `filter` operates on each tuple i
 
 ### Printing to output
 
-When our application detects interesting data (data outside of the expected parameters), we want to print results. You can do this by calling the [`TStream.print()`]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/TStream.html#print--) method, which prints using  `.toString()` on each tuple that passes through the stream:
+When our application detects interesting data (data outside of the expected parameters), we want to print results. You can do this by calling the [TStream.print()]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/TStream.html#print--) method, which prints using  `.toString()` on each tuple that passes through the stream:
 
 ```java
 filteredReadings.print();
@@ -168,7 +170,7 @@ Unlike `TStream.filter()`, `TStream.print()` does not produce another `TStream`.
 
 A real application typically publishes results to an MQTT server, IoT Hub, Kafka cluster, file, JDBC connection, or other external system. Edgent comes easy to use connectors for these. See the _connectors_ samples, [Edgent Javadoc]({{ site.docsurl }}), and _Edgent Cookbook_ for more information.
 
-You can define your own sink by invoking [`TStream.sink()`]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/TStream.html#sink-org.apache.{{ site.data.project.unix_name }}.function.Consumer-) and passing in your own function.
+You can define your own sink by invoking [TStream.sink()]({{ site.docsurl }}/org/apache/{{ site.data.project.unix_name }}/topology/TStream.html#sink-org.apache.{{ site.data.project.unix_name }}.function.Consumer-) and passing in your own function.
 
 ### Submitting your topology
 

http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/blob/2dc722d9/site/docs/faq.md
----------------------------------------------------------------------
diff --git a/site/docs/faq.md b/site/docs/faq.md
index b2fde5c..1f3a2f2 100644
--- a/site/docs/faq.md
+++ b/site/docs/faq.md
@@ -4,7 +4,8 @@ title: FAQ
 
 ## What is Apache Edgent?
 
-Edgent provides APIs and a lightweight runtime enabling you to easily create event-driven applications to analyze streaming data at the edge.
+Edgent provides APIs and a lightweight runtime enabling you to easily create event-driven flow-graph style applications to analyze streaming data at the edge.
+ Check out [The Power of Edgent](power-of-edgent)!
 
 ## What do you mean by the edge?
 
@@ -16,7 +17,7 @@ While Edgent's design center is executing on constrained edge devices, Edgent ap
 
 ## How are applications developed?
 
-Applications are developed using a functional flow API to define operations on data streams that are executed as a flow graph in a lightweight embeddable runtime. Edgent provides capabilities like windowing, aggregation and connectors with an extensible model for the community to expand its capabilities.
+Applications are developed using a functional flow API to define operations on data streams that are executed as a flow graph in a lightweight embeddable runtime. Edgent provides capabilities like windowing, aggregation and connectors with an extensible model for the community to expand its capabilities. Check out [The Power of Edgent](power-of-edgent)!
 
 You can develop Edgent applications using an IDE of your choice. 
 
@@ -34,7 +35,7 @@ Edgent comes with some initial analytics for aggregation and filtering that you
 
 ## What connectors does Apache Edgent support?
 
-Edgent provides easy to use connectors for MQTT, HTTP, JDBC, File, Apache Kafka and IBM Watson IoT Platform. Edgent is extensible; you can create connectors.
+Edgent provides easy to use connectors for MQTT, HTTP, JDBC, File, Apache Kafka and IBM Watson IoT Platform. Edgent is extensible; you can create connectors.  You can easily supply any code you want for ingesting data from and sinking data to external systems.
 
 ## What centralized streaming analytic systems does Apache Edgent support?
 
@@ -46,7 +47,9 @@ Edgent is designed for the edge. It has a small footprint, suitable for running
 
 ## Why do I need Apache Edgent, rather than coding the complete application myself?
 
-Edgent is designed to accellerate your development of edge analytic applications - to make you more productive! Edgent provides a simple yet powerful consistent data model (streams and windows) and provides useful functionality, such as aggregations, joins, and numerous connectors. Using Edgent lets you to take advantage of this functionality, allowing you to focus on your application needs.  For example, you can create an Edgent application that periodically samples a sensor and publishes values to an MQTT server in about 10 lines of code!
+Edgent is designed to accellerate your development of edge analytic applications - to make you more productive! Edgent provides a simple yet powerful consistent data model (streams and windows) and provides useful functionality, such as aggregations, joins, and connectors. Using Edgent lets you to take advantage of this functionality, allowing you to focus on your application needs.  
+
+Check out what you can do in 10 lines of code [The Power of Edgent](power-of-edgent)!
 
 ## Where can I download Apache Edgent?
 

http://git-wip-us.apache.org/repos/asf/incubator-edgent-website/blob/2dc722d9/site/docs/power-of-edgent.md
----------------------------------------------------------------------
diff --git a/site/docs/power-of-edgent.md b/site/docs/power-of-edgent.md
new file mode 100644
index 0000000..e0e4126
--- /dev/null
+++ b/site/docs/power-of-edgent.md
@@ -0,0 +1,406 @@
+---
+title: The Power of Apache Edgent
+---
+
+Edgent is designed to accellerate your development of event driven flow-graph
+style analytic applications running on edge devices.  This is achieved by
+Edgent's combination of API, connectors, basic analytics, utilities, and openness!
+
+Let's have some fun with a shallow but broad glimpse into what you
+can do in a few of lines of code... an introduction to Edgent's capabilities via
+a series of terse code fragments.
+
+See the [Getting Started Guide](edgent-getting-started) for a step by step introduction,
+and information about full samples and recipies.
+
+Let's start with a complete application that periodically samples a sensor
+and publishes its values to an Enterprise IoT Hub in less than 10 lines of code
+
+```java
+public class ImpressiveEdgentExample {
+  public static void main(String[] args) {
+    DirectProvider provider = new DirectProvider();
+    Topology top = provider.newTopology();
+
+    IotDevice iotConnector = IotpDevice.quickstart(top, "edgent-intro-device-2");
+    // open https://quickstart.internetofthings.ibmcloud.com/#/device/edgent-intro-device-2
+
+    // ingest -> transform -> publish
+    TStream<Double> readings = top.poll(new SimulatedTemperatureSensor(), 1, TimeUnit.SECONDS);
+    TStream<JsonObject> events = readings.map(JsonFunctions.valueOfNumber("temp"));
+    iotConnector.events(events, "readingEvents", QoS.FIRE_AND_FORGET);
+
+    provider.submit(top);
+  }
+}
+```
+
+Ok, that was 11 lines and it omitted the imports, but there are only 7 lines in main()!
+
+That leveraged the [IotpDevice]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/iotp/IotpDevice.html)
+connector to the 
+IBM Watson IoT Platform and the platform's Quickstart feature. 
+The value of its Quickstart feature is no account or device 
+preregistration and the ability to open a browser to see the
+data being published.  Great to quickly get started.
+
+Hopefully that had enough of a wow factor to encourage you
+to keep reading!
+
+### Connectors, Ingest and Sink
+
+Edgent Applications need to create streams of data from external entities,
+termed ingest, and sink streams of data to external entities.  
+There are primitives for those operations and a collection of
+connectors to common external entities.
+
+Connectors are just code the make it easier to for an Edgent application
+to integrate with an external entity.  They use Edgent ingest primitives
+like (`Topology.poll()`, `Topology.events()`, etc), and `TStream.sink()`
+like any other Edgent code.  But more Connectors contributions are welcome!
+
+OK... fewer words, more code!
+
+You've already seen publishing using the `IotpDevice` connector.
+
+Want to receive [IotDevice]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/iot/IotDevice.html) device commands? Simple!
+
+```java
+    TStream<JsonObject> cmds = iotConnector.commands();
+    cmds.sink(cmd -> System.out.println("I should handle received cmd: "+cmd));
+
+    or
+    TStream<JsonObject> xzyCmds = iotConnector.command("xyzCmds");
+```
+
+There's an [IotGateway]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/iot/IotGateway.html) device model too.
+
+Don't want no stinkin `IotDevice` model and just
+want to pub/sub to an MQTT server?  No worries! Use the [MqttStreams]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/mqtt/MqttStreams.html) connector
+
+```java
+    //IotDevice iotConnector = IotpDevice.quickstart(top, "edgent-intro-device-2");
+    MqttStreams iotConnector = new MqttStreams(top, "ssl://myMqttServer:8883", "my-device-client-id");
+
+    ...
+
+    //iotConnector.events(events, "readingEvents", QoS.FIRE_AND_FORGET);
+    iotConnector.publish(events, "readingEvents", QoS.FIRE_AND_FORGET, false);
+
+    TStream<JsonObject> xyzTopicMsgs = iotConnector.subscribe("xyzTopic");
+```
+
+Want to connect to Kafka?  Use the [KafkaProducer]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/kafka/KafkaProducer.html) and [KafkaConsumer]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/kafka/KafkaConsumer.html) connectors with similar ease.
+
+There's a [JdbcStreams]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/jdbc/JdbcStreams.html) connector too.
+
+Want to sink a `TStream` to rolling text files?  Use the [FileStreams]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/file/FileStreams.html) connector.
+
+```java
+    new File("/tmp/MY-DEMO-FILES").mkdir();
+    FileStreams.textFileWriter(events.asString(), () -> "/tmp/MY-DEMO-FILES/READINGS");
+
+    // tail -f /tmp/MY-DEMO-FILES/.READINGS
+```
+
+Or watch for, ingest and process text files?  [Csv]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/csv/Csv.html) can be useful if your input lines of comma separated values
+
+```java
+   String watchedDir = "/some/directory/path";
+   List<String> csvFieldNames = ...
+   TStream<String> pathnames = FileStreams.directoryWatcher(top, () -> watchedDir, null);
+   TStream<String> lines = FileStreams.textFileReader(pathnames);
+   TStream<JsonObject> parsedLines = lines.map(line -> Csv.toJson(Csv.parseCsv(line), csvFieldNames));
+```
+
+Want to sink to a command's stdin?  Use the [CommandStreams]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/connectors/command/CommandStreams.html) connector
+
+```java
+    TStream<MyEvent> events = ...
+    ProcessBuilder cmd = new ProcessBuilder("cat").redirectOutput(new File("/dev/stdout"));
+    CommandStreams.sink(events.asString(), cmd);
+```
+
+Or ingest a command's stdout/err?
+
+```java
+    ProcessBuilder cmd = new ProcessBuilder("date", "-R");
+    TStream<List<String>> readings = CommandStreams.periodicSource(top, cmd, 1, TimeUnit.SECONDS);
+
+    TStream<JsonObject> events =
+        readings
+          .flatMap(list -> list)
+          .map(JsonFunctions.valueOfString("date"));
+    
+    // also note TStream support for a fluent programming style
+    // and use of TStream.flatmap() to transform in input list to
+    // an output list and then add each output list item as a separate
+    // tuple to the output stream
+```
+
+
+Want to sink to a log via slf4j or another logging system?  Just do it!
+
+```java
+    import org.slf4j.Logger;
+    import org.slf4j.LoggerFactory;
+    private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
+
+    readings.sync(reading -> logger.info("reading: {}", reading));
+```
+
+### More on Ingest
+
+You've seen how to periodically poll a function to get a some data.  
+That's just one of the methods defined in [Topology]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/Topology.html) for ingesting data - for creating source streams.
+
+Also note that the tuples (a.k.a. events, data, objects) in a [TStream]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/TStream.html) can be any type. There's no special Edgent tuple type hierarchy.
+
+Want readings from multiple sensors in a single stream tuple?
+
+```java
+    SimulatedTemperatureSensor tempSensor = new SimulatedTemperatureSensor();
+    SimpleSimulatedSensor pressureSensor = new SimpleSimulatedSensor();
+    
+    TStream<JsonObject> events = top.poll( () -> {
+          JsonObject jo = new JsonObject();
+          jo.addProperty("temp", tempSensor.get());
+          jo.addProperty("pressure", pressureSensor.get());
+          return jo;
+        }, 1, TimeUnit.SECONDS);
+```
+
+Want to use define a class or use an existing one for a tuple?
+
+```java
+    public class SensorReading {
+        double temp;
+        double pressure;
+        public SensorReading(double temp, double pressure) {
+            this.temp = temp;
+            this.pressure = pressure;
+        }
+    }
+
+    SimulatedTemperatureSensor tempSensor = new SimulatedTemperatureSensor();
+    SimpleSimulatedSensor pressureSensor = new SimpleSimulatedSensor();
+    
+    TStream<SensorReading> readings = top.poll(
+        () -> new Reading(tempSensor.get(), pressureSensor.get()),
+        1, TimeUnit.SECONDS);
+```
+
+#### Simulated Sensors
+
+Edgent provides some simple simulated sensors that can be helpful to get going.
+You've already seen `SimulatedTemperatureSensor` and
+`SimpleSimulatedSensor` - included in the Edgent Samples source release bundle.
+There are a couple of more in the same Java package.
+
+#### Sensor library
+
+Wondering if Edgent has a sensor library?  It does not
+because there seems to be little value in supplying one.
+
+Using Edgent's stream ingest primitives (e.g., `Topology.poll(), Topology.events()`,
+etc) it's trivial for you to call the sensor's APIs to get 
+readings and compose them into a stream data object of your
+choosing to be added a `TStream`.
+
+### Filtering
+
+Let's get back to our original `ImpressiveEdgentExample`
+and explore making it smarter - push more analytics out
+to the edge!
+
+Want to only publish readings with values less than 5 and more than 30?
+
+```java
+    // add this after the poll()
+    readings = readings.filter(tuple -> tuple < 5d || tuple > 30d);
+```
+
+Your filter predicate function can do what ever it needs to do!
+
+Or use the [Range]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/analytics/sensors/Range.html) utility
+
+```java
+    Range<Double> range = Ranges.open(5, 30);
+    readings = readings.filter(reading -> !range.test(reading));
+```
+
+That alone isn't a very compelling use of Range but consider
+a larger context.
+A Range has a simple String representation (e.g., the above is `"(5d,30d)"`)
+so that value could be read from an application configuration file
+on startup, or received from a device command to create a
+dynamically configurable application.
+
+[Filters.deadband]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/analytics/sensors/Filters.html)
+offers a more sophisticated filter.
+There's a nice graphic depection of the filter behavior in the method's javadoc.
+
+```java
+    Range<Double> range = Ranges.open(5, 30);
+    readings = Filters.deadband(readings, reading -> reading, range);
+```
+
+There's also a [Filters.deadtime]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/analytics/sensors/Filters.html) filter can can come in handy.
+
+### Split
+
+Want to split a TStream into multiple TStreams - e.g., to handle
+different categories of tuples differently?  `TStream.split()`
+is essentially an efficient group of multiple filters.
+
+```java
+    List<TStream<Double>> twoStreams = readings.split(2, d -> range.test(d) ? 0 : 1);
+    TStream<Double> inRange = twoStreams.get(0);
+    TStream<Double> outOfRange = twoStreams.get(1);
+```
+
+Your split function can yield as many output streams as you need.
+There's also a form of `split()` that works with `Enum` identifiers.
+
+### Transforms
+
+Want to convert a value that's in Centigrade to Farenheit?
+
+```java
+    readings = readings.map(reading -> (reading * 1.8) + 32);
+```
+
+Your map function can do what ever it needs to do!
+E.g., a tuple could be a video image frame and the map
+function could generate face detection events.
+
+We've already seen converting a numeric value to a JsonObject using [JsonFunctions]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/json/JsonFunctions.html)
+
+```java
+    TStream<JsonObject> events = readings.map(JsonFunctions.valueOfNumber("temp"));
+```
+
+What about scoring against a model?  Edgent doesn't have
+anything special for that at least at this time.  
+But it's easy to integrate the use of some scoring model
+system into an Edgent application.  
+Imagine a package defined something like:
+
+```java
+    public class Model {
+      // load model from a File
+      public Model(File f) { ... };
+      // score String s against the model
+      // return a confidence score between 0.0 and 1.0
+      public Double score(String s);
+    }
+```
+
+An Edgent application might use such a model in this manner
+
+```java
+    final Model model = new Model(...);
+
+    TStream<String> events = ...
+    TStream<JsonObject> scoredEvents = events.map(event -> {
+        Double score = model.score(event);
+        JsonObject jo = new JsonObject();
+        jo.addProperty("event", event);
+        jo.addProperty("score", score);
+        return jo;
+        };
+    TStream<JsonObject> interestingEvents = 
+        scoredEvents.filter(jo -> jo.getAsJsonPrimitive.getAsDouble("score") > 0.75);
+```
+
+OK, maybe that one was a bit too large a code fragment for this introduction.
+
+### Windowing and aggregation
+
+Want to do signal smoothing - create a continuously aggregated average over the last 10 readings?
+Each time a tuple is added to the window a new aggregated value computed and is added to the output stream.
+
+```java
+    TWindow<Double,Integer> window = readings.last(10, Functions.unpartitioned());
+    TStream<Double> readings = window.aggregate((List<Double> list, Integer partition) -> {
+          double avg = 0.0;
+          for (Double d : list) avg += d;
+          if (list.size() > 0) avg /= list.size();
+          return avg;
+        });
+```
+
+
+Want a window over the last 10 seconds instead?
+
+```java
+    // TWindow<Double,Integer> window = readings.last(10, Functions.unpartitioned());
+    TWindow<Double,Integer> window = readings.last(10, TimeUnit.SECONDS, Functions.unpartitioned());
+    ...
+```
+
+Or want to do data reduction - reduce the readings to one average value every window batch?
+Once the window is full, the batch of tuples is aggregated, the result is added to the output
+stream and the window is cleared.  The next aggregation isn't generated until the window
+is full again.
+
+```java
+    TStream<Double> readings = window.batch((List<Double> list, Integer partition) -> {
+          double avg = 0.0;
+          for (Double d : list) avg += d;
+          if (list.size() > 0) avg /= list.size();
+          return avg;
+        });
+```
+
+Or use [Aggregations]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/analytics/math3/Aggregations.html) for simple statistics
+
+```java
+    TStream<Double> readings = window.batch((List<Double> list, Integer partition) ->
+          Aggregations.aggregate(list, Statistics2.MEAN));
+```
+
+Want to compute several basic statistics and a regression for an aggregation? Use [AggregationsN]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/analytics/math3/AggregationsN.html)
+
+```java
+    TWindow<Double,Integer> window = readings.last(10, Functions.unpartitioned());
+    TStream<ResultMap> aggResults = window.batch((List<Double> list, Integer partition) -> 
+                       AggregationsN(list, Statistic2.MIN, Statistic2.MAX,
+                                     Statistic2.SUM, Statistic2.STDDEV,
+                                     Statistic2.COUNT, Regression2.SLOPE));
+    TStream<JsonObject> joAggResults = aggResults(ResultMap.toJsonObject()); // optional
+```
+
+There's also support for multi-variable aggregations - independent statistic
+aggregations for multi variables in a list of tuples.  e.g., temperatures and
+pressures variables in each tuple.
+
+If the objects in the window are a JsonObject, [JsonAnalytics]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/analytics/math3/json/JsonAnalytics.html) can be handy.
+
+### Misc
+
+Want to run an expensive computation on multiple tuples in parallel?  
+Easy with [PlumbingStreams.parallel()]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/plumbing/PlumbingStreams.html)!
+
+```java
+    TStream<SensorReading> readings = ...
+    // 3 parallel channels - i.e., can process 3 tuples simultaneously on separate theads
+    TStream<MyData> analyzed = PlumbingStreams.parallel(readings,
+                   3, PlumbingStreams.roundRobinSplitter(),
+                   (input, channel, output) ->
+                     input.map(reading -> myExpensiveComputation(reading)));
+```
+
+See [PlumbingStreams.parallelBalanced]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/plumbing/PlumbingStreams.html) for a load balanced form that will assigns
+a tuple to any idle channel.
+
+There are a variety of useful features in [PlumbingStreams]({{ site.docsurl }}/index.html?org/apache/{{ site.data.project.unix_name }}/topology/plumbing/PlumbingStreams.html).
+
+### Wrap up
+
+We touched on a lot, but not all, of Edgent.
+Hopefully you're convinced that the combination of Edgent's API, connectors, etc are powerful and easy to use.
+
+See the full [Edgent APIs Javadoc]({{ site.docsurl }})
+and [Getting Started Guide](edgent-getting-started) for more information including pointers to more introductory
+material and samples and recipies.