Class: Driver::Datecs::Fp550

Inherits:
Extface::Driver::Base::Fiscal
  • Object
show all
Includes:
Extface::Driver::Datecs::CommandsV1
Defined in:
app/models/extface/driver/datecs/fp550.rb

Defined Under Namespace

Classes: Frame

Constant Summary collapse

NAME =
'Datecs FP550 (Serial)'.freeze
RESPONSE_TIMEOUT =

seconds

3
INVALID_FRAME_RETRIES =

count (bad length, bad checksum)

6
ACKS_MAX_WAIT =

count / nothing is forever

60
NAKS_MAX_COUNT =

count

3
BAD_SEQ_MAX_COUNT =
3
TAX_GROUPS_MAP =
{
  1 => "\xc0",
  2 => "\xc1",
  3 => "\xc2",
  4 => "\xc3",
  5 => "\xc4",
  6 => "\xc5",
  7 => "\xc6",
  8 => "\xc7"
}
PAYMENT_TYPE_MAP =
{
  1 => "P",
  2 => "N",
  3 => "C",
  4 => "D",
  5 => "B"
}

Instance Method Summary collapse

Instance Method Details

#add_comment(text) ⇒ Object



125
126
127
# File 'app/models/extface/driver/datecs/fp550.rb', line 125

def add_comment(text)
  raise "Not in fiscal session" unless @fiscal_session
end

#add_payment(value = nil, type_num = nil) ⇒ Object



129
130
131
132
133
134
135
136
137
# File 'app/models/extface/driver/datecs/fp550.rb', line 129

def add_payment(value = nil, type_num = nil)
  raise "Not in fiscal session" unless @fiscal_session
  payment_data = "".tap() do |data|
                        data << "\t"
                        data << PAYMENT_TYPE_MAP[type_num || 1]
                        data << ("%.2f" % value) unless value.blank?
                      end
  fsend(Sales::TOTAL, payment_data)
end

#add_sale(sale_item) ⇒ Object



120
121
122
123
# File 'app/models/extface/driver/datecs/fp550.rb', line 120

def add_sale(sale_item)
  raise "Not in fiscal session" unless @fiscal_session
  fsend Sales::SALE_AND_SHOW, build_sale_data(sale_item)
end

#build_packet(cmd, data = "") ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/extface/driver/datecs/fp550.rb', line 230

def build_packet(cmd, data = "")
  "".b.tap() do |packet|
    packet << STX                     #Preamble. 1 byte long. Value: 01H.
    packet << 0x20 + 4 + data.length  #Number of bytes from <01> preamble (excluded) to <05> (included) plus the fixed offset of 20H
    packet << sequence_number         #Sequence number of the frame. Length : 1 byte. Value: 20H – FFH.
    packet << cmd                     #Length: 1 byte. Value: 20H - 7FH.
    packet << data                    #Length: 0 - 218 bytes for Host to printer
    packet << PA1                     #Post-amble. Length: 1 byte. Value: 05H.
    packet << Frame.bcc(packet[1..-1])#Control sum (0000H-FFFFH). Length: 4 bytes. Value of each byte: 30H-3FH
    packet << ETX                     #Terminator. Length: 1 byte. Value: 03H.
  end
end

#cancel_doc_sessionObject



161
162
163
164
165
166
167
168
# File 'app/models/extface/driver/datecs/fp550.rb', line 161

def cancel_doc_session
  device.session("Doc cancel") do |s|
    s.notify "Doc Cancel Start"
    s.fsend Sales::CANCEL_FISCAL_DOC
    s.paper_cut
    s.notify "Doc Cancel End"
  end
end

#check_statusObject



102
103
104
105
106
# File 'app/models/extface/driver/datecs/fp550.rb', line 102

def check_status
  flush #clear receive buffer
  fsend(Info::GET_STATUS, 'X') # get 6 bytes status
  errors.empty?
end

#close_fiscal_docObject



114
115
116
117
118
# File 'app/models/extface/driver/datecs/fp550.rb', line 114

def close_fiscal_doc
  raise "Not in fiscal session" unless @fiscal_session
  fsend Sales::END_FISCAL_DOC
  @fiscal_session = false
end

#close_non_fiscal_docObject



97
98
99
100
# File 'app/models/extface/driver/datecs/fp550.rb', line 97

def close_non_fiscal_doc
  fsend Sales::END_NON_FISCAL_DOC
  @print_session = false
end

#fiscal_testObject



55
56
57
58
59
# File 'app/models/extface/driver/datecs/fp550.rb', line 55

def fiscal_test
  sale_and_pay_items_session([
    SaleItem.new( price: 0.01, text1: "Extface Test" )
  ])
end

#frecv(timeout) ⇒ Object

return Frame or nil



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'app/models/extface/driver/datecs/fp550.rb', line 210

def frecv(timeout) # return Frame or nil
  rframe = nil
  BAD_SEQ_MAX_COUNT.times do
    if frame_bytes = pull(timeout)
      rframe = Frame.new(frame_bytes.b)
      if rframe.seq.nil? || rframe.seq.ord == sequence_number(false) #accept only current sequence number as reply
        break
      else
        errors.add :base, "Sequence mismatch"
        p "Invalid sequence (expected: #{sequence_number(false).to_s(16)}, got: #{rframe.seq.ord.to_s(16)})"
        rframe = nil #invalidate mismatch sequence frame for the last retry
      end
    else
      errors.add :base, "No data received from device"
      break
    end
  end
  return rframe
end

#fsend(cmd, data = "") ⇒ Object

common



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/models/extface/driver/datecs/fp550.rb', line 171

def fsend(cmd, data = "") #return data or nil
  packet_data = build_packet(cmd, data) #store packet to be able to re-transmit it with the same sequence number
  result = false
  invalid_frames = 0 #counter for bad responses
  nak_messages = 0 #counter for rejected packets (should re-transmit the packet)
  push packet_data #send packet
  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 invalid_frames > 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

#handle(buffer) ⇒ Object



32
33
34
35
36
37
38
# File 'app/models/extface/driver/datecs/fp550.rb', line 32

def handle(buffer)
  #if i = buffer.index(/[\x03\x16\x15]/)   # find position of frame possible delimiter
  if i = buffer.index("\x03") || buffer.index("\x16") || buffer.index("\x15")
    rpush buffer[0..i]                    # this will make data available for #pull(timeout) method
    return i+1                            # return number of bytes processed
  end
end

#human_status_errors(status) ⇒ Object

6 bytes status



249
250
251
252
253
254
255
256
257
258
# File 'app/models/extface/driver/datecs/fp550.rb', line 249

def human_status_errors(status) #6 bytes status
  status_0 = status[0].ord
  errors.add :base, "Fiscal Device General Error" unless (status_0 & 0x20).zero?
  errors.add :base, "Invalid Command" unless (status_0 & 0x02).zero?
  errors.add :base, "Date & Time Not Set" unless (status_0 & 0x04).zero?
  errors.add :base, "Syntax Error" unless (status_0 & 0x01).zero?
  status_1 = status[1].ord
  errors.add :base, "Unpermitted Command In This Mode" unless (status_1 & 0x02).zero?
  errors.add :base, "Field Overflow" unless (status_1 & 0x01).zero?
end

#non_fiscal_testObject

tests



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'app/models/extface/driver/datecs/fp550.rb', line 41

def non_fiscal_test
  device.session("Non Fiscal Text") do |s|
    s.notify "Printing Non Fiscal Text"
    s.open_non_fiscal_doc
    s.print "********************************"
    s.print "Extface Print Test".center(32)
    s.print "********************************"
    s.fsend Printer::PAPER_MOVE, "1"
    s.print "Driver: " + "#{self.class::NAME}".truncate(24)
    s.close_non_fiscal_doc
    s.notify "Printing finished"
  end
end

#open_fiscal_doc(operator = "1", password = "000000") ⇒ Object

fiscal



109
110
111
112
# File 'app/models/extface/driver/datecs/fp550.rb', line 109

def open_fiscal_doc(operator = "1", password = "000000")
  fsend Sales::START_FISCAL_DOC, "#{operator.presence || "1"},#{password.presence || "000000"},00001"
  @fiscal_session = true
end

#open_non_fiscal_docObject

print



87
88
89
90
# File 'app/models/extface/driver/datecs/fp550.rb', line 87

def open_non_fiscal_doc
  fsend Sales::START_NON_FISCAL_DOC
  @print_session = true
end

#paper_cutObject



243
244
245
246
247
# File 'app/models/extface/driver/datecs/fp550.rb', line 243

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

#period_report_session(from, to, detailed = true) ⇒ Object



78
79
80
81
82
83
84
# File 'app/models/extface/driver/datecs/fp550.rb', line 78

def period_report_session(from, to, detailed = true)
  device.session("Period Report #{ '(detailed)' if detailed }") do |s|
    s.notify "Period Report Start #{ '(detailed)' if detailed }"
    s.fsend detailed ? Reports::REPORT_FP_BY_DATE : Reports::COMPACT_REPORT_FP_BY_DATE, "#{from.strftime('%d%m%y')},#{to.strftime('%d%m%y')}"
    s.notify "Period Report End"
  end
end

up to 38 sybols, TODO check



92
93
94
95
# File 'app/models/extface/driver/datecs/fp550.rb', line 92

def print(text) #up to 38 sybols, TODO check
  raise "Not in print session" unless @print_session
  fsend Sales::PRINT_NON_FISCAL_TEXT, text
end

#sale_and_pay_items_session(items = [], operator = "1", password = "1") ⇒ Object

basket



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'app/models/extface/driver/datecs/fp550.rb', line 145

def sale_and_pay_items_session(items = [], operator = "1", password = "1")
  device.session("Fiscal Doc") do |s|
    s.notify "Fiscal Doc Start"
    s.open_fiscal_doc
    s.notify "Register Sale"
    items.each do |item|
      s.add_sale(item)
    end
    s.notify "Register Payment"
    s.total_payment
    s.notify "Close Fiscal Receipt"
    s.close_fiscal_doc
    s.notify "Fiscal Doc End"
  end
end

#total_paymentObject



139
140
141
142
# File 'app/models/extface/driver/datecs/fp550.rb', line 139

def total_payment
  raise "Not in fiscal session" unless @fiscal_session
  fsend(Sales::TOTAL, "\t")
end

#x_report_sessionObject



70
71
72
73
74
75
76
# File 'app/models/extface/driver/datecs/fp550.rb', line 70

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

#z_report_sessionObject

reports



62
63
64
65
66
67
68
# File 'app/models/extface/driver/datecs/fp550.rb', line 62

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