Class: Driver::Datecs::Dp25

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

Defined Under Namespace

Classes: Frame

Constant Summary collapse

NAME =
'Datecs DP25'.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



126
127
128
# File 'app/models/extface/driver/datecs/dp25.rb', line 126

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

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



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

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



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

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



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

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



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

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



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

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

#close_fiscal_docObject



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

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



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

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

#fiscal_testObject



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

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

#frecv(timeout) ⇒ Object

return Frame or nil



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

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



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

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



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

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



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

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



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

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



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

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



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

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

#paper_cutObject



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

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



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

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



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

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



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

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



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

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

#x_report_sessionObject



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

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



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

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