forked from metalab-kassomat/kassomat-payout
-
Notifications
You must be signed in to change notification settings - Fork 0
/
payoutd.c
2520 lines (2160 loc) · 75.8 KB
/
payoutd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** \file payoutd.c
* \brief Main source file for the payoutd daemon.
*
* In a nutshell:
* - we are single threaded
* - libevent is used to trigger 2 periodic events ("poll event" and "check quit") which poll the hardware and check if we should quit
* - main() function supports arguments -h (redis hostname), -p (redis port), -d (serial device name) and -?
* - libevent calls cbOnPollEvent() for the "poll" event
* - libevent calls cbOnCheckQuitEvent() for the "check quit" event
* - redis is used in conjunction with libevent
* - if a message is detected in 'validator-request' or 'hopper-request' the cbOnRequestMessage() is called
* - the cbOnRequestMessage() checks if the command is known and if its known dispatches the call to a handle<Cmd> function
* - a command handler interprets the provided JSON message, issues commands to the money hardware and publishes a JSON response
* - the naming convention used most of the time is like: the JSON command is 'configure-bezel' so the handler function is called handleConfigureBezel()
* - handleConfigureBezel() itself calls mc_ssp_configure_bezel() which sends the SSP command to the hardware
* - each device has its own poll event handling function (responsible for publishing the events to the devices event topic)
* - those poll handler functions are hopperEventHandler() and validatorEventHandler()
* - on startup/exiting of the daemon started/exiting messages are published to the 'payout-event' topic
*/
#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
// lowlevel library provided by the cash hardware vendor
// innovative technologies (http://innovative-technology.com).
// ssp manual available at http://innovative-technology.com/images/pdocuments/manuals/SSP_Manual.pdf
#include "libitlssp/ssp_commands.h"
// json library
#include <jansson.h>
// c client for redis
#include <hiredis/hiredis.h>
#include <hiredis/async.h>
// adding libevent adapter for hiredis async
#include <hiredis/adapters/libevent.h>
#include <syslog.h>
// libuuid is used to generate msgIds for the responses
#include <uuid/uuid.h>
// https://sites.google.com/site/rickcreamer/Home/cc/c-implementation-of-stringbuffer-functionality
#include "StringBuffer.h"
#include "StringBuffer.c"
/** \brief redis context used for publishing messages */
redisAsyncContext *redisPublishCtx = NULL;
/** \brief redis context used for subscribing to topics */
redisAsyncContext *redisSubscribeCtx = NULL;
struct m_metacash;
/**
* \brief Structure which describes an actual physical ITL device
*/
struct m_device {
/** \brief Hardware Id (type of the device) */
int id;
/** \brief Human readable name of the device */
char *name;
/** \brief Indicates if the device is available */
int sspDeviceAvailable;
/** \brief Preshared secret key */
unsigned long long key;
/** \brief State of the channel inhibits */
unsigned char channelInhibits;
/** \brief SSP_COMMAND structure to use for communicating with this device */
SSP_COMMAND sspC;
/** \brief SSP6_REQUEST_DATA structure to use initializing this device */
SSP6_SETUP_REQUEST_DATA sspSetupReq;
/** \brief Callback function which is used to inspect and publish events reported by this device */
void (*eventHandlerFn) (struct m_device *device, struct m_metacash *metacash, SSP_POLL_DATA6 *poll);
};
/**
* \brief Structure which contains the generic setup data and
* the device structures for our two ITL devices.
*/
struct m_metacash {
/** \brief If !=0 then we should quit, checked via libevent callback */
int quit;
/** \brief If !=0 then we have actual hardware available */
int sspAvailable;
/** \brief The name of the device we should use to connect to the ITL hardware */
char *serialDevice;
/** \brief Should the hardware accept coins at all (default off for now) */
int acceptCoins;
/** \brief Should the syslog messages also be written to stderr (default no, enable with -e) */
int logSyslogStderr;
/** \brief The port of the redis server to which we connect */
int redisPort;
/** \brief The hostname of the redis server to which we connect */
char *redisHost;
/** \brief base struct for libevent */
struct event_base *eventBase;
/** \brief event struct for the periodic polling of the devices */
struct event evPoll;
/** \brief event struct for the periodic check for quitting */
struct event evCheckQuit;
/** \brief struct for the smart-hopper device */
struct m_device hopper;
/** \brief struct for the smart-payout device */
struct m_device validator;
};
/**
* \brief Structure which describes an actual command which we
* received in one of our request topics.
*/
struct m_command {
/** \brief The complete received message parsed as JSON */
json_t *jsonMessage;
/** \brief The command from the message */
char *command;
/** \brief The correlId to use in the response (this is the msgId from the message which contained the command) */
char *correlId;
/** \brief The msgId for the response */
char *msgId;
/** \brief The topic to which the response should be published */
char *responseTopic;
/** \brief The device to which the command should be issued */
struct m_device *device;
};
// mcSsp* : ssp helper functions
int mcSspOpenSerialDevice(struct m_metacash *metacash);
void mcSspCloseSerialDevice(struct m_metacash *metacash);
void mcSspSetupCommand(SSP_COMMAND *sspC, int deviceId);
void mcSspInitializeDevice(SSP_COMMAND *sspC, unsigned long long key, struct m_device *device);
void mcSspPollDevice(struct m_device *device, struct m_metacash *metacash);
// mc_ssp_* : ssp magic values and functions (each of these relate directly to a command specified in the ssp protocol)
/** \brief Magic Constant for the "GET FIRMWARE VERSION" command ID as specified in SSP */
#define SSP_CMD_GET_FIRMWARE_VERSION 0x20
/** \brief Magic Constant for the "GET DATASET VERSION" command ID as specified in SSP */
#define SSP_CMD_GET_DATASET_VERSION 0x21
/** \brief Magic Constant for the "GET ALL LEVELS" command ID as specified in SSP */
#define SSP_CMD_GET_ALL_LEVELS 0x22
/** \brief Magic Constant for the "SET DENOMINATION LEVEL" command ID as specified in SSP */
#define SSP_CMD_SET_DENOMINATION_LEVEL 0x34
/** \brief Magic Constant for the "SET CASHBOX PAYOUT LIMIT" command ID as specified in SSP */
#define SSP_CMD_SET_CASHBOX_PAYOUT_LIMIT 0x4E
/** \brief Magic Constant for the "LAST REJECT NOTE" command ID as specified in SSP */
#define SSP_CMD_LAST_REJECT_NOTE 0x17
/** \brief Magic Constant for the "CONFIGURE BEZEL" command ID as specified in SSP */
#define SSP_CMD_CONFIGURE_BEZEL 0x54
/** \brief Magic Constant for the "SMART EMPTY" command ID as specified in SSP */
#define SSP_CMD_SMART_EMPTY 0x52
/** \brief Magic Constant for the "CASHBOX PAYOUT OPERATION DATA" command ID as specified in SSP */
#define SSP_CMD_CASHBOX_PAYOUT_OPERATION_DATA 0x53
/** \brief Magic Constant for the "SET REFILL MODE " command ID as specified in SSP */
#define SSP_CMD_SET_REFILL_MODE 0x30
/** \brief Magic Constant for the "DISPLAY OFF" command ID as specified in SSP */
#define SSP_CMD_DISPLAY_OFF 0x4
/** \brief Magic Constant for the "DISPLAY ON" command ID as specified in SSP */
#define SSP_CMD_DISPLAY_ON 0x3
SSP_RESPONSE_ENUM mc_ssp_empty(SSP_COMMAND *sspC);
SSP_RESPONSE_ENUM mc_ssp_smart_empty(SSP_COMMAND *sspC);
SSP_RESPONSE_ENUM mc_ssp_cashbox_payout_operation_data(SSP_COMMAND *sspC, char **json);
SSP_RESPONSE_ENUM mc_ssp_configure_bezel(SSP_COMMAND *sspC, unsigned char r, unsigned char g,
unsigned char b, unsigned char volatileOption, unsigned char bezelTypeOption);
SSP_RESPONSE_ENUM mc_ssp_display_on(SSP_COMMAND *sspC);
SSP_RESPONSE_ENUM mc_ssp_display_off(SSP_COMMAND *sspC);
SSP_RESPONSE_ENUM mc_ssp_last_reject_note(SSP_COMMAND *sspC, unsigned char *reason);
SSP_RESPONSE_ENUM mc_ssp_set_refill_mode(SSP_COMMAND *sspC);
SSP_RESPONSE_ENUM mc_ssp_get_all_levels(SSP_COMMAND *sspC, char **json);
SSP_RESPONSE_ENUM mc_ssp_set_denomination_level(SSP_COMMAND *sspC, int amount, int level, const char *cc);
SSP_RESPONSE_ENUM mc_set_cashbox_payout_limit(SSP_COMMAND *sspC, unsigned int amount, int level, const char *cc);
SSP_RESPONSE_ENUM mc_ssp_float(SSP_COMMAND *sspC, const int value, const char *cc, const char option);
SSP_RESPONSE_ENUM mc_ssp_channel_security_data(SSP_COMMAND *sspC);
SSP_RESPONSE_ENUM mc_ssp_get_firmware_version(SSP_COMMAND *sspC, char *firmwareVersion);
SSP_RESPONSE_ENUM mc_ssp_get_dataset_version(SSP_COMMAND *sspC, char *datasetVersion);
/** \brief Magic Constant for the "route to cashbox" option as specified in SSP */
const char SSP_OPTION_ROUTE_CASHBOX = 0x01;
/** \brief Magic Constant for the "route to storage" option as specified in SSP */
const char SSP_OPTION_ROUTE_STORAGE = 0x00;
/** \brief Magic Constant for the "volatile" option in configure bezel as specified in SSP */
const unsigned char SSP_OPTION_VOLATILE = 0x00;
/** \brief Magic Constant for the "non volatile" option in configure bezel as specified in SSP */
const unsigned char SSP_OPTION_NON_VOLATILE = 0x01;
/** \brief Magic Constant for the "solid" option in configure bezel as specified in SSP */
const unsigned char SSP_OPTION_SOLID = 0x00;
/** \brief Magic Constant for the "flashing" option in configure bezel as specified in SSP */
const unsigned char SSP_OPTION_FLASHING = 0x01;
/** \brief Magic Constant for the "disabled" option in configure bezel as specified in SSP */
const unsigned char SSP_OPTION_DISABLED = 0x02;
static const unsigned long long DEFAULT_KEY = 0x123456701234567LL;
// metacash
int parseCmdLine(int argc, char *argv[], struct m_metacash *metacash);
void setup(struct m_metacash *metacash);
void hopperEventHandler(struct m_device *device, struct m_metacash *metacash, SSP_POLL_DATA6 *poll);
void validatorEventHandler(struct m_device *device, struct m_metacash *metacash, SSP_POLL_DATA6 *poll);
static const char *CURRENCY = "EUR";
/**
* \brief Set by the signalHandler function and checked in cbCheckQuit.
*/
int receivedSignal = 0;
/**
* \brief Signal handler
*/
void signalHandler(int signal) {
receivedSignal = signal;
}
/**
* \brief Waits for 300ms each time called.
* \details Details only to get graph.
* \callergraph
*/
void hardwareWaitTime() {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 300000000;
nanosleep(&ts, NULL);
}
/**
* \brief Connect to redis and return a new redisAsyncContext.
*/
redisAsyncContext* connectRedis(struct m_metacash *metacash) {
redisAsyncContext *conn = redisAsyncConnect(metacash->redisHost,
metacash->redisPort);
if (conn == NULL || conn->err) {
if (conn) {
syslog(LOG_ERR, "fatal: Connection error: %s\n", conn->errstr);
} else {
syslog(LOG_ERR,
"fatal: Connection error: can't allocate redis context\n");
}
} else {
// reference the metcash struct in data for use in connect/disconnect callback
conn->data = metacash;
}
return conn;
}
/**
* \brief Callback function for libEvent timer triggered "Poll" event.
* \details Details only to get graph.
* \callgraph
*/
void cbOnPollEvent(int fd, short event, void *privdata) {
struct m_metacash *metacash = privdata;
if (metacash->sspAvailable == 0) {
// return immediately if we can't communicate with the hardware
return;
}
// don't poll the hopper for events if it is unavailable
if(metacash->hopper.sspDeviceAvailable) {
mcSspPollDevice(&metacash->hopper, metacash);
}
// don't poll the validator for events if it is unavailable
if(metacash->validator.sspDeviceAvailable) {
mcSspPollDevice(&metacash->validator, metacash);
}
}
/**
* \brief Callback function for libEvent timer triggered "CheckQuit" event.
*/
void cbOnCheckQuitEvent(int fd, short event, void *privdata) {
if (receivedSignal != 0) {
syslog(LOG_NOTICE, "received signal or quit cmd. going to exit event loop.");
struct m_metacash *metacash = privdata;
event_base_loopexit(metacash->eventBase, NULL);
receivedSignal = 0;
}
}
/**
* \brief Callback function triggered by an incoming message in the "metacash" topic.
*/
void cbOnMetacashMessage(redisAsyncContext *c, void *r, void *privdata) {
// empty for now
}
/**
* \brief Test if cmd.command equals command
*/
int isCommand(struct m_command *cmd, const char *command) {
return ! strcmp(cmd->command, command);
}
/**
* \brief Helper function to publish a message to the "payout-event" topic.
*/
int publishPayoutEvent(char *format, ...) {
va_list varags;
va_start(varags, format);
char *reply = NULL;
vasprintf(&reply, format, varags);
va_end(varags);
redisAsyncCommand(redisPublishCtx, NULL, NULL, "PUBLISH %s %s", "payout-event", reply);
free(reply);
return 0;
}
/**
* \brief Helper function to publish a message to the "hopper-event" topic.
*/
int publishHopperEvent(char *format, ...) {
va_list varags;
va_start(varags, format);
char *reply = NULL;
vasprintf(&reply, format, varags);
va_end(varags);
redisAsyncCommand(redisPublishCtx, NULL, NULL, "PUBLISH %s %s", "hopper-event", reply);
free(reply);
return 0;
}
/**
* \brief Helper function to publish a message to the "validator-event" topic.
*/
int publishValidatorEvent(char *format, ...) {
va_list varags;
va_start(varags, format);
char *reply = NULL;
vasprintf(&reply, format, varags);
va_end(varags);
redisAsyncCommand(redisPublishCtx, NULL, NULL, "PUBLISH %s %s", "validator-event", reply);
free(reply);
return 0;
}
/**
* \brief Helper function to publish a message to the given topic.
* \details Details only to get graph.
* \callergraph
*/
int replyWith(char *topic, char *format, ...) {
va_list varags;
va_start(varags, format);
char *reply = NULL;
vasprintf(&reply, format, varags);
va_end(varags);
redisAsyncCommand(redisPublishCtx, NULL, NULL, "PUBLISH %s %s", topic, reply);
free(reply);
return 0;
}
/**
* \brief Helper function to publish a reply to a message which was missing a
* mandatory property (or the property was of the wrong type).
* \details Details only to get graph.
* \callergraph
*/
int replyWithPropertyError(struct m_command *cmd, char *name) {
char *msgId = "unknown";
if(cmd->msgId) {
msgId = cmd->msgId;
}
char *correlId = "unknown";
if(cmd->correlId) {
correlId = cmd->correlId;
}
return replyWith(cmd->responseTopic,
"{\"msgId\":\"%s\",\"correlId\":\"%s\",\"error\":\"Property '%s' missing or of wrong type\"}",
msgId,
correlId,
name);
}
/**
* \brief Helper function to publish a reply to a message which contains a human readable
* version of the SSP response.
* \details Details only to get graph.
* \callergraph
*/
int replyWithSspResponse(struct m_command *cmd, SSP_RESPONSE_ENUM response) {
if(response == SSP_RESPONSE_OK) {
return replyWith(cmd->responseTopic, "{\"msgId\":\"%s\",\"correlId\":\"%s\",\"result\":\"ok\"}",
cmd->msgId,
cmd->correlId);
} else {
char *errorMsg;
switch(response) {
case SSP_RESPONSE_UNKNOWN_COMMAND:
errorMsg = "unknown command";
break;
case SSP_RESPONSE_INCORRECT_PARAMETERS:
errorMsg = "incorrect parameters";
break;
case SSP_RESPONSE_INVALID_PARAMETER:
errorMsg = "invalid parameter";
break;
case SSP_RESPONSE_COMMAND_NOT_PROCESSED:
errorMsg = "command not processed";
break;
case SSP_RESPONSE_SOFTWARE_ERROR:
errorMsg = "software error";
break;
case SSP_RESPONSE_CHECKSUM_ERROR:
errorMsg = "checksum error";
break;
case SSP_RESPONSE_FAILURE:
errorMsg = "failure";
break;
case SSP_RESPONSE_HEADER_FAILURE:
errorMsg = "header failure";
break;
case SSP_RESPONSE_KEY_NOT_SET:
errorMsg = "key not set";
break;
case SSP_RESPONSE_TIMEOUT:
errorMsg = "timeout";
break;
default:
errorMsg = "unknown";
}
return replyWith(cmd->responseTopic, "{\"msgId\":\"%s\",\"correlId\":\"%s\",\"sspError\":\"%s\"}",
cmd->msgId,
cmd->correlId,
errorMsg);
}
}
/**
* \brief Handles the JSON "quit" command.
*/
void handleQuit(struct m_command *cmd) {
receivedSignal = 1;
replyWithSspResponse(cmd, SSP_RESPONSE_OK); // :D
}
/**
* \brief Handles the JSON "empty" command.
*/
void handleEmpty(struct m_command *cmd) {
replyWithSspResponse(cmd, mc_ssp_empty(&cmd->device->sspC));
}
/**
* \brief Handles the JSON "smart-empty" command.
*/
void handleSmartEmpty(struct m_command *cmd) {
replyWithSspResponse(cmd, mc_ssp_smart_empty(&cmd->device->sspC));
}
/**
* \brief Handles the JSON "do-payout" and "test-payout" commands.
*/
void handlePayout(struct m_command *cmd) {
int payoutOption = 0;
if (isCommand(cmd, "do-payout")) {
payoutOption = SSP6_OPTION_BYTE_DO;
} else {
payoutOption = SSP6_OPTION_BYTE_TEST;
}
json_t *jAmount = json_object_get(cmd->jsonMessage, "amount");
if(! json_is_integer(jAmount)) {
replyWithPropertyError(cmd, "amount");
return;
}
int amount = json_integer_value(jAmount);
SSP_RESPONSE_ENUM resp = ssp6_payout(&cmd->device->sspC, amount, CURRENCY,
payoutOption);
if (resp == SSP_RESPONSE_COMMAND_NOT_PROCESSED) {
char *error = NULL;
switch (cmd->device->sspC.ResponseData[1]) {
case 0x01:
error = "not enough value in smart payout";
break;
case 0x02:
error = "can't pay exact amount";
break;
case 0x03:
error = "smart payout busy";
break;
case 0x04:
error = "smart payout disabled";
break;
default:
error = "unknown";
break;
}
replyWith(cmd->responseTopic, "{\"correlId\":\"%s\",\"error\":\"%s\"}", cmd->correlId, error);
} else {
replyWithSspResponse(cmd, resp);
}
}
/**
* \brief Handles the JSON "do-float" and "test-float" commands.
*/
void handleFloat(struct m_command *cmd) {
// basically a copy of do/test-payout ...
int payoutOption = 0;
if (isCommand(cmd, "do-float")) {
payoutOption = SSP6_OPTION_BYTE_DO;
} else {
payoutOption = SSP6_OPTION_BYTE_TEST;
}
json_t *jAmount = json_object_get(cmd->jsonMessage, "amount");
if(! json_is_integer(jAmount)) {
replyWithPropertyError(cmd, "amount");
return;
}
int amount = json_integer_value(jAmount);
SSP_RESPONSE_ENUM resp = mc_ssp_float(&cmd->device->sspC, amount, CURRENCY,
payoutOption);
if (resp == SSP_RESPONSE_COMMAND_NOT_PROCESSED) {
char *error = NULL;
switch (cmd->device->sspC.ResponseData[1]) {
case 0x01:
error = "not enough value in smart payout";
break;
case 0x02:
error = "can't pay exact amount";
break;
case 0x03:
error = "smart payout busy";
break;
case 0x04:
error = "smart payout disabled";
break;
default:
error = "unknown";
break;
}
replyWith(cmd->responseTopic, "{\"correlId\":\"%s\",\"error\":\"%s\"}",
cmd->correlId, error);
} else {
replyWithSspResponse(cmd, resp);
}
}
/**
* \brief Print inhibits debug output.
*/
void dbgDisplayInhibits(unsigned char inhibits) {
syslog(LOG_DEBUG, "dbgDisplayInhibits: inhibits are: 0=%d 1=%d 2=%d 3=%d 4=%d 5=%d 6=%d 7=%d\n",
(inhibits >> 0) & 1,
(inhibits >> 1) & 1,
(inhibits >> 2) & 1,
(inhibits >> 3) & 1,
(inhibits >> 4) & 1,
(inhibits >> 5) & 1,
(inhibits >> 6) & 1,
(inhibits >> 7) & 1);
}
/**
* \brief Handles the JSON "enable-channels" command.
*/
void handleEnableChannels(struct m_command *cmd) {
json_t *jChannels = json_object_get(cmd->jsonMessage, "channels");
if(! json_is_string(jChannels)) {
replyWithPropertyError(cmd, "channels");
return;
}
char *channels = (char *) json_string_value(jChannels);
// this will be updated and written back to the device state
// if the update succeeds
unsigned char currentChannelInhibits = cmd->device->channelInhibits;
unsigned char highChannels = 0xFF; // actually not in use
// 8 channels for now, set the bit to 1 for each requested channel
if(strstr(channels, "1") != NULL) {
currentChannelInhibits |= 1 << 0;
}
if(strstr(channels, "2") != NULL) {
currentChannelInhibits |= 1 << 1;
}
if(strstr(channels, "3") != NULL) {
currentChannelInhibits |= 1 << 2;
}
if(strstr(channels, "4") != NULL) {
currentChannelInhibits |= 1 << 3;
}
if(strstr(channels, "5") != NULL) {
currentChannelInhibits |= 1 << 4;
}
if(strstr(channels, "6") != NULL) {
currentChannelInhibits |= 1 << 5;
}
if(strstr(channels, "7") != NULL) {
currentChannelInhibits |= 1 << 6;
}
if(strstr(channels, "8") != NULL) {
currentChannelInhibits |= 1 << 7;
}
SSP_RESPONSE_ENUM resp = ssp6_set_inhibits(&cmd->device->sspC, currentChannelInhibits, highChannels);
if(resp == SSP_RESPONSE_OK) {
// okay, update the channelInhibits in the device structure with the new state
cmd->device->channelInhibits = currentChannelInhibits;
if(0) {
syslog(LOG_DEBUG, "enable-channels:\n");
dbgDisplayInhibits(currentChannelInhibits);
}
}
replyWithSspResponse(cmd, resp);
}
/**
* \brief Handles the JSON "disable-channels" command.
*/
void handleDisableChannels(struct m_command *cmd) {
json_t *jChannels = json_object_get(cmd->jsonMessage, "channels");
if(! json_is_string(jChannels)) {
replyWithPropertyError(cmd, "channels");
return;
}
char *channels = (char *) json_string_value(jChannels);
// this will be updated and written back to the device state
// if the update succeeds
unsigned char currentChannelInhibits = cmd->device->channelInhibits;
unsigned char highChannels = 0xFF; // actually not in use
// 8 channels for now, set the bit to 0 for each requested channel
if(strstr(channels, "1") != NULL) {
currentChannelInhibits &= ~(1 << 0);
}
if(strstr(channels, "2") != NULL) {
currentChannelInhibits &= ~(1 << 1);
}
if(strstr(channels, "3") != NULL) {
currentChannelInhibits &= ~(1 << 2);
}
if(strstr(channels, "4") != NULL) {
currentChannelInhibits &= ~(1 << 3);
}
if(strstr(channels, "5") != NULL) {
currentChannelInhibits &= ~(1 << 4);
}
if(strstr(channels, "6") != NULL) {
currentChannelInhibits &= ~(1 << 5);
}
if(strstr(channels, "7") != NULL) {
currentChannelInhibits &= ~(1 << 6);
}
if(strstr(channels, "8") != NULL) {
currentChannelInhibits &= ~(1 << 7);
}
SSP_RESPONSE_ENUM resp = ssp6_set_inhibits(&cmd->device->sspC, currentChannelInhibits, highChannels);
if(resp == SSP_RESPONSE_OK) {
// okay, update the channelInhibits in the device structure with the new state
cmd->device->channelInhibits = currentChannelInhibits;
if(0) {
syslog(LOG_DEBUG, "disable-channels:\n");
dbgDisplayInhibits(currentChannelInhibits);
}
}
replyWithSspResponse(cmd, resp);
}
/**
* \brief Handles the JSON "inhibit-channels" command.
*/
void handleInhibitChannels(struct m_command *cmd) {
json_t *jChannels = json_object_get(cmd->jsonMessage, "channels");
if(! json_is_string(jChannels)) {
replyWithPropertyError(cmd, "channels");
return;
}
char *channels = (char *) json_string_value(jChannels);
unsigned char lowChannels = 0xFF;
unsigned char highChannels = 0xFF;
// 8 channels for now
if(strstr(channels, "1") != NULL) {
lowChannels &= ~(1 << 0);
}
if(strstr(channels, "2") != NULL) {
lowChannels &= ~(1 << 1);
}
if(strstr(channels, "3") != NULL) {
lowChannels &= ~(1 << 2);
}
if(strstr(channels, "4") != NULL) {
lowChannels &= ~(1 << 3);
}
if(strstr(channels, "5") != NULL) {
lowChannels &= ~(1 << 4);
}
if(strstr(channels, "6") != NULL) {
lowChannels &= ~(1 << 5);
}
if(strstr(channels, "7") != NULL) {
lowChannels &= ~(1 << 6);
}
if(strstr(channels, "8") != NULL) {
lowChannels &= ~(1 << 7);
}
replyWithSspResponse(cmd, ssp6_set_inhibits(&cmd->device->sspC, lowChannels, highChannels));
}
/**
* \brief Handles the JSON "enable" command.
*/
void handleEnable(struct m_command *cmd) {
replyWithSspResponse(cmd, ssp6_enable(&cmd->device->sspC));
}
/**
* \brief Handles the JSON "disable" command.
*/
void handleDisable(struct m_command *cmd) {
replyWithSspResponse(cmd, ssp6_disable(&cmd->device->sspC));
}
/**
* \brief Handles the JSON "set-denomination-levels" command.
*/
void handleSetDenominationLevels(struct m_command *cmd) {
json_t *jLevel = json_object_get(cmd->jsonMessage, "level");
if(! json_is_integer(jLevel)) {
replyWithPropertyError(cmd, "level");
return;
}
json_t *jAmount = json_object_get(cmd->jsonMessage, "amount");
if(! json_is_integer(jAmount)) {
replyWithPropertyError(cmd, "amount");
return;
}
int level = json_integer_value(jLevel);
int amount = json_integer_value(jAmount);
if(level > 0) {
/* Quote from the spec -.-
*
* A command to increment the level of coins of a denomination stored in the hopper.
* The command is formatted with the command byte first, amount of coins to *add*
* as a 2-byte little endian, the value of coin as 2-byte little endian and
* (if using protocol version 6) the country code of the coin as 3 byte ASCII. The level of coins for a
* denomination can be set to zero by sending a zero level for that value.
*
* In a nutshell: This command behaves only with a level of 0 as expected (setting the absolute value),
* otherwise it works like the not existing "increment denomination level" command.
*/
// ignore the result for now. we could not do much anyway now.
mc_ssp_set_denomination_level(&cmd->device->sspC, amount, 0, CURRENCY);
}
replyWithSspResponse(cmd, mc_ssp_set_denomination_level(&cmd->device->sspC, amount, level, CURRENCY));
}
/**
* \brief Handles the JSON "get-all-levels" command.
*/
void handleGetAllLevels(struct m_command *cmd) {
char *json = NULL;
SSP_RESPONSE_ENUM resp = mc_ssp_get_all_levels(&cmd->device->sspC, &json);
if(resp == SSP_RESPONSE_OK) {
replyWith(cmd->responseTopic, "{\"correlId\":\"%s\",\"levels\":[%s]}", cmd->correlId, json);
} else {
replyWithSspResponse(cmd, resp);
}
free(json);
}
/**
* \brief Handles the JSON "cashbox-payout-operation-data" command.
*/
void handleCashboxPayoutOperationData(struct m_command *cmd) {
char *json = NULL;
SSP_RESPONSE_ENUM resp = mc_ssp_cashbox_payout_operation_data(&cmd->device->sspC, &json);
if(resp == SSP_RESPONSE_OK) {
replyWith(cmd->responseTopic, "{\"correlId\":\"%s\",\"levels\":[%s]}", cmd->correlId, json);
} else {
replyWithSspResponse(cmd, resp);
}
free(json);
}
/**
* \brief Handles the JSON "get-firmware-version" command.
*/
void handleGetFirmwareVersion(struct m_command *cmd) {
char firmwareVersion[100] = { 0 };
SSP_RESPONSE_ENUM resp = mc_ssp_get_firmware_version(&cmd->device->sspC, &firmwareVersion[0]);
if(resp == SSP_RESPONSE_OK) {
replyWith(cmd->responseTopic, "{\"correlId\":\"%s\",\"version\":\"%s\"}", cmd->correlId, firmwareVersion);
} else {
replyWithSspResponse(cmd, resp);
}
}
/**
* \brief Handles the JSON "get-dataset-version" command.
*/
void handleGetDatasetVersion(struct m_command *cmd) {
char datasetVersion[100] = { 0 };
SSP_RESPONSE_ENUM resp = mc_ssp_get_dataset_version(&cmd->device->sspC, &datasetVersion[0]);
if(resp == SSP_RESPONSE_OK) {
replyWith(cmd->responseTopic, "{\"correlId\":\"%s\",\"version\":\"%s\"}",
cmd->correlId, datasetVersion);
} else {
replyWithSspResponse(cmd, resp);
}
}
/**
* \brief Handles the JSON "last-reject-note" command.
*/
void handleLastRejectNote(struct m_command *cmd) {
unsigned char reasonCode;
SSP_RESPONSE_ENUM resp = mc_ssp_last_reject_note(&cmd->device->sspC, &reasonCode);
if (resp == SSP_RESPONSE_OK) {
char *reason = NULL;
switch (reasonCode) {
case 0x00: // Note accepted
reason = "note accepted";
break;
case 0x01: // Note length incorrect
reason = "note length incorrect";
break;
case 0x02: // Average fail
reason = "internal validation failure: average fail";
break;
case 0x03: // Coastline fail
reason = "internal validation failure: coastline fail";
break;
case 0x04: // Graph fail
reason = "internal validation failure: graph fail";
break;
case 0x05: // Buried fail
reason = "internal validation failure: buried fail";
break;
case 0x06: // Channel inhibited
reason = "channel inhibited";
break;
case 0x07: // Second note inserted
reason = "second note inserted";
break;
case 0x08: // Reject by host
reason = "reject by host";
break;
case 0x09: // Note recognised in more than one channel
reason = "note recognised in more than one channel";
break;
case 0x0A: // Reject reason 10
reason = "rear sensor error";
break;
case 0x0B: // Note too long
reason = "note too long";
break;
case 0x0C: // Disabled by host
reason = "disabled by host";
break;
case 0x0D: // Mechanism slow/stalled
reason = "mechanism slow/stalled";
break;
case 0x0E: // Strimming attempt detected
reason = "strimming attempt detected";
break;
case 0x0F: // Fraud channel reject
reason = "fraud channel reject";
break;
case 0x10: // No notes inserted
reason = "no notes inserted";
break;
case 0x11: // Peak detect fail
reason = "peak detect fail";
break;
case 0x12: // Twisted note detected
reason = "twisted note detected";
break;
case 0x13: // Escrow time-out
reason = "escrow time-out";
break;
case 0x14: // Bar code scan fail
reason = "bar code scan fail";
break;
case 0x15: // Rear sensor 2 fail
reason = "rear sensor 2 fail";
break;
case 0x16: // Slot fail 1
reason = "slot fail 1";
break;
case 0x17: // Slot fail 2
reason = "slot fail 2";
break;
case 0x18: // Lens over-sample
reason = "lens over-sample";
break;
case 0x19: // Width detect fail
reason = "width detect fail";
break;
case 0x1A: // Short note detected
reason = "short note detected";
break;
case 0x1B: // Note payout
reason = "note payout";
break;
case 0x1C: // Unable to stack note
reason = "unable to stack note";
break;
default: // not defined in API doc
reason = "undefined in API";
break;
}
replyWith(cmd->responseTopic,
"{\"correlId\":\"%s\",\"reason\":\"%s\",\"code\":%ld}",
cmd->correlId, reason, reasonCode);
} else {
replyWithSspResponse(cmd, resp);
}
}
/**