|
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