The fax is one of those things that many want to see a speedy death. Nevertheless, in the regions this method of transmitting information is still used very often. So in our organization it became necessary to simplify the process as much as possible. After studying the articles that already exist here, I came to the conclusion that the solutions presented did not quite fit in my situation. In particular, I wanted a slightly more intelligent system than just based on call files. Such that she could call back several times in case of unsuccessful sending. In this case, the user must see the current delivery status. In conjunction with the fact that I have long wanted to look at web development in general and node.js in particular, it was decided to write my outgoing fax server
bike . What came out of this can be seen under the cut.
First of all, it was necessary to configure Asterisk so that it creates events that reflect the progress of the fax in real time. Below is a fragment of the Asterisk recruitment plan that will allow us to do this, as well as handle the following situations:
- The subscriber could not be reached (for example, the line was busy or the handset was not picked up before the timeout expired).
- The subscriber picked up the phone and put it on before the fax transmission started (i.e. at the moment of greeting replay).
- Fax transmission started successfully, but not completed.
In addition, the dial plan allows you to limit the number of simultaneous outgoing fax calls, in order not to accidentally take up all free SIP channels.
[OutgoingFaxInit] ; exten => _X.,1,NoOp() same => n,Set(GROUP()=faxout) ; - Asterisk, ; failed , ; . same => n,Set(DB(fax_group_count/${UUID})=${GROUP_COUNT(faxout)}) same => n,GotoIf($[${DB(fax_group_count/${UUID})}<=${MAX_PARALLELISM}]?call) same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL SUSPENDED) same => n,HangUp() same => n(call),Dial(Local/${EXTEN}@OutgoingCalls) same => n,HangUp() ; exten => router,1,NoOp() same => n,Set(__UUID=${UUID}) same => n,Set(__DATA=${DATA}) same => n,Dial(Local/fax@OutgoingFax) same => n,HangUp() exten => failed,1,NoOp() ; , - UserEvent ; same => n,GotoIf($[${DB_DELETE(fax_group_count/${UUID})}<=${MAX_PARALLELISM}]?:end) same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL PICKUP FAILED) same => n(end),HangUp() [OutgoingFax] exten => fax,1,NoOp() same => n,UserEvent(Fax,uuid: ${UUID},Status: CALL PICKUP SUCCESS); ; . . same => n,Set(DB(fax_sendstatus/${UUID})=0) same => n,Playback(autofax) same => n,Set(FAXOPT(headerinfo)=Company) same => n,Set(FAXOPT(localstationid)=XXX-XX-XX) ; same => n,Set(DB(fax_sendstatus/${UUID})=1) same => n,SendFax(${DATA}) same => n,HangUp() exten => h,1,NoOp() ; , ; same => n,GotoIf($[${DB_DELETE(fax_sendstatus/${UUID})}]?sendstatus) same => n,UserEvent(Fax,uuid: ${UUID},Status: FAX SEND FAILED) same => n,Goto(end) ; , ${FAXOPT} same => n(sendstatus),UserEvent(Fax,uuid: ${UUID},Status: FAX SEND ${FAXOPT(status)}) same => n(end),NoOp()
The main part of our fax server, as noted above, will work on node.js. With Asterisk we will interact via AMI. For full-fledged work, the client will have enough rights to create calls and read UserEvents. Thus, manager.conf will look like this:
[general] enabled=yes [FAX] secret=password read=user write=originate
To work with AMI, the
nami module was selected. In contrast, the analogue, he bribes a fairly large functionality out of the box. There are already ready methods for working with most events and generating actions. It is worth noting that the author of this module has implementations of AMI interfaces for other languages ​​besides JS.
')
The general mechanism of the fax server is the following:
- The user enters a special web page and in the window for creating a new fax indicates the file to send (PDF) and the recipient's number.
- The downloaded file is placed on the server and converted to a format supported by Asterisk .tiff. The fax is assigned a UUID.
- All information about the fax (time of sending, recipient's number, UUID, number of retry attempts) is stored in the database.
- When a new fax appears in the queue, it is sent and moved to the queue of faxes being processed.
- If a send error event is received via AMI, the number of sending attempts for this fax will be increased, and the fax itself will be moved to the delayed queue.
- When sending attempts expire, the fax is marked as undeliverable.
Redis is used to implement the queue and the database itself. The storage structure is as follows:
- Key value for each fax feature. The keys are fax: uuid: field. Initially, it was supposed to store all the data by one key as JSON, but then I decided that each time I parse and re-serialize JSON to change any information would be rather stupid.
- The fax queue itself is stored as LIST with the fax: send key. Subscribing to a new fax in the queue is implemented via the BLPOP command.
- All processed faxes are stored in the Sorted Set with the fax: processing key. The weight is the time of entry into the queue.
- To implement the task of chimes at a specified interval, another Sorted Set fax: delayed is used, also with a temporary weight
- Successful and undelivered faxes are saved in fax: failed and fax: success.
To create a nice looking web form, twitter bootstrap was chosen. Displaying information to the user is done via
jQuery datatables . There was a problem waiting for me with the fact that jQuery datatables is not adapted to the current version of bootstrap 3. Fortunately, on github there was a
repository of the fixed version.
The end result is the following:


All basic settings are located in config.json:
{ "logLevel": "info", "port": 80, // "FAX": { "uploadDir": "/tmp/faxout", // "storageDir": "/tmp/faxout", // TIFF "gsCommand": "gs", // Ghostscript ( ) "maxParallelism": 3, // "maxRetry": 5, // "retryInterval": 420, // "delayedProcessingInterval": 5 // }, "AMI": { // AMI "host": "192.168.1.1", "port": 5038, "username": "FAX", "secret": "password" }
Get the source code on
github . To work, you need to add the fragment described above to the Asterisk set plan as well as have redis and node.js on the server. I hope my “Hello world” (aka fax server) on node.js will be useful to you.