Journal

/A/ /M/A/G/N/I/F/I/C/E/N/T/ /P/L/A/C/E/ 

September 2016

We have a multifunction printer (MFP) ie a combined printer, scanner, and copier. It's a Brother DCP-7055. It doesn't have a wireless connection but I want my partner to be able use it from her laptop wirelessly to avoid dragging the printer out of its corner, to avoid cables trailing around, and avoid awkward seating arangements due to short cables etc.

Part of the solution is to have another computer on the LAN act as a print server. Ideally this print server would be one of my Raspberry Pi's or my NAS drive, but Brother, the manufacturer, have no intention of supporting the ARM architecture so that leaves only my desktop PC. So everytime something needs to be printed, my desktop PC must be turned on.

I use the Linux OS on all my systems (of course), specifically Arch Linux on the desktop and laptop, Debian on the NAS, and OSMC on the Pi's. My desktop PC has the CUPS print server running, and the laptop sends print jobs to it via wifi.

We've had it up and running like this for long enough that I'd forgotten that scanning wasn't working and why it wasn't working. The crux of the matter is the DCP-7055 is seen by Linux as a single USB device and both printer software and scanner software want to take ownership of it, but simultaneous ownership is not possible.

So I set out to find a way as simple as possible for my partner to switch between printer and scanner functionality with the least amount of aggro on her part. The ideal result would be she just needs to open a terminal on her laptop and type 'useprinter' or 'usescanner' and then go about doing whichever of these two tasks she wishes to do.

The first problem is that we need to discover how to change ownership of the device node for the MFP. How device nodes are created is handled by udev. Rules can be written to change how the device is treated. The brscan4 software supplied by Brother for the DCP-7055 installs a series of rules which identify a number of printers and changes the group ownership of the device node to 'scanner', promptly making it innaccessible to CUPS for printing.

The idea is to delete the udev rules installed by the scanner software when printing is to take place, and re-install them when scanning is to occur. Unfortunately, this is not enough, the device itself either needs to be unplugged and replugged in, or powered off and back on again before ownership of the device node will change. The only exception being if the device is powered off in the first place when issuing the command to change it for printing or scanning.

So I write two BASH scripts, one for printing, the other for scanning. Initially, the printer script simply checks if the rules file exists and deletes it. The scanner script just creates the rules file if it doesn't already exist.

#!/bin/bash
# file: /usr/local/bin/use-dcp-7055-as-printer

if [[ -f /etc/udev/rules.d/brscan4.rules ]]; then
  rm /etc/udev/rules.d/brscan4.rules
  udevadm control --reload
  udevadm trigger
fi

#!/bin/bash
# file: /usr/local/bin/use-dcp-7055-as-scanner

if [[ ! -f /etc/udev/rules.d/brscan4.rules ]]; then

  cat > /etc/udev/rules.d/$brscan4.rules << END_OF_RULES
ACTION=="add", ATTR{idVendor}=="04f9", GOTO="brscan4"
GOTO="brscan4_end"
LABEL="brscan4"
ATTR{idProduct}=="0248", MODE="0664", GROUP="scanner", ENV{libsane_matched}="yes"
LABEL="brscan4_end"
END_OF_RULES

  udevadm control --reload
  udevadm trigger
fi

However these actions can't be performed by ordinary users without super user privileges. So we add a line the sudoers file granting our user permission to run these two scripts (and only these two scripts) with super user privileges. visudo is the tool to do so, and the line is (replace USERNAME with user login name):

USERNAME ALL= NOPASSWD: /usr/local/bin/use-dcp-7055-as-printer,/usr/local/bin/use-dcp-7055-as-scanner

This allows our user to use the sudo command to gain the neccessary privileges. To keep it simple for our user, we create two more scripts, which do this for our user. We call these scripts useprinter and usescanner. Very easy to remember.

The scripts as they stand aren't very reassuring in their lack of feedback. We want our users to feel confident our scripts are working as they should, that all outcomes are catered for, and inconsistencies are handled. This requires a bit more knowledge.

If the name of the device file created by udev was known (assuming it even exists), it could be inspected to see the current state of ownership. For example we could tell our user to turn the MFP on, off and on again if necessary, or simply that it is ready.

I don't know all the ins and outs of Linux so there may be better ways of doing this, but lsusb was the obvious choice for me to use to build the information required to find out the device node state. A fifth script was created which would be sourced by each of the first two scripts so they could use it's function 'usb-dev-from-string':

#!/bin/bash
# file: /usr/local/bin/usb-dev-from-string

function usb-dev-from-string
{
    STR="$1"
    RES=$(lsusb | grep --max-count=1 "$STR")
    unset DEVBUS DEVNODE DEV
    if [[ "X" == "X$RES" ]]; then
        return
    fi
    set $RES
    DEVBUS=$2
    DEVNODE=${4%%:}
    VENDORID=${6%:*}
    PRODID=${6#*:}
    DEV="/dev/bus/usb/$DEVBUS/$DEVNODE"
}

You'll notice this script is deliberately generic, it's not targetted at the DCP-7055 in particular, nor even MFPs in general. It can be used to find the device node of any USB device (note: I haven't tested this claim). The caller of the script passes a string in which is contained the name of device, in our case all we need to give it is "DCP-7055" from which it finds the device node path in the file system (DEV), the vendor id value (VENDORID), and the product id value (PRODID). If it fails, DEV is not set.

The by-product of the more generic approach is the possibility to help a wider audience - this particular problem isn't limited to the DCP-7055. The initial two scripts are adapted to use the usb-dev-from-string function, and extended to provide feedback to the user. Both scripts contain two variables at their top in which the name of MFP is placed (this identifier should match lsusb output - it doesn't need to be the full name, just enough of it to be unique). The second variable is the driver name. I used the name of the rules file installed by brscan4, ie brscan4. The rules file created follows the same pattern as that created by the brscan4 software, except it is now targetted to a single device. Lastly, the filenames of the scripts are changed to reflect the slightly more generic approach they have taken on, requiring modification of the line in the sudoers file also.

#!/bin/bash
# file: /usr/local/bin/use-mfp-as-printer

MFP="DCP-7055"
DRIVER="brscan4"

. /usr/local/bin/usb-dev-from-string

if [[ -f /etc/udev/rules.d/${DRIVER}.rules ]]; then
  rm /etc/udev/rules.d/${DRIVER}.rules
  udevadm control --reload
  udevadm trigger
fi

usb-dev-from-string $MFP

if [[ -z $DEV ]]; then
  echo "$MFP need to be turned on or connected."
  exit
fi

GROUP=$(stat -c %G $DEV)
if [[ "$GROUP" == "lp" ]]; then
  echo "$MFP ready for printing."
else
  echo "$MFP needs to be turned off and on again."
fi

#!/bin/bash
# file: /usr/local/bin/use-mfp-as-scanner

MFP="DCP-7055"
DRIVER="brscan4"

. /usr/local/bin/usb-dev-from-string

usb-dev-from-string $MFP

if [[ ! -f /etc/udev/rules.d/${DRIVER}.rules ]]; then
  cat > /etc/udev/rules.d/${DRIVER}.rules << END_OF_RULES
ACTION=="add", ATTR{idVendor}=="$VENDORID", GOTO="$DRIVER"
GOTO="${DRIVER}_end"
LABEL="$DRIVER"
ATTR{idProduct}=="$PRODID", MODE="0664", GROUP="scanner", ENV{libsane_matched}="yes"
LABEL="${DRIVER}_end"
END_OF_RULES

  udevadm control --reload
  udevadm trigger
fi


if [[ -z $DEV ]]; then
  echo "$MFP needs to be turned on or connected."
  exit
fi

GROUP=$(stat -c %G $DEV)
if [[ "$GROUP" == "scanner" ]]; then
  echo "$MFP ready for scanning."
else
  echo "$MFP needs to be turned off and on again."
fi

#!/bin/bash
# file: /usr/local/bin/useprinter
sudo /usr/local/bin/use-mfp-as-printer

#!/bin/bash
# file: /usr/local/bin/usescanner
sudo /usr/local/bin/use-mfp-as-scanner

USERNAME ALL= NOPASSWD: /usr/local/bin/use-mfp-as-printer,/usr/local/bin/use-mfp-as-scanner

At this stage, we now have everything working, with one condition: the user must not only turn on the desktop machine running the print server, but must now also login to it if they want to change from printing to scanning or vice-versa. The last thing to do is, if you haven't already, setup a secure shell server. Do that via instructions from elsewhere. Once you have setup the user to be able to open a terminal on their laptop and use SSH to login to the desktop running the print server software (CUPS) then create the following two scripts to automate the procedure:

#!/bin/bash
# file: /usr/local/bin/useprinter
ssh USER@HOST -t /usr/local/bin/useprinter


#!/bin/bash
# file: /usr/local/bin/usescanner
ssh USER@HOST -t /usr/local/bin/usescanner


Ideally you will have set them up with a passphrase rather than password access. Maybe have changed the port number too, so the commands above might have other options or might not, depending on how you setup the SSH server. The -t option isn't really necessary but it will be useful if for some reason the sudoers file is misconfigured and your user is required to enter a password to be granted super user privileges. You really want this to be configured however otherwise the poor user will be required to enter a password for SSH followed by a password for permission to run the scripts.

Information

"Journal"

A page detailing new stuff and other random noise.

The journal is a general place for writing about what I am doing, or for making more official announcements concerning the things I do. It's also a place where I can write freely about my ideas, or just play with words and language.

DISCLAIMER: The opinions and attitudes of James W. Morris as expressed here in the past may or may not accurately reflect the opinions and attitudes of James W. Morris at present, moreover, they may never have.

this page last updated:29th April 2013 jwm-art.net (C) 2003 - 2015 James W. Morris

script time:0.0620