Coder Social home page Coder Social logo

tcladu's Introduction

tcladu

Tcl package supporting multiple ADU100s from Ontrak Control Systems via libusb and SWIG.

ADU100 BW

Table of Contents

Demonstration

Let's say you've downloaded a release binary from Sourceforge, and you have a few (two) ADU100s connected. You also need permissions to access the device, but let's say you have those.


johnpeck@darkstar:~/Downloads $ tar xzvf tcladu-1.0.0-linux-x64.tar.gz
tcladu/
tcladu/pkgIndex.tcl
tcladu/tcladu.so
johnpeck@darkstar:~/Downloads $ cd ~
johnpeck@darkstar:~ $ tclsh
% lappend auto_path ~/Downloads
/usr/share/tcltk/tcl8.6 /usr/share/tcltk ... ~/Downloads
% puts [package require tcladu]
1.0.0
% puts [tcladu::discovered_devices]
2
% puts [tcladu::serial_number 0]
B02597
% puts [tcladu::write_device 0 "SK0" 200]
0
% puts [tcladu::write_device 0 "RPK0" 200]
0
% puts [tcladu::read_device 0 8 200]
0 1
% puts [tcladu::write_device 0 "RK0" 200]
0
% puts [tcladu::write_device 0 "RPK0" 200]
0
% puts [tcladu::read_device 0 8 200]
0 0

What did we just do?

Unpacked the TGZ

The package is just two files: pkgIndex.tcl, used by Tcl's package procedure, and tcladu.so, a binary produced from some c code.

Appended the package to Tcl's auto_path

The auto_path list tells Tcl where to look for packages.

Required the package

This both loads procedures into the tcladu namespace and initializes libusb.

Populated the connected device database

The discovered_devices command will populate a device database with things like device handles and serial numbers. This must be called before writing to or reading from devices.

Queried the device database for device 0

The serial_number command doesn't do anything with connected hardware -- it just returns a serial number populated by discovered_devices.

Sent the command to set/close the ADU100's relay

The write_device command takes a device index instead of some kind of handle to identify the targeted device. It then takes an ASCII command that you can find in the ADU100 manual to manipulate the hardware relay. The last argument is a timeout for libusb (in milliseconds), which will become more interesting when we get into reading from the hardware.

Sent the command to read the relay status

Reading the relay status starts with telling the ADU100 to read the status. It will prepare the result to be read by the next libusb read. The return value for the RPK0 command is just a success code -- not the relay status.

Read from the ADU100

The read_device command takes a device index, followed by the number of bytes we want to read. This payload size is a placeholder for now, although it has to be 8 bytes or larger. I want to keep it to handle larger payloads on other Ontrak devices this might support in the future.

The final argument is the familiar ms timeout. Libusb will throw a timeout error if the read takes longer than this value. But this error isn't fatal, and your code can catch this and simply try again. This gives your application a chance to stay active while you wait for a long hardware read.

The result is a Tcl list containing the success code and return value. In this case, a 1 shows us that the relay is set/closed.

Sent the command to reset/open the ADU100's relay

This is the opposite of the set command.

Sent the command to check the relay status again

We'll now expect the hardware to report 0 for the relay status.

Read from the ADU100

The returned list is now 0 0, telling us that the command succeeded and that the relay is reset/open.

Getting started

Install libusb-1.0

We need libusb-1.0-0 to call the libusb functions in tcladu. This comes from Ubuntu's libusb-1.0-0 package, and I'm using version 2:1.0.25-1ubuntu2 on 2024-Mar-07.

We need libusb-1.0/libusb.h to build the package, which comes from Ubuntu's libusb-1.0-0-dev package. I have the same version of the binary and dev packages.

Install a udev rule

You can't communicate with the ADU100 without permission, and udev allows configuring that permission when devices are plugged in. I copy the rule here to /usr/lib/udev/rules.d and then call

sudo udevadm control --reload-rules

...to activate the new rule. Remember that these rules are only applied to new devices, so you'll need to unplug and plug your device after reloading the rules. The rule I've linked is for any Ontrak device, and it sets the device mode to 0666. You can use a nice permissions calculator to set whatever permissions you need.

You can make sure permissions are working with lsusb from usbutils.


johnpeck@darkstar:~ $ lsusb | grep Ontrak
Bus 001 Device 017: ID 0a07:0064 Ontrak Control Systems Inc. ADU100 Data Acquisition Interface

...which means the device is located at /dev/bus/usb/001/017. We can check the permissions with


johnpeck@darkstar:~ $ ls -al /dev/bus/usb/001/017
crw-rw-rw- 1 root root 189, 16 Mar  2 05:44 /dev/bus/usb/001/017

...showing that our rule is working.

Requiring the package

Place the package somewhere the Tcl auto loader can find it. I like usr/share/tcltk. This path must be in the auto_path list. For example,

% puts $auto_path
/usr/share/tcltk/tcllib1.20 ... /usr/share/tcltk ...

...my auto_path includes /usr/share/tcltk. When I put tcladu in that directory, I can require it with

% package require tcladu
1.1.1

...where the command returns the loaded version. I can then make sure the package got loaded from the right place with

% package ifneeded tcladu 1.1.1
load /usr/share/tcltk/tcladu1.1.1/tcladu.so
source /usr/share/tcltk/tcladu1.1.1/tcladu.tcl

...showing the path I expected. This step is more important if you build the package yourself, as you might have intermediate builds around with the same version number. See the references below for more information about package ifneeded.

Command reference

Low level commands

These are commands implemented in tcladu.c and broken out via SWIG.

discovered_devices

This command returns the number of ADU100 devices discovered on USB. The key line is

if ( desc.idVendor == 0x0a07 && desc.idProduct == 0x0064 ) {

...showing how USB descriptors are used to discover ADU100s. This command also populates the device database -- required for using numbers like the device index in other commands.

Arguments

None

Returns
  • 0 to the number of discovered ADU100 devices if successful
  • -1 if there was an error during discovery
Example

We can get the number of connected ADU100s and populate the internal database with

% package require tcladu
1.1.1
% tcladu::discovered_devices
1

...where tcladu correctly found 1 connected ADU100.

initialize_device

This command is more about initializing the USB interface than it is about initializing the ADU100 hardware. But it acts on one device instead of some broader initialization. It does two things:

  1. Enable libusb's automatic kernel driver detachment for the chosen ADU100.
  2. Claim interface number 0 for the chosen ADU100. See the libusb documentation.

The device database must be populated with tcladu::serial_number_list or tcladu::discovered_devices before calling this function.

Arguments
  1. Device index (0, 1, ..., connected ADU100s -1)
Returns
  • 0 on success
Example
% package require tcladu
1.1.1
% tcladu::serial_number_list
B02797
% tcladu::initialize_device 0
0

High level commands

serial_number_list

Returns a list of connected ADU100 devices. This calls tcladu::discovered_devices internally to populate the connected device database.

Arguments

None

Returns

A list of discovered ADU100 devices. This list will be empty if no devices are discovered.

Example
% package require tcladu
1.1.0
% tcladu::serial_number_list
B02597 B02797

clear_queue

Returns a list of success code execution time after repeatedly calling read_device() to clear the ADU100's transmit buffer. This prevents confusion from queries returning old data.

Arguments
  1. Device index (0, 1, ..., connected ADU100s -1)
Example

Clear the queue (of the ADU100 at index 0) with

% package require tcladu
1.1.1
% tcladu::serial_number_list
B02597
% tcladu::initialize_device 0
0
% tcladu::clear_queue 0
0 12

...and the return tells us this took 12ms to succeed. We need to call initialize_device here because clear_queue sends commands to the device.

send_command

Returns a list of success code execution time after sending an ASCII command. You must call serial_number_list to populate the device database before calling send_command.

Arguments
  1. Device index (0, 1, ..., connected ADU100s -1)
  2. Command
Example

This sequence shows populating the device database, then setting (closing) the ADU100's relay.

% package require tcladu
1.1.0
% tcladu::serial_number_list
B02597 B02797
% tcladu::initialize_device 0
0
% tcladu::send_command 0 "SK0"
0 8

The return is 0 (success), followed by 8 -- it took 8ms to get a response from the ADU100 (this is not how long it takes to close the relay).

query

Returns a list of success code response execution time after sending an ASCII query command. You must call serial_number_list to populate the device database before calling query.

Arguments
  1. Device index (0, 1, ..., connected ADU100s -1)
  2. Command
Example

This sequence shows querying the (open/reset) relay status, closing the relay, then querying again.

% package require tcladu
1.1.1
% tcladu::serial_number_list
B02597
% tcladu::clear_queue 0
0 13
% tcladu::query 0 RPK0
0 0 10
% tcladu::send_command 0 SK0
0 5
% tcladu::query 0 RPK0
0 1 14

The final response of 1 shows that the relay is closed.

References

  1. See the Tcler's Wiki for a description of package ifneeded.
  2. See the libusb 1.0 API documentation for better descriptions of the low-level commands.

tcladu's People

Contributors

johnpeck avatar

Watchers

 avatar

tcladu's Issues

initialize_device() should not have a printf debug output

initialize device has the lines:

if ( retval < 0 ) {
    printf( "Error claiming interface: %s\n", libusb_error_name( retval ) );
  }

...which is an artifact of the Ontrak example. These debug strings aren't useful in the package, since SWIG doesn't set up the interface to this function to return anything other than an integer. This printf command should be commented out and replaced with a return command returning the libusb error integer.

The Tcl code then needs a function with a switch statement to turn this error code into a proper error. Something like tcladu::check_return. This also means making the low-level initialize_device function private (like _initialize_device) and making a Tcl function to call it. This also triggers a documentation change, but it's not a breaking change.

query example must include a call to clear_queue

The current query command example does not include a call to clear_queue. I think this is causing some unexpected behavior when I move between the test sequence and a REPL session. And it's good practice to clear the queue before starting any command sequence involving a query.

Document initialize_device() as a low-level command

initialize_device() is used in the test code but none of the examples. I suspect it ends up causing some of the instability I see once in awhile, where using the code with a REPL only works after running the test script.

The first step will be to add documentation for this, including a link to the libusb recommendation for calling it.

Add documentation for discovered_devices() in the README

This low-level function simply returns the number of ADU100s found on the bus, if successful. It also might return a negative number if there's a problem with the ADU100 , USB, or libusb. It will return -1 if it didn't find anything.

I'll need to create a category for low-level commands to document this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.