========================= PyCollect - Live protocol ========================= After :ref:`Install` and :ref:`Run` `UBT Launcher` in any of the available :ref:`Modes`, the system are ready to handle clients connections (*take note about the selected port*). Data structure ============== Requests -------- * Each request sent to the WebSocket must be done in ``JSON`` format through a ``stringify`` command. * Each request must include at least one common argument: ``command``. Response -------- Each system response must include an ``debug`` object with the argumments: ``command``, ``type`` and ``origin``: * ``command``: With the same request command that trigger the response. * ``type``: One of ``DISP``, ``WAVE``, ``CHANNELS``, ``LOG``, ``ERROR``, ``RESP``. * ``origin``: One of ``PYCOLLECT``, ``PYMASIMO``. * ``buffersize``: The size of internal buffer. * ``recording``: Boolean that indicate if system are recording data. * ``transmitting``: Boolean that indicate if system are transmitting data, in this case, will send extra information about the data transmitted. Types ----- * ``DISP``: Indicate the presense os subrecords. * ``WAVE``: Indicate the presense os waveforms. * ``CHANNELS``: Indicate the presense of subrecords and waveforms. * ``LOG``: When a command not need a response, this type is include a ``message`` argument. * ``RESP``: Contain the response of a request. * ``ERROR``: Contain an error message, this type is include a ``message`` argument. Datetimes --------- All datetimes data are in `Unix Timestamp` format, for example: * ``Sun Aug 25 20:57:08 1991`` is equivalent to ``683171828`` * ``Fri Jul 6 11:23:20 2018`` is equivalent to ``1530894200`` Special values -------------- * ``DATA_INVALID_LIMIT``: -32001 * ``DATA_INVALID``: -32767 * ``DATA_NOT_UPDATED``: -32766 * ``DATA_DISCONT``: -32765 * ``DATA_UNDER_RANGE``: -32764 * ``DATA_OVER_RANGE``: -32763 * ``DATA_NOT_CALIBRATED``: -32762 Commands ======== This list a commands are available, some of them need extra arguments. .. include:: ../pycollect_commands.rst Connection with WebSocket ========================= A standard connection is enough, the default port is ``8890``, the complete ip could looks like: ``ws://localhost:8890/ws``, note the ``/ws`` that complete the address. .. brython:: :hide-input: from browser import window, html from mdcframework.mdc import MDCButton, MDCFormField, MDCForm from pycollect_ws import PyCollectWS ff = MDCFormField() form = MDCForm(formfield=ff) input_port = form.mdc.TextField('IP', box=True, value='ws://localhost:8890/ws', style={'width': '30vw'}) container <= form button_open = MDCButton('Open WS', id='open_socket', raised=True, style={'margin-top': '30px', 'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_open button_close = MDCButton('Close WS', id='close_socket', disabled=True, raised=True, style={'margin-top': '30px', 'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_close def connect(event): ip = input_port.mdc.value window.pycollect = PyCollectWS(ip) button_close.bind('click', window.pycollect.close_connection) button_open.bind('click', connect) out = html.PRE(id='connect-status', style={'margin': '-15px'}) container <= out Quick test ========== This request will ask for ``available_channels``, start a default ``request_channels``, wait for 5 seconds, ``start_recording``, wait for 10 seconds ``stop_transfer`` and ``get_edf``, ``get_csv``, and ``get_raw``. The result can be checked with the browser `developer tools`. .. brython:: :hide-input: :merge-output: from browser import window, html, timer from mdcframework.mdc import MDCButton def start_rec(): print('\n'*5) print("start_recording") print('\n'*5) window.pycollect.send({'command': 'start_recording'}) set_item('start_recording', True) timer.set_timeout(stop_rec, 10000) def stop_rec(): print('\n'*5) print("stop_transfer") print('\n'*5) window.pycollect.send({'command': 'stop_transfer'}) set_item('stop_transfer', True) print('\n'*5) print("get_files") print('\n'*5) window.pycollect.send({'command': 'get_edf'}) set_item('get_edf', True) window.pycollect.send({'command': 'get_csv'}) set_item('get_csv', True) window.pycollect.send({'command': 'get_raw'}) set_item('get_raw', True) setattr(button_q, 'disabled', False) def quick_test(): setattr(button_q, 'disabled', True) set_item('available_channels', False) set_item('request_channels', False) set_item('start_recording', False) set_item('stop_transfer', False) set_item('get_edf', False) set_item('get_csv', False) set_item('get_raw', False) print('\n'*5) print("available_channels") print('\n'*5) window.pycollect.send({'command': 'available_channels'}) set_item('available_channels', True) print('\n'*5) print("request_channels") print('\n'*5) window.pycollect.send({'command': 'request_channels', 'channels':[]}) set_item('request_channels', True) timer.set_timeout(start_rec, 5000) button_q = MDCButton('Quick test', disabled=True, raised=True, style={'margin-bottom': '20px', 'margin-right': '30px'}) button_q.bind('click', quick_test) container <= button_q container <= html.BR() items = {} def add_item(name, description): global items parent = html.DIV() icon = html.SPAN('hourglass_empty', style={'position': 'relative', 'bottom': '-4px'}) desc = html.SPAN(description, style={'margin-left': '5px'}) parent <= icon + desc items[name] = icon container <= parent container <= html.BR() def set_item(name, done): global items if done: items[name].select('i')[0].style = {'color': 'rgb(91, 224, 42)'} items[name].select('i')[0].text = 'done' else: items[name].select('i')[0].style = {'color': 'rgb(0, 0, 0)'} items[name].select('i')[0].text = 'hourglass_empty' add_item('available_channels', 'Available channels') add_item('request_channels', 'Request channels') add_item('start_recording', 'Start recording') add_item('stop_transfer', 'Stop transfer') add_item('get_edf', 'Get EDF+') add_item('get_csv', 'Get CSV') add_item('get_raw', 'Get RAW') Available subrecords ==================== Request ------- This command will stop a possible `Subrecord` transfer. .. raw:: html
request
.. code:: { "command": "available_subrecords" } Response -------- .. raw:: html
response
.. code:: { "command": "available_subrecords", "origin": "PYCOLLECT", "type": "RESP", "subrecords": [], //A list of available subrecords labels. } Live example ------------ .. raw:: html
request
.. brython:: :hide-output: request = { "command": "available_subrecords", } #!ignore_bellow from browser import window window.request_available_subrecords = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_av_subrecords = MDCButton('Request available subrecords', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_av_subrecords button_av_subrecords.bind('click', lambda event:window.pycollect.send(window.request_available_subrecords)) out = html.PRE(id='available_subrecords', Class='ubtlauncher-response') container <= out Request subrecords ================== Request ------- The value of `subrecords must be a sublist from the list with available subrecords. If the list of `subrecords` is empty the response will contain all available measures. .. raw:: html
request
.. code:: { "command": "request_subrecords", "subrecords": [], //List of subrecords labels. } Response -------- .. raw:: html
response
.. code:: { "type": "DISP", "origin": "PYCOLLECT", "command": "request_subrecords" "datetime": 1530894200, "data": {}, //A dictionary with the subrecords label and their respective measure. } Live example ------------ .. brython:: :hide-input: from browser import window from mdcframework.mdc import MDCButton, MDCForm window.list_subrecords = MDCForm() container <= window.list_subrecords .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton out = html.PRE(id='available_subrecords_selected', Class='ubtlauncher-request') container <= out button_subrecords = MDCButton('Request subrecords', disabled=True, raised=True, style={'margin-bottom': '30px','margin-top': '30px', 'margin-right': '30px'}) container <= button_subrecords button_subrecords.bind('click', lambda event:window.pycollect.send(window.request_subrecords)) out = html.PRE(id='request_subrecords', Class='ubtlauncher-response') container <= out Request possible waveforms ========================== There is no way to list the `waveforms` that the device can send, this request return all possible `waveforms` Request ------- .. raw:: html
request
.. code:: { "command": "possible_waveforms", } Response -------- The response is always the same. .. raw:: html
response
.. code:: { "type": "RESP", "origin": "PYCOLLECT", "command": "possible_waveforms", "waveforms": [], //A list of all waveforms. } Live example ------------ .. raw:: html
request
.. brython:: :hide-output: request = { "command": "possible_waveforms" } #!ignore_bellow from browser import window window.request_available_waveforms = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_av_waveforms = MDCButton('Request possible waveforms', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_av_waveforms button_av_waveforms.bind('click', lambda event:window.pycollect.send(window.request_available_waveforms)) out = html.PRE(id='possible_waveforms', Class='ubtlauncher-response') container <= out Request waveforms ================= Request ------- The value of `waveforms` must be a sublist from the list with all waveforms. If the list of `wafeforms` is empty the request will be ignored. .. raw:: html
request
.. code:: { "command": "request_waveforms", "waveforms": [], //A list with the desired waveforms labels. } Response -------- The monitor only will send the available `waveforms` and not necessarily all that was requested. .. raw:: html
response
.. code:: { "type": "WAVE", "origin": "PYCOLLECT", "command": "request_waveforms" "channels": [ //list of channels, each channel is a dictionary { //channel dictionary "label": "FLOW", "datetime": 1530894182, "physicalDimension": "l/min", "samplefrequency": 25, "samples": 100, "physicalMaximum": null, "physicalMinimum": null, "digitalMaximum": 32768, "digitalMinimum": -32768, "prefilter": "", "transducer": "", "data": [...], //list of integers or floats }, ... ] } Live example ------------ .. brython:: :hide-input: from browser import window from mdcframework.mdc import MDCButton, MDCForm window.list_waveforms = MDCForm() container <= window.list_waveforms .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton out = html.PRE(id='available_waveforms_selected', Class='ubtlauncher-request') container <= out button_waveforms = MDCButton('Request waveforms', disabled=True, raised=True, style={'margin-bottom': '30px','margin-top': '30px', 'margin-right': '30px'}) container <= button_waveforms button_waveforms.bind('click', lambda event:window.pycollect.send(window.request_waveforms)) out = html.PRE(id='request_waveforms', Class='ubtlauncher-response') out.style = { 'max-height': '300px', 'overflow': 'scroll', } button_waveforms.bind('click', lambda event:setattr(out, 'text', '')) container <= out Available channels ================== Request ------- This command will stop a possible `Subrecord` and `Waveforms` transfer. .. raw:: html
request
.. code:: { "command": "available_channels" } Response -------- .. raw:: html
response
.. code:: { "command": "available_channels", "origin": "PYCOLLECT", "type": "RESP", "labels": [], //A list of available subrecords and waveforms labels. } Live example ------------ .. raw:: html
request
.. brython:: :hide-output: request = { "command": "available_channels", } #!ignore_bellow from browser import window window.request_available_labels = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_av_labels = MDCButton('Request available channels', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_av_labels button_av_labels.bind('click', lambda event:window.pycollect.send(window.request_available_labels)) out = html.PRE(id='available_channels', Class='ubtlauncher-response') container <= out Request channels ================ Request ------- The value of `channels` must be a sublist from the list with all waveforms and subrecords. If the list of `labels` is empty, then will reques the default dataset. .. raw:: html
request
.. code:: { "command": "request_channels", "channels": [], //A list with the desired waveforms and subrecords labels. } Response -------- The monitor only will send the available `waveforms` and not necessarily all that was requested. .. raw:: html
response
.. code:: { "channels": [ //list of channels, each channel is a dictionary { //channel dictionary "label": "FLOW", "datetime": 1530894182, "physicalDimension": "l/min", "samplefrequency": 25, "samples": 100, "physicalMaximum": null, "physicalMinimum": null, "digitalMaximum": 32768, "digitalMinimum": -32768, "prefilter": "", "transducer": "", "data": [...], //list of integers, floats, strings or Nones }, ... ] } Live example ------------ .. brython:: :hide-input: from browser import window from mdcframework.mdc import MDCButton, MDCForm window.list_labels = MDCForm() container <= window.list_labels .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton out = html.PRE(id='available_labels_selected', Class='ubtlauncher-request') container <= out button_waveforms = MDCButton('Request channels', disabled=True, raised=True, style={'margin-bottom': '30px','margin-top': '30px', 'margin-right': '30px'}) container <= button_waveforms button_waveforms.bind('click', lambda event:window.pycollect.send(window.request_labels)) out = html.PRE(id='request_channels', Class='ubtlauncher-response') out.style = { 'max-height': '300px', 'overflow': 'scroll', } button_waveforms.bind('click', lambda event:setattr(out, 'text', '')) container <= out Stop transfer ============= This command will send a request to the monitor for stop the data transmitting. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "stop_transfer" } #!ignore_bellow from browser import window window.request_stop_transfer = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_stop_subrecords = MDCButton('Stop transfer', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_stop_subrecords button_stop_subrecords.bind('click', lambda event:window.pycollect.send(window.request_stop_transfer)) out = html.PRE(id='stop_transfer', Class='ubtlauncher-response') container <= out Close stream ============ This command will send a request to the monitor for stop the data transmitting and will save possible data recording, the serial port will be closed too. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "close_stream" } #!ignore_bellow from browser import window window.request_close_stream = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_stop_subrecords = MDCButton('Close stream', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_stop_subrecords button_stop_subrecords.bind('click', lambda event:window.pycollect.send(window.request_close_stream)) out = html.PRE(id='close_stream', Class='ubtlauncher-response') container <= out Reconnect with device ===================== This command will send a request for connect with the monitor, this method is called internally with the first connect, will return a ``debug`` response. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "reconnect_device" } #!ignore_bellow from browser import window window.request_reconnect_device = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_stop_subrecords = MDCButton('Connect with device', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_stop_subrecords button_stop_subrecords.bind('click', lambda event:window.pycollect.send(window.request_reconnect_device)) out = html.PRE(id='reconnect_device', Class='ubtlauncher-response') container <= out Start recording =============== This command will clear the data buffer. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "start_recording" } #!ignore_bellow from browser import window window.request_start_recording = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_stop_subrecords = MDCButton('Start recording', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_stop_subrecords button_stop_subrecords.bind('click', lambda event:window.pycollect.send(window.request_start_recording)) out = html.PRE(id='start_recording', Class='ubtlauncher-response') container <= out Clear data ========== This command will clear the data buffer and stop the data transmitting from monitor too. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "clear_data" } #!ignore_bellow from browser import window window.request_clear_data = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_stop_subrecords = MDCButton('Clear data', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_stop_subrecords button_stop_subrecords.bind('click', lambda event:window.pycollect.send(window.request_clear_data)) out = html.PRE(id='clear_data', Class='ubtlauncher-response') container <= out Get debug information ===================== Request ------- This request will return useful information about connection and transmission. .. raw:: html
request
.. code:: { "command": "debug", } Response -------- A complete response, with transmission in process contains the next data. .. raw:: html
response
.. code:: { "device": "gehealthcare", //Name of device connected "port": null, //Serial port name "ip_port": "8890", //IP port "debug_mode": true, //Debug mode is enable "user_dir": "/home/user/ubtdriver", "download": "http://localhost:8890/download/", "debug": { "buffersize": 14976, "recording": true, "transmitting": true, "origin": "PYCOLLECT", "command": "debug", "type": "RESP", "subrecords": { "startdatetime": 1532624765, //Timestamp for the first data "enddatetime": 1532624805, //Timestamp for the last data "totalseconds": 40, //Total of seconds processed "labels": [] //List of subrecords labels }, "waveforms": { "startdatetime": 1532624798, //Timestamp for the first data "enddatetime": 1532624804, //Timestamp for the last data "totalseconds": 6, //Total of seconds processed "labels": [] //List of waveforms labels }, } } Live example ------------ .. raw:: html
request
.. brython:: :hide-output: request = { "command": "debug" } #!ignore_bellow from browser import window window.request_status = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_status = MDCButton('Get debug', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_status button_status.bind('click', lambda event:window.pycollect.send(window.request_status)) out = html.PRE(id='debug', Class='ubtlauncher-response') container <= out List all measures ================= Request ------- This request return information about all measures for all modules (availables or not). .. raw:: html
request
.. code:: { "command": "list_all_measures", } Response -------- Each measure contain: label, description, unit and other information for internal usage. .. raw:: html
response
.. code:: { "ECG HR": { //Label as dictionary key "label": "ECG HR", //Label "desc": "Heart rate", //Description "key": "ecg:hr", //For internal usage "unit": "1/min", //The physical unit "subclass": "basic" //For unternal usage }, ... } Live example ------------ .. raw:: html
request
.. brython:: :hide-output: request = { "command": "list_all_measures" } #!ignore_bellow from browser import window window.request_measures = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_status = MDCButton('List all measures', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_status button_status.bind('click', lambda event:window.pycollect.send(window.request_measures)) out = html.PRE(id='list_all_measures', Class='ubtlauncher-response') out.style = { 'max-height': '600px', 'overflow': 'scroll', } container <= out List all waveforms ================== Request ------- This request return information about all waveforms. .. raw:: html
request
.. code:: { "command": "list_all_waveforms", } Response -------- Each waveform contain: label, unit, samples per second and other information for internal usage. .. raw:: html
response
.. code:: { "ECG1": { //Label as dictionary key "label": "ECG1", //Label "unit": "uV", //The physical unit "shift": 0.000001, //For internal usage "samps": 300, //Samples per second "transducer": "", //Transductor used, used for generate the EDF+ file "prefilter": "", //Prefilter, used for generate the EDF+ file "physical_min": null, //Physical minimum, used for generate the EDF+ file "physical_max": null, //Physical maximum, used for generate the EDF+ file }, ... } Live example ------------ .. raw:: html
request
.. brython:: :hide-output: request = { "command": "list_all_waveforms" } #!ignore_bellow from browser import window window.request_list_waveforms = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_list_waveforms = MDCButton('List all waveforms', disabled=True, raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_list_waveforms button_list_waveforms.bind('click', lambda event:window.pycollect.send(window.request_list_waveforms)) out = html.PRE(id='list_all_waveforms', Class='ubtlauncher-response') out.style = { 'max-height': '600px', 'overflow': 'scroll', } container <= out Save as RAW =========== In this implementation download event will kill the websocket. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "get_raw" } #!ignore_bellow from browser import window window.request_get_raw = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_gen_raw = MDCButton('Generate RAW', disabled=True, id='button_gen_raw', raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_gen_raw placeholder = html.DIV(id='raw_download_placeholder') container <= placeholder button_gen_raw.bind('click', lambda event:window.pycollect.send(window.request_get_raw)) out = html.PRE(id='get_raw', Class='ubtlauncher-response') container <= out Save as CSV =========== In this implementation download event will kill the websocket. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "get_csv" } #!ignore_bellow from browser import window window.request_get_csv = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_gen_csv = MDCButton('Generate CSV', disabled=True, id='button_gen_csv', raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_gen_csv placeholder = html.DIV(id='csv_download_placeholder') container <= placeholder button_gen_csv.bind('click', lambda event:window.pycollect.send(window.request_get_csv)) out = html.PRE(id='get_csv', Class='ubtlauncher-response') container <= out Save as EDF+ ============ In this implementation download event will kill the websocket. .. brython:: :hide-input: from browser import window, html from mdcframework.mdc import MDCButton, MDCFormField, MDCForm from pycollect_ws import EDFHeader ff = MDCFormField() #ff.style = {'width': '100%', # 'min-height': '40px', # 'display': 'flow-root'} form = MDCForm(formfield=ff) form.mdc.TextField('Admin code', id='edf_admincode', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Birthdate', id='edf_birthdate', box=True, type='number', value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Equipment', id='edf_equipment', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.Select('Gender', id='edf_gender', options=[('Male', '0'), ('Female', '1')], selected='0', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Patient code', id='edf_patientcode', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Patient name', id='edf_patientname', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Patient additional', id='edf_patientaditional', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Recording additional', id='edf_recordingaditional', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) form.mdc.TextField('Technician', id='edf_technician', box=True, value='', style={'width': '100%'}).bind('input', EDFHeader.update_edf_header) container <= form out = html.PRE(id='request_edf_header', Class='ubtlauncher-request', style={'margin-top': '15px'}) container <= out .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_gen_csv = MDCButton('Generate EDF', disabled=True, id='button_gen_csv', raised=True, style={'margin-bottom': '30px', 'margin-top': '15px', 'margin-right': '30px'}) container <= button_gen_csv placeholder = html.DIV(id='edf_download_placeholder') container <= placeholder button_gen_csv.bind('click', lambda event:window.pycollect.send(window.request_get_edf)) out = html.PRE(id='get_edf', Class='ubtlauncher-response') container <= out Bad request =========== When a request is not understood. .. raw:: html
request
.. brython:: :hide-output: request = { "command": "this_request_not_exist" } #!ignore_bellow from browser import window window.request_bad = request .. brython:: :hide-input: :merge-output: from browser import window, html from mdcframework.mdc import MDCButton button_gen_raw = MDCButton('Bad request', disabled=True, id='button_gen_raw', raised=True, style={'margin-bottom': '30px', 'margin-right': '30px'}) container <= button_gen_raw button_gen_raw.bind('click', lambda event:window.pycollect.send(window.request_bad)) out = html.PRE(id='this_request_not_exist', Class='ubtlauncher-response') container <= out