Class: Driver::Datecs::DpX5New

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

Defined Under Namespace

Classes: Frame

Constant Summary collapse

NAME =
'Datecs DPX5 New'.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
NO_RESP_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



127
128
129
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 127

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

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



131
132
133
134
135
136
137
138
139
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 131

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



122
123
124
125
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 122

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



242
243
244
245
246
247
248
249
250
251
252
253
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 242

def build_packet(cmd, data = "")
  "".b.tap() do |packet|
    packet << STX                     #Preamble. 1 byte long. Value: 01H.
    packet << 0x20 + 4 + data.b.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.b                  #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



163
164
165
166
167
168
169
170
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 163

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



104
105
106
107
108
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 104

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

#close_fiscal_docObject



116
117
118
119
120
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 116

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



99
100
101
102
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 99

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

#fiscal_testObject



57
58
59
60
61
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 57

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

#frecv(timeout) ⇒ Object

return Frame or nil



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 221

def frecv(timeout) # return Frame or nil
  rframe = nil
  BAD_SEQ_MAX_COUNT.times do
    errors.clear
    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



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
209
210
211
212
213
214
215
216
217
218
219
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 173

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)
  no_resp = 0
  flush #prevent double packet response issue like daisy driver
  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
    else
      no_resp += 1
      if no_resp > NO_RESP_MAX_COUNT
        p "No reply in #{NO_RESP_MAX_COUNT * RESPONSE_TIMEOUT} seconds. Abort!"
        errors.add :base, "No reply in #{NO_RESP_MAX_COUNT * RESPONSE_TIMEOUT} seconds. Abort!"
        return result
      end
    end
    errors.add :base, "#{ACKS_MAX_WAIT} ACKs Received. Abort!"
  end
  return result
end

#handle(buffer) ⇒ Object



34
35
36
37
38
39
40
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 34

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



261
262
263
264
265
266
267
268
269
270
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 261

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



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

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 = "1") ⇒ Object

fiscal



111
112
113
114
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 111

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

#open_non_fiscal_docObject

print



89
90
91
92
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 89

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

#paper_cutObject



255
256
257
258
259
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 255

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



80
81
82
83
84
85
86
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 80

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



94
95
96
97
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 94

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



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

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



141
142
143
144
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 141

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

#x_report_sessionObject



72
73
74
75
76
77
78
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 72

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



64
65
66
67
68
69
70
# File 'app/models/extface/driver/datecs/dp_x5_new.rb', line 64

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