Extface driver writing guide 2

First post about Extface driver programming (Extface driver writing guide), was about base send and receive methods. In this article, we will create the second layer methods, that will allow us to reach full device functionality. As I said before, we need to create a complex method that will:

  • build the packet for a specific command
  • send it to the device
  • re-transmit it if necessary
  • read and decode the response packet
  • check status bytes (returned with every command) for errors
  • and return unpacked data if everything is OK
  • or stop the execution by rising an exception

The following implementation is not so nice, but it works with Daisy driver, which is very similar to Datecs. My desire now is to go alive, and then we can optimize the code. First, we need to declare some constants about max retries and timeouts. Best place for them is at he beginning of the file, so we can easily manipulate them later to tune the process. And then to create the “smart” frecv and fsend methods.


RESPONSE_TIMEOUT = 3  #seconds
INVALID_FRAME_RETRIES = 6  #count (bad length, bad checksum)
ACKS_MAX_WAIT = 60 #count / nothing is forever
NAKS_MAX_COUNT = 3 #count
def frecv(timeout) # return Frame or nil
  if frame_bytes = pull(timeout)
    return Frame.new(frame_bytes.b)
    errors.add :base, "No data received from device"
    return nil

def fsend(cmd, data = "") #return data or nil
    packet_data = build_packet(cmd, data)
    result = false
    invalid_frames = 0
    nak_messages = 0
    push packet_data
    ACKS_MAX_WAIT.times do |retries|
      if resp = frecv(RESPONSE_TIMEOUT)
        if resp.valid?
          if errors.empty?
            result = resp.data
            raise errors.full_messages.join(',')
        else #ack, nak or bad
          if resp.nak?
            nak_messages += 1
            if nak_messages > NAKS_MAX_COUNT
              errors.add :base, "#{NAKS_MAX_COUNT} NAKs Received. Abort!"
          elsif !resp.ack?
            invalid_frames += 1
            if nak_messages > INVALID_FRAME_RETRIES
              errors.add :base, "#{INVALID_FRAME_RETRIES} Broken Packets Received. Abort!"
          push packet_data unless resp.ack?
      errors.add :base, "#{ACKS_MAX_WAIT} ACKs Received. Abort!"
    return result

Now we should find a way to test this method.The Extface core is using Redis server to communicate with the device. If Redis is available in test mode, we can run the command in a thread, then simulate the response and then join the thread to test the result. Extface core requires a job to be associated with the driver commands, otherwise it will raise an exception (This is a part of the queue mechanism, taking care of the consistent implementation of tasks. I was not planning to talk about this, but it is required for our test).

It is hard to create a fully functional test for the “fsend” method… I had to do an ActiveSupport::TestCase helper method “simulate_device_pull” to simulate the device connection. Here is the result:


test "fsend" do
  job = extface_jobs(:one)
  job_thread = Thread.new do
    result = @driver.fsend(0x2C) # paper move command
  assert @driver.errors.empty?

Hmm.. I have to think about some kind of device simulator to be able to test the drivers. This test simply checks if there is a syntax error in the code that will raise an exception. Anyway it is still useful.

I start writing this article, because I need this driver up and running in production. A friend of mine has prepared a real Datecs FP550 device connected to a windows computer in Bulgaria. It is 4700 miles away (about 8000 kilometers), but this is the magic of Extface – I will be able to control the device no mater where it is. I could not resist and did a simple paper cut command for a real test.


def paper_cut
  device.session('Paper Cut') do |s|
    s.push build_packet(Printer::PAPER_CUT)


<%= button_to 'Paper Cut', fiscal_device_path(@device), remote: true, name: :paper_cut, value: true %>

Commit, gem push, bundle update extface 😉 deploy! And surprisingly the command works.

Extface gem provides a low level communication log in ‘log/extface/:device_id/:driver_name.log’. Let’s take a look at it:

D, [2015-05-21T02:00:49.669430 #9703] DEBUG — : –> 01 25 20 4A 58 05 30 30 3E 3C 03
D, [2015-05-21T02:00:51.375926 #9736] DEBUG — : <– 01 2B 20 4A 04 80 80 92 8F 80 B2 05 30 33 3F 31 03
D, [2015-05-21T02:00:51.391514 #9703] DEBUG — : –> 01 24 21 2D 05 30 30 37 37 03
D, [2015-05-21T02:00:51.504858 #9727] DEBUG — : <– 16
D, [2015-05-21T02:00:51.573943 #9703] DEBUG — : <– 16
D, [2015-05-21T02:00:51.642706 #9727] DEBUG — : <– 16
D, [2015-05-21T02:00:51.714377 #9739] DEBUG — : <– 16 16
D, [2015-05-21T02:00:51.785687 #9712] DEBUG — : <– 16 16 01 2C 21 2D 46 04 80 80 92 8F 80 B2 05 30 34 31
D, [2015-05-21T02:00:51.855080 #9739] DEBUG — : <– 16 01 2C 21 2D 46 04 80 80 92 8F 80 B2 05 30 34 31 3C 03

This log contains the binary packets data in a way it is transferred via the TCP protocol. First the driver sends a GET_STATUS (0x4A) command, if there is no error in the response (check status bits), the driver begins to execute commands in the job session. We can see the PAPER_CUT (0x2D) command on row 3. Since the operation is delayed, the device sends several ACK packets until the paper is cut, and then returns the response packet. Looks like it is transferred in two pieces, but our driver is ready to handle this behavior, and the job ends with the receipt of 0x01..0x03 pattern packet.

The final step is to overwrite the base methods in ‘/app/models/extface/driver/base/fiscal.rb‘, which are required for a fiscal memory device driver model. This will get in the next article coming soon.

10x for reading!