win32/nicdrv.c
Go to the documentation of this file.
1 /*
2  * Simple Open EtherCAT Master Library
3  *
4  * File : nicdrv.c
5  * Version : 1.3.0
6  * Date : 24-02-2013
7  * Copyright (C) 2005-2013 Speciaal Machinefabriek Ketels v.o.f.
8  * Copyright (C) 2005-2013 Arthur Ketels
9  * Copyright (C) 2008-2009 TU/e Technische Universiteit Eindhoven
10  *
11  * SOEM is free software; you can redistribute it and/or modify it under
12  * the terms of the GNU General Public License version 2 as published by the Free
13  * Software Foundation.
14  *
15  * SOEM is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18  * for more details.
19  *
20  * As a special exception, if other files instantiate templates or use macros
21  * or inline functions from this file, or you compile this file and link it
22  * with other works to produce a work based on this file, this file does not
23  * by itself cause the resulting work to be covered by the GNU General Public
24  * License. However the source code for this file must still be made available
25  * in accordance with section (3) of the GNU General Public License.
26  *
27  * This exception does not invalidate any other reasons why a work based on
28  * this file might be covered by the GNU General Public License.
29  *
30  * The EtherCAT Technology, the trade name and logo “EtherCAT” are the intellectual
31  * property of, and protected by Beckhoff Automation GmbH. You can use SOEM for
32  * the sole purpose of creating, using and/or selling or otherwise distributing
33  * an EtherCAT network master provided that an EtherCAT Master License is obtained
34  * from Beckhoff Automation GmbH.
35  *
36  * In case you did not receive a copy of the EtherCAT Master License along with
37  * SOEM write to Beckhoff Automation GmbH, Eiserstraße 5, D-33415 Verl, Germany
38  * (www.beckhoff.com).
39  */
40 
68 #ifdef WIN32
69 
70 
71 #include <sys/types.h>
72 #include <stdio.h>
73 #include <fcntl.h>
74 #include <string.h>
75 
76 #include <winsock2.h>
77 #include "ethercattype.h"
78 #include <Mmsystem.h>
79 #include "nicdrv.h"
80 #include "osal_win32.h"
81 
82 #endif
83 
85 enum
86 {
91 };
92 
99 const uint16 priMAC[3] = { 0x0101, 0x0101, 0x0101 };
101 const uint16 secMAC[3] = { 0x0404, 0x0404, 0x0404 };
102 
104 #define RX_PRIM priMAC[1]
105 
106 #define RX_SEC secMAC[1]
107 
108 static char errbuf[PCAP_ERRBUF_SIZE];
109 
116 int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
117 {
118  int i, rval;
119  pcap_t **psock;
120 
121  rval = 0;
122  if (secondary)
123  {
124  /* secondary port stuct available? */
125  if (port->redport)
126  {
127  /* when using secondary socket it is automatically a redundant setup */
128  psock = &(port->redport->sockhandle);
129  *psock = NULL;
130  port->redstate = ECT_RED_DOUBLE;
131  port->redport->stack.sock = &(port->redport->sockhandle);
132  port->redport->stack.txbuf = &(port->txbuf);
133  port->redport->stack.txbuflength = &(port->txbuflength);
134  port->redport->stack.tempbuf = &(port->redport->tempinbuf);
135  port->redport->stack.rxbuf = &(port->redport->rxbuf);
136  port->redport->stack.rxbufstat = &(port->redport->rxbufstat);
137  port->redport->stack.rxsa = &(port->redport->rxsa);
138  }
139  else
140  {
141  /* fail */
142  return 0;
143  }
144  }
145  else
146  {
147  InitializeCriticalSection(&(port->getindex_mutex));
148  InitializeCriticalSection(&(port->tx_mutex));
149  InitializeCriticalSection(&(port->rx_mutex));
150  port->sockhandle = NULL;
151  port->lastidx = 0;
152  port->redstate = ECT_RED_NONE;
153  port->stack.sock = &(port->sockhandle);
154  port->stack.txbuf = &(port->txbuf);
155  port->stack.txbuflength = &(port->txbuflength);
156  port->stack.tempbuf = &(port->tempinbuf);
157  port->stack.rxbuf = &(port->rxbuf);
158  port->stack.rxbufstat = &(port->rxbufstat);
159  port->stack.rxsa = &(port->rxsa);
160  psock = &(port->sockhandle);
161  }
162  /* we use pcap socket to send RAW packets in windows user mode*/
163  *psock = pcap_open(ifname, 65536, PCAP_OPENFLAG_PROMISCUOUS |
164  PCAP_OPENFLAG_MAX_RESPONSIVENESS |
165  PCAP_OPENFLAG_NOCAPTURE_LOCAL, 0, NULL , errbuf);
166  if (NULL == *psock)
167  {
168  printf("interface %s could not open with pcap\n", ifname);
169  return 0;
170  }
171 
172  for (i = 0; i < EC_MAXBUF; i++)
173  {
174  ec_setupheader(&(port->txbuf[i]));
175  port->rxbufstat[i] = EC_BUF_EMPTY;
176  }
177  ec_setupheader(&(port->txbuf2));
178 
179  return 1;
180 }
181 
187 {
188  timeEndPeriod(15);
189 
190  if (port->sockhandle != NULL)
191  {
192  DeleteCriticalSection(&(port->getindex_mutex));
193  DeleteCriticalSection(&(port->tx_mutex));
194  DeleteCriticalSection(&(port->rx_mutex));
195  pcap_close(port->sockhandle);
196  port->sockhandle = NULL;
197  }
198  if ((port->redport) && (port->redport->sockhandle != NULL))
199  {
200  pcap_close(port->redport->sockhandle);
201  port->redport->sockhandle = NULL;
202  }
203 
204  return 0;
205 }
206 
212 void ec_setupheader(void *p)
213 {
214  ec_etherheadert *bp;
215  bp = p;
216  bp->da0 = htons(0xffff);
217  bp->da1 = htons(0xffff);
218  bp->da2 = htons(0xffff);
219  bp->sa0 = htons(priMAC[0]);
220  bp->sa1 = htons(priMAC[1]);
221  bp->sa2 = htons(priMAC[2]);
222  bp->etype = htons(ETH_P_ECAT);
223 }
224 
230 {
231  int idx;
232  int cnt;
233 
234  EnterCriticalSection(&(port->getindex_mutex));
235 
236  idx = port->lastidx + 1;
237  /* index can't be larger than buffer array */
238  if (idx >= EC_MAXBUF)
239  {
240  idx = 0;
241  }
242  cnt = 0;
243  /* try to find unused index */
244  while ((port->rxbufstat[idx] != EC_BUF_EMPTY) && (cnt < EC_MAXBUF))
245  {
246  idx++;
247  cnt++;
248  if (idx >= EC_MAXBUF)
249  {
250  idx = 0;
251  }
252  }
253  port->rxbufstat[idx] = EC_BUF_ALLOC;
254  if (port->redstate != ECT_RED_NONE)
255  port->redport->rxbufstat[idx] = EC_BUF_ALLOC;
256  port->lastidx = idx;
257 
258  LeaveCriticalSection(&(port->getindex_mutex));
259 
260  return idx;
261 }
262 
268 void ecx_setbufstat(ecx_portt *port, int idx, int bufstat)
269 {
270  port->rxbufstat[idx] = bufstat;
271  if (port->redstate != ECT_RED_NONE)
272  port->redport->rxbufstat[idx] = bufstat;
273 }
274 
281 int ecx_outframe(ecx_portt *port, int idx, int stacknumber)
282 {
283  int lp, rval;
284  ec_stackT *stack;
285 
286  if (!stacknumber)
287  {
288  stack = &(port->stack);
289  }
290  else
291  {
292  stack = &(port->redport->stack);
293  }
294  lp = (*stack->txbuflength)[idx];
295  rval = pcap_sendpacket(*stack->sock, (*stack->txbuf)[idx], lp);
296  (*stack->rxbufstat)[idx] = EC_BUF_TX;
297 
298  return rval;
299 }
300 
306 int ecx_outframe_red(ecx_portt *port, int idx)
307 {
308  ec_comt *datagramP;
309  ec_etherheadert *ehp;
310  int rval;
311 
312  ehp = (ec_etherheadert *)&(port->txbuf[idx]);
313  /* rewrite MAC source address 1 to primary */
314  ehp->sa1 = htons(priMAC[1]);
315  /* transmit over primary socket*/
316  rval = ecx_outframe(port, idx, 0);
317  if (port->redstate != ECT_RED_NONE)
318  {
319  EnterCriticalSection( &(port->tx_mutex) );
320  ehp = (ec_etherheadert *)&(port->txbuf2);
321  /* use dummy frame for secondary socket transmit (BRD) */
322  datagramP = (ec_comt*)&(port->txbuf2[ETH_HEADERSIZE]);
323  /* write index to frame */
324  datagramP->index = idx;
325  /* rewrite MAC source address 1 to secondary */
326  ehp->sa1 = htons(secMAC[1]);
327  /* transmit over secondary socket */
328  pcap_sendpacket(port->redport->sockhandle, (u_char const *)&(port->txbuf2), port->txbuflength2);
329  LeaveCriticalSection( &(port->tx_mutex) );
330  port->redport->rxbufstat[idx] = EC_BUF_TX;
331  }
332 
333  return rval;
334 }
335 
341 static int ecx_recvpkt(ecx_portt *port, int stacknumber)
342 {
343  int lp, bytesrx;
344  ec_stackT *stack;
345  struct pcap_pkthdr * header;
346  unsigned char const * pkt_data;
347  int res;
348 
349  if (!stacknumber)
350  {
351  stack = &(port->stack);
352  }
353  else
354  {
355  stack = &(port->redport->stack);
356  }
357  lp = sizeof(port->tempinbuf);
358 
359  res = pcap_next_ex(*stack->sock, &header, &pkt_data);
360  if (res <=0 )
361  {
362  port->tempinbufs = 0;
363  return 0;
364  }
365  bytesrx = header->len;
366  if (bytesrx > lp)
367  {
368  bytesrx = lp;
369  }
370  memcpy(*stack->tempbuf, pkt_data, bytesrx);
371  port->tempinbufs = bytesrx;
372  return (bytesrx > 0);
373 }
374 
391 int ecx_inframe(ecx_portt *port, int idx, int stacknumber)
392 {
393  uint16 l;
394  int rval;
395  int idxf;
396  ec_etherheadert *ehp;
397  ec_comt *ecp;
398  ec_stackT *stack;
399  ec_bufT *rxbuf;
400 
401  if (!stacknumber)
402  {
403  stack = &(port->stack);
404  }
405  else
406  {
407  stack = &(port->redport->stack);
408  }
409  rval = EC_NOFRAME;
410  rxbuf = &(*stack->rxbuf)[idx];
411  /* check if requested index is already in buffer ? */
412  if ((idx < EC_MAXBUF) && ((*stack->rxbufstat)[idx] == EC_BUF_RCVD))
413  {
414  l = (*rxbuf)[0] + ((uint16)((*rxbuf)[1] & 0x0f) << 8);
415  /* return WKC */
416  rval = ((*rxbuf)[l] + ((uint16)(*rxbuf)[l + 1] << 8));
417  /* mark as completed */
418  (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE;
419  }
420  else
421  {
422  EnterCriticalSection(&(port->rx_mutex));
423  /* non blocking call to retrieve frame from socket */
424  if (ecx_recvpkt(port, stacknumber))
425  {
426  rval = EC_OTHERFRAME;
427  ehp =(ec_etherheadert*)(stack->tempbuf);
428  /* check if it is an EtherCAT frame */
429  if (ehp->etype == htons(ETH_P_ECAT))
430  {
431  ecp =(ec_comt*)(&(*stack->tempbuf)[ETH_HEADERSIZE]);
432  l = etohs(ecp->elength) & 0x0fff;
433  idxf = ecp->index;
434  /* found index equals reqested index ? */
435  if (idxf == idx)
436  {
437  /* yes, put it in the buffer array (strip ethernet header) */
438  memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idx] - ETH_HEADERSIZE);
439  /* return WKC */
440  rval = ((*rxbuf)[l] + ((uint16)((*rxbuf)[l + 1]) << 8));
441  /* mark as completed */
442  (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE;
443  /* store MAC source word 1 for redundant routing info */
444  (*stack->rxsa)[idx] = ntohs(ehp->sa1);
445  }
446  else
447  {
448  /* check if index exist? */
449  if (idxf < EC_MAXBUF)
450  {
451  rxbuf = &(*stack->rxbuf)[idxf];
452  /* put it in the buffer array (strip ethernet header) */
453  memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idxf] - ETH_HEADERSIZE);
454  /* mark as received */
455  (*stack->rxbufstat)[idxf] = EC_BUF_RCVD;
456  (*stack->rxsa)[idxf] = ntohs(ehp->sa1);
457  }
458  else
459  {
460  /* strange things happend */
461  }
462  }
463  }
464  }
465  LeaveCriticalSection( &(port->rx_mutex) );
466 
467  }
468 
469  /* WKC if mathing frame found */
470  return rval;
471 }
472 
485 static int ecx_waitinframe_red(ecx_portt *port, int idx, osal_timert *timer)
486 {
487  osal_timert timer2;
488  int wkc = EC_NOFRAME;
489  int wkc2 = EC_NOFRAME;
490  int primrx, secrx;
491 
492  /* if not in redundant mode then always assume secondary is OK */
493  if (port->redstate == ECT_RED_NONE)
494  wkc2 = 0;
495  do
496  {
497  /* only read frame if not already in */
498  if (wkc <= EC_NOFRAME)
499  wkc = ecx_inframe(port, idx, 0);
500  /* only try secondary if in redundant mode */
501  if (port->redstate != ECT_RED_NONE)
502  {
503  /* only read frame if not already in */
504  if (wkc2 <= EC_NOFRAME)
505  wkc2 = ecx_inframe(port, idx, 1);
506  }
507  /* wait for both frames to arrive or timeout */
508  } while (((wkc <= EC_NOFRAME) || (wkc2 <= EC_NOFRAME)) && !osal_timer_is_expired(timer));
509  /* only do redundant functions when in redundant mode */
510  if (port->redstate != ECT_RED_NONE)
511  {
512  /* primrx if the reveived MAC source on primary socket */
513  primrx = 0;
514  if (wkc > EC_NOFRAME) primrx = port->rxsa[idx];
515  /* secrx if the reveived MAC source on psecondary socket */
516  secrx = 0;
517  if (wkc2 > EC_NOFRAME) secrx = port->redport->rxsa[idx];
518 
519  /* primary socket got secondary frame and secondary socket got primary frame */
520  /* normal situation in redundant mode */
521  if ( ((primrx == RX_SEC) && (secrx == RX_PRIM)) )
522  {
523  /* copy secondary buffer to primary */
524  memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
525  wkc = wkc2;
526  }
527  /* primary socket got nothing or primary frame, and secondary socket got secondary frame */
528  /* we need to resend TX packet */
529  if ( ((primrx == 0) && (secrx == RX_SEC)) ||
530  ((primrx == RX_PRIM) && (secrx == RX_SEC)) )
531  {
532  /* If both primary and secondary have partial connection retransmit the primary received
533  * frame over the secondary socket. The result from the secondary received frame is a combined
534  * frame that traversed all slaves in standard order. */
535  if ( (primrx == RX_PRIM) && (secrx == RX_SEC) )
536  {
537  /* copy primary rx to tx buffer */
538  memcpy(&(port->txbuf[idx][ETH_HEADERSIZE]), &(port->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
539  }
540  osal_timer_start (&timer2, EC_TIMEOUTRET);
541  /* resend secondary tx */
542  ecx_outframe(port, idx, 1);
543  do
544  {
545  /* retrieve frame */
546  wkc2 = ecx_inframe(port, idx, 1);
547  } while ((wkc2 <= EC_NOFRAME) && !osal_timer_is_expired(&timer2));
548  if (wkc2 > EC_NOFRAME)
549  {
550  /* copy secondary result to primary rx buffer */
551  memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
552  wkc = wkc2;
553  }
554  }
555  }
556 
557  /* return WKC or EC_NOFRAME */
558  return wkc;
559 }
560 
568 int ecx_waitinframe(ecx_portt *port, int idx, int timeout)
569 {
570  int wkc;
571  osal_timert timer;
572 
573  osal_timer_start (&timer, timeout);
574  wkc = ecx_waitinframe_red(port, idx, &timer);
575  /* if nothing received, clear buffer index status so it can be used again */
576  if (wkc <= EC_NOFRAME)
577  {
578  ec_setbufstat(idx, EC_BUF_EMPTY);
579  }
580 
581  return wkc;
582 }
583 
596 int ecx_srconfirm(ecx_portt *port, int idx, int timeout)
597 {
598  int wkc = EC_NOFRAME;
599  osal_timert timer1, timer2;
600 
601  osal_timer_start (&timer1, timeout);
602  do
603  {
604  /* tx frame on primary and if in redundant mode a dummy on secondary */
605  ecx_outframe_red(port, idx);
606  if (timeout < EC_TIMEOUTRET)
607  {
608  osal_timer_start (&timer2, timeout);
609  }
610  else
611  {
612  /* normally use partial timout for rx */
613  osal_timer_start (&timer2, EC_TIMEOUTRET);
614  }
615  /* get frame from primary or if in redundant mode possibly from secondary */
616  wkc = ecx_waitinframe_red(port, idx, &timer2);
617  /* wait for answer with WKC>=0 or otherwise retry until timeout */
618  } while ((wkc <= EC_NOFRAME) && !osal_timer_is_expired (&timer1));
619  /* if nothing received, clear buffer index status so it can be used again */
620  if (wkc <= EC_NOFRAME)
621  {
622  ec_setbufstat(idx, EC_BUF_EMPTY);
623  }
624 
625  return wkc;
626 }
627 
628 
629 #ifdef EC_VER1
630 
631 int ec_setupnic(const char *ifname, int secondary)
632 {
633  return ecx_setupnic(&ecx_port, ifname, secondary);
634 }
635 
636 int ec_closenic(void)
637 {
638  return ecx_closenic(&ecx_port);
639 }
640 
641 int ec_getindex(void)
642 {
643  return ecx_getindex(&ecx_port);
644 }
645 
646 void ec_setbufstat(int idx, int bufstat)
647 {
648  ecx_setbufstat(&ecx_port, idx, bufstat);
649 }
650 
651 int ec_outframe(int idx, int stacknumber)
652 {
653  return ecx_outframe(&ecx_port, idx, stacknumber);
654 }
655 
656 int ec_outframe_red(int idx)
657 {
658  return ecx_outframe_red(&ecx_port, idx);
659 }
660 
661 int ec_inframe(int idx, int stacknumber)
662 {
663  return ecx_inframe(&ecx_port, idx, stacknumber);
664 }
665 
666 int ec_waitinframe(int idx, int timeout)
667 {
668  return ecx_waitinframe(&ecx_port, idx, timeout);
669 }
670 
671 int ec_srconfirm(int idx, int timeout)
672 {
673  return ecx_srconfirm(&ecx_port, idx, timeout);
674 }
675 
676 #endif
int ecx_getindex(ecx_portt *port)
Definition: win32/nicdrv.c:229
ec_bufT rxbuf[EC_MAXBUF]
Definition: linux/nicdrv.h:81
int rxsa[EC_MAXBUF]
Definition: linux/nicdrv.h:85
ec_bufT txbuf[EC_MAXBUF]
Definition: linux/nicdrv.h:106
void ec_setupheader(void *p)
Definition: win32/nicdrv.c:212
int(* rxsa)[EC_MAXBUF]
Definition: linux/nicdrv.h:72
int txbuflength[EC_MAXBUF]
Definition: linux/nicdrv.h:108
#define RX_PRIM
Definition: win32/nicdrv.c:104
#define EC_MAXBUF
Definition: ethercattype.h:88
ec_bufT txbuf2
Definition: linux/nicdrv.h:110
static int ecx_recvpkt(ecx_portt *port, int stacknumber)
Definition: win32/nicdrv.c:341
int rxsa[EC_MAXBUF]
Definition: linux/nicdrv.h:100
int ecx_outframe_red(ecx_portt *port, int idx)
Definition: win32/nicdrv.c:306
static char errbuf[PCAP_ERRBUF_SIZE]
Definition: win32/nicdrv.c:108
int(* txbuflength)[EC_MAXBUF]
Definition: linux/nicdrv.h:64
PACKED_BEGIN struct PACKED ec_etherheadert
ec_bufT * tempbuf
Definition: linux/nicdrv.h:66
int ecx_srconfirm(ecx_portt *port, int idx, int timeout)
Definition: win32/nicdrv.c:596
const uint16 secMAC[3]
Definition: win32/nicdrv.c:101
int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
Definition: win32/nicdrv.c:116
int txbuflength2
Definition: linux/nicdrv.h:112
uint16_t uint16
Definition: osal.h:34
std_msgs::Header * header(M &m)
#define RX_SEC
Definition: win32/nicdrv.c:106
boolean osal_timer_is_expired(osal_timert *self)
Definition: linux/osal.c:78
General typedefs and defines for EtherCAT.
#define etohs(A)
Definition: ethercattype.h:550
const uint16 priMAC[3]
Definition: win32/nicdrv.c:99
#define ETH_HEADERSIZE
Definition: ethercattype.h:127
int rxbufstat[EC_MAXBUF]
Definition: linux/nicdrv.h:83
ec_stackT stack
Definition: linux/nicdrv.h:93
ec_bufT rxbuf[EC_MAXBUF]
Definition: linux/nicdrv.h:96
pthread_mutex_t tx_mutex
Definition: linux/nicdrv.h:120
ec_stackT stack
Definition: linux/nicdrv.h:78
ec_bufT tempinbuf
Definition: linux/nicdrv.h:102
ec_bufT(* rxbuf)[EC_MAXBUF]
Definition: linux/nicdrv.h:68
int ecx_outframe(ecx_portt *port, int idx, int stacknumber)
Definition: win32/nicdrv.c:281
void ecx_setbufstat(ecx_portt *port, int idx, int bufstat)
Definition: win32/nicdrv.c:268
int tempinbufs
Definition: linux/nicdrv.h:104
int ecx_inframe(ecx_portt *port, int idx, int stacknumber)
Definition: win32/nicdrv.c:391
ec_bufT(* txbuf)[EC_MAXBUF]
Definition: linux/nicdrv.h:62
ecx_redportt * redport
Definition: linux/nicdrv.h:118
PACKED_BEGIN struct PACKED ec_comt
int sockhandle
Definition: linux/nicdrv.h:94
static int ecx_waitinframe_red(ecx_portt *port, int idx, osal_timert *timer)
Definition: win32/nicdrv.c:485
void osal_timer_start(osal_timert *self, uint32 timeout_us)
Definition: linux/osal.c:63
#define EC_TIMEOUTRET
Definition: ethercattype.h:90
#define EC_OTHERFRAME
Definition: ethercattype.h:75
pthread_mutex_t getindex_mutex
Definition: linux/nicdrv.h:119
#define ETH_P_ECAT
Definition: ethercattype.h:475
int rxbufstat[EC_MAXBUF]
Definition: linux/nicdrv.h:98
int ecx_closenic(ecx_portt *port)
Definition: win32/nicdrv.c:186
uint8 ec_bufT[EC_BUFSIZE]
Definition: ethercattype.h:111
ec_bufT tempinbuf
Definition: linux/nicdrv.h:87
int ecx_waitinframe(ecx_portt *port, int idx, int timeout)
Definition: win32/nicdrv.c:568
int(* rxbufstat)[EC_MAXBUF]
Definition: linux/nicdrv.h:70
#define EC_NOFRAME
Definition: ethercattype.h:73
int * sock
Definition: linux/nicdrv.h:60
pthread_mutex_t rx_mutex
Definition: linux/nicdrv.h:121


youbot_driver
Author(s): Jan Paulus
autogenerated on Mon Jun 10 2019 15:46:24