Skip to content
Permalink
Browse files

Add a printer emulator.

  • Loading branch information...
teh committed Mar 13, 2015
1 parent d66ba5d commit 11db07cccb744011a79d8d05bb9e6d2a785365ed
@@ -0,0 +1,39 @@
# Emulation

The project comes with a very simple emulator layer. As a first step
we have to create a fake printer:

```console
$ ./manage.py fake printer
Importing environment from .env...
--------------------------------------------------------------------------------
DEBUG in webapp [/home/tom/devel/wizards/sirius/sirius/web/webapp.py:65]:
Creating app.
--------------------------------------------------------------------------------
DEBUG:sirius.web.webapp:Creating app.
address: cc18aff27ed5cf3d
DB id: 15
secret: 6b79e72280
xor: 15444370
claim code: ebqp-pyg7-4b0f-qbdk

Created printer and saved as cc18aff27ed5cf3d.printer
```

This command wrote the file `cc18aff27ed5cf3d.printer`.

## Running a fake printer

I refer to the [README](README.md) file for running the main server
and assume it is running at `127.0.0.1:5000`.

You can run:

```console
$ ./manage.py emulate printer cc18aff27ed5cf3d.printer ws://127.0.0.1:5000/api/v1/connection
[...]
Asked for encryption key
Received encryption key, switching to heartbeat mode.
Heartbeat. Pom pom.
Heartbeat. Pom pom.
```
@@ -10,6 +10,7 @@

from sirius.web import webapp
from sirius.fake import commands as fake_commands
from sirius.emulate import commands as emulate_commands
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand

@@ -22,6 +23,7 @@ def make_shell_context():
manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)
manager.add_command('fake', fake_commands.fake_manager)
manager.add_command('emulate', emulate_commands.manager)


@manager.command
@@ -17,6 +17,7 @@ WTForms==2.0.1
Werkzeug==0.9.6
alembic==0.6.7
argparse==1.2.1
backports.ssl-match-hostname==3.4.0.2
gevent==1.0.1
gevent-websocket==0.9.3
greenlet==0.4.4
@@ -32,5 +33,7 @@ redis==2.10.3
requests==2.5.1
requests-oauthlib==0.4.2
selenium==2.43.0
six==1.9.0
twitter==1.15.0
websocket-client==0.26.0
wsgiref==0.1.2
No changes.
@@ -0,0 +1,117 @@
"""Fake a few printer interactions for testing.
"""
from __future__ import print_function

import os
import gevent
import gevent.monkey
gevent.monkey.patch_all()
import websocket
import json
import re
import random
import time

from flask.ext import script

from sirius.emulate import protocol_fragments as pf

def sub_opts(app, **kwargs):
pass

manager = script.Manager(
sub_opts, usage="Emulate a real printer, websockets and all.")


class State:
"""Keep track of printer state. Only one per process so global state
is OK."""
online = True
needs_key = True
device_address = None
bridge_address = None

def heartbeat(ws):
while True:
if (State.online, State.needs_key) == (True, True):
ws.send(pf.ENCRYPTION_KEY_REQUIRED %
dict(bridge_address=State.bridge_address,
device_address=State.device_address))
print("Asked for encryption key")
gevent.sleep(30.0) # key request is sent every 30s
elif (State.online, State.needs_key) == (True, False):
ws.send(pf.HEARTBEAT %
dict(bridge_address=State.bridge_address,
device_address=State.device_address))
print("Heartbeat. Pom pom.")
gevent.sleep(10.0) # heartbeate is sent every 10s
elif State.online == False:
gevent.sleep(10.0) # sleep for a while to burn cycles

else:
assert False # unreachable


def _decode(data):
try:
data = json.loads(data)
except ValueError as e:
print("Server sent invalid JSON!", e)
return

if data['type'] == 'BridgeCommand':
key = data['json_payload'].get('params', {}).get('encryption_key')
print("Received encryption key, switching to heartbeat mode.")
State.needs_key = False

if data['type'] == 'DeviceCommand':
payload = data['binary_payload']
command_id = data['command_id']

# TODO(tom): Decode binary payload for debugging.

return {u'device_address': State.device_address,
u'timestamp': time.time(),
u'transfer_time': 3.44,
u'bridge_address': State.bridge_address,
u'return_code': 0,
u'rssi_stats': [-19,-19,-19],
u'type': u'DeviceCommandResponse',
u'command_id': command_id}


@manager.command
def printer(printer_data_path, websocket_url):

with open(printer_data_path) as f:
printer_data = f.read()

print("Contacting", websocket_url)
print(printer_data)
print("-----------------------------")

# Parse data from printer file
State.device_address = re.search('address: ([a-f0-9]{16})', printer_data).group(1)
State.bridge_address = '{:016x}'.format(random.randrange(0, 2**64))

# Sanity check websocket connection URL:
if not websocket_url.endswith('/api/v1/connection'):
print("Websocket URL must end with '/api/v1/connection'")
print("Maybe you meant {}/api/v1/connection ?".format(websocket_url))
return 1

# Connect and initialize
ws = websocket.create_connection(websocket_url)
ws.send(pf.CONNECT % dict(bridge_address=State.bridge_address))

gevent.spawn(heartbeat, ws)

try:
while True:
data = ws.recv()
response = _decode(data)
if response is not None:
ws.send(json.dumps(response))

finally:
ws.close()
@@ -0,0 +1,69 @@
CONNECT = """\
{
"type" : "BridgeEvent",
"json_payload" : {
"ncp_version" : "0x46C5",
"uptime" : "45.71 23.94",
"firmware_version" : "v2.3.1-f3c7946",
"network_info" : {
"extended_pan_id" : "0x42455247fbbbbd7f",
"node_type" : "EMBER_COORDINATOR",
"radio_power_mode" : "EMBER_TX_POWER_MODE_BOOST",
"security_level" : 5,
"network_status" : "EMBER_JOINED_NETWORK",
"channel" : 11,
"security_profile" : "Custom",
"power" : 8,
"node_eui64" : "0x000d6f0001b3719d",
"pan_id" : "0xDF3A",
"node_id" : "0x0000"
},
"name" : "power_on",
"local_ip_address" : "192.168.1.98",
"uboot_environment" : "YXBwZW5kX3J1bl9tb2RlPXNldGVudiBib290YXJncyAke2Jvb3RhcmdzfSBydW5tb2RlPSR7cnVubW9kZX07CmF1dG9sb2FkPW5vCmJhdWRyYXRlPTExNTIwMApib2FyZF9tYW51ZmFjdHVyZV9pbmZvPUJSSURHRUFBMTI0MzA0MzYKYm9vdGFyZ3M9Y29uc29sZT10dHlTMCwxMTUyMDAgcm9vdD0vZGV2L210ZGJsb2NrNSBtdGRwYXJ0cz1hdG1lbF9uYW5kOjEyOGsoYm9vdHN0cmFwKXJvLDI1NmsodWJvb3Qpcm8sMTI4ayhlbnYxKXJvLDEyOGsoZW52MilybywyTShsaW51eCksLShyb290KSBydyByb290ZnN0eXBlPWpmZnMyCmJvb3RjbWQ9cnVuIGNhdGNoX2J0bjsgcnVuIGluc3RfaWZfcmVxdWlyZWQ7IHJ1biBhcHBlbmRfcnVuX21vZGU7IHJ1biByZXNldF9sZWRzOyBuYW5kIHJlYWQgJHtrZXJuZWxfbG9hZF9hZGRyZXNzfSAke2tlcm5lbF9mbGFzaF9hZGRyZXNzfSAke2tlcm5lbF9zaXplfTsgYm9vdG0gJHtrZXJuZWxfbG9hZF9hZGRyZXNzfQpib290ZGVsYXk9MQpjYXRjaF9idG49c2V0ZW52IHJ1bm1vZGUgc3RhbmRhcmQ7IHNldGVudiBsb29wIDE7IHdoaWxlIHRlc3QgJHtsb29wfSAtZXEgIjEiIDsgZG8gcnVuIGZsYXNoX3doZW5faGVsZDsgZG9uZTsgc2V0X2Nsb3VkX2xlZCAwOyBzZXRfZXRoX2xlZCAwOyBzZXRfemlnYmVlX2xlZCAwCmV0aGFjdD1tYWNiMApldGhhZGRyPTQwOkQ4OjU1OjAxOkMxOkIzCmZhY3RvcnlfZmV0Y2hfaW1hZ2U9ZGhjcDsgdGZ0cCAweDIwODAwMDAwIGZhY3RvcnlfZmxhc2guaW1nCmZhY3RvcnlfcnVuX2ltYWdlPXNvdXJjZSAweDIwODAwMDAwCmZhY3Rvcnlfc2V0dXA9cnVuIGZhY3RvcnlfZmV0Y2hfaW1hZ2UgZmFjdG9yeV9ydW5faW1hZ2UKZmFpbF9sb29wPXNldGVudiBsb29wIDE7IHdoaWxlIHRlc3QgJHtsb29wfSAtZXEgIjEiIDsgZG8gbGVkX3NlcSAwOyBkb25lCmZpbGVhZGRyPTIwMDAwMDAwCmZpbGVzaXplPTEwRTUwM0MKZmxhc2hfd2hlbl9oZWxkPWdldF9lbmdfYnRuOyBpZiB0ZXN0ICQ/IC1lcSAwOyB0aGVuIHNldGVudiBydW5tb2RlIGVuZ3Rlc3Q7IGxlZF9zZXEgMTsgZWxzZSBzZXRlbnYgbG9vcCAwOyBmaTsKZ2F0ZXdheWlwPTEwLjEwLjAuMQppbnN0X2ZldGNoX2ltYWdlPWRoY3A7IHRmdHAgJHtrZXJuZWxMb2FkQWRkcn0gJHtmYWN0b3J5U2NyaXB0RmlsZW5hbWV9Cmluc3RfaWZfcmVxdWlyZWQ9aWYgdGVzdCAiJHtydW5tb2RlfSIgPSAiZW5ndGVzdCI7IHRoZW4gcnVuIGluc3Rfc2V0dXA7IGZpOwppbnN0X3J1bl9pbWFnZT1zb3VyY2UgJHtrZXJuZWxMb2FkQWRkcn0KaW5zdF9zZXR1cD1ydW4gZmFjdG9yeV9mZXRjaF9pbWFnZSBmYWN0b3J5X3J1bl9pbWFnZQppcGFkZHI9MTAuMTAuMC4yNwprZXJuZWxfZmlsZW5hbWU9dUltYWdlCmtlcm5lbF9mbGFzaF9hZGRyZXNzPTB4YTAwMDAKa2VybmVsX2xvYWRfYWRkcmVzcz0weDIwODAwMDAwCmtlcm5lbF9zaXplPTB4MjAwMDAwCm1lbV9sb2FkX2FkZHJlc3M9MHgyMDAwMDAwMApuZXRtYXNrPTI1NS4yNTUuMjQ4LjAKcmVzZXRfbGVkcz1zZXRfZXRoX2xlZCAwOyBzZXRfemlnYmVlX2xlZCAwOyBzZXRfY2xvdWRfbGVkIDA7CnJvb3Rmc19lcmFzZV9zaXplPTB4ZmQ2MDAwMApyb290ZnNfZmlsZW5hbWU9cm9vdGZzLmpmZnMyCnJvb3Rmc19mbGFzaF9hZGRyZXNzPTB4MmEwMDAwCnJvb3Rmc193cml0ZV9zaXplPTB4MTUwMDAwMApzZXJ2ZXJpcD0xMC4xMC4wLjEKc3RkZXJyPXNlcmlhbApzdGRpbj1zZXJpYWwKc3Rkb3V0PXNlcmlhbAp3cml0ZV9rZXJuZWw9ZWNobyAiRmV0Y2ggKyBXcml0ZSBrZXJuZWwiOyBtdy5iICR7bWVtX2xvYWRfYWRkcmVzc30gMHhmZiAke2tlcm5lbF9zaXplfTsgdGZ0cCAke21lbV9sb2FkX2FkZHJlc3N9ICR7a2VybmVsX2ZpbGVuYW1lfTsgbmFuZCBlcmFzZSAke2tlcm5lbF9mbGFzaF9hZGRyZXNzfSAke2tlcm5lbF9zaXplfTsgbmFuZCB3cml0ZSAke21lbV9sb2FkX2FkZHJlc3N9ICR7a2VybmVsX2ZsYXNoX2FkZHJlc3N9ICR7a2VybmVsX3NpemV9Owp3cml0ZV9yb290ZnM9ZWNobyAiRmV0Y2ggKyBXcml0ZSByb290ZnMiOyBtdy5iICR7bWVtX2xvYWRfYWRkcmVzc30gMHhmZiAke3Jvb3Rmc193cml0ZV9zaXplfTsgdGZ0cCAke21lbV9sb2FkX2FkZHJlc3N9ICR7cm9vdGZzX2ZpbGVuYW1lfTsgc2V0X2Nsb3VkX2xlZCAxOyBuYW5kIGVyYXNlICR7cm9vdGZzX2ZsYXNoX2FkZHJlc3N9ICR7cm9vdGZzX2VyYXNlX3NpemV9OyBuYW5kIHdyaXRlICR7bWVtX2xvYWRfYWRkcmVzc30gJHtyb290ZnNfZmxhc2hfYWRkcmVzc30gJHtyb290ZnNfd3JpdGVfc2l6ZX07Cg==",
"mac_address" : "40:d8:55:01:c1:b3",
"model" : "A"
},
"bridge_address" : "%(bridge_address)s",
"timestamp" : 1426256447.70695
}
"""

DEVICE_CONNECT = """\
{
"timestamp" : 1426256449.81701,
"json_payload" : {
"device_address" : "%(device_address)s",
"name" : "device_connect"
},
"bridge_address" : "%(bridge_address)s",
"type" : "BridgeEvent"
}
"""

HEARTBEAT = """\
{
"rssi_stats" : [
-20,
-20,
-19
],
"device_address" : "%(device_address)s",
"binary_payload" : "AQAAAAAABAAAAG4AAAA=",
"type" : "DeviceEvent",
"bridge_address" : "%(bridge_address)s",
"timestamp" : 1426256547.52687
}
"""

ENCRYPTION_KEY_REQUIRED = """\
{
"timestamp" : 1419107228.91187,
"type" : "BridgeEvent",
"json_payload" : {
"name" : "encryption_key_required",
"device_address" : "%(device_address)s"
},
"bridge_address" : "%(bridge_address)s"
}
"""
@@ -3,6 +3,7 @@
from __future__ import print_function

import os
import sys

from flask.ext import script
from sirius.models import hardware
@@ -33,12 +34,19 @@ def printer():
db.session.add(printer)
db.session.commit()

print('Created printer')
print(' address: {}'.format(device_address))
print(' DB id: {}'.format(printer.id))
print(' secret: {}'.format(secret))
print(' xor: {}'.format(xor))
print(' claim code: {}'.format(cc))
def out(x):
x.write(' address: {}\n'.format(device_address))
x.write(' DB id: {}\n'.format(printer.id))
x.write(' secret: {}\n'.format(secret))
x.write(' xor: {}\n'.format(xor))
x.write(' claim code: {}\n'.format(cc))

path = device_address + '.printer'
with open(path, 'w') as f:
out(f)

out(sys.stdout)
print('\nCreated printer and saved as {}'.format(path))


@fake_manager.command

0 comments on commit 11db07c

Please sign in to comment.
You can’t perform that action at this time.