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
/*      File: slave_device.h
 *       This file is part of the program ethercatcpp-core
 *       Program description : EtherCAT driver libraries for UNIX
 *       Copyright (C) 2017-2024 -  Robin Passama (LIRMM / CNRS) Arnaud Meline
 * (LIRMM / CNRS) Benjamin Navarro (LIRMM / CNRS). All Right reserved.
 *
 *       This software is free software: you can redistribute it and/or modify
 *       it under the terms of the CeCILL-C license as published by
 *       the CEA CNRS INRIA, either version 1
 *       of the License, or (at your option) any later version.
 *       This software is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *       CeCILL-C License for more details.
 *
 *       You should have received a copy of the CeCILL-C License
 *       along with this software. If not, it can be found on the official
 * website of the CeCILL licenses family (http://www.cecill.info/index.en.html).
 */
/**
 * @file slave_device.h
 * @author Robin Passama (design and maintenance)
 * @author Arnaud Meline (original)
 * @author Benjamin Navarro (refactoring)
 * @brief Header file for SlaveDevice class
 * @date October 2018 12.
 * @ingroup ethercatcpp-core
 */

#pragma once

#include <ethercatcpp/device.h>
#include <ethercatcpp/coe_utilities.h>

#include <string>
#include <vector>
#include <functional>
#include <memory>

/*! \namespace ethercatcpp
 *
 * Root namespace for common and general purpose ethercatcpp packages
 */
namespace ethercatcpp {

/** @brief This class define an EtherCAT unit device
 *
 * A unit device is an EtherCAT device who contain slave informations (it is
 * composed by a dedicated slave object). It define all steps function (run,
 * init and end), define EtherCAT I/O buffers and all EtherCAT slave
 * configurations.
 */
class SlaveDevice : public Device {
public:
    //! This enum define all type of buffers (SyncManager type)
    enum syncmanager_buffer_t {
        ASYNCHROS_OUT =
            1, //!< define an asynchro mailbox out (from master to slave)
        ASYNCHROS_IN =
            2, //!< define an asynchro mailbox in (from slave to master)
        SYNCHROS_OUT =
            3,          //!< define a synchro buffer out (from master to slave)
        SYNCHROS_IN = 4 //!< define a synchro buffer in (from slave to master)
    };

    /**
     * @brief Constructor of SlaveDevice class
     */
    SlaveDevice();
    virtual ~SlaveDevice();

    /**
     * @brief Set the serial number of the device.
     *
     * This make it posible to force the serial number of the device. So at
     * Master::init() step the serial number will be check, and an error throw
     * if the device serial number dos not match.
     *
     * @param [in] serial_number the serial number.
     */
    void set_serial_number(uint32_t serial_number);

    /**
     * @brief Get EtherCAT device manufacturer id.
     *
     * @return EtherCAT device manufacturer id.
     */
    uint32_t eep_manufacturer() const;

    /**
     * @brief Get EtherCAT device id.
     *
     * @return the device id.
     */
    uint32_t eep_device() const;

    /**
     * @brief Get device serial number.
     *
     * @return device serial number.
     */
    uint32_t serial_number() const;

protected:
    friend class coe::PDOMapping;
    friend class coe::PDOBuffer;

    /**
     * @brief Define a physical buffer (EtherCAT syncManager buffer).
     *
     * This function define a physical buffer and updates size of total I/O
     * buffer
     *
     * @tparam T is the first or unique data type defining buffer content.
     * @tparam U are the other data types defining buffer content
     * sequentially happend to T.
     * @param [in] type is the type of buffer selected in
     * syncmanager_buffer_t.
     * @param [in] start_addr is the buffer physical start address.
     * @param [in] flags is the specific flag for this buffer.
     */
    template <typename T, typename... U>
    void define_physical_buffer(syncmanager_buffer_t type, uint16_t start_addr,
                                uint32_t flags) {
        if constexpr (sizeof...(U) == 0) {
            define_physical_buffer(type, start_addr, flags,
                                   static_cast<uint16_t>(sizeof(T)));
        } else {
            define_physical_buffer(
                type, start_addr, flags,
                static_cast<uint16_t>(sizeof(T) + (sizeof(U) + ...)));
        }
    }

    /**
     * @brief Define a physical buffer (EtherCAT syncManager buffer).
     *
     * This function defines a physical buffer and update size of total I/O
     * buffer
     *
     * @param [in] type is the type of buffer selected in syncmanager_buffer_t.
     * @param [in] start_addr is the buffer physical start address.
     * @param [in] flags is the specific flag for this buffer.
     * @param [in] length is the size of the buffer in bytes.
     */
    void define_physical_buffer(syncmanager_buffer_t type, uint16_t start_addr,
                                uint32_t flags, uint16_t length);

    /**
     * @brief Get data of input physical buffer.
     *
     * This function get the raw datas of the input physical buffer and cast
     * them in the data structure type indicate with "template param" to more
     * easyest used.
     *
     * @tparam T is the data structure type of the buffer.
     * @param [in] start_addr is the input buffer physical start address.
     * @return pointer to buffer data with "template param" structure type.
     */
    template <typename T>
    T* input_buffer(uint16_t start_addr) {
        return reinterpret_cast<T*>(input_buffer(start_addr));
    }

    template <typename T, typename U, typename... Other>
    std::tuple<T*, U*, Other*...> input_buffer(uint16_t start_addr) {
        return input_buffer_tuple<T, U, Other...>(start_addr, 0);
    }

    /**
     * @brief Get data of input physical buffer.
     *
     * @param [in] start_addr is the input buffer physical start address.
     * @return pointer to buffer data.
     */
    uint8_t* input_buffer(uint16_t start_addr);

    /**
     * @brief Get data of output physical buffer.
     *
     * This function get the raw datas of the output physical buffer and cast
     * them in the data structure type indicate with "template param" to more
     * easyest used.
     *
     * @tparam T is the data structure type of the buffer.
     * @param [in] start_addr is the output buffer physical start address.
     * @return pointer to buffer data with "template param" structure type.
     */
    template <typename T>
    T* output_buffer(uint16_t start_addr) {
        return reinterpret_cast<T*>(output_buffer(start_addr));
    }

    template <typename T, typename U, typename... Other>
    std::tuple<T*, U*, Other*...> output_buffer(uint16_t start_addr) {
        return output_buffer_tuple<T, U, Other...>(start_addr, 0);
    }

    /**
     * @brief Get data of output physical buffer.
     *
     * @param [in] start_addr is the output buffer physical start address.
     * @return pointer to buffer data.
     */
    uint8_t* output_buffer(uint16_t start_addr);

    /**
     * @brief Add a run step and define pre_function and
     * post_function run step.
     *
     * @param [in] pre is the function witch is execute before a run step
     * (generally set commands).
     * @param [in] post is the function witch is execute after a run step
     * (generally get status and datas).
     */
    void add_run_step(std::function<void()>&& pre,
                      std::function<void()>&& post);

    /**
     * @brief Add a init step and define pre_function and post_function for this
     * init step.
     *
     * @param [in] pre is the function witch is execute before a init step
     * (generally set commands).
     * @param [in] post is the function witch is execute after a init step
     * (generally get status and datas).
     */
    void add_init_step(std::function<void()>&& pre,
                       std::function<void()>&& post);

    /**
     * @brief Add a end step and define pre_function and post_function for this
     * end step.
     *
     * @param [in] pre is the function witch is execute before a end step
     * (generally set commands).
     * @param [in] post is the function witch is execute after a end step
     * (generally get status and datas).
     */
    void add_end_step(std::function<void()>&& pre,
                      std::function<void()>&& post);

    /**
     * @brief Set a specific ID to the slave.
     *
     * @param [in] name is slave name.
     * @param [in] manufacturer is the slave manufacturer ID.
     * @param [in] model is the slave model ID.
     */
    void set_id(const std::string& name, uint32_t manufacturer, uint32_t model);

    /**
     * @brief Define if the slave have a distributed clock.
     *
     * @param [in] have_dc state for distributed clock (TRUE if have DC).
     */
    void define_distributed_clock(bool have_dc);

    /**
     * @brief Define the period between two non cyclic steps.
     *
     * @param [in] period time desired to wait in us.
     */
    void define_period_for_non_cyclic_steps(int period);

    /**
     * @brief Define the function that configures the slave in PREOP
     * state at initialization of the ethercat bus (Master::init()) .
     * @details This is typically used to configure CanOpen over EtherCAT
     * devices, by calling write_sdo/read_sdo, and functions to
     * define PDO mappings and PDO buffers configuration. It can also be used
     * for any operations using FoE.
     *
     * @param [in] func is the function witch is executed witch is executed at
     * bus initialization time.
     * @see Master::init()
     */
    void configure_at_init(std::function<void()>&& func);

    /**
     * @brief Define the function that performs configuration actions in PREOP
     * state at termination of the ethercat bus. (Master::end()).
     * @details This is typically used to configure CanOpen over EtherCAT
     * devices, by calling write_sdo/read_sdo.
     * @param [in] func is the function witch is executed at bus termination
     * time.
     * @see Master::end()
     */
    void configure_at_end(std::function<void()>&& func);

    //////////////////////////////////////////////////////////////
    ////////////////////// CoE configuration /////////////////////
    //////////////////////////////////////////////////////////////

    /**
     * @brief write a SDO capable object field
     *
     * @param index index of the object
     * @param sub_index sub-index of the field
     * @param buffer_size size of field data
     * @param buffer pointer to the memory zone holding the data to xrite
     * @return int the workcounter. 0 on failure.
     */
    int write_sdo(uint16_t index, uint8_t sub_index, int buffer_size,
                  void* buffer) const;

    /**
     * @brief read a SDO capable object field
     *
     * @param index index of the object
     * @param sub_index sub-index of the field
     * @param buffer_size size of field data
     * @param buffer pointer to the memory zone holding the data read
     * @return int the workcounter. 0 on failure.
     */
    int read_sdo(uint16_t index, uint8_t sub_index, int buffer_size,
                 void* buffer) const;

    /**
     * @brief Send a CoE write SDO packet to the slave
     *
     * @param [in] index is the SDO index to write data.
     * @param [in] sub_index is the SDO sub index to write data.
     * @param [in] value is the data to write.
     *
     * @tparam T represent the data type of input parameter value.
     * @return 0 if communication failed. Worcounter value if success.
     */
    template <typename T>
    int write_sdo(uint16_t index, uint8_t sub_index, T& value) const {
        return write_sdo(index, sub_index, static_cast<int>(sizeof(T)),
                         reinterpret_cast<void*>(&value));
    }

    /**
     * @brief Send a CoE read SDO packet to the slave
     *
     * @param [in] index is the SDO index to read data.
     * @param [in] sub_index is the SDO sub index to read data.
     * @param [in] value is the data readed.
     *
     * @tparam T represent the data type of input parameter value.
     * @return 0 if communication failed. Worcounter value if success.
     */
    template <typename T>
    int read_sdo(uint16_t index, uint8_t sub_index, T& value) const {
        return read_sdo(index, sub_index, static_cast<int>(sizeof(T)),
                        reinterpret_cast<void*>(&value));
    }

    /**
     * @brief Start the definition of the command PDO mapping.
     *
     * @tparam T represent the type of data register (generally uint8 or
     * uint16).
     * @return false if communication failed, true if success.
     */
    template <typename T>
    bool start_command_pdo_mapping() {
        // Have to desactivate buffer to change it
        T val = 0;
        set_command_mappings(0);
        return write_sdo(coe::coe_rx_pdo_assign, 0x00, val) != 0;
    }

    /**
     * @brief Add a new command PDO map link.
     *
     * @param [in] pdo_address is the map address who want to add to the PDO.
     * @tparam T represent the type of data register (generally uint8 or
     * uint16).
     * @return false if communication failed, true if success.
     */
    template <typename T>
    bool add_command_pdo_mapping(uint16_t pdo_address) {
        T val = 0;
        // Check if buffer is desactivate
        int wkc = read_sdo(coe::coe_rx_pdo_assign, 0x00, val);
        if (val != 0) { // not in config PDO mode
            return false;
        }
        // Send new PDO address mapping
        set_command_mappings(get_command_mappings() + 1);
        wkc += write_sdo(coe::coe_rx_pdo_assign, get_command_mappings(),
                         pdo_address);
        return (wkc == 2);
    }

    /**
     * @brief Finish the definition of the command PDO mapping.
     *
     * @tparam T represent the type of date register (generally uint8 or
     * uint16).
     * @return false if communication failed, true if success.
     */
    template <typename T>
    bool end_command_pdo_mapping() {
        T val = static_cast<T>(get_command_mappings());
        // Have to reactivate buffer with map number to valid it
        return write_sdo(coe::coe_rx_pdo_assign, 0x00, val) != 0;
    }

    /**
     * @brief Start definition of the status PDO mapping.
     *
     * @tparam T represent the type of date register (generally uint8 or
     * uint16).
     * @return false if communication failed, true if success.
     */
    template <typename T>
    bool start_status_pdo_mapping() {
        // Have to desactivate buffer to change it
        T val = 0;
        set_status_mappings(0);
        return write_sdo(coe::coe_tx_pdo_assign, 0x00, val) != 0;
    }

    /**
     * @brief Add a new STATUS PDO map link.
     *
     * @param [in] pdo_address is the map address who want to add to the PDO.
     * @tparam T represent the type of "date register" (generally uint8 or
     * uint16).
     * @return false if communication failed, true if success.
     */
    template <typename T>
    bool add_status_pdo_mapping(uint16_t pdo_address) {
        T val = 0;
        // Check if buffer is desactivate
        int wkc = read_sdo(coe::coe_tx_pdo_assign, 0x00, val);
        if (val != 0) { // no in config PDO mode
            return false;
        }
        // Send new PDO address mapping
        set_status_mappings(get_status_mappings() + 1);
        // increment number of Status pdo mapping to
        // write in good index
        wkc += write_sdo(coe::coe_tx_pdo_assign, get_status_mappings(),
                         pdo_address);
        return (wkc == 2);
    }

    /**
     * @brief Finish definition of the status PDO mapping.
     *
     * @tparam T represent the type of date register (generally uint8 or
     * uint16).
     * @return false if communication failed, true if success.
     */
    template <typename T>
    bool end_status_pdo_mapping() {
        int wkc = 0;
        T val = get_status_mappings();
        // Have to reactivate buffer with map number to valid it
        wkc += write_sdo(coe::coe_tx_pdo_assign, 0x00, val);
        return wkc > 0;
    }

    //////////////////////////////////////////////////////////////
    ////////////////////// DC configuration///////////////////////
    //////////////////////////////////////////////////////////////

    /**
     * @brief Define DC synchro signal 0.
     *
     * This function active DC sync 0 mode and set timers for a slave.
     *
     * @param [in] cycle_time_0 is Cycltime SYNC0 in ns.
     * @param [in] cycle_shift is CyclShift in ns.
     */
    void configure_dc_sync0(uint32_t cycle_time_0, int32_t cycle_shift) const;

    /**
     * @brief Define DC synchro signals 0 and 1.
     *
     * This function active DC sync 0 and 1 mode and set timers for a slave.
     *
     * @param [in] cycle_time_0 is Cycltime SYNC0 in ns.
     * @param [in] cycle_time_1 is Cycltime SYNC1 in ns. This time is a delta
     * time in relation to the SYNC0 fire. If CylcTime1 = 0 then SYNC1 fires a
     * the same time as SYNC0.
     * @param [in] cycle_shift is CyclShift in ns.
     */
    void configure_dc_sync0_1(uint32_t cycle_time_0, uint32_t cycle_time_1,
                              int32_t cycle_shift) const; // time in ns

    //////////////////////////////////////////////////////////////
    ////////////////////// FoE configuration /////////////////////
    //////////////////////////////////////////////////////////////

    int32_t read_file(std::string_view filename, uint32_t password,
                      int32_t size, uint8_t* buffer) const;
    bool write_file(std::string_view filename, uint32_t password, int32_t size,
                    uint8_t* buffer) const;

    //////////////////////////////////////////////////////////////
    ////////////////////// SoE configuration /////////////////////
    //////////////////////////////////////////////////////////////

    bool read_sercos(uint8_t drive, uint8_t flags, uint16_t idn, int32_t size,
                     uint8_t* buffer) const;
    bool write_sercos(uint8_t drive, uint8_t flags, uint16_t idn, int32_t size,
                      uint8_t* buffer) const;

#ifdef EOE_AVAILABLE
    //////////////////////////////////////////////////////////////
    ////////////////////// EoE configuration /////////////////////
    //////////////////////////////////////////////////////////////
    struct EthernetConfiguration {
        std::string ip;
        std::string mask;
        std::string mac;
        std::string gateway;
        std::string dns;
    };

    bool detect_ethernet_configuration(uint8_t port,
                                       EthernetConfiguration& config);
    bool configure_ethernet(uint8_t port, const EthernetConfiguration& config);

    int32_t read_ethernet(uint8_t port, int32_t size, uint8_t* buffer);
    bool write_ethernet(uint8_t port, int32_t size, uint8_t* buffer);
#endif
private:
    friend class Master;
    friend class SlaveInfo;

    /**
     * @brief Print all slave informations
     *
     */
    void print_slave_info() const;
    /**
     * @brief Get the number of run steps for the device
     *
     * @return number of run steps
     */
    uint8_t run_steps() const;

    /**
     * @brief Launch the pre_function for one specific run step
     *
     * @param [in] step is the specific step number who want to launch.
     */
    void pre_run_step(uint8_t step);
    /**
     * @brief Launch the post_function for one specific run step
     *
     * @param [in] step is the specific step number who want to launch.
     */
    void post_run_step(uint8_t step);

    /**
     * @brief Get the number of init steps for the device
     *
     * @return number of init steps
     */
    uint8_t init_steps() const;

    /**
     * @brief Launch the pre_function for one specific init
     * step
     *
     * @param [in] step is the specific step number who want to launch.
     */
    void pre_init_step(uint8_t step);

    /**
     * @brief Launch the post_function for one specific init step
     *
     * @param [in] step is the specific step number who want to launch.
     */
    void post_init_step(uint8_t step);

    /***
     * @brief Get the number of end steps for the device
     *
     * @return number of end steps
     */
    uint8_t end_steps() const;

    /**
     * @brief Launch the pre_function for one specific end step
     *
     * @param [in] step is the specific step number who want to launch.
     */
    void pre_end_step(uint8_t step);

    /**
     * @brief Launch the post_function for one specific end
     * step
     *
     * @param [in] step is the specific step number who want to launch.
     */
    void post_end_step(uint8_t step);

    /**
     * @brief Update In/Out buffers address
     */
    void update_buffers();

    /**
     * @brief Call the init configuration function
     */
    void launch_init_configuration();

    /**
     * @brief Call the end configuration function
     */
    void launch_end_configuration();

    void set_command_mappings(uint8_t mappings);
    uint8_t get_command_mappings() const;
    void set_status_mappings(uint8_t mappings);
    uint8_t get_status_mappings() const;

    std::vector<Device*> device_vector() final;

    SlaveInfo* slave_address() final;

    class Impl;
    std::unique_ptr<Impl> impl_;

    // Auxiliary template functions
    template <typename T>
    std::tuple<T*> input_buffer_tuple(uint16_t start_addr,
                                      size_t ptr_increment) {
        return std::make_tuple(
            reinterpret_cast<T*>(input_buffer(start_addr) + ptr_increment));
    }

    template <typename T, typename U, typename... Other>
    std::tuple<T*, U*, Other*...> input_buffer_tuple(uint16_t start_addr,
                                                     size_t ptr_increment) {
        return std::tuple_cat(input_buffer_tuple<T>(start_addr, ptr_increment),
                              input_buffer_tuple<U, Other...>(
                                  start_addr, ptr_increment + sizeof(T)));
    }

    template <typename T>
    std::tuple<T*> output_buffer_tuple(uint16_t start_addr,
                                       size_t ptr_increment) {
        return std::make_tuple(
            reinterpret_cast<T*>(output_buffer(start_addr) + ptr_increment));
    }

    template <typename T, typename U, typename... Other>
    std::tuple<T*, U*, Other*...> output_buffer_tuple(uint16_t start_addr,
                                                      size_t ptr_increment) {
        return std::tuple_cat(output_buffer_tuple<T>(start_addr, ptr_increment),
                              output_buffer_tuple<U, Other...>(
                                  start_addr, ptr_increment + sizeof(T)));
    }
};
} // namespace ethercatcpp