{ "info": { "author": "Dan Weaver", "author_email": "danweaver@exosite.com", "bugtrack_url": null, "classifiers": [], "description": "Exoline\n=======\n[![PyPI version](https://badge.fury.io/py/exoline.svg)](http://badge.fury.io/py/exoline) [![PyPI](https://img.shields.io/pypi/dm/exoline.svg)]() [![Join the chat at https://gitter.im/exosite/exoline](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/exosite/exoline?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\nExoline is a command line interface for the Exosite [One Platform](http://exosite.com/products/onep). Here's a [video introduction](http://docs.exosite.com/videos/#using-exoline).\n\n![Command line usage of Exoline tree feature](images/twee_example.png)\n\nInstallation \n------------\n\nInstall the latest released version of Exoline from PyPI. \n\n```\n$ sudo pip install --upgrade exoline\n```\n\npip is a package manager for Python. To get pip, try `sudo easy_install pip` in Mac OS X, `sudo apt-get install python-setuptools;sudo easy_install pip` in Ubuntu. See below for Windows instructions.\n\nHere's how to install from source:\n\n```\n$ git clone git://github.com/exosite/exoline\n$ cd exoline\n$ python setup.py install\n```\n\n[virtualenvwrapper](http://virtualenvwrapper.readthedocs.org/en/latest/) is a great way to manage Python environments and avoid needing to use sudu for package installs.\n\nExoline supports Python 2.6 and above. (Tests run against 2.6, 2.7, 3.2, 3.3, and 3.4.)\n\nInstallation - Windows\n----------------------\n\nFor a Windows installer, look [here](https://github.com/exosite/exoline/releases/).\n\nTo install from PyPI, first install the prerequisites:\n\n- [Python](http://www.python.org/downloads/windows/)\n\n- [pip-win](https://sites.google.com/site/pydatalog/python/pip-for-windows) (Alternatively, you can install [setuptools](https://pypi.python.org/pypi/setuptools) and [pip](https://pypi.python.org/pypi/pip) individually. pip-win just saves a few steps.)\n\nAfter pip-win is installed, a GUI window will pop up. To install Exoline, type\n`pip install exoline` into the command field.\n\nUpgrading\n---------\nTo upgade your version of exoline you can use the following command. \n\n```\n$ sudo pip install exoline --upgrade\n```\n\nUsage\n-----\n\n```\nExoline - Exosite IoT Command Line\nhttps://github.com/exosite/exoline\n\nUsage:\n exo [--help] [options] [ ...]\n\nCommands:\n read Read data from a resource.\n write Write data at the current time.\n record Write data at a specified time.\n create Create a resource from a json description passed on stdin (with -),\n or using command line shorthand (other variants).\n listing List the RIDs of a client's children.\n info Get metadata for a resource in json format.\n update Update a resource from a json description passed on stdin.\n map Add an alias to a resource.\n unmap Remove an alias from a resource.\n lookup Look up a resource's RID based on its alias cik.\n drop Drop (permanently delete) a resource.\n flush Remove time series data from a resource.\n usage Display usage of One Platform resources over a time period.\n tree Display a resource's descendants.\n twee Display a resource's descendants. Like tree, but more wuvable.\n find Search resource's descendants for matches.\n script Upload a Lua script\n spark Show distribution of intervals between points.\n copy Make a copy of a client.\n diff Show differences between two clients.\n ip Get IP address of the server.\n data Read or write with the HTTP Data API.\n portals Invalidate the Portals cache for a CIK by telling Portals\n a particular procedure was taken on client identified by .\n share Generate a code that allows non-owners to access resources\n revoke Revoke a share code\n activate Activate a share code\n deactivate Deactivate a share code\n clone Create a clone of a client\n aliases Get dataport aliases from a CIK\n dump Write a zip file with all of a client's data\n keys Get keys from ~/.exolinerc\n makeShortcuts Build a list of shortcuts from a client\n ndup Duplicate a value in a dataport\n model Manage client models for a subdomain (alpha)\n sn Manage serial numbers (alpha)\n content Manage content, e.g. firmware images, for a model (alpha)\n search Search resource names, aliases, serial numbers, and script content\n spec Determine whether a client matches a specification (beta)\n switches Get switches for a command from its documentation\n transform Transform data on in a dataport by mapping all values (alpha)\n\nOptions:\n --host= OneP host. Default is $EXO_HOST or m2.exosite.com\n --port= OneP port. Default is $EXO_PORT or 443\n -c --config= Config file Default is $EXO_CONFIG or ~/.exoline\n --httptimeout= HTTP timeout [default: 60] (default for copy is 480)\n --https Enable HTTPS (deprecated, HTTPS is default)\n --http Disable HTTPS\n --useragent= Set User-Agent Header for outgoing requests\n --debug Show debug info (stack traces on exceptions)\n -d --debughttp Turn on debug level logging in pyonep\n --curl Show curl calls for requests. Implies --debughttp\n --discreet Obfuscate RIDs in stdout and stderr\n -e --clearcache Invalidate Portals cache after running command\n --portals= Portals server [default: https://portals.exosite.com]\n -t --vendortoken= Vendor token (/admin/home in Portals)\n -n --vendor= Vendor identifier (/admin/managemodels in Portals)\n (See http://github.com/exosite/exoline#provisioning)\n -h --help Show this screen\n -v --version Show version\n\nSee 'exo --help' for more information on a specific command.\n```\n\n\nExamples\n--------\n\nShow a tree view of a client\n\n```\n$ exo tree 5de0cfcf7b5bed2ea7a801234567890123456789\nDev client cik: 5de0cfcf7b5bed2ea7a801234567890123456789 (aliases: (see parent))\n \u251c\u2500device1 client cik: 970346d3391a2d8c703a01234567890123456789 (aliases: ['device1'])\n \u2514\u2500device2 client cik: e95052ab56f985e6807d01234567890123456789 (aliases: ['device2'])\n \u2514\u2500json string dataport rid: 82209d5888a3bd1530d201234567890123456789 (aliases: ['json'])\n```\n\nShow a tree view of a client with values\n\n```\n$ exo tree 2ca4f441538c1f2cc8bf01234567890123456789 --values\nArduinoWifi client cik: 2ca4f441538c1f2cc8bf01234567890123456789 (aliases: see parent)\n \u251c\u2500event string dataport rid: f264984bc4f9cf205e8801234567890123456789 (aliases: [\"event\"], value: button/1 years ago)\n \u251c\u2500gas integer dataport rid: 5c9d695fdbe1503c662201234567890123456789 (aliases: [\"gas\"], value: 263/1 years ago)\n \u251c\u2500Humidity float dataport rid: 4fa572ba020cd921038801234567890123456789 (aliases: [\"humidity\"], value: 71.7/1 years ago)\n \u251c\u2500Image URL string dataport rid: 76143aaf0930802775e201234567890123456789 (aliases: [\"image-url\"], value: http://exosite.co.../1 years ago)\n \u251c\u2500light integer dataport rid: 8dc131ea3fff528b122301234567890123456789 (aliases: [\"light\"], value: 1/1 years ago)\n \u251c\u2500Metadata string dataport rid: e93eea75d58615e78e8f01234567890123456789 (aliases: [\"metadata\"], value: {\"foo\":\"bar\",\"baz.../1 years ago)\n \u2514\u2500Temperature float dataport rid: 3bbee56c446f546b546901234567890123456789 (aliases: [\"temperature\"], value: 22/1 years ago)\n```\n\nWrite Lua script\n\n```\n$ exo script translate_gps.lua e469e336ff9c8ed9176bc05ed7fa40daaaaaaaaa \nUpdated script RID: 6c130838e14903f7e12d39b5e76c8e3aaaaaaaaa\n```\n\nRead Lua script (with help from the awesome [jq](http://stedolan.github.io/jq/))\n\n```\n$ exo info e469e336ff9c8ed9176bc05ed7fa40daaaaaaaaa translate_gps.lua --include=description | jq -r .description.rule.script \n```\n\nMonitor debug output of a script\n\n```\n$ exo read e469e336ff9c8ed9176bc05ed7fa40daaaaaaaaa translate_gps.lua --follow \n2013-07-09 11:57:45,line 2: Running translate_gps.lua...\n2013-07-09 12:00:17,\"line 12: New 4458.755987,N,09317.538945,W\nline 23: Writing 4458.755987_-09317.538945\"\n2013-07-09 12:15:41,\"line 12: New 4458.755987,N,09317.538945,W\nline 23: Writing 4458.755987_-09317.538945\"\n```\n\nWrite raw data\n\n```\n$ exo write e469e336ff9c8ed9176bc05ed7fa40daaaaaaaa gps-raw --value=4458.755987,N,09317.538945,W\n```\n\nRecord a bunch of data without timestamps\n\n```\n$ cat myrawgps | exo record e469e336ff9c8ed9176bc05ed7fa40daaaaaaaaa gps-raw - \n```\n\nRead data from multiple dataports to Excel-compatible CSV\n\n```\n$ time exo read 2ca4f441538c1f2cc8bfaaaaaaaaaaaaaaaaaaaa gas temperature humidity event --timeformat=excel --start=5/1/2013 --end=8/1/2013 --limit=10000 > alldata.csv\n\nreal 1m58.377s\nuser 0m10.981s\nsys 0m0.506s\n\n$ wc -l alldata.csv\n 316705 alldata.csv\n```\n\nDump client and all its descendants and time series data to a zip file.\n\n```\n$ exo dump 5fbbf00000000000000000000000000000000000 clientdump.zip\ninfotree.json: 3 resources\nb4a243a16c702caccc991c8b771ef838623445db.json\ndump.json\n{\"points\": 88, \"errors\": [], \"resources\": 3}\n$ unzip -l clientdump.zip\nArchive: clientdump.zip\n Length Date Time Name\n -------- ---- ---- ----\n 1772 12-30-14 15:27 infotree.json\n 2212 12-30-14 15:27 dataport.b4a243a16c702caccc991c8b771ef838623445db.json\n 75 12-30-14 15:27 dump.json\n -------- -------\n 4059 3 files\n```\n\n\n\nMake a clone of device with RID ed6c3f... within portal with CIK e469e3...\n\n```\n$ exo clone e469e336ff9c8ed9176bc05ed7fa40daaaaaaaaa --rid=ed6c3facb6a3ac68c4de9a6996a89594aaaaaaaa\ncik: c81e6ae0fbbd7e9635aa74053b3ab6aaaaaaaaaa\nrid: 9635aa74053b3ab681e6ae0fb8187a0000000000\n```\n\nCopy a device with CIK e469e3... to a different portal with CIK ed6c3f... Note that whereas clone can clone all types of devices and device data within the same portal, copy is somewhat limited in the types of devices it supports but can do inter-portal copies.\n\n```\n$ exo copy e469e336ff9c8ed9176bc05ed7fa40daaaaaaaaa ed6c3facb6a3ac68c4de9a6996a89594aaaaaaaa\ncik: c81e6ae0fbbd7e9635aa74053b3ab6aaaaaaaaaa\nrid: 9635aa74053b3ab681e6ae0fb8187a0000000000\n```\n\nCreate a new client or resource\n\n```\n$ exo create ad02824a8c7cb6b98fdfe0a9014b3c0faaaaaaaa --type=dataport --format=string --alias=stringport --name=\"Original Name\"\nrid: 34eaae237988167d90bfc2ffeb666daaaaaaaaaa\n```\n\n\n\nUpdate a the name of a resource\n\n```\n$ echo '{\"name\":\"New Name\"}' | exo update ad02824a8c7cb6b98fdfe0a9014b3c0faaaaaaaa stringport -\n```\n\nCreate a resource with a custom retention\n\n```\n$ c=`curl cik.herokuapp.com`\n$ echo '{\"format\": \"string\", \"retention\": {\"count\": 4, \"duration\": \"infinity\"}}' | exo create $c --type=dataport --alias=myconfig -\n$ exo info $c myconfig --include=description\n{\"description\": {\"name\": \"\", \"format\": \"string\", \"subscribe\": null, \"meta\": \"\", \"preprocess\": [], \"public\": false, \"retention\": {\"count\": 4, \"duration\": \"infinity\"}}}\n```\n\nGet the RID for CIK ad0282...\n\n```\n$ exo lookup ad02824a8c7cb6b98fdfe0a9014b3c0faaaaaaaa\n34eaae237988167d90bfc2ffeb666daaaaaaaaaa\n```\n\nShow differences between two clients\n\n```\n$ exo copy 3ae52bdd5280d7cb96a2077b0cd5aaaaaaaaaaaa 5de0cfcf7b5bed2ea7a802ebe0679baaaaaaaaaa\ncik: cc080a86b1c9b53d5371e0fa793faaaaaaaaaaa\n$ exo diff 3ae52bdd5280d7cb96a2077b0cd5aaaaaaaaaaaa cc080a86b1c9b53d5371e0fa793f1daaaaaaaaaa\n$ exo create cc080a86b1c9b53d5371e0fa793f1aaaaaaaaaaa --type=dataport --format=float --name=Humidity\nrid: 6a8974d3d7d1f0ffd28385c90a1bebaaaaaaaaaa\n$ exo diff 3ae52bdd5280d7cb96a2077b0cd5dbaaaaaaaaaa cc080a86b1c9b53d5371e0fa793f1daaaaaaaaaa\n{\n \"<>\": {\n \"aliases\": {\n \"<>\": [\n \"temp\"\n ]\n }, \n \"basic\": {\n \"subscribers\": 0, \n \"type\": \"client\"\n }, \n \"children\": {\n \"<>\": {\n+ \"basic\": {\n+ \"subscribers\": 0, \n+ \"type\": \"dataport\"\n+ }, \n+ \"children\": {}, \n+ \"comments\": [], \n+ \"description\": {\n+ \"format\": \"float\", \n+ \"meta\": \"\", \n+ \"name\": \"Humidity\", \n+ \"preprocess\": [], \n+ \"public\": false, \n+ \"retention\": {\n+ \"count\": \"infinity\", \n+ \"duration\": \"infinity\"\n+ }, \n+ \"subscribe\": null\n+ }, \n+ \"shares\": [], \n+ \"subscribers\": [], \n+ \"tags\": []\n+ }, \n+ \"Temperature.f2a40b81cb677401dffdc2cfad0f8a266d63590b\": {\n \"basic\": {\n \"subscribers\": 0, \n \"type\": \"dataport\"\n }, \n \"children\": {}, \n \"comments\": [], \n \"description\": {\n \"format\": \"float\", \n \"meta\": \"\", \n \"name\": \"Temperature\", \n \"preprocess\": [], \n \"public\": false, \n \"retention\": {\n \"count\": \"infinity\", \n \"duration\": \"infinity\"\n }, \n \"subscribe\": null\n }, \n \"shares\": [], \n \"subscribers\": [], \n \"tags\": []\n }\n }, \n \"comments\": [], \n \"counts\": {\n \"client\": 0, \n- \"dataport\": 1, \n? ^\n+ \"dataport\": 2, \n? ^\n \"datarule\": 0, \n \"dispatch\": 0\n }, \n \"description\": {\n \"limits\": {\n \"client\": \"inherit\", \n \"dataport\": \"inherit\", \n \"datarule\": \"inherit\", \n \"disk\": \"inherit\", \n \"dispatch\": \"inherit\", \n \"email\": \"inherit\", \n \"email_bucket\": \"inherit\", \n \"http\": \"inherit\", \n \"http_bucket\": \"inherit\", \n \"share\": \"inherit\", \n \"sms\": \"inherit\", \n \"sms_bucket\": \"inherit\", \n \"xmpp\": \"inherit\", \n \"xmpp_bucket\": \"inherit\"\n }, \n \"locked\": false, \n \"meta\": \"\", \n \"name\": \"MyDevice\", \n \"public\": false\n }, \n \"shares\": [], \n \"subscribers\": [], \n \"tagged\": [], \n \"tags\": []\n }\n}\n```\n\nSee the HTTP requests and responses being made by pyonep:\n\n```\n$ exo --debughttp read sensor1 temperature\nDEBUG:pyonep.onep:POST /api:v1/rpc/process\nHost: m2.exosite.com:80\nHeaders: {'Content-Type': 'application/json; charset=utf-8'}\nBody: {\"calls\": [{\"id\": 70, \"procedure\": \"read\", \"arguments\": [{\"alias\": \"temperature\"}, {\"sort\": \"desc\", \"selection\": \"all\", \"limit\": 1, \"endtime\": 1376943416, \"starttime\": 1}]}], \"auth\": {\"cik\": \"2ca4f441538c1f2cc8bf01234567890123456789\"}}\nDEBUG:pyonep.onep:HTTP/1.1 200 OK\nHeaders: [('date', 'Mon, 19 Aug 2013 20:16:53 GMT'), ('content-length', '54'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('server', 'nginx')]\nBody: [{\"id\":70,\"status\":\"ok\",\"result\":[[1376819736,24.1]]}]\n2013-08-18 04:55:36,24.1\n```\n\n`--curl` is like `--debughttp`, but logs requests in curl format that may be run directly.\n\n```\n$ exo --curl read sensor1 temperature\nDEBUG:pyonep.onep:curl https://m2.exosite.com:443/onep:v1/rpc/process -X POST -m 60 -H 'Content-Type: application/json; charset=utf-8' -H 'User-Agent: Exoline 0.9.0' -d '{\"calls\": [{\"id\": 25, \"procedure\": \"read\", \"arguments\": [{\"alias\": \"temperature\"}, {\"sort\": \"desc\", \"selection\": \"all\", \"limit\": 1}]}], \"auth\": {\"cik\": \"2ca4f441538c1f2cc8bf01234567890123456789\"}}'\nDEBUG:pyonep.onep:HTTP/1.1 200 OK\nHeaders: [('date', 'Tue, 18 Nov 2014 03:02:11 GMT'), ('content-length', '52'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('server', 'misultin/0.8.2-exosite')]\nDEBUG:pyonep.onep:Body: [{\"id\":25,\"status\":\"ok\",\"result\":[[1379607152,22]]}]\n2013-09-19 11:12:32-05:00,22\n\n$ curl https://m2.exosite.com:443/onep:v1/rpc/process -X POST -m 60 -H 'Content-Type: application/json; charset=utf-8' -H 'User-Agent: Exoline 0.9.0' -d '{\"calls\": [{\"id\": 42, \"procedure\": \"read\", \"arguments\": [{\"alias\": \"temperature\"}, {\"sort\": \"desc\", \"selection\": \"all\", \"limit\": 1}]}], \"auth\": {\"cik\": \"2ca4f441538c1f2cc8bf01234567890123456789\"}}'\n[{\"id\":42,\"status\":\"ok\",\"result\":[[1379607152,22]]}]\n```\n\nShare a dataport with another client.\n\n```\n# let's say we want to share client1/dataport1 with client2\n$ exo tree wb\nDev client cik: 5de0cf0000000000000000000000000000000000 (aliases: (see parent))\n \u251c\u2500client1 client cik: 0a35320000000000000000000000000000000000 (aliases: [u'client1'])\n \u2502 \u2514\u2500dataport1 string dataport rid: 4775090000000000000000000000000000000000 (aliases: [u'dataport1'])\n \u2514\u2500client2 client cik: c2d4f30000000000000000000000000000000000 (aliases: [u'client2'])\n\n# generate a share code\n$ exo share 0a35320000000000000000000000000000000000 dataport1\ne9a52a0000000000000000000000000000000000\n\n# activate the share code\n$ exo activate c2d4f30000000000000000000000000000000000 --share=e9a52a0000000000000000000000000000000000\n\n# share appears in tree\n$ exo tree wb\nDev client cik: 5de0cf0000000000000000000000000000000000 (aliases: (see parent))\n \u251c\u2500client1 client cik: 0a35320000000000000000000000000000000000 (aliases: [u'client1'])\n \u2502 \u2514\u2500dataport1 string dataport rid: 4775090000000000000000000000000000000000 (aliases: [u'dataport1'])\n \u2514\u2500client2 client cik: c2d4f30000000000000000000000000000000000 (aliases: [u'client2'])\n \u2514\u2500dataport1 string dataport rid: 4775090000000000000000000000000000000000 \n\n# listing shows owned children by default (not shares)\n$ exo listing c2d4f30000000000000000000000000000000000\n{\"dataport\": [], \"datarule\": [], \"client\": [], \"dispatch\": []}\n\n# ...unless you filter for activated shares\n$ exo listing c2d4f30000000000000000000000000000000000 --filter=activated\n{\"dataport\": [\"4775090000000000000000000000000000000000\"], \"datarule\": [], \"client\": [], \"dispatch\": []}\n\n# write to the shared dataport from its owner\n$ exo write 0a35320000000000000000000000000000000000 dataport1 --value=\"Share me\"\n\n# you can read the dataport from the non-owner\n$ exo read c2d4f30000000000000000000000000000000000 4775090000000000000000000000000000000000\n2013-12-13 11:34:13-06:00,Share me\n\n# ...but you can't write from a non-owner\n$ exo write c2d4f30000000000000000000000000000000000 4775090000000000000000000000000000000000 --value=\"Non-owner can't write\"\nOne Platform error: restricted\n\n# look up RID for a share code\n$ exo lookup c2d4f30000000000000000000000000000000000 --share e9a52a0000000000000000000000000000000000\n4775090000000000000000000000000000000000\n\n# the non-owner can deactivate a share code\n$ exo deactivate c2d4f30000000000000000000000000000000000 --share=e9a52a0000000000000000000000000000000000\n\n# now the share is gone\n$ exo tree wb\nDev client cik: 5de0cf0000000000000000000000000000000000 (aliases: (see parent))\n \u251c\u2500client1 client cik: 0a35320000000000000000000000000000000000 (aliases: [u'client1'])\n \u2502 \u2514\u2500dataport1 string dataport rid: 4775090000000000000000000000000000000000 (aliases: [u'dataport1'])\n \u2514\u2500client2 client cik: c2d4f30000000000000000000000000000000000 (aliases: [u'client2'])\n\n# the owner may also revoke the share code. This makes it unusable.\n$ exo revoke 0a35320000000000000000000000000000000000 --share=e9a52a0000000000000000000000000000000000\nok\n```\n\nCreate a dump of a client. The dump is a zip file containing the info tree (as output by info --recursive), the timestamp at which timeseries values were read, and each timeseries resource under the client. Timeseries resources include type dataport and type datarule.\n\n```\n$ exo dump sensor1 sensor1.zip\n$ unzip -l sensor1.zip\nArchive: sensor1.zip\n Length Date Time Name\n -------- ---- ---- ----\n 3938 12-16-14 22:58 infotree\n 10 12-16-14 22:58 timestamp\n 5367020 12-16-14 23:00 dataport.3bbee56c446f546b5469f629610b8afbcd1fe093\n 5367610 12-16-14 23:02 dataport.4fa572ba020cd9210388f9f60e4708bd623a7c8a\n 10747240 12-16-14 23:06 dataport.5c9d695fdbe1503c6622b0d0f603edc231349c53\n 127 12-16-14 23:06 dataport.76143aaf0930802775e295b190d540d709ebc6b1\n 767969 12-16-14 23:06 dataport.8dc131ea3fff528b122324def5b65159523f7c77\n 151 12-16-14 23:06 dataport.e93eea75d58615e78e8fd0915e7166edf7ad0525\n 23949 12-16-14 23:06 dataport.f264984bc4f9cf205e88a548f42f5ffbfdd21f09\n -------- -------\n 22278014 9 files\n```\n\n\n\n\nProvisioning\n------------\n\nExoline includes provisioning for doing fleet management operations-- everything related to serial numbers, firmware content, and client models. To use these commands, you need to configure Exoline with a vendor identifier and vendor token. This requires having administrator access to a subdomain. If you have that level of access on a subdomain, log in to portals and go to `https://.exosite.com/admin/home` and copy the thing called the \"Vendor API Token\" to your Exoline config file. You'll also need your vendor identification, which can be found at `https://.exosite.com/admin/managemodels`.\n\n```\necho \"vendortoken: 30c8b0123456789abcdef0123456789abcdef012\" >> ~/.exoline\necho \"vendor: myvendor\" >> ~/.exoline\n```\n\nOnce you do this, provisioning commands `model`, `sn`, and `content` work:\n\n```\n$ exo model list\ntestmodel\nPetFoodDispenserModel\n```\n\nThere is a limit of one `vendor` and `vendortoken` per config file. If you're working with multiple subdomains, you'll need to create multiple Exoline config files and pass them in at the command line. For example:\n\n```\n$ exo --config=~/.exoline-another model list \n```\n\nYou can also pass the vendor token and vendor identifier at the command line like this:\n\n```\n$ exo --vendor=myvendor --vendortoken=30c8b0123456789abcdef0123456789abcdef012 model list\n```\n\n#### Provisioning examples\n\nProvision a new device based on a client model\n\n```\n$ exo model list\ntestmodel\nPetFoodDispenserModel\n$ exo sn addrange PetFoodDispenserModel 00000000 000000FF --length=8\n$ exo sn enable PetFoodDispenserModel 00000001 myportal\nae33a5010c0791b758c6ee89437b38d4d44666e6\n$ exo twee myportal\nMy Portal cl cik: f9586af62f8517b24a5f01234567890123456789\n \u2514\u2500Dispenser cl cik: d3846d708c9e6efab8ec01234567890123456789 (PetFoodDispenserModel#00000001)\n \u2514\u2500Percent Full dp.i percentFull:\n$ exo write d3846d708c9e6efab8ec01234567890123456789 percentFull --value=100\nOne Platform exception: {u'message': u'Authorization failed', u'code': 401}\n$ exo sn activate PetFoodDispenserModel 00000001\nd3846d708c9e6efab8ecbad9966872aac77b99e8\n$ exo write d3846d708c9e6efab8ec01234567890123456789 percentFull --value=100\n$ exo read d3846d708c9e6efab8ec01234567890123456789 percentFull\n2014-11-17 21:37:52-06:00,100\n```\n\nWrite some firmware content, read it back, and verify it\n\n```\n$ exo content PetFoodDispenserModel list\n$ # create a 4k binary file\n$ dd if=/dev/random iflag=fullblock of=random_firmware.bin bs=4096 count=1\ndd if=/dev/random of=random_firmware.bin bs=4096 count=1\n1+0 records in\n1+0 records out\n4096 bytes transferred in 0.000298 secs (13743895 bytes/sec)\n$ exo content put PetFoodDispenserModel firmware.bin random_firmware.bin --meta=v0.0.1\n$ exo content list PetFoodDispenserModel --long\nfirmware.bin,4k,Mon Nov 17 22:01:34 2014,false,application/octet-stream,v0.0.1\n$ exo content get PetFoodDispenserModel firmware.bin firmware.bin.downloaded\n$ diff firmware.bin.downloaded random_firmware.bin\n```\n\nRegenerate a CIK for a client. Its status becomes `notactivated`. Activate it again and see its status becomes `activated` and the 24 hour activation window closes. A second call to `activate` shows that the window is closed.\n\n```\n$ exo twee myportal\nMy Portal cl cik: f9586af62f8517b24a5f01234567890123456789\n \u2514\u2500Dispenser cl cik: d3846d708c9e6efab8ec01234567890123456789 (PetFoodDispenserModel#00000001)\n \u2514\u2500Percent Full dp.i percentFull:\n$ exo sn regen PetFoodDispenserModel 00000001\n$ DeviceRID=`exo lookup d3846d708c9e6efab8ec01234567890123456789`\n$ exo info myportal $DeviceRID --include=basic\n{\"basic\": {\"status\": \"notactivated\", \"type\": \"client\", \"modified\": 1416281490, \"subscribers\": 0}}\n$ exo twee myportal\nMy Portal cl cik: f9586af62f8517b24a5f01234567890123456789\n \u2514\u2500Dispenser cl cik: 70522b0830b8e4c4574f01234567890123456789 (PetFoodDispenserModel#00000001)\n \u2514\u2500Percent Full dp.i percentFull: 100 (14 hours ago)\n$ exo sn activate PetFoodDispenserModel 00000001\n70522b0830b8e4c4574f01234567890123456789\n$ exo info myportal $DeviceRID --include=basic\n{\"basic\": {\"status\": \"activated\", \"type\": \"client\", \"modified\": 1416281490, \"subscribers\": 0}}\n$ exo sn activate PetFoodDispenserModel 00000001\nOne Platform provisioning exception: 409 Conflict (HTTP/1.1 409 Conflict)\n```\n\nDisable a client based on its serial number. Its status becomes `expired`. Then call `regen` to regenerate its CIK and activate to activate it.\n\n```\n$ exo sn disable PetFoodDispenserModel 00000001\n$ exo info myportal $DeviceRID --include=basic\n{\"basic\": {\"status\": \"expired\", \"type\": \"client\", \"modified\": 1416281490, \"subscribers\": 0}}\n$ exo sn activate PetFoodDispenserModel 00000001\nOne Platform provisioning exception: 409 Conflict (HTTP/1.1 409 Conflict)\n$ exo sn regen PetFoodDispenserModel 00000001\n$ exo sn activate PetFoodDispenserModel 00000001\n40368ebd8f9923fb189b01234567890123456789\n$ exo info myportal $DeviceRID --include=basic\n{\"basic\": {\"status\": \"activated\", \"type\": \"client\", \"modified\": 1416281490, \"subscribers\": 0}}\n```\n\nGet a log of activations for a serial number\n\n```\n$ exo sn log PetFoodDispenserModel 00000001\n1416281778,127.0.0.1,model=PetFoodDispenserModel&vendor=weaver&sn=00000001\n1416332704,127.0.0.1,model=PetFoodDispenserModel&vendor=weaver&sn=00000001\n1416333004,127.0.0.1,model=PetFoodDispenserModel&vendor=weaver&sn=00000001\n```\n\nSpec\n----\n\nExoline's `spec` command allows you to use a specification file to succinctly specify the way a One Platform client should be set up. Here's an example of creating a client from scratch based on [this spec](https://github.com/exosite/exoline/blob/master/test/files/spec_script_embedded.yaml). Note that this uses a 1 hour time-limited CIK generated from CIK Fountain (cik.herokuapp.com).\n\n```\n$ TEMP_CIK=`curl cik.herokuapp.com`\n$ exo spec $TEMP_CIK https://raw.githubusercontent.com/exosite/exoline/master/test/files/spec_script_embedded.yaml --create\nRunning spec on: cbcae94d523bc29b0937b759b7d3fde5c1670086\ntemp_f not found.\nCreating dataport with name: temp_f, alias: temp_f, format: float\ntemp_c not found.\nCreating dataport with name: temp_c, alias: temp_c, format: float\nconvert.lua not found.\nNew script RID: 7d7c475af2aad7d9c770672cc3640835c36a7cd9\nAliased script to: convert.lua\n$ exo twee $TEMP_CIK\nTemporary CIK cl cik: cbcae94d523bc29b0937b759b7d3fde5c1670086\n \u251c\u2500temp_c dp.f temp_c:\n \u251c\u2500temp_f dp.f temp_f:\n \u2514\u2500convert.lua dr.s convert.lua: line 3: Starting convert.lua... (35 seconds ago)\n$ exo write $TEMP_CIK temp_c --value=-40\n$ exo read $TEMP_CIK temp_f\n2014-11-24 10:50:18-06:00,-40.0\n```\n\nSpec also works with shortened URLs.\n\n```\n$ TEMP_CIK=`curl cik.herokuapp.com`\n$ exo spec $TEMP_CIK http://tinyurl.com/exospec-tempconvert --create\n```\n\nThe `spec` command has a lot of other capabilities, including `--generate` to create a spec file based on an existing device. Try `--help` and `--example` for information about usage.\n\n```\n$ exo spec --help\n$ exo spec --example \n```\n\nTab Completion\n--------------\n\nThere is now tab completion with Exoline. To use it, you must download the complete script with \n\n```\nwget -O ~/.exoline_autocomplete https://raw.githubusercontent.com/exosite/exoline/master/exoline/complete.sh\n```\n\nThen add the script to your ~/.bash_profile so it works whenever you log in.\n\n`echo \"source ~/.exoline_autocomplete\" >> ~/.bash_profile`\n\nThen re-source your current bash_profile to activate the autocompleter.\n\n`source ~/.bash_profile`\n\nOr all together:\n```\nwget -O ~/.exoline_autocomplete https://raw.githubusercontent.com/exosite/exoline/master/exoline/complete.sh; echo \"source ~/.exoline_autocomplete\" >> ~/.bash_profile; source ~/.bash_profile\n```\n\nCompletion will complete anything that should be completed. \n\n```\n$ exo \nactivate content data drop flush keys makeShortcuts ndup record search spark transform unmap write\n```\n\n```\n$ exo read \n12345678 my_other_key my_cool_device coffee\n```\n\n```\n$ exo copy coffee \nadc cur e_waterheat errors powsw upstatus\n```\n\n```\n$ exo read coffee dailybrews --\n--chunksize --end --follow --format --header --help --limit --selection --sort --start --timeformat --tz\n```\n\nCIK Shortcuts\n-------------\n\nStore your commonly used CIKs in a config file:\n\n```\n$ printf \"keys:\\n\" > ~/.exoline\n$ printf \" mydevice: 2ca4f441538c1f2cc8bf01234567890123456789\\n\" >> ~/.exoline\n$ exo read mydevice temperature\n2013-08-18 04:55:36,24.1\n>>>>>>> Stashed changes\n```\n\nEnvironment Variables\n---------------------\n\nFor convenience, several command line options may be replaced by environment variables.\n\n* `EXO_HOST`: host, e.g. m2.exosite.com. This supplies --host to exo and --url for exodata.\n* `EXO_PORT`: port, e.g. 80. Currently this only applies to exo, not exodata.\n* `EXO_PLUGIN_PATH`: additional places to look for plugins\n* `EXO_CONFIG`: location of config file. If not specified, this is `~/.exoline`\n\nIn general, command line options may be set from the environment using the convention `EXO_` + `