app/models/extface/driver/daisy_fx1200.rb in extface-0.1.7 vs app/models/extface/driver/daisy_fx1200.rb in extface-0.1.8
- old
+ new
@@ -19,34 +19,280 @@
# Select driver features
RAW = true #responds to #push(data) and #pull
PRINT = false #POS, slip printers
FISCAL = true #cash registers, fiscal printers
- REPORT = false #only transmit data that must be parsed by handler, CDR, report devices
+ REPORT = false #only transmit data that must be parsed by handler, CDR, report devices
+ RESPONSE_TIMEOUT = 6 #seconds
+ INVALID_FRAME_RETRIES = 6 #seconds
+
+ TAX_GROUPS_MAP = {
+ 1 => "\xc0",
+ 2 => "\xc1",
+ 3 => "\xc2",
+ 4 => "\xc3",
+ 5 => "\xc4",
+ 6 => "\xc5",
+ 7 => "\xc6",
+ 8 => "\xc7"
+ }
+
has_serial_config
+
+ include Extface::Driver::Daisy::CommandsFx1200
- def handle(buffer)
- rpush buffer
- return buffer.length # return number of bytes processed
+ def handle(buffer) #buffer is filled with multiple pushes, wait for full frame (ACKs)STX..PA2..PA1..ETX
+ if buffer[/^\x16+$/] # skip if only ACKs
+ return buffer.length
+ else
+ if frame_len = buffer.index("\x03") || buffer.index("\x15")
+ rpush buffer[0..frame_len]
+ return frame_len+1 # return number of bytes processed
+ else
+ #TODO check buffer.length
+ return 0 #no bytes processed
+ end
+ end
+ return buffer.length
end
- def autocut(partial = true)
- # <ESC> “d” “0” - Full-cut command
- # <ESC> “d” “1” - Partial-cut command
- #push build_packet(CMD)
- #push partial ? "\x1B\x64\x31" : "\x1B\x64\x30"
+ def autocut(partial = true) # return "P" - success, "F" - failed
+ resp = fsend(Printer::CUT)
+ resp == "P"
end
+
+ def non_fiscal_test
+ device.session("Non Fiscal Text") do |s|
+ s.notify "Printing Non Fiscal Text"
+ s.fsend Sales::START_NON_FISCAL_DOC
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "********************************"
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "Extface Print Test".center(32)
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "********************************"
+ s.fsend Printer::MOVE, "1"
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "Driver: " + "#{self.class::NAME}".truncate(24)
+ s.fsend(Sales::END_NON_FISCAL_DOC)
+ s.notify "Printing finished"
+ end
+ end
- private
- def build_packet(cmd, data)
+ def fiscal_test
+ sale_and_pay_items_session([
+ { price: 0.01, text1: "Extface Test" }
+ ])
+ end
+
+ def build_sale_data(price, text1 = nil, text2 = nil, tax_group = 2, qty = nil, percent = nil, neto = nil)
+ "".tap() do |data|
+ data << text1 unless text1.blank?
+ data << "\x0a#{text2}" unless text2.blank?
+ data << "\t"
+ data << TAX_GROUPS_MAP[tax_group || 2]
+ data << price.to_s
+ data << "*#{qty.to_s}" unless qty.blank?
+ data << ",#{percent}" unless percent.blank?
+ data << "$#{neto}" unless neto.blank?
+ end
+ end
+
+ def sale_and_pay_items_session(items = [], operator = "1", password = "1")
+ device.session("Fiscal Doc") do |s|
+ s.notify "Fiscal Doc Start"
+ s.fsend Sales::START_FISCAL_DOC, "#{operator},#{password},00001"
+ items.each do |item|
+ s.fsend Sales::SALE_AND_SHOW, build_sale_data(item[:price], item[:text1], item[:text2], item[:tax_group], item[:qty], item[:percent], item[:neto])
+ end
+ s.fsend(Sales::TOTAL, "\t")
+ s.fsend(Sales::END_FISCAL_DOC)
+ s.notify "Fiscal Doc End"
+ end
+ end
+
+ def z_report_session
+ device.session("Z Report") do |s|
+ s.notify "Z Report Start"
+ s.fsend Closure::DAY_FIN_REPORT, "0"
+ s.notify "Z Report End"
+ end
+ end
+
+ def x_report_session
+ device.session("X Report") do |s|
+ s.notify "X Report Start"
+ s.fsend Closure::DAY_FIN_REPORT, "2"
+ s.notify "X Report End"
+ end
+ end
+
+ def cancel_doc_session
+ device.session("Doc cancel") do |s|
+ s.notify "Doc Cancel Start"
+ s.fsend Sales::CANCEL_DOC
+ s.notify "Doc Cancel End"
+ end
+ end
+
+ def check_status
+ flush
+ fsend(Info::STATUS) # return 6 byte status
+ errors.empty?
+ end
+
+ def build_packet(cmd, data = "")
+ String.new.tap() do |packet|
+ packet << STX
+ packet << 0x20 + 4 + data.length
+ packet << sequence_number
+ packet << cmd
+ packet << data
+ packet << PA1
+ packet << bcc(packet[1..-1])
+ packet << ETX
+ end
+ end
+
+ def fsend!(cmd, data = "") # return data or raise
+ push build_packet(cmd, data) # return 6 byte status
+ if resp = frecv(RESPONSE_TIMEOUT)
+ return resp.data if resp.valid?
+ else
+ raise errors.full_messages.join(', ')
+ end
+ end
+
+ def fsend(cmd, data = "") #return data or nil
+ packet_data = build_packet(cmd, data)
+ result = false
+ INVALID_FRAME_RETRIES.times do |retries|
+ errors.clear
+ push packet_data
+ if resp = frecv(RESPONSE_TIMEOUT)
+ if resp.valid?
+ result = resp.data
+ break
+ end
+ end
+ errors.add :base, "#{INVALID_FRAME_RETRIES} Broken Packets Received. Abort!"
+ end
+ return result
+ end
+
+ def frecv(timeout) # return RespFrame or nil
+ if frame_bytes = pull(timeout)
+ return RespFrame.new(frame_bytes.b)
+ else
+ errors.add :base, "No data received from device"
+ return nil
+ end
+ end
+
+ #private
+ def bcc(buffer)
+ sum = 0
+ buffer.each_byte do |byte|
+ sum += byte
+ end
+ "".tap() do |bcc|
+ 4.times do |halfbyte|
+ bcc.insert 0, (0x30 + ((sum >> (halfbyte*4)) & 0x0f)).chr
+ end
+ end
+ end
+
+ def sequence_number(increment = true)
+ @seq ||= 0x1f
+ @seq += 1 if increment
+ @seq = 0x1f if @seq == 0x7f
+ @seq
+ end
+
+ def human_status_errors(status) #6 bytes status
+ errors.add :base, "Fiscal Device General Error" unless (status[0].ord & 0x20).zero?
+ errors.add :base, "Invalid Command" unless (status[0].ord & 0x02).zero?
+ errors.add :base, "Date & Time Not Set" unless (status[0].ord & 0x04).zero?
+ errors.add :base, "Syntax Error" unless (status[0].ord & 0x02).zero?
+ errors.add :base, "Wrong Password" unless (status[1].ord & 0x40).zero?
+ errors.add :base, "Cutter Error" unless (status[1].ord & 0x20).zero?
+ errors.add :base, "Unpermitted Command In This Mode" unless (status[1].ord & 0x02).zero?
+ errors.add :base, "Field Overflow" unless (status[1].ord & 0x01).zero?
+
+ #errors.add :base, "Print Doc Allowed" unless (status[2].ord & 0x40).zero?
+ #errors.add :base, "Non Fiscal Doc Open" unless (status[2].ord & 0x20).zero?
+ errors.add :base, "Less Paper (Control)" unless (status[2].ord & 0x20).zero?
+ #errors.add :base, "Fiscal Doc Open" unless (status[2].ord & 0x08).zero?
+ errors.add :base, "No Paper (Control)" unless (status[2].ord & 0x04).zero?
+ errors.add :base, "Less Paper" unless (status[2].ord & 0x02).zero?
+ errors.add :base, "No Paper" unless (status[2].ord & 0x01).zero?
+
+ case (status[3] & 0x7f)
+ when 1 then errors.add :base, "Operation will result in the overflow"
+ when 3 then errors.add :base, "No more sales for this doc"
+ when 4 then errors.add :base, "No more payments for this doc"
+ when 5 then errors.add :base, "Null transaction attempt"
+ when 6 then errors.add :base, "Sale not allowed"
+ when 7 then errors.add :base, "No rights for this operation"
+ when 8 then errors.add :base, "Tax group forbidden"
+ when 11 then errors.add :base, "Multiple decimal points"
+ when 12 then errors.add :base, "Multiple sign symbols"
+ when 13 then errors.add :base, "Sign not at first position"
+ when 14 then errors.add :base, "Wrong symbol"
+ when 15 then errors.add :base, "Too many symbols after decimal point"
+ when 16 then errors.add :base, "Too many symbols"
+ when 20 then errors.add :base, "Service not allowed"
+ when 21 then errors.add :base, "Wrong Value"
+ when 22 then errors.add :base, "Disabled operation"
+ when 23 then errors.add :base, "Deep void after discount"
+ when 24 then errors.add :base, "Deep void for not existing record"
+ when 24 then errors.add :base, "Payment without sale"
+ end
+
+ errors.add :base, "General Fiscal Memory Error" unless (status[4].ord & 0x20).zero?
+ errors.add :base, "Fiscal Memory Full" unless (status[4].ord & 0x10).zero?
+ errors.add :base, "Less Than 50 Fiscal Records" unless (status[4].ord & 0x08).zero?
+ errors.add :base, "Invalid Fiscal Records" unless (status[4].ord & 0x04).zero?
+ errors.add :base, "Fiscal Write Error" unless (status[4].ord & 0x01).zero?
+
+ #errors.add :base, "Fiscal Memory Ready" unless (status[5].ord & 0x40).zero?
+ #errors.add :base, "Fiscal ID Set" unless (status[5].ord & 0x20).zero?
+ #errors.add :base, "Fiscal Tax set" unless (status[5].ord & 0x10).zero?
+ #errors.add :base, "Real Sales Mode" unless (status[5].ord & 0x80).zero?
+ errors.add :base, "Fiscal Memory Full" unless (status[5].ord & 0x01).zero?
end
- def bcc(data)
- bcc = 0
- data.each_byte do |byte|
- bcc += byte
+ class RespFrame
+ include ActiveModel::Validations
+ attr_reader :frame, :len, :seq, :cmd, :data, :status, :bcc
+
+ validates_presence_of :frame
+ #validate :bcc_validation
+ #validate :len_validation
+
+ def initialize(buffer)
+ # test Extface::Driver::DaisyFx1200::RespFrame.new("\x16\x16\x01\x2c\x20\x2dP\x04SSSSSS\x05\BBBB\x03")
+ # LEN SEQ CMD DATA STATUS BCC
+ if match = buffer.match(/\x01(.{1})(.{1})(.{1})(.*)\x04(.{6})\x05(.{4})\x03/n)
+ @frame = match.string[match.pre_match.length..-1]
+ @len, @seq, @cmd, @data, @status, @bcc = match.captures
+ end
end
+
+ private
+ def bcc_validation
+ sum = 0
+ frame[1..-6].each_byte do |byte|
+ sum += byte
+ end
+ calc_bcc = "".tap() do |tbcc|
+ 4.times do |halfbyte|
+ tbcc.insert 0, (0x30 + ((sum >> (halfbyte*4)) & 0x0f)).chr
+ end
+ end
+ errors.add(:bcc, I18n.t('errors.messages.invalid')) if bcc != calc_bcc
+ end
+
+ def len_validation
+ errors.add(:len, I18n.t('errors.messages.invalid')) if len.ord != (frame[1..-6].length + 0x20)
+ end
end
end
end
\ No newline at end of file