Back to top

WebDAQ REST API

Introduction

This documentation defines a subset of the API endpoints used to monitor and control a Measurement Computing Corporation (MCC) WebDAQ data logger.

The API provided is used to retrieve information about the system, schedules, and jobs, get the status of an acquisition and alarm(s), and to start and stop an acquisition.

MCC strongly recommends that the WebDAQ web interface be used to create or modify schedules, jobs and device settings. For users interested in performing these tasks outside of the WebDAQ web interface, refer to {host_address}/api for a list of all WebDAQ API endpoints.
The schemas that define job and schedule descriptors are available from these endpoints:

  • Schedule schema {host_address}/api/{version}/schedule/schema

  • Job schema {host_address}/api/{version}/jobs/schema

API Commands

This document defines the following WebDAQ REST API commands:

Status Codes

HTTP Status Codes

Successful Get requests return an HTTP status of 200 along with the request content as a json object.
Successful Post requests only return an HTTP status of 200; no additional content is returned.

If an error occurs within the WebDAQ DAQ service during a Get or Post request, an HTTP status of 400 is returned, along with additional content as a json object. An example is shown here:

{
    "code": "10003",                // An error code returned from the WebDAQ
                                    // device specifying the error.
    "message": "Invalid content",   // The error message associated with the 
                                    // error code.
    "info":"invalid json"           // Additional information about the error.
}

If an HTTP error status other than 400 is returned, refer to the HTTP Status Code documentation for a description of the error.

Endpoint Status Codes

Schedule Status Codes

Status CodeValueDescription
empty0The schedule is empty.
waiting1The schedule is waiting for a start condition to be met before starting the first job.
running2The schedule is currently running one of the jobs.
completed3The schedule ran to completion.
stopped4The schedule was stopped manually.
error5An error occurred while executing the schedule.
initializing6The schedule is being initialized.

Job Status Codes

Status CodeValueDescription
queued1The job is scheduled to run.
started2The job is currently running but is not yet waiting for a trigger or acquiring data.
waitingForTrigger3The job is started and waiting for a trigger condition to be met before logging data.
acquiring4The job is currently acquiring data.
completed5The job ran to completion.
stopped6The job was stopped before it completed.
canceled7The job was canceled before it started.
jumped8The job exited due to an alarm condition.
error9An error occurred while executing the job.

Device Information

API version

Get the API version
GET/{host_address}/api/version

Retrieves the API version. The version is a required parameter to all other API endpoints.

Example URI

GET /mywebdaq.local/api/version
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the version of the REST API supported by the WebDAQ device. An example is shown here.

Headers
Content-Type: application/json
Body
{
    "apiVersion": "v1.0",   // The version of the REST API as a string.
    "ver": 1.0              // The version of the REST API as a number.
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

System information

Get System Information
GET/{host_address}/api/{version}/system/info

Retrieves system information for the specified WebDAQ device. This endpoint is useful for getting the name of the WebDAQ device.

Example URI

GET /mywebdaq.local/api/v1.0/system/info
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the system information for the specified WebDAQ device.

Headers
Content-Type: application/json
Body
{
    "id": "314",                // The device ID.
    "model": "WebDAQ-316",      // The device model.
    "name": "WebDAQ-demo",      // The device name.
    "serial": "01C176C5",       // The device serial number.
    "mac": "00:80:2F:AA:AA:AA"  // The device MAC address.
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Schedule Information

Endpoints that begin with {host_address}/api/{version}/schedule refer to the schedule that is loaded into memory on the WebDAQ device. A schedule is loaded into memory when the device is started, and remains in memory until one of the following actions occur:

  • The schedule is restarted; restarting a schedule reloads it and clears all status.

  • The schedule is modified from the web interface; modifying a schedule removes it from memory and clears all status. The modified schedule is not loaded into memory until the WebDAQ is started again.

  • The WebDAQ device is rebooted from either the web interface or the device Power button; rebooting the device removes the schedule from memory and clears all status. The schedule descriptor is stored on the device, and is loaded into memory when the WebDAQ is started again.

Schedule Descriptor

Get the Schedule Descriptor
GET/{host_address}/api/{version}/schedule/descriptor

Returns the descriptor of the specified schedule on the specified WebDaq device. This endpoint is useful for getting a list of jobs in the schedule along with the start/stop information for each job.

This document describes the Get method only. A description of the Post method is beyond the scope of this document.

The contents of the schedule descriptor can vary depending upon the schedule start type. Therefore, MCC strongly recommends that the WebDAQ web interface be used to create or modify schedule descriptors instead of using the Post method. For users who wish to go beyond the scope of this document and create or modify schedules from within their own code, refer to the WebDAQ REST API schedule schema at /{host_address}/api/v1.0/schedule/schema.

Example URI

GET /mywebdaq.local/api/v1.0/schedule/descriptor
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the job descriptor.

This example response below is for a job that contains a single channel and a single alarm, and does not attempt to document all of the possible job descriptor options:

Headers
Content-Type: application/json
Body
{
    "type": "schedule",       // The type of descriptor.
    "version": "1",           // The REST API version.
    "jobs": [
        "Job1", 
    ],                        // An array of jobs.
    "start": {
        "type": "immediate",
    },                        // An object describing the start conditions 
                              // for the schedule. 
                              // Refer to the schedule schema at 
                              // `/{host_address}/api/v1.0/schedule/schema` 
                              // for a full set of start options.
    "repeat": {
        "enable": "false",
        "interval": "0",
        "count": "0"
    },                        // Describes the interval and the number of   
                              // times that the schedule may be repeated.   
                              // Refer to the schedule schema at 
                              // `/{host_address}/api/v1.0/schedule/schema` 
                              // for all start options.
    "stopOnJobError": "true", // Indicates whether ot not the schedule  
                              // should be stopped when an error occurs.
    "startOnBoot": "false"    // Indicates whether ot not the schedule   
                              // should be started when the WebDAQ device 
                              // powers up.
    "www": {                  // This section is for internal use by the
                              // web page integrated into the WebDAQ, and 
                              // has no effect on the operation of the schedule.
        "modified": 1541783763070,
        "isJobStart": false,
        "status": 0,
        "errorText": ""
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Schedule Status

Get the Schedule Status
GET/{host_address}/api/{version}/schedule/status

Returns the status of the current schedule for the specified WebDaq device. Refer to Schedule Status Codes for a list of valid codes.

Example URI

GET /mywebdaq.local/api/v1.0/schedule/status
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the schedule status.

Headers
Content-Type: application/json
Body
{
    "status": "running",       // The status of the schedule as a string.
    "statusCode": "2",         // The status of the schedule as an integer.
    "currentJobname": "Job1"   // The name of the job currently executing.
                               // May be an empty string if no job is
                               // currently executing.
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Start or Stop the Schedule
POST/{host_address}/api/{version}/schedule/status

Starts or stops the current schedule.

Refer below to the Request Body for the json object needed to start or stop the schedule.
See the example programs, or a third-party application like Postman, for usage in different languages.

Example URI

POST /mywebdaq.local/api/v1.0/schedule/status
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

Request
HideShow
Headers
Content-Type: application/json
Body
{
    "run" : true    // True to start the schedule, or false to stop the
                    // schedule.
}
Response  200
HideShow

A successful Post response returns an HTTP status of 200. No json object is returned.

Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Job Information

Job Descriptor

Get the Job Descriptor
GET/{host_address}/api/{version}/schedule/jobs/{job_name}/descriptor

Returns the descriptor of the specified job on the specified WebDaq device. This endpoint is useful for getting channel and alarm information for the specified job.

This document describes the Get method only. A description of the Post method is beyond the scope of this document.

The contents of the job descriptor can vary depending upon the device capabilities and the channel types in the job.

MCC strongly recommends that the WebDAQ web interface be used to create or modify job descriptors instead of using the Post method. For users who wish to go beyond the scope of this document and create or modify jobs from within their own code, refer to the WebDAQ REST API job schema at {host_address}/api/v1.0/jobs/schema.

Example URI

GET /mywebdaq.local/api/v1.0/schedule/jobs/job1/descriptor
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

job_name
string (required) Example: job1

The name of the job whose descriptor is being requested. This can be the name of any job in the schedule.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the job descriptor.

The response shown here is for a job that contains a single channel and a single alarm, and does not attempt to document all of the possible job descriptor options:

Headers
Content-Type: application/json
Body
{
    "type": "job",                      // The type of descriptor.
    "version": 1,                       // The REST API version.
    "productId": 314,                   // The device product ID.
    "name": "Job1",                     // The name of the job.
    "channels": [                       // An array of channel objects. 
                                        // Refer to the job schema at 
                                        // `{host_address}/api/v1.0/jobs/schema`
                                        // for all channel options.
        {
            "number": 0,
            "type": "voltage",
            "name": "Voltage 0",
            "range": "±78.125mV",
            "unit": "V",
            "customScaling": {
                "type": "none",
                "linear": {
                    "multiplier": 1,
                    "offset": 0
                }
        },
        "www": {                        // This section is for internal use by the
                                        // web page integrated into the WebDAQ, and 
                                        // has no effect on the operation of the 
                                        // schedule.
            "color": "#DD3222",
            "dashboardScalar": true,
            "dashboardStripChart": true
            }
        }
    ],
    "acquisition": {                    // An object containing acquisition  
                                        // information for the job. Refer to 
                                        // the job schema at
                                        // `{host_address}/api/v1.0/jobs/schema`
                                        // for all acquisition options.
        "sample": {
            "rate": 3,
            "autoZeroMode": "everySample"
        },
        "startTrigger": {
            "type": "immediate"
        },
        "stopTrigger": {
            "type": "sampleCount",
            "sampleCount": 20
        }
    },
    "logging": {                        // An object containing logging 
                                        // information for the job. Refer to  
                                        // the job schema at
                                        // `{host_address}/api/v1.0/jobs/schema`
                                        // for all logging options.
        "enable": true,
        "logFile": {
            "name": "Job1",
            "path": "/storage/data/",
            "appendTime": false
        },
        "note": ""
    },
    "alarms": [                         // An object containing alarm  
                                        // information for the job. Refer to 
                                        // the job schema at
                                        // `{host_address}/api/v1.0/jobs/schema`
                                        // for all alarm options.
        {
            "name": "Alarm0",
            "condition": {
                "type": "analog",
                "analog": {
                    "source": 0,
                    "type": "above",
                    "highThreshold": 0,
                    "lowThreshold": 0
                }
            },
            "reset": false,
            "resetInterval": 1,
            "actions": {
                "log": {
                    "enable": true,
                    "fileName": "Job1",
                    "filePath": "/storage/data/"
                },
                "email": {
                    "enable": false,
                    "smtpName": "GMAIL",
                    "senderName": "webdaq-demo",
                    "to": "",
                    "subject": "Job1 Alarm 0 Occurred",
                    "body": "",
                    "retry": false
                },
                "sms": {
                    "enable": false,
                    "smtpName": "GMAIL",
                    "senderName": "webdaq-demo",
                    "to": "",
                    "subject": "Job1 Alarm 0 Occurred",
                    "message": "",
                    "retry": false
                },
                "jump": {
                    "enable": false,
                    "jobName": ""
                },
                "digital": {
                    "enable": false,
                    "source": 0,
                    "level": "low"
                }
            }
        }
    ],
    "www": {                  // This section is for internal use by the
                              // web page integrated into the WebDAQ, and 
                              // has no effect on the operation of the 
                              // schedule. 
        "status": 1,
        "errorText": "",
        "iterationIndex": 0,
        "dashboardScalarCollapse": false,
        "dashboardAlarmCollapse": false,
        "dashboardStripChartCollapse": false,
        "dashboardStripChartYMin": null,
        "dashboardStripChartYMax": null,
        "dashboardStripChartXAxisMode": 0,
        "dashboardStripChartXAxisLength": 50,
        "logToFTP": false
    }
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Job Status

Get the Job Status
GET/{host_address}/api/{version}/schedule/jobs/{job_name}/status

Returns the status of the specified job for the specified WebDaq device. This endpoint is useful for getting the status of the specified job along with the total number of samples that have been acquired since the job started.
Refer to Job Status Codes for a list of valid codes.

Example URI

GET /mywebdaq.local/api/v1.0/schedule/jobs/job1/status
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

job_name
string (required) Example: job1

The name of the job whose status is being requested. This can be the name of any job in the schedule.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the job status.

Headers
Content-Type: application/json
Body
{
    "status": "acquiring data", // The current status of the job as a string.
    "statusCode": "4",          // The status of the job as an integer.
    "iterationIndex": "1",      // The number of times the job has been 
                                // executed during the current schedule
                                // execution.
    "samplesAcquired": "46"     // The number of samples acquired since the 
                                // start of the job.
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Stop the Current Job
POST/{host_address}/api/{version}/schedule/jobs/{job_name}/status

Stops the specified job.

Refer below to the Request Body for the json object needed to stop the job.
See the example programs, or a third-party application like Postman, for usage in different languages.

Example URI

POST /mywebdaq.local/api/v1.0/schedule/jobs/job1/status
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

job_name
string (required) Example: job1

The name of the job whose status is being requested. This can be the name of any job in the schedule.

Request
HideShow
Headers
Content-Type: application/json
Body
{
    "stop" : true  // true to stop the job.
}
Response  200
HideShow

A successful Post response returns an HTTP status of 200. No json object is returned.

Headers
Content-Type: application/json
Body
{
    "stop": "true"      // "true" to stop the job.
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Alarm Status

Get the Alarm Status
GET/{host_address}/api/{version}/schedule/jobs/{job_name}/alarms/{alarm_name}/status

Returns the status of the specified alarm for the specified WebDaq device.
This endpoint is useful for getting the alarm state along with the number of times the alarm occurred.

Example URI

GET /mywebdaq.local/api/v1.0/schedule/jobs/job1/alarms/alarm1/status
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

job_name
string (required) Example: job1

The name of the job whose status is being requested. This can be the name of any job in the schedule.

alarm_name
string (required) Example: alarm1

The name of the alarm whose status is being requested. This can be the name of any alarm associated with the job specified by job_name.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object that contains the alarm descriptor.

Headers
Content-Type: application/json
Body
{
    "inAlarmState": "true",     // True if in an alarm state; otherwise false.
    "alarmOccurredCount": "1",  // The number of times the specified alarm 
                                // has occurred.
    "triggerValue": "77.6162"   // The value that triggered the alarm, 
                                // or an empty string if not in an 
                                // alarm state.
}
Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Read Data

Read the Job Data
GET/{host_address}/api/{version}/schedule/jobs/{job_name}/samples/{sample_index}/{sample_count}/bin

Reads available data for a specified job from the specified WebDaq device.
Data can be read while the job is running or after it has completed, and is returned in binary format. Up to 10,000 samples per channel can be read with a single call.

Example URI

GET /mywebdaq.local/api/v1.0/schedule/jobs/job1/samples/123/1000/bin
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

job_name
string (required) Example: job1

The name of the job whose status is being requested; this value can be the name of any job in the schedule.

sample_index
number (required) Example: 123

The index of the first sample to be read.

sample_count
number (required) Example: 1000

The number of samples to be read. If more than 10,000 samples per channel are requested, the amount of data returned is limited to 10,000 samples per channel.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with an array buffer containing the requested data.

Headers
Content-Type: application/octet-stream
Body
An array buffer of interleaved IEEE-754 double values.
Response  400
HideShow

An error response returns an HTTP response of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Digital Information

Digital Port

Read a Digital Port
GET/{host_address}/api/{version}/system/digital/port

Reads the value of a digital port.

Ports can be read while the job is running or after it has completed.

Example URI

GET /mywebdaq.local/api/v1.0/system/digital/port
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object containing the requested data.

Headers
Content-Type: application/json
Body
{
    "value": 15    // The digital port value.
                   // The return value is 2^4 - 1 for a 4-bit port.
}
Response  400
HideShow

An error response returns an HTTP response of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Write to a Digital Port
POST/{host_address}/api/{version}/system/digital/port

Ports cannot be written while the schedule is running.

Ports intended to be used as input should be left at the power-up default value of 15.

Refer below to the Request Body for the json object needed to set the value.
See the example programs, or a third-party application like Postman, for usage in different languages.

Example URI

POST /mywebdaq.local/api/v1.0/system/digital/port
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

Request
HideShow
Headers
Content-Type: application/json
Body
{
    "value" : 4  // The value to set the port to.
                 // The value will be 0 to 15 for all devices.
                 // The value is 15 by default so all bits
                 // can be used as input.
}
Response  200
HideShow

A successful Post response returns an HTTP status of 200. No json object is returned.

Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Digital Bit

Read a Digital Bit
GET/{host_address}/api/{version}/system/digital/bit/{bit_number}

Reads the state of a digital bit.

Bits can be read while the job is running or after it has completed.

Example URI

GET /mywebdaq.local/api/v1.0/system/digital/bit/1
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

bit_number
string (required) Example: 1

The number of the bit whose state is being read.

Response  200
HideShow

A successful response returns an HTTP status of 200 along with a json object containing the requested data.

Headers
Content-Type: application/json
Body
{
    "value": 1    // The digital bit value.
                  // Value will be 0 (logic low) or 1 (logic high).
}
Response  400
HideShow

An error response returns an HTTP response of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

Write to a Digital Bit
POST/{host_address}/api/{version}/system/digital/bit/{bit_number}

Bits cannot be written while the schedule is running.

Bits intended to be used as input should be left at the power-up default value of 1.

Refer below to the Request Body for the json object needed to set the value.
See the example programs, or a third-party application like Postman, for usage in different languages.

Example URI

POST /mywebdaq.local/api/v1.0/system/digital/bit/1
URI Parameters
HideShow
host_address
string (required) Example: mywebdaq.local

The host address of the WebDAQ device. This can be either an IP address or host name for the device. Refer to the API Version endpoint for an example of how to specify the host address.

version
string (required) Example: v1.0

The version of the REST API for the WebDAQ device. Use the API Version endpoint to get the version string.

bit_number
string (required) Example: 1

The number of the bit whose value is being set.

Request
HideShow
Headers
Content-Type: application/json
Body
{
    "value" : 1  // The value to set the bit to.
                 // Value will be 0 (logic low) or 1 (logic high).
}
Response  200
HideShow

A successful Post response returns an HTTP status of 200. No json object is returned.

Response  400
HideShow

An error response returns an HTTP status of 400 along with additional content as a json object. For more information, refer to HTTP Status Codes.

More Information

Example Programs

The WebDAQ API includes example programs developed with Python, C#, VB, and NI LabVIEWTM. Users are encouraged to run the examples to become familiar with the WebDAQ hardware and REST API.

WebDAQ REST API example programs are available for download here.

Refer to the README file in the example folder of each language for more information.

Contact Us

Measurement Computing Corporation
10 Commerce Way
Norton, MA 02766

Jun20 Rev1.2

Generated by aglio on 08 Jun 2020