extface 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d419941d1c2d3dd9cb911afa7e835e059b230dd
4
- data.tar.gz: 6021598855e22bda8cd379b566ed93dc81b48449
3
+ metadata.gz: 900544046448abb9c9c713e46a328fb7024a6d7b
4
+ data.tar.gz: bb25d5f73e3cc268ee61178863c9a40768afa006
5
5
  SHA512:
6
- metadata.gz: c28049ac7e43208bb04894aade5f02bbfff1b3049cbcc3812ea47d8248bfbcc60d269c60f7a541939dd00ea372abf98d61002096afd9f4ad64e75d103117c488
7
- data.tar.gz: 25698e9e60feddf64ae16e36421c119574d439e810cca672c762f6bfd55506988686f83af887cc3c768a67f45a25cd09e4b84dfe4f6815f33952cf86b75e4f4f
6
+ metadata.gz: f8e3ab792ef789a4a5ae4999ce42837b53b1c898a8f588306f660bf8f59a9707427177b962932800ead930d957f30df9ad0ed1c628cddc40ce16b82cab1d95b1
7
+ data.tar.gz: 77251dc8768f3ad7645a46d740a8d0b89cbc5082e97129116a59fa47f62de4822f7a51228fd6612ccd838577ab339dfe10c655f0a445bb76b1700d393d7f4857
@@ -54,6 +54,20 @@ module Extface
54
54
  @job = @device.driver.print_test_page if params[:test_page]
55
55
  render action: :show
56
56
  end
57
+
58
+ def fiscal
59
+ set_device
60
+ if @device.fiscal?
61
+ @job = case
62
+ when params[:non_fiscal_test].present? then @device.driver.non_fiscal_test
63
+ when params[:fiscal_test].present? then @device.driver.fiscal_test
64
+ when params[:x_report].present? then @device.driver.x_report_session
65
+ when params[:z_report].present? then @device.driver.z_report_session
66
+ when params[:cancel_fiscal_doc].present? then @device.driver.cancel_doc_session
67
+ end
68
+ end
69
+ render action: :show
70
+ end
57
71
 
58
72
  private
59
73
  # Use callbacks to share common setup or constraints between actions.
@@ -33,7 +33,7 @@ module Extface
33
33
  r.append device.uuid, request.body.read
34
34
  @full_buffer = r.get device.uuid
35
35
  end
36
- if bytes_porcessed = device.driver.handle(@full_buffer)
36
+ if bytes_porcessed = device.driver.handle(@full_buffer.b)
37
37
  Extface.redis_block do |r|
38
38
  r.set device.uuid, r.get(device.uuid)[bytes_porcessed]
39
39
  end
@@ -1,5 +1,14 @@
1
1
  module Extface
2
2
  module Driver::Daisy::CommandsFx1200
3
+
4
+ STX = 0x01
5
+ PA1 = 0x05
6
+ PA2 = 0x04
7
+ ETX = 0x03
8
+
9
+ NAK = 0x15
10
+ SYN = 0x16
11
+
3
12
  module Init
4
13
  DATE_TIME = 0x3d
5
14
  PRINT_OPTIONS = 0x2b
@@ -81,8 +90,8 @@ module Extface
81
90
  end
82
91
 
83
92
  module Printer
84
- CUT = 0x2c
85
- MOVE = 0x2d
93
+ MOVE = 0x2c
94
+ CUT = 0x2d
86
95
  end
87
96
 
88
97
  module Other
@@ -21,32 +21,278 @@ module Extface
21
21
  RAW = true #responds to #push(data) and #pull
22
22
  PRINT = false #POS, slip printers
23
23
  FISCAL = true #cash registers, fiscal printers
24
- REPORT = false #only transmit data that must be parsed by handler, CDR, report devices
24
+ REPORT = false #only transmit data that must be parsed by handler, CDR, report devices
25
+
26
+ RESPONSE_TIMEOUT = 6 #seconds
27
+ INVALID_FRAME_RETRIES = 6 #seconds
28
+
29
+ TAX_GROUPS_MAP = {
30
+ 1 => "\xc0",
31
+ 2 => "\xc1",
32
+ 3 => "\xc2",
33
+ 4 => "\xc3",
34
+ 5 => "\xc4",
35
+ 6 => "\xc5",
36
+ 7 => "\xc6",
37
+ 8 => "\xc7"
38
+ }
25
39
 
26
40
  has_serial_config
41
+
42
+ include Extface::Driver::Daisy::CommandsFx1200
27
43
 
28
- def handle(buffer)
29
- rpush buffer
30
- return buffer.length # return number of bytes processed
44
+ def handle(buffer) #buffer is filled with multiple pushes, wait for full frame (ACKs)STX..PA2..PA1..ETX
45
+ if buffer[/^\x16+$/] # skip if only ACKs
46
+ return buffer.length
47
+ else
48
+ if frame_len = buffer.index("\x03") || buffer.index("\x15")
49
+ rpush buffer[0..frame_len]
50
+ return frame_len+1 # return number of bytes processed
51
+ else
52
+ #TODO check buffer.length
53
+ return 0 #no bytes processed
54
+ end
55
+ end
56
+ return buffer.length
57
+ end
58
+
59
+ def autocut(partial = true) # return "P" - success, "F" - failed
60
+ resp = fsend(Printer::CUT)
61
+ resp == "P"
62
+ end
63
+
64
+ def non_fiscal_test
65
+ device.session("Non Fiscal Text") do |s|
66
+ s.notify "Printing Non Fiscal Text"
67
+ s.fsend Sales::START_NON_FISCAL_DOC
68
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "********************************"
69
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "Extface Print Test".center(32)
70
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "********************************"
71
+ s.fsend Printer::MOVE, "1"
72
+ s.fsend Sales::PRINT_NON_FISCAL_TEXT, "Driver: " + "#{self.class::NAME}".truncate(24)
73
+ s.fsend(Sales::END_NON_FISCAL_DOC)
74
+ s.notify "Printing finished"
75
+ end
76
+ end
77
+
78
+ def fiscal_test
79
+ sale_and_pay_items_session([
80
+ { price: 0.01, text1: "Extface Test" }
81
+ ])
82
+ end
83
+
84
+ def build_sale_data(price, text1 = nil, text2 = nil, tax_group = 2, qty = nil, percent = nil, neto = nil)
85
+ "".tap() do |data|
86
+ data << text1 unless text1.blank?
87
+ data << "\x0a#{text2}" unless text2.blank?
88
+ data << "\t"
89
+ data << TAX_GROUPS_MAP[tax_group || 2]
90
+ data << price.to_s
91
+ data << "*#{qty.to_s}" unless qty.blank?
92
+ data << ",#{percent}" unless percent.blank?
93
+ data << "$#{neto}" unless neto.blank?
94
+ end
95
+ end
96
+
97
+ def sale_and_pay_items_session(items = [], operator = "1", password = "1")
98
+ device.session("Fiscal Doc") do |s|
99
+ s.notify "Fiscal Doc Start"
100
+ s.fsend Sales::START_FISCAL_DOC, "#{operator},#{password},00001"
101
+ items.each do |item|
102
+ s.fsend Sales::SALE_AND_SHOW, build_sale_data(item[:price], item[:text1], item[:text2], item[:tax_group], item[:qty], item[:percent], item[:neto])
103
+ end
104
+ s.fsend(Sales::TOTAL, "\t")
105
+ s.fsend(Sales::END_FISCAL_DOC)
106
+ s.notify "Fiscal Doc End"
107
+ end
108
+ end
109
+
110
+ def z_report_session
111
+ device.session("Z Report") do |s|
112
+ s.notify "Z Report Start"
113
+ s.fsend Closure::DAY_FIN_REPORT, "0"
114
+ s.notify "Z Report End"
115
+ end
116
+ end
117
+
118
+ def x_report_session
119
+ device.session("X Report") do |s|
120
+ s.notify "X Report Start"
121
+ s.fsend Closure::DAY_FIN_REPORT, "2"
122
+ s.notify "X Report End"
123
+ end
124
+ end
125
+
126
+ def cancel_doc_session
127
+ device.session("Doc cancel") do |s|
128
+ s.notify "Doc Cancel Start"
129
+ s.fsend Sales::CANCEL_DOC
130
+ s.notify "Doc Cancel End"
131
+ end
31
132
  end
32
133
 
33
- def autocut(partial = true)
34
- # <ESC> “d” “0” - Full-cut command
35
- # <ESC> “d” “1” - Partial-cut command
36
- #push build_packet(CMD)
37
- #push partial ? "\x1B\x64\x31" : "\x1B\x64\x30"
134
+ def check_status
135
+ flush
136
+ fsend(Info::STATUS) # return 6 byte status
137
+ errors.empty?
38
138
  end
39
139
 
40
- private
41
- def build_packet(cmd, data)
140
+ def build_packet(cmd, data = "")
141
+ String.new.tap() do |packet|
142
+ packet << STX
143
+ packet << 0x20 + 4 + data.length
144
+ packet << sequence_number
145
+ packet << cmd
146
+ packet << data
147
+ packet << PA1
148
+ packet << bcc(packet[1..-1])
149
+ packet << ETX
150
+ end
151
+ end
152
+
153
+ def fsend!(cmd, data = "") # return data or raise
154
+ push build_packet(cmd, data) # return 6 byte status
155
+ if resp = frecv(RESPONSE_TIMEOUT)
156
+ return resp.data if resp.valid?
157
+ else
158
+ raise errors.full_messages.join(', ')
159
+ end
160
+ end
161
+
162
+ def fsend(cmd, data = "") #return data or nil
163
+ packet_data = build_packet(cmd, data)
164
+ result = false
165
+ INVALID_FRAME_RETRIES.times do |retries|
166
+ errors.clear
167
+ push packet_data
168
+ if resp = frecv(RESPONSE_TIMEOUT)
169
+ if resp.valid?
170
+ result = resp.data
171
+ break
172
+ end
173
+ end
174
+ errors.add :base, "#{INVALID_FRAME_RETRIES} Broken Packets Received. Abort!"
175
+ end
176
+ return result
177
+ end
178
+
179
+ def frecv(timeout) # return RespFrame or nil
180
+ if frame_bytes = pull(timeout)
181
+ return RespFrame.new(frame_bytes.b)
182
+ else
183
+ errors.add :base, "No data received from device"
184
+ return nil
185
+ end
186
+ end
187
+
188
+ #private
189
+ def bcc(buffer)
190
+ sum = 0
191
+ buffer.each_byte do |byte|
192
+ sum += byte
193
+ end
194
+ "".tap() do |bcc|
195
+ 4.times do |halfbyte|
196
+ bcc.insert 0, (0x30 + ((sum >> (halfbyte*4)) & 0x0f)).chr
197
+ end
198
+ end
199
+ end
200
+
201
+ def sequence_number(increment = true)
202
+ @seq ||= 0x1f
203
+ @seq += 1 if increment
204
+ @seq = 0x1f if @seq == 0x7f
205
+ @seq
206
+ end
207
+
208
+ def human_status_errors(status) #6 bytes status
209
+ errors.add :base, "Fiscal Device General Error" unless (status[0].ord & 0x20).zero?
210
+ errors.add :base, "Invalid Command" unless (status[0].ord & 0x02).zero?
211
+ errors.add :base, "Date & Time Not Set" unless (status[0].ord & 0x04).zero?
212
+ errors.add :base, "Syntax Error" unless (status[0].ord & 0x02).zero?
213
+
214
+ errors.add :base, "Wrong Password" unless (status[1].ord & 0x40).zero?
215
+ errors.add :base, "Cutter Error" unless (status[1].ord & 0x20).zero?
216
+ errors.add :base, "Unpermitted Command In This Mode" unless (status[1].ord & 0x02).zero?
217
+ errors.add :base, "Field Overflow" unless (status[1].ord & 0x01).zero?
218
+
219
+ #errors.add :base, "Print Doc Allowed" unless (status[2].ord & 0x40).zero?
220
+ #errors.add :base, "Non Fiscal Doc Open" unless (status[2].ord & 0x20).zero?
221
+ errors.add :base, "Less Paper (Control)" unless (status[2].ord & 0x20).zero?
222
+ #errors.add :base, "Fiscal Doc Open" unless (status[2].ord & 0x08).zero?
223
+ errors.add :base, "No Paper (Control)" unless (status[2].ord & 0x04).zero?
224
+ errors.add :base, "Less Paper" unless (status[2].ord & 0x02).zero?
225
+ errors.add :base, "No Paper" unless (status[2].ord & 0x01).zero?
42
226
 
227
+ case (status[3] & 0x7f)
228
+ when 1 then errors.add :base, "Operation will result in the overflow"
229
+ when 3 then errors.add :base, "No more sales for this doc"
230
+ when 4 then errors.add :base, "No more payments for this doc"
231
+ when 5 then errors.add :base, "Null transaction attempt"
232
+ when 6 then errors.add :base, "Sale not allowed"
233
+ when 7 then errors.add :base, "No rights for this operation"
234
+ when 8 then errors.add :base, "Tax group forbidden"
235
+ when 11 then errors.add :base, "Multiple decimal points"
236
+ when 12 then errors.add :base, "Multiple sign symbols"
237
+ when 13 then errors.add :base, "Sign not at first position"
238
+ when 14 then errors.add :base, "Wrong symbol"
239
+ when 15 then errors.add :base, "Too many symbols after decimal point"
240
+ when 16 then errors.add :base, "Too many symbols"
241
+ when 20 then errors.add :base, "Service not allowed"
242
+ when 21 then errors.add :base, "Wrong Value"
243
+ when 22 then errors.add :base, "Disabled operation"
244
+ when 23 then errors.add :base, "Deep void after discount"
245
+ when 24 then errors.add :base, "Deep void for not existing record"
246
+ when 24 then errors.add :base, "Payment without sale"
247
+ end
248
+
249
+ errors.add :base, "General Fiscal Memory Error" unless (status[4].ord & 0x20).zero?
250
+ errors.add :base, "Fiscal Memory Full" unless (status[4].ord & 0x10).zero?
251
+ errors.add :base, "Less Than 50 Fiscal Records" unless (status[4].ord & 0x08).zero?
252
+ errors.add :base, "Invalid Fiscal Records" unless (status[4].ord & 0x04).zero?
253
+ errors.add :base, "Fiscal Write Error" unless (status[4].ord & 0x01).zero?
254
+
255
+ #errors.add :base, "Fiscal Memory Ready" unless (status[5].ord & 0x40).zero?
256
+ #errors.add :base, "Fiscal ID Set" unless (status[5].ord & 0x20).zero?
257
+ #errors.add :base, "Fiscal Tax set" unless (status[5].ord & 0x10).zero?
258
+ #errors.add :base, "Real Sales Mode" unless (status[5].ord & 0x80).zero?
259
+ errors.add :base, "Fiscal Memory Full" unless (status[5].ord & 0x01).zero?
43
260
  end
44
261
 
45
- def bcc(data)
46
- bcc = 0
47
- data.each_byte do |byte|
48
- bcc += byte
262
+ class RespFrame
263
+ include ActiveModel::Validations
264
+ attr_reader :frame, :len, :seq, :cmd, :data, :status, :bcc
265
+
266
+ validates_presence_of :frame
267
+ #validate :bcc_validation
268
+ #validate :len_validation
269
+
270
+ def initialize(buffer)
271
+ # test Extface::Driver::DaisyFx1200::RespFrame.new("\x16\x16\x01\x2c\x20\x2dP\x04SSSSSS\x05\BBBB\x03")
272
+ # LEN SEQ CMD DATA STATUS BCC
273
+ if match = buffer.match(/\x01(.{1})(.{1})(.{1})(.*)\x04(.{6})\x05(.{4})\x03/n)
274
+ @frame = match.string[match.pre_match.length..-1]
275
+ @len, @seq, @cmd, @data, @status, @bcc = match.captures
276
+ end
49
277
  end
278
+
279
+ private
280
+ def bcc_validation
281
+ sum = 0
282
+ frame[1..-6].each_byte do |byte|
283
+ sum += byte
284
+ end
285
+ calc_bcc = "".tap() do |tbcc|
286
+ 4.times do |halfbyte|
287
+ tbcc.insert 0, (0x30 + ((sum >> (halfbyte*4)) & 0x0f)).chr
288
+ end
289
+ end
290
+ errors.add(:bcc, I18n.t('errors.messages.invalid')) if bcc != calc_bcc
291
+ end
292
+
293
+ def len_validation
294
+ errors.add(:len, I18n.t('errors.messages.invalid')) if len.ord != (frame[1..-6].length + 0x20)
295
+ end
50
296
  end
51
297
  end
52
298
  end
@@ -1,4 +1,76 @@
1
1
  module Extface
2
2
  module Driver::Epson::Fiscal
3
+ STX = 0x02
4
+ ETX = 0x03
5
+ SEP = 0x1c
6
+ DC2 = 0x12
7
+ DC4 = 0x14
8
+ NAK = 0x15
9
+
10
+ module Control
11
+ STATUS_IF = 0x38
12
+ X_Z_REPORTS = 0x39
13
+ FISC_MEM_REPORT_DATE = 0x3a
14
+ FISC_MEM_REPORT = 0x3b
15
+ end
16
+ module Vouchers
17
+ OPEN_FISCAL_VOUCHER = 0x40
18
+ PRINT_FISCAL_TEXT = 0x41
19
+ PRINT_FISCAL_ARTICLE = 0x42
20
+ SUBTOTAL_FISCAL = 0x43
21
+ FISCAL_VOUCHER = 0x44 # pay / cancel / discount in
22
+ CLOSE_FISCAL_VOUCHER = 0x45
23
+ end
24
+ module NonFiscal
25
+ OPEN_NON_FISCAL_DOC = 0x48
26
+ PRINT_NON_FISCAL_TEXT = 0x49
27
+ CLOSE_NON_FISCAL_DOC = 0x4a
28
+ end
29
+ module Printer
30
+ CUT_PAPER = 0x4b
31
+ ADVANCE_PAPER = 0x50
32
+ ACTIVATE_SLIP = 0xa0 #TMU950, TMU675
33
+ DISABLE_SLIP = 0xa1
34
+ FORMAT_CHECKS = 0xaa
35
+ FORMAT_ENDOSEMENT = 0xab
36
+ end
37
+ module General
38
+ SET_DATE_HOUR = 0x58
39
+ GET_DATE_HOUR = 0x59
40
+ HEADED = 0x5d
41
+ FOOT_OF_PAGE = 0x5e
42
+ OPEN_DRAWER_1 = 0x7b
43
+ OPEN_DRAWER_2 = 0x7c
44
+ end
45
+
46
+ def build_packet(cmd, fields = [])
47
+ String.new.tap() do |frame|
48
+ frame << STX
49
+ frame << sequence_number
50
+ frame << cmd
51
+ fields.each do |field|
52
+ frame << SEP
53
+ frame << field
54
+ end
55
+ frame << ETX
56
+ frame << bcc(frame)
57
+ end
58
+ end
59
+
60
+ private
61
+ def bcc(buffer)
62
+ sum = 0
63
+ buffer.each_byte do |b|
64
+ sum += b
65
+ end
66
+ sum.to_s(16).rjust(4, '0')
67
+ end
68
+
69
+ def sequence_number(increment = true)
70
+ @seq ||= 0x20
71
+ @seq += 1 if increment
72
+ @seq = 0x20 if @seq == 0x7f
73
+ @seq
74
+ end
3
75
  end
4
76
  end
@@ -69,6 +69,13 @@
69
69
  <% if @device.print? %>
70
70
  <%= button_to 'Print Test Page', test_page_device_path(@device), remote: true, name: :test_page, value: true, class: 'btn btn-warning' %>
71
71
  <% end %>
72
+ <% if @device.fiscal? %>
73
+ <%= button_to 'Non Fiscal Test', fiscal_device_path(@device), remote: true, name: :non_fiscal_test, value: true, class: 'btn btn-warning' %>
74
+ <%= button_to 'Fiscal Test', fiscal_device_path(@device), remote: true, name: :fiscal_test, value: true, class: 'btn btn-warning' %>
75
+ <%= button_to 'X Report', fiscal_device_path(@device), remote: true, name: :x_report, value: true, class: 'btn btn-warning' %>
76
+ <%= button_to 'Z Report', fiscal_device_path(@device), remote: true, name: :z_report, value: true, class: 'btn btn-warning' %>
77
+ <%= button_to 'Cancel Fiscal Doc', fiscal_device_path(@device), remote: true, name: :cancel_fiscal_doc, value: true, class: 'btn btn-warning' %>
78
+ <% end %>
72
79
 
73
80
  <br />Simulate push data from device
74
81
 
data/config/routes.rb CHANGED
@@ -4,6 +4,7 @@ Extface::Engine.routes.draw do
4
4
  resources :devices do
5
5
  resources :jobs, only: [:show]
6
6
  post :test_page, on: :member
7
+ post :fiscal, on: :member
7
8
  end
8
9
 
9
10
  get ':device_uuid' => 'handler#pull', as: :pull
@@ -1,3 +1,3 @@
1
1
  module Extface
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extface
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Vangelov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-13 00:00:00.000000000 Z
11
+ date: 2014-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails