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.

app/models/extface/driver/datecs/fp550.rb

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)
  else
    errors.add :base, "No data received from device"
    return nil
  end
end

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|
      errors.clear
      if resp = frecv(RESPONSE_TIMEOUT)
        if resp.valid?
          human_status_errors(resp.status)
          if errors.empty?
            result = resp.data
            break
          else
            raise errors.full_messages.join(',')
          end
        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!"
              break
            end
          elsif !resp.ack?
            invalid_frames += 1
            if nak_messages > INVALID_FRAME_RETRIES
              errors.add :base, "#{INVALID_FRAME_RETRIES} Broken Packets Received. Abort!"
              break
            end
          end
          push packet_data unless resp.ack?
        end
      end
      errors.add :base, "#{ACKS_MAX_WAIT} ACKs Received. Abort!"
    end
    return result
  end

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/models/extface/driver/datecs/fp550_test.rb

test "fsend" do
  job = extface_jobs(:one)
  job_thread = Thread.new do
    @driver.set_job(job)
    result = @driver.fsend(0x2C) # paper move command
  end
  simulate_device_pull(job)
  @driver.handle("\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x39\x03".b)
  simulate_device_pull(job)
  @driver.handle("\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x39\x03".b)
  job_thread.join
  assert @driver.errors.empty?
end

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.

app/models/extface/driver/datecs/fp550.rb

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

app/views/extface/driver/datecs/fp550/_control.html.erb

<%= 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!

Advertisements

One thought on “Extface driver writing guide 2

  1. Pingback: Extface driver writing guide (Ruby) | AlexVangelov

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s