{"id":30684,"date":"2024-07-03T15:05:45","date_gmt":"2024-07-03T22:05:45","guid":{"rendered":"https:\/\/digilent.com\/blog\/?p=30684"},"modified":"2024-07-03T15:07:17","modified_gmt":"2024-07-03T22:07:17","slug":"webdaq-rest-control-introduction","status":"publish","type":"post","link":"https:\/\/digilent.com\/blog\/webdaq-rest-control-introduction\/","title":{"rendered":"WebDAQ REST Control Introduction"},"content":{"rendered":"<p>Digilent WebDAQ devices, such as the 316, 504, and 904, are standalone data acquisition devices that offer a user-friendly web server interface. This interface, accessible through your preferred Internet browser like Chrome, Edge, or Firefox, eliminates the need to download and install a program. It&#8217;s a straightforward approach that ensures you don&#8217;t have to worry about software compatibility with your operating system, making your experience with WebDAQ devices a breeze.<\/p>\n<p>The WebDAQ&#8217;s user interface is perfect for most everyday use. However, updating the configuration on multiple WebDAQs or ensuring the data aligns with your expectations can be tedious. That&#8217;s where the WebDAQ REST API comes in. It&#8217;s a powerful tool that allows you to create a program interface using HTTP requests to exchange data, giving you more control over data acquisition and device configuration.<\/p>\n<p>The WebDAQ REST API is limited to GET and POST requests, similar to read and write file operations. A few resources also support DELETE, but it should be avoided. The complete list of uniquely identifiable resources (URI) and associated REST commands can be found by including \u2018\/API\u2019 at the end of your WebDAQ IP address in your browser.<\/p>\n<p>When performing a REST request, the command takes the form of a URL. For instance, the WebDAQ API version can be read by appending the string &#8216;\/api\/version&#8217; to the end of the IP address. This URL is called the data endpoint. To facilitate communication, we need to import a few standard modules in Python. The Session module is primarily used for GET and POST read and write requests.\u00a0 To demonstrate a simple request, the following code sample gets the API version from a WebDAQ at IP address 192.168.100.100.<\/p>\n<pre style=\"font: inherit;\">from requests import Session\r\nfrom utility import ScheduleStatus\r\nfrom numpy import array\r\n\r\ns = Session()\r\nr = s.get(\u2018192.168.100.100\/api\/version\u2019)\r\n<\/pre>\n<p>The raise_for_status function waits for the response.<\/p>\n<pre style=\"font: inherit;\">r.raise_for_status()\r\nversion_json = r.json()\r\nversion = version_json[\u2018apiVersion\u2019]<\/pre>\n<p>The version number is important because it is used in all endpoint requests. Fortunately, WebDAQs currently have only one version, \u2018v1.0\u2019. So, our URLs will always begin with the IP address followed by \/api\/v1.0\/:<\/p>\n<pre style=\"font: inherit;\">basel_url = \u2018http:\/\/\u2019 + \u2018192.168.100.100\/api\/v1.0\/\u2019<\/pre>\n<p>If you are wondering how to obtain the IP address, Python makes this easy with the socket module.<\/p>\n<pre style=\"font: inherit;\">import socket\r\nip_address = socket.gethostbyname(\u2018webdaq-xxxxxx\u2019)<\/pre>\n<p>where xxxxxx are the lower six digits of the factory-assigned MAC address. This works with the factory-assigned name as long as it is unchanged. If it was changed in the Browser UI, then use the new name.<\/p>\n<p>A more meaningful WebDAQ operation is to get the schedule because it contains the job name(s) that will be used to request the job JSON.<\/p>\n<pre style=\"font: inherit;\">request_response = s.get(base_url + \u2018\/schedule\/descriptor\u2019)\r\nrequest_response.raise_for_status()\r\nschedule_json = request_response.json()\r\njobs = schedule_json[\u2018jobs\u2019]<\/pre>\n<p>However, when the WebDAQ is powered on, its schedule may not be set for auto-start, which means it is not in memory. Therefore, before retrieving the JSON code for the job, we must check the status and, if necessary, load the schedule.<\/p>\n<pre style=\"font: inherit;\">schedule_status_endpoint = base_url + \u2018\/schedule\/status\u2019\r\nschedule_status_response = s.get(schedule_status_endpoint)\r\nschedule_status_response.raise_for_status()\r\nschedule_status_json = schedule_status_response.json()\r\n<\/pre>\n<p>An empty status means the schedule must be started to load it into memory:<\/p>\n<pre style=\"font: inherit;\">if schedule_status_response[\u2018statusCode\u2019] == ScheduleStatus[\u2018empty\u2019]:\r\n          r = s.post(schedule_status_endpoint, json={\u201crun\u201d:True})<\/pre>\n<p>If you wish to modify a job configuration, such as the number of samples to acquire, get the job JSON code and access the appropriate property. For example, to change the number of samples to acquire we can do this:<\/p>\n<pre style=\"font: inherit;\">jobs = schedule_status_json[\u2018jobs\u2019]\r\njob_name = jobs[0]\r\njob_descriptor_endpoint = base_url + \/schedule\/jobs\/\u2019 + job_name + \u2018\/descriptor\u2019\r\njob_descriptor_response = s.get(job_descriptor_endpoint)\r\njob_descriptor_response.raise_for_status()\r\njob = job_descriptor_response.json()\r\njob[\u2018acquisition\u2019][\u2018stopTrigger\u2019][\u2018sampleCount\u2019] = new_count\r\n<\/pre>\n<p>We can also modify the channel list besides changing acquisition settings. The following is an example of the JSON descriptor from a WebDAQ 504 IEPE channel, typically used with dynamic sensors such as an accelerometer.<\/p>\n<pre style=\"font: inherit;\">              IEPE = [{'number': 0,\r\n                         'type': 'acceleration',\r\n                         'www': {'productId': 316,\r\n                                 'color': '#DD3222',\r\n                                 'dashboardScalar': True,\r\n                                 'dashboardStripChart': True,\r\n                                 'dashboardFFT': True,\r\n                                 'showFFTPeak': True,\r\n                                 'fftWindowType': 'none',\r\n                                 'fftIntegration': 'none'},\r\n                         'name': 'Camshaft',\r\n                         'range': '12',\r\n                         'unit': 'psi',\r\n                         'couplingMode': 'ac',\r\n                         'iepe': True,\r\n                         'sensorSensitivity': 1,\r\n                         'customScaling': {'type': 'linear', 'linear': {'multiplier': 1000, 'offset': 0}}}]\r\n<\/pre>\n<p>We can use it as a template to rewrite the channel list. First, we delete the items from the list using the pop() method.<\/p>\n<pre style=\"font: inherit;\">While len( job[\u2018channels\u2019] ):\r\n\tDummy = job[\u2018channels'].pop()\r\n<\/pre>\n<p>Then, we can make a copy of the template. We want a completely independent copy of the template object, so we use the Python deepcopy method like this:<\/p>\n<pre style=\"font: inherit;\">Accelerometer0 = copy.deepcopy(IEPE)\r\nAccelerometer1 = copy.deepcopy(IEPE)\r\n<\/pre>\n<p>The channels must be assigned a number beginning with zero and have a unique name.<\/p>\n<pre style=\"font: inherit;\">Accelerometer0 [0][\u2018number\u2019] = 0\r\nAccelerometer0 [0][\u2018name\u2019] = \u2018Camshaft sensor\u2019\r\n\r\nAccelerometer1 [0][\u2018number\u2019] = 1\r\nAccelerometer1 [0][\u2018name\u2019] = \u2018Crankshaft sensor\u2019\r\n\r\njob[\u2018channels\u2019].extend(Accelerometer0)\r\njob[\u2018channels\u2019].extend(Accelerometer1)\r\n<\/pre>\n<p>Upload the modified job descriptor.<\/p>\n<pre style=\"font: inherit;\">r = s.post(base_url + \/jobs\/job1\/descriptor\u2019, json=job)\r\nprint(\u2018Status response = \u2018 + str(r.status_code)\r\n<\/pre>\n<p>The WebDAQ will check the uploaded JSON job descriptor and return 200 if no errors are found. So, ensure that the status response indicates 200 and not 400.<\/p>\n<p>Next, now that we made the changes we want to the job descriptor, we can request the WebDAQ send back data from the current job. This could be helpful for automatic monitoring. For instance, you could need to monitor many WebDAQs to ensure the measurements align with expectations.<\/p>\n<p>The data request URL format for our job named \u2018job1\u2019 is:<\/p>\n<pre style=\"font: inherit;\">base_url + \u2018\/schedule\/jobs\/job1\/samples\/index\/max_count\/bin\u2019<\/pre>\n<p>The index parameter starts at zero and advances with each request. The max_count is the amount of data to request and must not exceed 10000. Here, we set the index to zero and use GET to request data:<\/p>\n<pre style=\"font: inherit;\">index = 0\r\ndata_request = base_url + \u2018\/schedule\/jobs\/job1\/samples\/\u2019 + str(int(index)) +  \u2018\/100\/bin\u2019\r\ndata = s.get(data_request)\r\ndata.raise_for_status()\r\n<\/pre>\n<p>Now that data contains data, we must determine how much data it gave us. This is done by dividing the buffer length by the number of channels times the data type size, which is eight bytes for the double data type.<\/p>\n<pre style=\"font: inherit;\">num_of_samples = len( data.content ) \/ 8 * len( job[\u2018channels\u2019] )<\/pre>\n<p>Next, we need to convert the string of binary codes to meaningful numbers. To do this, we use calcsize and unpack from the Struct module. First, we create a format string specifying &#8216;d\u2019 for the double data type. We then get the size and use it to unpack the string of binary codes into an array of usable numbers.<\/p>\n<pre style=\"font: inherit;\">read_format = str( int(num_of_samples * num_of_channels ) + \u2018d\u2019 )\r\nread_size = calcsize(read_format)\r\nnumpy_data = array( unpack( read_format, data.content[0: read_size] ))\r\n<\/pre>\n<p>More data can be read with another request, but we must advance the index by num_of_samples. It is also good to check the job status after each read to ensure it has stopped.<\/p>\n<pre style=\"font: inherit;\">read_format = str( int(num_of_samples * num_of_channels ) + \u2018d\u2019 )\r\nread_size = calcsize(read_format)\r\nnumpy_data = array( unpack( read_format, data.content[0: read_size] ))\r\nfor chan, item in enumerate(channels):\r\n          print('{:2.5f}'.format(numpy_data[int(chan)]), item['unit'], ' ', end='')\r\n<\/pre>\n<p>Depending on the WebDAQ and the rate at which data is being acquired, you could get old data. The WebDAQ 504 can acquire data quickly; by the time you have requested 100 samples, it may have recorded several thousand. To minimize this, query the job status for \u2018samplesAcquired\u2019 and subtract 100 to get the most recent data.<\/p>\n<p>The purpose of this article was to give you an idea of what is possible with the REST API. The code samples were not meant to be pasted into a program; they were provided merely for demonstration. We covered only the minimum necessary for control. We began by requesting its API version and finished by retrieving live data. In between, we modified a single property in the job JSON code called \u2018sampleCount\u2019, and showed how to modify the channel list. We used Python for the demonstration but could have used C#, VB.NET, and LabVIEW. For a complete example program, go to the support page for any WebDAQ to download Digilent\u2019s suite of REST API example programs. The read_data.py example provides all the checking necessary when reading data.<\/p>\n<div class='watch-action'><div class='watch-position align-left'><div class='action-like'><a class='lbg-style6 like-30684 jlk' data-task='like' data-post_id='30684' data-nonce='8896bc70a6' rel='nofollow'><img src='https:\/\/digilent.com\/blog\/wp-content\/plugins\/wti-like-post-pro\/images\/pixel.gif' title='Like' \/><span class='lc-30684 lc'>+1<\/span><\/a><\/div><div class='action-unlike'><a class='unlbg-style6 unlike-30684 jlk' data-task='unlike' data-post_id='30684' data-nonce='8896bc70a6' rel='nofollow'><img src='https:\/\/digilent.com\/blog\/wp-content\/plugins\/wti-like-post-pro\/images\/pixel.gif' title='Unlike' \/><span class='unlc-30684 unlc'>0<\/span><\/a><\/div><\/div> <div class='status-30684 status align-left'><\/div><\/div><div class='wti-clear'><\/div>","protected":false},"excerpt":{"rendered":"<p>Digilent WebDAQ devices, such as the 316, 504, and 904, are standalone data acquisition devices that offer a user-friendly web server interface. This interface, accessible through your preferred Internet browser &hellip; <\/p>\n","protected":false},"author":60,"featured_media":30818,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4325,4361,1561],"tags":[4848,4849,4847],"ppma_author":[4461],"class_list":["post-30684","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-debug-validation-test","category-data-acquisition","category-applications","tag-rest","tag-rest-api","tag-webdaq"],"jetpack_featured_media_url":"https:\/\/digilent.com\/blog\/wp-content\/uploads\/2024\/07\/2024-WebDAQ-Announcements-AcquireView-1080sq.png","jetpack_sharing_enabled":true,"authors":[{"term_id":4461,"user_id":60,"is_guest":0,"slug":"jrys","display_name":"John Rys","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/b5f589ceec35aa72d93bdb885888fe28157d060d3a9bd5484c6388d3f10fc51d?s=96&d=mm&r=g","1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":"","9":"","10":""}],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts\/30684","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/users\/60"}],"replies":[{"embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/comments?post=30684"}],"version-history":[{"count":15,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts\/30684\/revisions"}],"predecessor-version":[{"id":30806,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts\/30684\/revisions\/30806"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/media\/30818"}],"wp:attachment":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/media?parent=30684"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/categories?post=30684"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/tags?post=30684"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=30684"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}