extface 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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