Amazon ALEXA Lists Exercise – Put’em on Paper…

4.5 Raspberry Pi print server

This is perhaps the most complicated step, I hope I didn’t forget anything important.

4.5.1 Hardware and OS setup

  • Well, plug the SD card into your desktop computer and install noobs lite onto it. It’s as simple as formating with FAT and copying the NOOBS files onto the card.
  • Then, plug in the sd card into the pi and wire it up with Screen, keyboard and PSU. It should start the booting process. This link covers setup in detail.
  • After choosing a WIFI or wired network connection, install Raspbian Lite as only operating system; you don’t need the data partion for this project.
  • This may take a while.

With the default user „pi“ and password „raspberry“, do a sudo apt-get update and a sudo apt-get upgrade . This also takes a while.

4.5.2 Install the USB printer

Connect the Thermal printer via USB to your pi and power it up. In my case, I’d now see the appropriate dmesg  (less /var/log/messages ) output and a new device /dev/usb/lp0 – perfect.

While it’s possible to access the printer already as super user, we’d probably not want Apache’s www-data  user to have su  privileges all the time, or ever, so – more work.

  • use udevadm info -a -p $(udevadm info -q path -n /dev/usb/lp0)  to view the chain of devices leading to the last endpoint within the printer. This is very tricky, in my case it was this:
  looking at device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/usbmisc/lp0':
    KERNEL=="lp0"
    SUBSYSTEM=="usbmisc"
    DRIVER==""

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4/1-1.4:1.0':
    KERNELS=="1-1.4:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="usblp"
    ATTRS{authorized}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceClass}=="07"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bInterfaceProtocol}=="02"
    ATTRS{bInterfaceSubClass}=="01"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{ieee1284_id}==""
    ATTRS{supports_autosuspend}=="1"

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.4':
    KERNELS=="1-1.4"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bcdDevice}=="0200"
    ATTRS{bmAttributes}=="c0"
    ATTRS{busnum}=="1"
    ATTRS{configuration}==""
    ATTRS{devnum}=="5"
    ATTRS{devpath}=="1.4"
    ATTRS{idProduct}=="0808"
    ATTRS{idVendor}=="0456"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Thermal Printer   "
    ATTRS{maxchild}=="0"
    ATTRS{product}=="H58 Printer USB         "
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="removable"
    ATTRS{serial}=="Printer"
    ATTRS{speed}=="12"
    ATTRS{urbnum}=="55"
    ATTRS{version}==" 2.00"

[...]
  • The very first item is the interesting endpoint, see the /…/lp0  at the end? We need an appropriate /etc/udev/rules.d  udev rule. I named it /etc/udev/rules.d/98-hillpow.rules and it contains only one single line:
KERNEL=="lp0", SUBSYSTEM=="usbmisc",ATTRS{idVendor}=="0456",ATTRS{idProduct}=="0808",MODE="0664",GROUP="plugdev",SYMLINK+="listprinter%n"

This generates a symlink /dev/listprinter0  accessible for read/write operations to the plugdev  group as soon as the printer is plugged in and powered on. Do note that the vendor and product attributes may vary and that the end point tree may be completely different in your model. Hence the udevadm  command to find the exact tree structure.

Remove and replug your USB printer. There should be a symlink named /dev/listprinter0  with 664 access permissions. Voila.

4.5.3 Install PHP7 and Apache 2

This is trivial even to the layman, I’d recommend going with PHP 7 and not installing anything fancy:

  • sudo apt-get install apache2 php7.0 libapache2-mod-php7.0 
  • When this is done, create an index.php  file in /var/www/html with the content <?php phpinfo(); ?> .
  • Now rename the index.html into index.html_ with sudo mv index.html index.html_ .
  • Test PHP with a web browser by navigating to your print server’s host name or IP address. The large PHP info page should be displayed.
  • Lastly, add the www-data  user to to plugdev  group with sudo usermod -a -G plugdev www-data 
  • Restart Apache with sudo service apache2 restart 

4.5.4 Installing mike24’s lib

  • Navigate to your html directory cd /var/www/html . 
  • Get Mike24’s excellent code from here: https://github.com/mike42/escpos-php by issuing git clone https://github.com/mike42/escpos-php vendor/mike42/escpos-php 

Last but not least, we need to create the internal_printer.php  file in /var/www/html/  with the following code:

<?php
  require __DIR__ . '/vendor/mike42/escpos-php/autoload.php';
  use Mike42\Escpos\PrintConnectors\FilePrintConnector;
  use Mike42\Escpos\Printer;
  // ***** FIND THE FIRST USB PRINTER
  $printers = scandir("/dev");
  foreach ($printers as $printerDevice) {
    if (strpos($printerDevice, "listprinter")===false) continue;
    try {
      $connector = new FilePrintConnector("/dev/". $printerDevice);
      $printer = new Printer($connector);
      break;
    } catch (Exception $e) {
      header("HTTP/1.1 503 Service Unavailable");
      $error = array(
        "statusCode"=>500,
        "statusText"=>"Thermal printer '$printerDevice' could not be accessed."
      );
      echo json_encode($error);
      exit(1);
    }
  }
  if ($printer==null) {
    header("HTTP/1.1 503 Service Unavailable");
    $error = array(
      "statusCode"=>500,
      "statusText"=>"No thermal printer connected."
    );
    echo json_encode($error);
    exit(1);
  }
  // ***** INPUT SANITY CHECKING
  $listItems = json_decode($_REQUEST["listItems"]);
  if (!is_array($listItems) || count($listItems)<1 || count($listItems)>30) {
    header("HTTP/1.1 400 Bad Request");
    $error = array(
      "statusCode"=>400,
      "statusText"=>"Please provide at least 1 and at best 30 list item(s) in request parameter named 'listItems' in form of a JSON UTF8 list."
    );
    //var_dump($listItems);
    //var_dump($_REQUEST["listItems"]);
    echo json_encode($error);
    exit(1);
  }
  $listName = mb_strtoupper($_REQUEST["listName"]);
  // make a default
  if ($listName=="") { $listName = "UNBENANNTE LISTE"; }
  // truncate to 32 chars
  $listName = mb_substr($listName, 0, 32);
  // Set print mode to double height only
  if (mb_strlen($listName)<=16) {
    $printer -> selectPrintMode(Printer::MODE_DOUBLE_HEIGHT|Printer::MODE_DOUBLE_WIDTH);
  } else {
    $printer -> selectPrintMode(Printer::MODE_DOUBLE_HEIGHT);
  }
  // **** PRINT THE HEADER
  $printer -> setJustification(Printer::JUSTIFY_CENTER);
  $printer -> setEmphasis(true);
  $printer -> text($listName."\n");
  $printer -> selectPrintMode();
  // **** PRINT THE DIME (10.10.2017  16:43)
  //$printer -> setReverseColors(true);
  $printer -> text(date("---- d.m.Y ----- H:i ----\n"));
  $printer -> setReverseColors(false);
  $printer -> setEmphasis(false);
  $printer -> setJustification(Printer::JUSTIFY_LEFT);
  $printer -> feed(1);
  // **** PRINT THE LIST
  foreach($listItems as $item) {
    $item = "* ". ucwords(strtolower(mb_substr($item, 0, 30))) ."\n";
    $printer -> text($item);
  }
  $printer -> feed(5);
  $printer -> cut();
  $printer -> close();
  $error = array(
    "statusCode"=>200,
    "statusText"=>"List '$listName' was printed successfully."
  );
  echo json_encode($error);
?>

The code should be fairly self-explanatory, you don’t need to change anything here. In less detail, this is what happens:

  • find the first printer and assume it has a line width of 32 characters
  • make sure we have a list name (or default to UNBENANNT)
  • make sure we have from 1 to 30 items
  • for each item:
    • truncate to 30 chars
    • capitalise each word
    • prepend with „*“
  • print it
  • return success

If you want to test the printer stand-alone, just call http://IP/internal_printer.php?listName=TEST&listItems=[„item 1“, „item 2“, „item 3“] 

5 Kommentare zu “Amazon ALEXA Lists Exercise – Put’em on Paper…”

1.   Kommentar von Tom
Erstellt am 09. Dezember 2018 um 22:40 Uhr.

Hi,
great post.
Could please tell which thermal printer you have used?
The amazon link isnt‘ working anymore.
Thanks

2.   Kommentar von McSeven
Erstellt am 10. Dezember 2018 um 20:27 Uhr.

hi, Thanks. The one I used isn’t available on Amazon anymore, but any USB receipt printer should work… Just look for what mike24’s ESC/POS library is supporting and buy one of those models… Best, Christoph

3.   Kommentar von Tom
Erstellt am 11. Dezember 2018 um 20:53 Uhr.

Thanks for the reply. This product list on github helps a lot.
One further question: Could you really delete the original shopping list with the skill? In your video you use the original „Einkaufsliste“ so you could also delete this one per API? In your code for the lambda it is „shopping list“ and not the original code snipped?

Danke und Gruß
Tom

4.   Kommentar von McSeven
Erstellt am 12. Dezember 2018 um 20:23 Uhr.

hi, well, the Einkaufsliste is used by the ALEXA language model. it translates into „shopping list“ in the skill. Therefore the difference… Cheers.

5.   Kommentar von BennyBoom
Erstellt am 07. Oktober 2020 um 00:10 Uhr.

Hello!

Nice work ! I try to integrate it to home assistant .
Your video link is dead could you update it please?

regards

Ben

Einen Kommentar hinterlassen