|
1 | 1 | Basic usage |
2 | 2 | =========== |
3 | 3 |
|
4 | | -The easiest way to get started with Python-LabThings is via the :mod:`labthings.quick` module, and the :class:`labthings.LabThing` builder methods. |
5 | | - |
6 | | -We will assume that for basic usage you already have some basic instrument control code. In our example, this is in the form of a ``PretendSpectrometer`` class, which will generate some data like your instrument control code might. Our ``PretendSpectrometer`` class has a ``data`` property which quickly returns a spectrum, an ``x_range`` property which determines the range of data we'll return, a ``magic_denoise`` property for cleaning up our signal, and a slow ``average_data(n)`` method to average ``n`` individual data measurements. |
7 | | - |
8 | | -Building an API from this class requires a few extra considerations. In order to tell our API what data to expect from users, we need to construct a schema for each of our interactions. This schema simply maps variable names to JSON-compatible types, and is made simple via the :mod:`labthings.fields` module. |
9 | | - |
10 | | -For properties, the input and output MUST be formatted the same, and so a single ``schema`` argument handles both. For actions, the input parameters and output response may be different. In this case, we can pass a ``schema`` argument to format the output, and an ``args`` argument to specify the input parameters, |
11 | | - |
12 | | -An example Lab Thing built from our ``PretendSpectrometer`` class, complete with schemas, might look like: |
13 | | - |
14 | | - |
15 | | -.. code-block:: python |
16 | | -
|
17 | | - from labthings.server.quick import create_app |
18 | | - from labthings.server import fields |
19 | | -
|
20 | | - from my_components import PretendSpectrometer |
21 | | -
|
22 | | -
|
23 | | - # Create LabThings Flask app |
24 | | - app, labthing = create_app( |
25 | | - __name__, |
26 | | - title="My PretendSpectrometer API", |
27 | | - description="LabThing API for PretendSpectrometer", |
28 | | - version="0.1.0" |
29 | | - ) |
30 | | -
|
31 | | -
|
32 | | - # Make some properties and actions out of our component |
33 | | - my_spectrometer = PretendSpectrometer() |
34 | | -
|
35 | | - # Single-shot data property |
36 | | - labthing.build_property( |
37 | | - my_spectrometer, # Python object |
38 | | - "data", # Objects attribute name |
39 | | - description="A single-shot measurement", |
40 | | - readonly=True, |
41 | | - schema=fields.List(fields.Number()) |
42 | | - ) |
43 | | -
|
44 | | - # Magic denoise property |
45 | | - labthing.build_property( |
46 | | - my_spectrometer, # Python object |
47 | | - "magic_denoise", # Objects attribute name |
48 | | - description="A magic denoise property", |
49 | | - schema=fields.Int(min=100, max=500, example=200) |
50 | | - ) |
51 | | -
|
52 | | - # Averaged measurement action |
53 | | - labthing.build_action( |
54 | | - my_spectrometer.average_data, # Python function |
55 | | - description="Take an averaged measurement", |
56 | | - args={ # How do we convert from the request input to function arguments? |
57 | | - "n": fields.Int(description="Number of averages to take", example=5, default=5) |
58 | | - }, |
59 | | - ) |
60 | | -
|
61 | | -
|
62 | | - # Start the app |
63 | | - if __name__ == "__main__": |
64 | | - from labthings.server.wsgi import Server |
65 | | - Server(app).run() |
66 | | -
|
67 | | -
|
68 | | -Once started, the app will build and serve a full web API, and generate the following Thing Description: |
69 | | - |
70 | | -.. code-block:: JSON |
71 | | -
|
72 | | - { |
73 | | - "@context": [ |
74 | | - "https://www.w3.org/2019/wot/td/v1", |
75 | | - "https://iot.mozilla.org/schemas/" |
76 | | - ], |
77 | | - "id": "http://127.0.0.1:7486/", |
78 | | - "base": "http://127.0.0.1:7486/", |
79 | | - "title": "My PretendSpectrometer API", |
80 | | - "description": "LabThing API for PretendSpectrometer", |
81 | | - "properties": { |
82 | | - "pretendSpectrometerData": { |
83 | | - "title": "PretendSpectrometer_data", |
84 | | - "description": "A single-shot measurement", |
85 | | - "readOnly": true, |
86 | | - "links": [{ |
87 | | - "href": "/properties/PretendSpectrometer/data" |
88 | | - }], |
89 | | - "forms": [{ |
90 | | - "op": "readproperty", |
91 | | - "htv:methodName": "GET", |
92 | | - "href": "/properties/PretendSpectrometer/data", |
93 | | - "contentType": "application/json" |
94 | | - }], |
95 | | - "type": "array", |
96 | | - "items": { |
97 | | - "type": "number", |
98 | | - "format": "decimal" |
99 | | - } |
100 | | - }, |
101 | | - "pretendSpectrometerMagicDenoise": { |
102 | | - "title": "PretendSpectrometer_magic_denoise", |
103 | | - "description": "A magic denoise property", |
104 | | - "links": [{ |
105 | | - "href": "/properties/PretendSpectrometer/magic_denoise" |
106 | | - }], |
107 | | - "forms": [{ |
108 | | - "op": "readproperty", |
109 | | - "htv:methodName": "GET", |
110 | | - "href": "/properties/PretendSpectrometer/magic_denoise", |
111 | | - "contentType": "application/json" |
112 | | - }, |
113 | | - { |
114 | | - "op": "writeproperty", |
115 | | - "htv:methodName": "PUT", |
116 | | - "href": "/properties/PretendSpectrometer/magic_denoise", |
117 | | - "contentType": "application/json" |
118 | | - } |
119 | | - ], |
120 | | - "type": "number", |
121 | | - "format": "integer", |
122 | | - "min": 100, |
123 | | - "max": 500, |
124 | | - "example": 200 |
125 | | - } |
126 | | - }, |
127 | | - "actions": { |
128 | | - "averageDataAction": { |
129 | | - "title": "average_data_action", |
130 | | - "description": "Take an averaged measurement", |
131 | | - "links": [{ |
132 | | - "href": "/actions/PretendSpectrometer/average_data" |
133 | | - }], |
134 | | - "forms": [{ |
135 | | - "op": "invokeaction", |
136 | | - "htv:methodName": "POST", |
137 | | - "href": "/actions/PretendSpectrometer/average_data", |
138 | | - "contentType": "application/json" |
139 | | - }], |
140 | | - "input": { |
141 | | - "type": "object", |
142 | | - "properties": { |
143 | | - "n": { |
144 | | - "type": "number", |
145 | | - "format": "integer", |
146 | | - "default": 5, |
147 | | - "description": "Number of averages to take", |
148 | | - "example": 5 |
149 | | - } |
150 | | - } |
151 | | - } |
152 | | - } |
153 | | - }, |
154 | | - "links": [...], |
155 | | - "securityDefinitions": {...}, |
156 | | - "security": "nosec_sc" |
157 | | - } |
158 | | -
|
159 | | -
|
160 | | -For completeness of the examples, our ``PretendSpectrometer`` class code is: |
161 | | - |
162 | | -.. code-block:: python |
163 | | -
|
164 | | - import random |
165 | | - import math |
166 | | - import time |
167 | | -
|
168 | | - class PretendSpectrometer: |
169 | | - def __init__(self): |
170 | | - self.x_range = range(-100, 100) |
171 | | - self.magic_denoise = 200 |
172 | | -
|
173 | | - def make_spectrum(self, x, mu=0.0, sigma=25.0): |
174 | | - """ |
175 | | - Generate a noisy gaussian function (to act as some pretend data) |
176 | | - |
177 | | - Our noise is inversely proportional to self.magic_denoise |
178 | | - """ |
179 | | - x = float(x - mu) / sigma |
180 | | - return ( |
181 | | - math.exp(-x * x / 2.0) / math.sqrt(2.0 * math.pi) / sigma |
182 | | - + (1 / self.magic_denoise) * random.random() |
183 | | - ) |
184 | | -
|
185 | | - @property |
186 | | - def data(self): |
187 | | - """Return a 1D data trace.""" |
188 | | - return [self.make_spectrum(x) for x in self.x_range] |
189 | | -
|
190 | | - def average_data(self, n: int): |
191 | | - """Average n-sets of data. Emulates a measurement that may take a while.""" |
192 | | - summed_data = self.data |
193 | | -
|
194 | | - for _ in range(n): |
195 | | - summed_data = [summed_data[i] + el for i, el in enumerate(self.data)] |
196 | | - time.sleep(0.25) |
197 | | -
|
198 | | - summed_data = [i / n for i in summed_data] |
199 | | -
|
200 | | - return summed_data |
| 4 | +.. toctree:: |
| 5 | + :maxdepth: 2 |
| 6 | + :caption: Contents: |
| 7 | + |
| 8 | + app_thing_server.rst |
| 9 | + http_api_structure.rst |
| 10 | + websocket_api_structure.rst |
| 11 | + serialising.rst |
| 12 | + action_tasks.rst |
| 13 | + synchronisation.rst |
0 commit comments