AgIsoStack++
A control-function-focused implementation of the major ISOBUS and J1939 protocols
Loading...
Searching...
No Matches
isobus_task_controller_client.cpp
Go to the documentation of this file.
1//================================================================================================
8//================================================================================================
10
14#include "isobus/utility/system_timing.hpp"
15#include "isobus/utility/to_string.hpp"
16
17#include <algorithm>
18#include <array>
19#include <cassert>
20#include <cstring>
21#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
22#include <thread>
23#endif
24
25namespace isobus
26{
27 TaskControllerClient::TaskControllerClient(std::shared_ptr<PartneredControlFunction> partner, std::shared_ptr<InternalControlFunction> clientSource, std::shared_ptr<PartneredControlFunction> primaryVT) :
28 languageCommandInterface(clientSource, partner),
29 partnerControlFunction(partner),
30 myControlFunction(clientSource),
31 primaryVirtualTerminal(primaryVT)
32 {
33 }
34
39
40 void TaskControllerClient::initialize(bool spawnThread)
41 {
42 // You cannot use this interface without having valid control functions.
43 assert(nullptr != myControlFunction);
44 assert(nullptr != partnerControlFunction);
45
46 partnerControlFunction->add_parameter_group_number_callback(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData), process_rx_message, this);
47 partnerControlFunction->add_parameter_group_number_callback(static_cast<std::uint32_t>(CANLibParameterGroupNumber::Acknowledge), process_rx_message, this);
48 CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData), process_rx_message, this);
49
51 {
53 }
54
56 {
57 shouldTerminate = false;
58 initialized = false;
59 }
60
61 if (!initialized)
62 {
63#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
64 if (spawnThread)
65 {
66 workerThread = new std::thread([this]() { worker_thread_function(); });
67 }
68#endif
69 initialized = true;
70 }
71 }
72
74 {
75 LOCK_GUARD(Mutex, clientMutex);
76
77 RequestValueCommandCallbackInfo callbackData = { callback, parentPointer };
78 requestValueCallbacks.push_back(callbackData);
79 }
80
82 {
83 LOCK_GUARD(Mutex, clientMutex);
84
85 ValueCommandCallbackInfo callbackData = { callback, parentPointer };
86 valueCommandsCallbacks.push_back(callbackData);
87 }
88
90 {
91 LOCK_GUARD(Mutex, clientMutex);
92
93 RequestValueCommandCallbackInfo callbackData = { callback, parentPointer };
94 auto callbackLocation = std::find(requestValueCallbacks.begin(), requestValueCallbacks.end(), callbackData);
95
96 if (requestValueCallbacks.end() != callbackLocation)
97 {
98 requestValueCallbacks.erase(callbackLocation);
99 }
100 }
101
103 {
104 LOCK_GUARD(Mutex, clientMutex);
105
106 ValueCommandCallbackInfo callbackData = { callback, parentPointer };
107 auto callbackLocation = std::find(valueCommandsCallbacks.begin(), valueCommandsCallbacks.end(), callbackData);
108
109 if (valueCommandsCallbacks.end() != callbackLocation)
110 {
111 valueCommandsCallbacks.erase(callbackLocation);
112 }
113 }
114
115 void TaskControllerClient::configure(std::shared_ptr<DeviceDescriptorObjectPool> DDOP,
116 std::uint8_t maxNumberBoomsSupported,
117 std::uint8_t maxNumberSectionsSupported,
118 std::uint8_t maxNumberChannelsSupportedForPositionBasedControl,
119 bool reportToTCSupportsDocumentation,
120 bool reportToTCSupportsTCGEOWithoutPositionBasedControl,
121 bool reportToTCSupportsTCGEOWithPositionBasedControl,
122 bool reportToTCSupportsPeerControlAssignment,
123 bool reportToTCSupportsImplementSectionControl)
124 {
126 {
127 assert(nullptr != DDOP); // Client will not work without a DDOP.
128 generatedBinaryDDOP.clear();
129 ddopStructureLabel.clear();
130 userSuppliedVectorDDOP = nullptr;
131 ddopLocalizationLabel.fill(0x00);
133 clientDDOP = DDOP;
134 userSuppliedBinaryDDOP = nullptr;
136 set_common_config_items(maxNumberBoomsSupported,
137 maxNumberSectionsSupported,
138 maxNumberChannelsSupportedForPositionBasedControl,
139 reportToTCSupportsDocumentation,
140 reportToTCSupportsTCGEOWithoutPositionBasedControl,
141 reportToTCSupportsTCGEOWithPositionBasedControl,
142 reportToTCSupportsPeerControlAssignment,
143 reportToTCSupportsImplementSectionControl);
144 }
145 else
146 {
147 // We don't want someone to erase our object pool or something while it is being used.
148 LOG_ERROR("[TC]: Cannot reconfigure TC client while it is running!");
149 }
150 }
151
152 void TaskControllerClient::configure(const std::uint8_t *binaryDDOP,
153 std::uint32_t DDOPSize,
154 std::uint8_t maxNumberBoomsSupported,
155 std::uint8_t maxNumberSectionsSupported,
156 std::uint8_t maxNumberChannelsSupportedForPositionBasedControl,
157 bool reportToTCSupportsDocumentation,
158 bool reportToTCSupportsTCGEOWithoutPositionBasedControl,
159 bool reportToTCSupportsTCGEOWithPositionBasedControl,
160 bool reportToTCSupportsPeerControlAssignment,
161 bool reportToTCSupportsImplementSectionControl)
162 {
164 {
165 assert(nullptr != binaryDDOP); // Client will not work without a DDOP.
166 assert(0 != DDOPSize);
167 generatedBinaryDDOP.clear();
168 ddopStructureLabel.clear();
169 userSuppliedVectorDDOP = nullptr;
170 ddopLocalizationLabel.fill(0x00);
172 userSuppliedBinaryDDOP = binaryDDOP;
174 set_common_config_items(maxNumberBoomsSupported,
175 maxNumberSectionsSupported,
176 maxNumberChannelsSupportedForPositionBasedControl,
177 reportToTCSupportsDocumentation,
178 reportToTCSupportsTCGEOWithoutPositionBasedControl,
179 reportToTCSupportsTCGEOWithPositionBasedControl,
180 reportToTCSupportsPeerControlAssignment,
181 reportToTCSupportsImplementSectionControl);
182 }
183 else
184 {
185 // We don't want someone to erase our object pool or something while it is being used.
186 LOG_ERROR("[TC]: Cannot reconfigure TC client while it is running!");
187 }
188 }
189
190 void TaskControllerClient::configure(std::shared_ptr<std::vector<std::uint8_t>> binaryDDOP,
191 std::uint8_t maxNumberBoomsSupported,
192 std::uint8_t maxNumberSectionsSupported,
193 std::uint8_t maxNumberChannelsSupportedForPositionBasedControl,
194 bool reportToTCSupportsDocumentation,
195 bool reportToTCSupportsTCGEOWithoutPositionBasedControl,
196 bool reportToTCSupportsTCGEOWithPositionBasedControl,
197 bool reportToTCSupportsPeerControlAssignment,
198 bool reportToTCSupportsImplementSectionControl)
199 {
201 {
202 assert(nullptr != binaryDDOP); // Client will not work without a DDOP.
203 ddopStructureLabel.clear();
204 generatedBinaryDDOP.clear();
205 ddopLocalizationLabel.fill(0x00);
206 userSuppliedVectorDDOP = binaryDDOP;
208 userSuppliedBinaryDDOP = nullptr;
210 set_common_config_items(maxNumberBoomsSupported,
211 maxNumberSectionsSupported,
212 maxNumberChannelsSupportedForPositionBasedControl,
213 reportToTCSupportsDocumentation,
214 reportToTCSupportsTCGEOWithoutPositionBasedControl,
215 reportToTCSupportsTCGEOWithPositionBasedControl,
216 reportToTCSupportsPeerControlAssignment,
217 reportToTCSupportsImplementSectionControl);
218 }
219 else
220 {
221 // We don't want someone to erase our object pool or something while it is being used.
222 LOG_ERROR("[TC]: Cannot reconfigure TC client while it is running!");
223 }
224 }
225
227 {
228 if (initialized)
229 {
230 LOCK_GUARD(Mutex, clientMutex);
232 }
233 }
234
236 {
237 if (initialized)
238 {
239 if (nullptr != partnerControlFunction)
240 {
241 partnerControlFunction->remove_parameter_group_number_callback(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData), process_rx_message, this);
242 partnerControlFunction->remove_parameter_group_number_callback(static_cast<std::uint32_t>(CANLibParameterGroupNumber::Acknowledge), process_rx_message, this);
243 CANNetworkManager::CANNetwork.remove_global_parameter_group_number_callback(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData), process_rx_message, this);
244 }
245
246 shouldTerminate = true;
247
248#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
249 if ((nullptr != workerThread) && (workerThread->get_id() != std::this_thread::get_id()))
250 {
251 workerThread->join();
252 delete workerThread;
253 workerThread = nullptr;
254 }
255#endif
256 }
257 }
258
259 std::shared_ptr<InternalControlFunction> TaskControllerClient::get_internal_control_function() const
260 {
261 return myControlFunction;
262 }
263
264 std::shared_ptr<PartneredControlFunction> TaskControllerClient::get_partner_control_function() const
265 {
267 }
268
270 {
272 }
273
278
283
288
293
298
303
308
310 {
311 return initialized;
312 }
313
318
320 {
321 return (get_is_connected() && (0 != (0x01 & tcStatusBitfield)));
322 }
323
324 bool TaskControllerClient::reupload_device_descriptor_object_pool(std::shared_ptr<std::vector<std::uint8_t>> binaryDDOP)
325 {
326 bool retVal = false;
327 LOCK_GUARD(Mutex, clientMutex);
328
330 {
331 assert(nullptr != binaryDDOP); // Client will not work without a DDOP.
332 assert(!binaryDDOP->empty()); // Client will not work without a DDOP.
333 ddopStructureLabel.clear();
334 generatedBinaryDDOP.clear();
335 ddopLocalizationLabel.fill(0x00);
336 userSuppliedVectorDDOP = binaryDDOP;
338 userSuppliedBinaryDDOP = nullptr;
342 clear_queues();
343 retVal = true;
344 LOG_INFO("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while.");
345 }
346 return retVal;
347 }
348
349 bool TaskControllerClient::reupload_device_descriptor_object_pool(std::uint8_t const *binaryDDOP, std::uint32_t DDOPSize)
350 {
351 bool retVal = false;
352 LOCK_GUARD(Mutex, clientMutex);
353
355 {
356 assert(nullptr != binaryDDOP); // Client will not work without a DDOP.
357 assert(0 != DDOPSize);
358 generatedBinaryDDOP.clear();
359 ddopStructureLabel.clear();
360 userSuppliedVectorDDOP = nullptr;
361 ddopLocalizationLabel.fill(0x00);
363 userSuppliedBinaryDDOP = binaryDDOP;
367 clear_queues();
368 retVal = true;
369 LOG_INFO("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while.");
370 }
371 return retVal;
372 }
373
374 bool TaskControllerClient::reupload_device_descriptor_object_pool(std::shared_ptr<DeviceDescriptorObjectPool> DDOP)
375 {
376 bool retVal = false;
377 LOCK_GUARD(Mutex, clientMutex);
378
380 {
381 assert(nullptr != DDOP); // Client will not work without a DDOP.
382 generatedBinaryDDOP.clear();
383 ddopStructureLabel.clear();
384 userSuppliedVectorDDOP = nullptr;
385 ddopLocalizationLabel.fill(0x00);
387 clientDDOP = DDOP;
388 userSuppliedBinaryDDOP = nullptr;
392 clear_queues();
393 retVal = true;
394 LOG_INFO("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while.");
395 }
396 return retVal;
397 }
398
400 {
401 switch (currentState)
402 {
404 {
405 enableStatusMessage = false;
407
409 {
411 }
412 }
413 break;
414
416 {
417 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, SIX_SECOND_TIMEOUT_MS))
418 {
419 LOG_DEBUG("[TC]: Startup delay complete, waiting for TC server status message.");
421 }
422 }
423 break;
424
426 {
428 {
430 }
431 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
432 {
433 LOG_ERROR("[TC]: Timeout sending working set master message. Resetting client connection.");
435 }
436 }
437 break;
438
440 {
441 // Start sending the status message
442 if (send_status())
443 {
444 enableStatusMessage = true;
445 statusMessageTimestamp_ms = SystemTiming::get_timestamp_ms();
447 }
448 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
449 {
450 LOG_ERROR("[TC]: Timeout sending first status message. Resetting client connection.");
452 }
453 }
454 break;
455
457 {
459 {
461 }
462 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
463 {
464 LOG_ERROR("[TC]: Timeout sending version request message. Resetting client connection.");
466 }
467 }
468 break;
469
471 {
472 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
473 {
474 LOG_ERROR("[TC]: Timeout waiting for version request response. Resetting client connection.");
476 }
477 }
478 break;
479
481 {
482 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, SIX_SECOND_TIMEOUT_MS))
483 {
484 LOG_WARNING("[TC]: Timeout waiting for version request from TC. This is not required, so proceeding anways.");
487 }
488 }
489 break;
490
492 {
494 {
497 }
498 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
499 {
500 LOG_ERROR("[TC]: Timeout sending version request response. Resetting client connection.");
502 }
503 }
504 break;
505
507 {
509 {
511 languageCommandWaitingTimestamp_ms = SystemTiming::get_timestamp_ms();
512 }
513 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, SIX_SECOND_TIMEOUT_MS))
514 {
515 LOG_ERROR("[TC]: Timeout trying to send request for language command message. Resetting client connection.");
517 }
518 }
519 break;
520
522 {
523 if ((SystemTiming::get_time_elapsed_ms(languageCommandInterface.get_language_command_timestamp()) < TWO_SECOND_TIMEOUT_MS) &&
525 {
527 }
528 else if (SystemTiming::time_expired_ms(languageCommandWaitingTimestamp_ms, SIX_SECOND_TIMEOUT_MS))
529 {
530 LOG_WARNING("[TC]: Timeout waiting for language response. Moving on to processing the DDOP anyways.");
532 }
533 else if ((SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) &&
535 {
536 LOG_WARNING("[TC]: No response to our request for the language command data, which is unusual.");
537
538 if (nullptr != primaryVirtualTerminal)
539 {
540 LOG_WARNING("[TC]: Falling back to VT for language data.");
543 stateMachineTimestamp_ms = SystemTiming::get_timestamp_ms();
544 }
545 else
546 {
547 LOG_WARNING("[TC]: Since no VT was specified, falling back to a global request for language data.");
550 stateMachineTimestamp_ms = SystemTiming::get_timestamp_ms();
551 }
552 }
553 }
554 break;
555
557 {
559 {
560 assert(0 != clientDDOP->size()); // Need to have a valid object pool!
561
562 if (serverVersion < clientDDOP->get_task_controller_compatibility_level())
563 {
564 clientDDOP->set_task_controller_compatibility_level(serverVersion); // Manipulate the DDOP slightly if needed to upload a version compatible DDOP
565 LOG_INFO("[TC]: DDOP will be generated using the server's version instead of the specified version. New version: " +
566 isobus::to_string(static_cast<int>(serverVersion)));
567 }
568
569 if (generatedBinaryDDOP.empty())
570 {
571 // Binary DDOP has not been generated before.
572 if (clientDDOP->generate_binary_object_pool(generatedBinaryDDOP))
573 {
575 LOG_DEBUG("[TC]: DDOP Generated, size: " + isobus::to_string(static_cast<int>(generatedBinaryDDOP.size())));
576
578 {
579 LOG_ERROR("[TC]: You didn't properly update your new DDOP's structure label. ISO11783-10 states that an update to an object pool must include an updated structure label.");
580 }
582
584 }
585 else
586 {
587 LOG_ERROR("[TC]: Cannot proceed with connection to TC due to invalid DDOP. Check log for [DDOP] events. TC client will now terminate.");
588 terminate();
589 }
590 }
591 else
592 {
593 LOG_DEBUG("[TC]: Using previously generated DDOP binary");
595 }
596 }
597 else
598 {
599 if ((ddopLocalizationLabel.empty()) ||
600 (ddopStructureLabel.empty()))
601 {
602 LOG_DEBUG("[TC]: Beginning a search of pre-serialized DDOP for device structure and localization labels.");
604
605 if ((ddopLocalizationLabel.empty()) ||
606 (ddopStructureLabel.empty()))
607 {
608 LOG_ERROR("[TC]: Failed to parse the DDOP. Ensure you provided a valid device object. TC client will now terminate.");
609 terminate();
610 }
611 }
612 else
613 {
614 LOG_DEBUG("[TC]: Reusing previously located device labels.");
615 }
617 }
618 }
619 break;
620
622 {
624 {
626 }
627 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
628 {
629 LOG_ERROR("[TC]: Timeout trying to send request for TC structure label. Resetting client connection.");
631 }
632 }
633 break;
634
636 {
637 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
638 {
639 LOG_ERROR("[TC]: Timeout waiting for TC structure label. Resetting client connection.");
641 }
642 }
643 break;
644
646 {
648 {
650 }
651 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
652 {
653 LOG_ERROR("[TC]: Timeout trying to send request for TC localization label. Resetting client connection.");
655 }
656 }
657 break;
658
660 {
661 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
662 {
663 LOG_ERROR("[TC]: Timeout waiting for TC localization label. Resetting client connection.");
665 }
666 }
667 break;
668
670 {
672 {
674 }
675 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
676 {
677 LOG_ERROR("[TC]: Timeout trying to send delete object pool message. Resetting client connection.");
679 }
680 }
681 break;
682
684 {
685 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
686 {
687 LOG_ERROR("[TC]: Timeout waiting for delete object pool response. Resetting client connection.");
689 }
690 }
691 break;
692
694 {
696 {
698 }
699 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
700 {
701 LOG_ERROR("[TC]: Timeout trying to send request to transfer object pool. Resetting client connection.");
703 }
704 }
705 break;
706
708 {
709 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
710 {
711 LOG_ERROR("[TC]: Timeout waiting for request transfer object pool response. Resetting client connection.");
713 }
714 }
715 break;
716
718 {
719 bool transmitSuccessful = false;
720 std::uint32_t dataLength = 0;
721
722 switch (ddopUploadMode)
723 {
725 {
726 dataLength = static_cast<std::uint32_t>(generatedBinaryDDOP.size() + 1); // Account for Mux byte
727 }
728 break;
729
731 {
732 dataLength = static_cast<std::uint32_t>(userSuppliedBinaryDDOPSize_bytes + 1);
733 }
734 break;
735
737 {
738 dataLength = static_cast<std::uint32_t>(userSuppliedVectorDDOP->size() + 1);
739 }
740 break;
741
742 default:
743 break;
744 }
745
746 assert(0 != dataLength);
747 transmitSuccessful = CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
748 nullptr,
749 dataLength,
754 this,
756 if (transmitSuccessful)
757 {
759 }
760 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
761 {
762 LOG_ERROR("[TC]: Timeout trying to begin the object pool upload. Resetting client connection.");
764 }
765 }
766 break;
767
770 {
771 // Waiting...
772 }
773 break;
774
776 {
777 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
778 {
779 LOG_ERROR("[TC]: Timeout waiting for object pool transfer response. Resetting client connection.");
781 }
782 }
783 break;
784
786 {
788 {
790 }
791 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
792 {
793 LOG_ERROR("[TC]: Timeout trying to activate object pool. Resetting client connection.");
795 }
796 }
797 break;
798
800 {
801 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
802 {
803 LOG_ERROR("[TC]: Timeout waiting for activate object pool response. Resetting client connection.");
805 }
806 }
807 break;
808
810 {
811 if (SystemTiming::time_expired_ms(serverStatusMessageTimestamp_ms, SIX_SECOND_TIMEOUT_MS))
812 {
813 LOG_ERROR("[TC]: Server Status Message Timeout. The TC may be offline.");
815 }
816 else
817 {
820 }
821 }
822 break;
823
825 {
827 {
829 }
830 else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
831 {
832 LOG_ERROR("[TC]: Timeout sending object pool deactivate. Client terminated.");
834 terminate();
835 }
836 }
837 break;
838
840 {
841 if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS))
842 {
844 {
845 LOG_WARNING("[TC]: Timeout waiting for deactivate object pool response. This is unusual, but we're just going to reconnect anyways.");
848 }
849 else
850 {
851 LOG_ERROR("[TC]: Timeout waiting for deactivate object pool response. Client terminated.");
853 terminate();
854 }
855 }
856 }
857 break;
858
859 default:
860 {
861 assert(false); // Unknown state? File a bug on GitHub if you see this happen.
862 }
863 break;
864 }
865
866 if ((enableStatusMessage) &&
867 (SystemTiming::time_expired_ms(statusMessageTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) &&
868 (send_status()))
869 {
870 statusMessageTimestamp_ms = SystemTiming::get_timestamp_ms();
871 }
872 }
873
875 {
876 return ((obj.ddi == this->ddi) && (obj.elementNumber == this->elementNumber));
877 }
878
880 {
881 return (obj.callback == this->callback) && (obj.parent == this->parent);
882 }
883
885 {
886 return (obj.callback == this->callback) && (obj.parent == this->parent);
887 }
888
898
900 {
901 bool retVal = false;
902
903 switch (ddopUploadMode)
904 {
906 {
907 retVal = (nullptr != clientDDOP);
908 }
909 break;
910
912 {
913 retVal = (nullptr != userSuppliedBinaryDDOP) &&
915 }
916 break;
917
919 {
920 retVal = (nullptr != userSuppliedVectorDDOP) &&
921 (!userSuppliedVectorDDOP->empty());
922 }
923 break;
924
925 default:
926 break;
927 }
928 return retVal;
929 }
930
932 {
933 std::uint32_t currentByteIndex = 0;
934 const std::string DEVICE_TABLE_ID = "DVC";
935 constexpr std::uint8_t DESIGNATOR_BYTE_OFFSET = 5;
936 constexpr std::uint8_t CLIENT_NAME_LENGTH = 8;
937
938 switch (ddopUploadMode)
939 {
941 {
942 assert(nullptr != clientDDOP); // You need a DDOP
943 // Does your DDOP have a device object? Device object 0 is required by ISO11783-10
944 auto deviceObject = clientDDOP->get_object_by_id(0);
945 assert(nullptr != deviceObject);
946 assert(task_controller_object::ObjectTypes::Device == deviceObject->get_object_type());
947
948 ddopStructureLabel = std::static_pointer_cast<task_controller_object::DeviceObject>(deviceObject)->get_structure_label();
949
951 {
952 ddopStructureLabel.push_back(' ');
953 }
954
955 ddopLocalizationLabel = std::static_pointer_cast<task_controller_object::DeviceObject>(deviceObject)->get_localization_label();
956 }
957 break;
958
961 {
962 auto getDDOPSize = [this]() {
964 {
966 }
967 else
968 {
969 return static_cast<std::uint32_t>(userSuppliedVectorDDOP->size());
970 }
971 };
972
973 auto getDDOPByteAt = [this](std::size_t index) {
975 {
976 return userSuppliedBinaryDDOP[index];
977 }
978 else
979 {
980 return userSuppliedVectorDDOP->at(index);
981 }
982 };
983 // Searching for "DVC"
984 while (currentByteIndex < (getDDOPSize() - DEVICE_TABLE_ID.size()))
985 {
986 if ((DEVICE_TABLE_ID[0] == getDDOPByteAt(currentByteIndex)) &&
987 (DEVICE_TABLE_ID[1] == getDDOPByteAt(currentByteIndex + 1)) &&
988 (DEVICE_TABLE_ID[2] == getDDOPByteAt(currentByteIndex + 2)))
989 {
990 // We have to do a lot of error checking on the DDOP length
991 // This is because we don't control the content of this DDOP, and have no
992 // assurances that the schema is even valid
993
994 assert((currentByteIndex + DESIGNATOR_BYTE_OFFSET) < getDDOPSize()); // Not enough bytes to read the designator length
995 currentByteIndex += DESIGNATOR_BYTE_OFFSET; // Skip to the next variable length part of the object
996
997 const std::uint32_t DESIGNATOR_LENGTH = getDDOPByteAt(currentByteIndex); // "N", See Table A.1
998 assert(currentByteIndex + DESIGNATOR_LENGTH < getDDOPSize()); // Not enough bytes in your DDOP!
999 currentByteIndex += DESIGNATOR_LENGTH + 1;
1000
1001 const std::uint32_t SOFTWARE_VERSION_LENGTH = getDDOPByteAt(currentByteIndex); // "M", See Table A.1
1002 assert(currentByteIndex + SOFTWARE_VERSION_LENGTH + CLIENT_NAME_LENGTH < getDDOPSize()); // Not enough bytes in your DDOP!
1003 currentByteIndex += SOFTWARE_VERSION_LENGTH + CLIENT_NAME_LENGTH + 1;
1004
1005 const std::uint32_t SERIAL_NUMBER_LENGTH = getDDOPByteAt(currentByteIndex); // "O", See Table A.1
1006 assert(currentByteIndex + SERIAL_NUMBER_LENGTH < getDDOPSize()); // Not enough bytes in your DDOP!
1007 currentByteIndex += SERIAL_NUMBER_LENGTH + 1;
1008
1009 assert(currentByteIndex + task_controller_object::DeviceObject::MAX_STRUCTURE_AND_LOCALIZATION_LABEL_LENGTH < getDDOPSize()); // // Not enough bytes in your DDOP!
1011 {
1012 ddopStructureLabel.push_back(getDDOPByteAt(currentByteIndex + i)); // Read the descriptor
1013 }
1014
1016 assert(currentByteIndex + task_controller_object::DeviceObject::MAX_STRUCTURE_AND_LOCALIZATION_LABEL_LENGTH < getDDOPSize()); // // Not enough bytes in your DDOP!
1018 {
1019 ddopLocalizationLabel[i] = (getDDOPByteAt(currentByteIndex + i)); // Read the localization label
1020 }
1021 break;
1022 }
1023 }
1024 }
1025 break;
1026
1027 default:
1028 break;
1029 }
1030 }
1031
1033 {
1034 LOCK_GUARD(Mutex, clientMutex);
1035 bool transmitSuccessful = true;
1036
1037 while (!queuedValueRequests.empty() && transmitSuccessful)
1038 {
1039 const auto &currentRequest = queuedValueRequests.front();
1040
1041 for (auto &currentCallback : requestValueCallbacks)
1042 {
1043 std::int32_t newValue = 0;
1044 if (currentCallback.callback(currentRequest.elementNumber, currentRequest.ddi, newValue, currentCallback.parent))
1045 {
1046 transmitSuccessful = send_value_command(currentRequest.elementNumber, currentRequest.ddi, newValue);
1047 break;
1048 }
1049 }
1050 queuedValueRequests.pop_front();
1051 }
1052 while (!queuedValueCommands.empty() && transmitSuccessful)
1053 {
1054 const auto &currentRequest = queuedValueCommands.front();
1055
1056 for (auto &currentCallback : valueCommandsCallbacks)
1057 {
1058 if (currentCallback.callback(currentRequest.elementNumber, currentRequest.ddi, currentRequest.processDataValue, currentCallback.parent))
1059 {
1060 break;
1061 }
1062 }
1063 queuedValueCommands.pop_front();
1064
1066 if (currentRequest.ackRequested)
1067 {
1068 transmitSuccessful = send_pdack(currentRequest.elementNumber, currentRequest.ddi);
1069 }
1070 }
1071 }
1072
1074 {
1075 bool transmitSuccessful = false;
1076
1077 for (auto &measurementTimeCommand : measurementTimeIntervalCommands)
1078 {
1079 if (SystemTiming::time_expired_ms(static_cast<std::uint32_t>(measurementTimeCommand.lastValue), static_cast<std::uint32_t>(measurementTimeCommand.processDataValue)))
1080 {
1081 // Time to update this time interval variable
1082 transmitSuccessful = false;
1083 for (auto &currentCallback : requestValueCallbacks)
1084 {
1085 std::int32_t newValue = 0;
1086 if (currentCallback.callback(measurementTimeCommand.elementNumber, measurementTimeCommand.ddi, newValue, currentCallback.parent))
1087 {
1088 transmitSuccessful = send_value_command(measurementTimeCommand.elementNumber, measurementTimeCommand.ddi, newValue);
1089 break;
1090 }
1091 }
1092
1093 if (transmitSuccessful)
1094 {
1095 measurementTimeCommand.lastValue = static_cast<std::int32_t>(SystemTiming::get_timestamp_ms());
1096 }
1097 }
1098 }
1099 for (auto &measurementMaxCommand : measurementMaximumThresholdCommands)
1100 {
1101 // Get the current process data value
1102 std::int32_t newValue = 0;
1103 for (auto &currentCallback : requestValueCallbacks)
1104 {
1105 if (currentCallback.callback(measurementMaxCommand.elementNumber, measurementMaxCommand.ddi, newValue, currentCallback.parent))
1106 {
1107 break;
1108 }
1109 }
1110
1111 if (!measurementMaxCommand.thresholdPassed)
1112 {
1113 if ((newValue > measurementMaxCommand.processDataValue) &&
1114 (send_value_command(measurementMaxCommand.elementNumber, measurementMaxCommand.ddi, newValue)))
1115 {
1116 measurementMaxCommand.thresholdPassed = true;
1117 }
1118 }
1119 else
1120 {
1121 if (newValue < measurementMaxCommand.processDataValue)
1122 {
1123 measurementMaxCommand.thresholdPassed = false;
1124 }
1125 }
1126 }
1127 for (auto &measurementMinCommand : measurementMinimumThresholdCommands)
1128 {
1129 // Get the current process data value
1130 std::int32_t newValue = 0;
1131 for (auto &currentCallback : requestValueCallbacks)
1132 {
1133 if (currentCallback.callback(measurementMinCommand.elementNumber, measurementMinCommand.ddi, newValue, currentCallback.parent))
1134 {
1135 break;
1136 }
1137 }
1138
1139 if (!measurementMinCommand.thresholdPassed)
1140 {
1141 if ((newValue < measurementMinCommand.processDataValue) &&
1142 (send_value_command(measurementMinCommand.elementNumber, measurementMinCommand.ddi, newValue)))
1143 {
1144 measurementMinCommand.thresholdPassed = true;
1145 }
1146 }
1147 else
1148 {
1149 if (newValue > measurementMinCommand.processDataValue)
1150 {
1151 measurementMinCommand.thresholdPassed = false;
1152 }
1153 }
1154 }
1155 for (auto &measurementChangeCommand : measurementOnChangeThresholdCommands)
1156 {
1157 // Get the current process data value
1158 std::int32_t newValue = 0;
1159 for (auto &currentCallback : requestValueCallbacks)
1160 {
1161 if (currentCallback.callback(measurementChangeCommand.elementNumber, measurementChangeCommand.ddi, newValue, currentCallback.parent))
1162 {
1163 break;
1164 }
1165 }
1166
1167 std::int64_t lowerLimit = (static_cast<int64_t>(measurementChangeCommand.lastValue) - measurementChangeCommand.processDataValue);
1168 if (lowerLimit < 0)
1169 {
1170 lowerLimit = 0;
1171 }
1172
1173 if ((newValue != measurementChangeCommand.lastValue) &&
1174 ((newValue >= (measurementChangeCommand.lastValue + measurementChangeCommand.processDataValue)) ||
1175 (newValue <= lowerLimit)))
1176 {
1177 if (send_value_command(measurementChangeCommand.elementNumber, measurementChangeCommand.ddi, newValue))
1178 {
1179 measurementChangeCommand.lastValue = newValue;
1180 }
1181 }
1182 }
1183 }
1184
1185 void TaskControllerClient::process_rx_message(const CANMessage &message, void *parentPointer)
1186 {
1187 if ((nullptr != parentPointer) &&
1188 (CAN_DATA_LENGTH <= message.get_data_length()) &&
1189 (nullptr != message.get_source_control_function()))
1190 {
1191 auto parentTC = static_cast<TaskControllerClient *>(parentPointer);
1192 auto &clientMutex = parentTC->clientMutex;
1193 const auto &messageData = message.get_data();
1194
1195 switch (message.get_identifier().get_parameter_group_number())
1196 {
1197 case static_cast<std::uint32_t>(CANLibParameterGroupNumber::Acknowledge):
1198 {
1199 if (AcknowledgementType::Negative == static_cast<AcknowledgementType>(message.get_uint8_at(0)))
1200 {
1201 std::uint32_t targetParameterGroupNumber = message.get_uint24_at(5);
1202 if (static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData) == targetParameterGroupNumber)
1203 {
1204 LOG_ERROR("[TC]: The TC Server is NACK-ing our messages. Disconnecting.");
1205 parentTC->set_state(StateMachineState::Disconnected);
1206 }
1207 }
1208 }
1209 break;
1210
1211 case static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData):
1212 {
1213 switch (static_cast<ProcessDataCommands>(messageData[0] & 0x0F))
1214 {
1216 {
1217 switch (static_cast<TechnicalDataMessageCommands>(messageData[0] >> 4))
1218 {
1220 {
1221 if (StateMachineState::WaitForRequestVersionFromServer == parentTC->get_state())
1222 {
1224 }
1225 else
1226 {
1227 LOG_WARNING("[TC]: Server requested version information at a strange time.");
1228 }
1229 }
1230 break;
1231
1233 {
1234 parentTC->serverVersion = messageData[1];
1235 parentTC->maxServerBootTime_s = messageData[2];
1236 parentTC->serverOptionsByte1 = messageData[3];
1237 parentTC->serverOptionsByte2 = messageData[4];
1238 parentTC->serverNumberOfBoomsForSectionControl = messageData[5];
1239 parentTC->serverNumberOfSectionsForSectionControl = messageData[6];
1240 parentTC->serverNumberOfChannelsForPositionBasedControl = messageData[7];
1241
1242 if (messageData[1] > static_cast<std::uint8_t>(Version::SecondPublishedEdition))
1243 {
1244 LOG_WARNING("[TC]: Server version is newer than client's maximum supported version.");
1245 }
1246 LOG_DEBUG("[TC]: TC Server supports version %u with %u booms, %u sections, and %u position based control channels.",
1247 messageData[1],
1248 messageData[5],
1249 messageData[6],
1250 messageData[7]);
1251
1252 if (StateMachineState::WaitForRequestVersionResponse == parentTC->get_state())
1253 {
1255 }
1256 }
1257 break;
1258
1259 default:
1260 {
1261 LOG_WARNING("[TC]: Unsupported process data technical data message received. Message will be dropped.");
1262 }
1263 break;
1264 }
1265 }
1266 break;
1267
1269 {
1270 switch (static_cast<DeviceDescriptorCommands>(messageData[0] >> 4))
1271 {
1273 {
1274 if (StateMachineState::WaitForStructureLabelResponse == parentTC->get_state())
1275 {
1276 if ((0xFF == messageData[1]) &&
1277 (0xFF == messageData[2]) &&
1278 (0xFF == messageData[3]) &&
1279 (0xFF == messageData[4]) &&
1280 (0xFF == messageData[5]) &&
1281 (0xFF == messageData[6]) &&
1282 (0xFF == messageData[7]) &&
1283 (CAN_DATA_LENGTH == messageData.size()))
1284 {
1285 // TC has no structure label for us. Need to upload the DDOP.
1287 }
1288 else
1289 {
1290 std::string tcStructure;
1291
1292 for (std::size_t i = 1; i < messageData.size(); i++)
1293 {
1294 tcStructure.push_back(messageData[i]);
1295 }
1296
1297 if (tcStructure.size() > 40)
1298 {
1299 LOG_WARNING("[TC]: Structure Label from TC exceeds the max length allowed by ISO11783-10");
1300 }
1301
1302 if (parentTC->ddopStructureLabel == tcStructure)
1303 {
1304 // Structure label matched. No upload needed yet.
1305 LOG_DEBUG("[TC]: Task controller structure labels match");
1307 }
1308 else
1309 {
1310 // Structure label did not match. Need to delete current DDOP and re-upload.
1311 LOG_INFO("[TC]: Task controller structure labels do not match. DDOP will be deleted and reuploaded.");
1312 parentTC->set_state(StateMachineState::SendDeleteObjectPool);
1313 }
1314 }
1315 }
1316 else
1317 {
1318 LOG_WARNING("[TC]: Structure label message received, but ignored due to current state machine state.");
1319 }
1320 }
1321 break;
1322
1324 {
1325 // Right now, we'll just reload the pool if the localization doesn't match, but
1326 // in the future we should permit modifications to the localization and DVP objects
1328 if (StateMachineState::WaitForLocalizationLabelResponse == parentTC->get_state())
1329 {
1330 if ((0xFF == messageData[1]) &&
1331 (0xFF == messageData[2]) &&
1332 (0xFF == messageData[3]) &&
1333 (0xFF == messageData[4]) &&
1334 (0xFF == messageData[5]) &&
1335 (0xFF == messageData[6]) &&
1336 (0xFF == messageData[7]) &&
1337 (CAN_DATA_LENGTH == messageData.size()))
1338 {
1339 // TC has no localization label for us. Need to upload the DDOP.
1341 }
1342 else
1343 {
1344 assert(7 == parentTC->ddopLocalizationLabel.size()); // Make sure the DDOP is valid before we access the label. It must be 7 bytes
1345 bool labelsMatch = true;
1346
1347 for (std::uint_fast8_t i = 0; i < (CAN_DATA_LENGTH - 1); i++)
1348 {
1349 if (messageData[i + 1] != parentTC->ddopLocalizationLabel[i])
1350 {
1351 labelsMatch = false;
1352 break;
1353 }
1354 }
1355
1356 if (labelsMatch)
1357 {
1358 // DDOP labels all matched
1359 LOG_DEBUG("[TC]: Task controller localization labels match");
1360 parentTC->set_state(StateMachineState::SendObjectPoolActivate);
1361 }
1362 else
1363 {
1364 // Labels didn't match. Reupload
1365 LOG_INFO("[TC]: Task controller localization labels do not match. DDOP will be deleted and reuploaded.");
1366 parentTC->set_state(StateMachineState::SendDeleteObjectPool);
1367 }
1368 }
1369 }
1370 else
1371 {
1372 LOG_WARNING("[TC]: Localization label message received, but ignored due to current state machine state.");
1373 }
1374 }
1375 break;
1376
1378 {
1380 {
1381 if (0 == messageData[1])
1382 {
1383 // Because there is overhead associated with object storage, it is impossible to predict whether there is enough memory available, technically.
1384 LOG_DEBUG("[TC]: Server indicates there may be enough memory available.");
1385 parentTC->set_state(StateMachineState::BeginTransferDDOP);
1386 }
1387 else
1388 {
1389 LOG_ERROR("[TC]: Server states that there is not enough memory available for our DDOP. Client will terminate.");
1390 parentTC->terminate();
1391 }
1392 }
1393 else
1394 {
1395 LOG_WARNING("[TC]: Request Object-pool Transfer Response message received, but ignored due to current state machine state.");
1396 }
1397 }
1398 break;
1399
1401 {
1402 if (StateMachineState::WaitForObjectPoolActivateResponse == parentTC->get_state())
1403 {
1404 if (0 == messageData[1])
1405 {
1406 LOG_INFO("[TC]: DDOP Activated without error.");
1407 parentTC->set_state(StateMachineState::Connected);
1408 }
1409 else
1410 {
1411 LOG_ERROR("[TC]: DDOP was not activated.");
1412 if (0x01 & messageData[1])
1413 {
1414 LOG_ERROR("[TC]: There are errors in the DDOP. Faulting parent ID: " +
1415 isobus::to_string(static_cast<int>(static_cast<std::uint16_t>(messageData[2]) |
1416 static_cast<std::uint16_t>(messageData[3] << 8))) +
1417 " Faulting object: " +
1418 isobus::to_string(static_cast<int>(static_cast<std::uint16_t>(messageData[4]) |
1419 static_cast<std::uint16_t>(messageData[5] << 8))));
1420 if (0x01 & messageData[6])
1421 {
1422 LOG_ERROR("[TC]: Method or attribute not supported by the TC");
1423 }
1424 if (0x02 & messageData[6])
1425 {
1426 LOG_ERROR("[TC]: Unknown object reference (missing object)");
1427 }
1428 if (0x04 & messageData[6])
1429 {
1430 LOG_ERROR("[TC]: Unknown error (Any other error)");
1431 }
1432 if (0x08 & messageData[6])
1433 {
1434 LOG_ERROR("[TC]: Device descriptor object pool was deleted from volatile memory");
1435 }
1436 if (0xF0 & messageData[6])
1437 {
1438 LOG_WARNING("[TC]: The TC sent illegal errors in the reserved bits of the response.");
1439 }
1440 }
1441 if (0x02 & messageData[1])
1442 {
1443 LOG_ERROR("[TC]: Task Controller ran out of memory during activation.");
1444 }
1445 if (0x04 & messageData[1])
1446 {
1447 LOG_ERROR("[TC]: Task Controller indicates an unknown error occurred.");
1448 }
1449 if (0x08 & messageData[1])
1450 {
1451 LOG_ERROR("[TC]: A different DDOP with the same structure label already exists in the TC.");
1452 }
1453 if (0xF0 & messageData[1])
1454 {
1455 LOG_WARNING("[TC]: The TC sent illegal errors in the reserved bits of the response.");
1456 }
1457 parentTC->set_state(StateMachineState::Disconnected);
1458 LOG_ERROR("[TC]: Client terminated.");
1459 parentTC->terminate();
1460 }
1461 }
1462 else if (StateMachineState::WaitForObjectPoolDeactivateResponse == parentTC->get_state())
1463 {
1464 if (0 == messageData[1])
1465 {
1466 LOG_INFO("[TC]: Object pool deactivated OK.");
1467
1468 if (parentTC->shouldReuploadAfterDDOPDeletion)
1469 {
1470 parentTC->set_state(StateMachineState::SendDeleteObjectPool);
1471 }
1472 }
1473 else
1474 {
1475 LOG_ERROR("[TC]: Object pool deactivation error.");
1476 }
1477 }
1478 else
1479 {
1480 LOG_WARNING("[TC]: Object pool activate/deactivate response received at a strange time. Message dropped.");
1481 }
1482 }
1483 break;
1484
1486 {
1487 // Message content of this is unreliable, the standard is ambiguous on what to even check.
1488 // Plus, if the delete failed, the recourse is the same, always proceed.
1489 if (StateMachineState::WaitForDeleteObjectPoolResponse == parentTC->get_state())
1490 {
1492 }
1493 }
1494 break;
1495
1497 {
1498 if (StateMachineState::WaitForObjectPoolTransferResponse == parentTC->get_state())
1499 {
1500 if (0 == messageData[1])
1501 {
1502 LOG_DEBUG("[TC]: DDOP upload completed with no errors.");
1503 parentTC->set_state(StateMachineState::SendObjectPoolActivate);
1504 }
1505 else
1506 {
1507 if (0x01 == messageData[1])
1508 {
1509 LOG_ERROR("[TC]: DDOP upload completed but TC ran out of memory during transfer.");
1510 }
1511 else
1512 {
1513 LOG_ERROR("[TC]: DDOP upload completed but TC had some unknown error.");
1514 }
1515 LOG_ERROR("[TC]: Client terminated.");
1516 parentTC->terminate();
1517 }
1518 }
1519 else
1520 {
1521 LOG_WARNING("[TC]: Recieved unexpected object pool transfer response");
1522 }
1523 }
1524 break;
1525
1526 default:
1527 {
1528 LOG_WARNING("[TC]: Unsupported device descriptor command message received. Message will be dropped.");
1529 }
1530 break;
1531 }
1532 }
1533 break;
1534
1536 {
1537 if (parentTC->partnerControlFunction->get_NAME() == message.get_source_control_function()->get_NAME())
1538 {
1539 // Many values in the status message were undefined in version 2 and before, so the
1540 // standard explicitly tells us to ignore those attributes. The only things that really
1541 // matter are that we got the mesesage, and bytes 5, 6 and 7.
1542 parentTC->tcStatusBitfield = messageData[4];
1543 parentTC->sourceAddressOfCommandBeingExecuted = messageData[5];
1544 parentTC->commandBeingExecuted = messageData[6];
1545 parentTC->serverStatusMessageTimestamp_ms = SystemTiming::get_timestamp_ms();
1546 if (StateMachineState::WaitForServerStatusMessage == parentTC->currentState)
1547 {
1548 parentTC->set_state(StateMachineState::SendWorkingSetMaster);
1549 }
1550 }
1551 }
1552 break;
1553
1555 {
1556 LOG_WARNING("[TC]: Server sent the client task message, which is not meant to be sent by servers.");
1557 }
1558 break;
1559
1561 {
1562 ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false };
1563 LOCK_GUARD(Mutex, clientMutex);
1564
1565 requestData.ackRequested = false;
1566 requestData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1567 requestData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1568 (static_cast<std::uint16_t>(messageData[3]) << 8);
1569 requestData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1570 (static_cast<std::int32_t>(messageData[5]) << 8) |
1571 (static_cast<std::int32_t>(messageData[6]) << 16) |
1572 (static_cast<std::int32_t>(messageData[7]) << 24));
1573 parentTC->queuedValueRequests.push_back(requestData);
1574 }
1575 break;
1576
1578 {
1579 ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false };
1580 LOCK_GUARD(Mutex, clientMutex);
1581
1582 requestData.ackRequested = false;
1583 requestData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1584 requestData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1585 (static_cast<std::uint16_t>(messageData[3]) << 8);
1586 requestData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1587 (static_cast<std::int32_t>(messageData[5]) << 8) |
1588 (static_cast<std::int32_t>(messageData[6]) << 16) |
1589 (static_cast<std::int32_t>(messageData[7]) << 24));
1590 parentTC->queuedValueCommands.push_back(requestData);
1591 }
1592 break;
1593
1595 {
1596 ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false };
1597 LOCK_GUARD(Mutex, clientMutex);
1598
1599 requestData.ackRequested = true;
1600 requestData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1601 requestData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1602 (static_cast<std::uint16_t>(messageData[3]) << 8);
1603 requestData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1604 (static_cast<std::int32_t>(messageData[5]) << 8) |
1605 (static_cast<std::int32_t>(messageData[6]) << 16) |
1606 (static_cast<std::int32_t>(messageData[7]) << 24));
1607 parentTC->queuedValueCommands.push_back(requestData);
1608 }
1609 break;
1610
1612 {
1613 ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false };
1614 LOCK_GUARD(Mutex, clientMutex);
1615
1616 commandData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1617 commandData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1618 (static_cast<std::uint16_t>(messageData[3]) << 8);
1619 commandData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1620 (static_cast<std::int32_t>(messageData[5]) << 8) |
1621 (static_cast<std::int32_t>(messageData[6]) << 16) |
1622 (static_cast<std::int32_t>(messageData[7]) << 24));
1623 commandData.lastValue = static_cast<std::int32_t>(SystemTiming::get_timestamp_ms());
1624
1625 auto previousCommand = std::find(parentTC->measurementTimeIntervalCommands.begin(), parentTC->measurementTimeIntervalCommands.end(), commandData);
1626 if (parentTC->measurementTimeIntervalCommands.end() == previousCommand)
1627 {
1628 parentTC->measurementTimeIntervalCommands.push_back(commandData);
1629 LOG_DEBUG("[TC]: TC Requests element: " +
1630 isobus::to_string(static_cast<int>(commandData.elementNumber)) +
1631 " DDI: " +
1632 isobus::to_string(static_cast<int>(commandData.ddi)) +
1633 " every: " +
1634 isobus::to_string(static_cast<int>(commandData.processDataValue)) +
1635 " milliseconds.");
1636 }
1637 else
1638 {
1639 // Use the existing one and update the value
1640 previousCommand->processDataValue = commandData.processDataValue;
1641 LOG_DEBUG("[TC]: TC Altered time interval request for element: " +
1642 isobus::to_string(static_cast<int>(commandData.elementNumber)) +
1643 " DDI: " +
1644 isobus::to_string(static_cast<int>(commandData.ddi)) +
1645 " every: " +
1646 isobus::to_string(static_cast<int>(commandData.processDataValue)) +
1647 " milliseconds.");
1648 }
1649 }
1650 break;
1651
1653 {
1654 ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false };
1655 LOCK_GUARD(Mutex, clientMutex);
1656
1657 commandData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1658 commandData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1659 (static_cast<std::uint16_t>(messageData[3]) << 8);
1660 commandData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1661 (static_cast<std::int32_t>(messageData[5]) << 8) |
1662 (static_cast<std::int32_t>(messageData[6]) << 16) |
1663 (static_cast<std::int32_t>(messageData[7]) << 24));
1664
1665 auto previousCommand = std::find(parentTC->measurementMaximumThresholdCommands.begin(), parentTC->measurementMaximumThresholdCommands.end(), commandData);
1666 if (parentTC->measurementMaximumThresholdCommands.end() == previousCommand)
1667 {
1668 parentTC->measurementMaximumThresholdCommands.push_back(commandData);
1669 LOG_DEBUG("[TC]: TC Requests element: " +
1670 isobus::to_string(static_cast<int>(commandData.elementNumber)) +
1671 " DDI: " +
1672 isobus::to_string(static_cast<int>(commandData.ddi)) +
1673 " when it is above the raw value: " +
1674 isobus::to_string(static_cast<int>(commandData.processDataValue)));
1675 }
1676 else
1677 {
1678 // Just update the existing one with the new value
1679 previousCommand->processDataValue = commandData.processDataValue;
1680 previousCommand->thresholdPassed = false;
1681 }
1682 }
1683 break;
1684
1686 {
1687 ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false };
1688 LOCK_GUARD(Mutex, clientMutex);
1689
1690 commandData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1691 commandData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1692 (static_cast<std::uint16_t>(messageData[3]) << 8);
1693 commandData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1694 (static_cast<std::int32_t>(messageData[5]) << 8) |
1695 (static_cast<std::int32_t>(messageData[6]) << 16) |
1696 (static_cast<std::int32_t>(messageData[7]) << 24));
1697
1698 auto previousCommand = std::find(parentTC->measurementMinimumThresholdCommands.begin(), parentTC->measurementMinimumThresholdCommands.end(), commandData);
1699 if (parentTC->measurementMinimumThresholdCommands.end() == previousCommand)
1700 {
1701 parentTC->measurementMinimumThresholdCommands.push_back(commandData);
1702 LOG_DEBUG("[TC]: TC Requests Element " +
1703 isobus::to_string(static_cast<int>(commandData.elementNumber)) +
1704 " DDI: " +
1705 isobus::to_string(static_cast<int>(commandData.ddi)) +
1706 " when it is below the raw value: " +
1707 isobus::to_string(static_cast<int>(commandData.processDataValue)));
1708 }
1709 else
1710 {
1711 // Just update the existing one with the new value
1712 previousCommand->processDataValue = commandData.processDataValue;
1713 previousCommand->thresholdPassed = false;
1714 }
1715 }
1716 break;
1717
1719 {
1720 ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false };
1721 LOCK_GUARD(Mutex, clientMutex);
1722
1723 commandData.elementNumber = (static_cast<std::uint16_t>(messageData[0] >> 4) | (static_cast<std::uint16_t>(messageData[1]) << 4));
1724 commandData.ddi = static_cast<std::uint16_t>(messageData[2]) |
1725 (static_cast<std::uint16_t>(messageData[3]) << 8);
1726 commandData.processDataValue = (static_cast<std::int32_t>(messageData[4]) |
1727 (static_cast<std::int32_t>(messageData[5]) << 8) |
1728 (static_cast<std::int32_t>(messageData[6]) << 16) |
1729 (static_cast<std::int32_t>(messageData[7]) << 24));
1730
1731 auto previousCommand = std::find(parentTC->measurementOnChangeThresholdCommands.begin(), parentTC->measurementOnChangeThresholdCommands.end(), commandData);
1732 if (parentTC->measurementOnChangeThresholdCommands.end() == previousCommand)
1733 {
1734 parentTC->measurementOnChangeThresholdCommands.push_back(commandData);
1735 LOG_DEBUG("[TC]: TC Requests element " +
1736 isobus::to_string(static_cast<int>(commandData.elementNumber)) +
1737 " DDI: " +
1738 isobus::to_string(static_cast<int>(commandData.ddi)) +
1739 " on change by at least: " +
1740 isobus::to_string(static_cast<int>(commandData.processDataValue)));
1741 }
1742 else
1743 {
1744 // Just update the existing one with the new value
1745 previousCommand->processDataValue = commandData.processDataValue;
1746 previousCommand->thresholdPassed = false;
1747 }
1748 }
1749 break;
1750
1752 {
1753 if (0 != messageData[4])
1754 {
1755 LOG_WARNING("[TC]: TC sent us a PDNACK");
1756 }
1757 }
1758 break;
1759
1760 default:
1761 {
1762 LOG_WARNING("[TC]: Unhandled process data message!");
1763 }
1764 break;
1765 }
1766 }
1767 break;
1768
1769 default:
1770 {
1771 }
1772 break;
1773 }
1774 }
1775 }
1776
1778 std::uint32_t bytesOffset,
1779 std::uint32_t numberOfBytesNeeded,
1780 std::uint8_t *chunkBuffer,
1781 void *parentPointer)
1782 {
1783 auto parentTCClient = static_cast<TaskControllerClient *>(parentPointer);
1784 bool retVal = false;
1785
1786 // These assertions should never fail, but if they do, please consider reporting it on our GitHub page
1787 // along with a CAN trace and accompanying CANStackLogger output of the issue.
1788 assert(nullptr != parentTCClient);
1789 assert(nullptr != chunkBuffer);
1790 assert(0 != numberOfBytesNeeded);
1791
1792 if (((bytesOffset + numberOfBytesNeeded) <= parentTCClient->generatedBinaryDDOP.size() + 1) ||
1793 ((bytesOffset + numberOfBytesNeeded) <= parentTCClient->userSuppliedBinaryDDOPSize_bytes + 1))
1794 {
1795 retVal = true;
1796 if (0 == bytesOffset)
1797 {
1798 chunkBuffer[0] = static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1799 (static_cast<std::uint8_t>(DeviceDescriptorCommands::ObjectPoolTransfer) << 4);
1800
1801 if (DDOPUploadType::UserProvidedBinaryPointer == parentTCClient->ddopUploadMode)
1802 {
1803 memcpy(&chunkBuffer[1], &parentTCClient->userSuppliedBinaryDDOP[bytesOffset], numberOfBytesNeeded - 1);
1804 }
1805 else
1806 {
1807 memcpy(&chunkBuffer[1], &parentTCClient->generatedBinaryDDOP[bytesOffset], numberOfBytesNeeded - 1);
1808 }
1809 }
1810 else
1811 {
1812 if (DDOPUploadType::UserProvidedBinaryPointer == parentTCClient->ddopUploadMode)
1813 {
1814 // Subtract off 1 to account for the mux in the first byte of the message
1815 memcpy(chunkBuffer, &parentTCClient->userSuppliedBinaryDDOP[bytesOffset - 1], numberOfBytesNeeded);
1816 }
1817 else
1818 {
1819 // Subtract off 1 to account for the mux in the first byte of the message
1820 memcpy(chunkBuffer, &parentTCClient->generatedBinaryDDOP[bytesOffset - 1], numberOfBytesNeeded);
1821 }
1822 }
1823 }
1824 else
1825 {
1826 LOG_ERROR("[TC]: DDOP internal data callback received out of range request.");
1827 }
1828 return retVal;
1829 }
1830
1831 void TaskControllerClient::process_tx_callback(std::uint32_t parameterGroupNumber,
1832 std::uint32_t,
1833 std::shared_ptr<InternalControlFunction>,
1834 std::shared_ptr<ControlFunction> destinationControlFunction,
1835 bool successful,
1836 void *parentPointer)
1837 {
1838 if ((nullptr != parentPointer) &&
1839 (static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData) == parameterGroupNumber) &&
1840 (nullptr != destinationControlFunction))
1841 {
1842 auto parent = reinterpret_cast<TaskControllerClient *>(parentPointer);
1843
1844 if (StateMachineState::WaitForDDOPTransfer == parent->get_state())
1845 {
1846 if (successful)
1847 {
1849 }
1850 else
1851 {
1852 LOG_ERROR("[TC]: DDOP upload did not complete. Resetting.");
1853 parent->set_state(StateMachineState::Disconnected);
1854 }
1855 }
1856 }
1857 }
1858
1860 {
1861 return send_generic_process_data(static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1862 (static_cast<std::uint8_t>(DeviceDescriptorCommands::ObjectPoolDelete) << 4));
1863 }
1864
1865 bool TaskControllerClient::send_generic_process_data(std::uint8_t multiplexor) const
1866 {
1867 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { multiplexor,
1868 0xFF,
1869 0xFF,
1870 0xFF,
1871 0xFF,
1872 0xFF,
1873 0xFF,
1874 0xFF };
1875
1876 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
1877 buffer.data(),
1881 }
1882
1884 {
1885 return send_generic_process_data(static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1886 (static_cast<std::uint8_t>(DeviceDescriptorCommands::ObjectPoolActivateDeactivate) << 4));
1887 }
1888
1890 {
1891 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1892 (static_cast<std::uint8_t>(DeviceDescriptorCommands::ObjectPoolActivateDeactivate) << 4),
1893 0x00,
1894 0xFF,
1895 0xFF,
1896 0xFF,
1897 0xFF,
1898 0xFF,
1899 0xFF };
1900
1901 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
1902 buffer.data(),
1906 }
1907
1908 bool TaskControllerClient::send_pdack(std::uint16_t elementNumber, std::uint16_t ddi) const
1909 {
1910 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { static_cast<std::uint8_t>(static_cast<std::uint8_t>(ProcessDataCommands::ProcessDataAcknowledge) |
1911 static_cast<std::uint8_t>(elementNumber & 0x0F) << 4),
1912 static_cast<std::uint8_t>(elementNumber >> 4),
1913 static_cast<std::uint8_t>(ddi & 0xFF),
1914 static_cast<std::uint8_t>(ddi >> 8),
1915 0xFF,
1916 0xFF,
1917 0xFF,
1918 0xFF };
1919 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
1920 buffer.data(),
1924 }
1925
1927 {
1928 return send_generic_process_data(static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1929 (static_cast<std::uint8_t>(DeviceDescriptorCommands::RequestLocalizationLabel) << 4));
1930 }
1931
1933 {
1934 std::size_t binaryPoolSize = generatedBinaryDDOP.size();
1935
1937 {
1938 binaryPoolSize = userSuppliedBinaryDDOPSize_bytes;
1939 }
1940
1941 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1942 (static_cast<std::uint8_t>(DeviceDescriptorCommands::RequestObjectPoolTransfer) << 4),
1943 static_cast<std::uint8_t>(binaryPoolSize & 0xFF),
1944 static_cast<std::uint8_t>((binaryPoolSize >> 8) & 0xFF),
1945 static_cast<std::uint8_t>((binaryPoolSize >> 16) & 0xFF),
1946 static_cast<std::uint8_t>((binaryPoolSize >> 24) & 0xFF),
1947 0xFF,
1948 0xFF,
1949 0xFF };
1950
1951 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
1952 buffer.data(),
1956 }
1957
1959 {
1960 // When all bytes are 0xFF, the TC will tell us about the latest structure label
1961 return send_generic_process_data(static_cast<std::uint8_t>(ProcessDataCommands::DeviceDescriptor) |
1962 (static_cast<std::uint8_t>(DeviceDescriptorCommands::RequestStructureLabel) << 4));
1963 }
1964
1966 {
1967 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { (static_cast<std::uint8_t>(TechnicalDataMessageCommands::ParameterVersion) << 4),
1968 static_cast<std::uint8_t>(Version::SecondPublishedEdition),
1969 0xFF, // Must be 0xFF when a client sends it (boot time)
1970 static_cast<std::uint8_t>(static_cast<std::uint8_t>(supportsDocumentation) |
1971 (static_cast<std::uint8_t>(supportsTCGEOWithoutPositionBasedControl) << 1) |
1972 (static_cast<std::uint8_t>(supportsTCGEOWithPositionBasedControl) << 2) |
1973 (static_cast<std::uint8_t>(supportsPeerControlAssignment) << 3) |
1974 (static_cast<std::uint8_t>(supportsImplementSectionControl) << 4)),
1975 0x00,
1979
1980 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
1981 buffer.data(),
1985 }
1986
1988 {
1989 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { static_cast<std::uint8_t>(ProcessDataCommands::ClientTask) | 0xF0,
1990 0xFF, // Element number N/A
1991 0xFF, // DDI N/A
1992 0xFF, // DDI N/A
1993 static_cast<std::uint8_t>(tcStatusBitfield & 0x01), // Actual TC or DL status
1994 0x00, // Reserved (0)
1995 0x00, // Reserved (0)
1996 0x00 }; // Reserved (0)
1997
1998 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
1999 buffer.data(),
2003 }
2004
2005 bool TaskControllerClient::send_value_command(std::uint16_t elementNumber, std::uint16_t ddi, std::int32_t value) const
2006 {
2007 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { static_cast<std::uint8_t>(static_cast<std::uint8_t>(ProcessDataCommands::Value) |
2008 (static_cast<std::uint8_t>(elementNumber & 0x0F) << 4)),
2009 static_cast<std::uint8_t>(elementNumber >> 4),
2010 static_cast<std::uint8_t>(ddi & 0xFF),
2011 static_cast<std::uint8_t>(ddi >> 8),
2012 static_cast<std::uint8_t>(value),
2013 static_cast<std::uint8_t>(value >> 8),
2014 static_cast<std::uint8_t>(value >> 16),
2015 static_cast<std::uint8_t>(value >> 24) };
2016 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
2017 buffer.data(),
2021 }
2022
2027
2029 {
2030 const std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { numberOfWorkingSetMembers, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
2031
2032 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::WorkingSetMaster),
2033 buffer.data(),
2036 nullptr);
2037 }
2038
2039 void TaskControllerClient::set_common_config_items(std::uint8_t maxNumberBoomsSupported,
2040 std::uint8_t maxNumberSectionsSupported,
2041 std::uint8_t maxNumberChannelsSupportedForPositionBasedControl,
2042 bool reportToTCSupportsDocumentation,
2043 bool reportToTCSupportsTCGEOWithoutPositionBasedControl,
2044 bool reportToTCSupportsTCGEOWithPositionBasedControl,
2045 bool reportToTCSupportsPeerControlAssignment,
2046 bool reportToTCSupportsImplementSectionControl)
2047 {
2048 numberBoomsSupported = maxNumberBoomsSupported;
2049 numberSectionsSupported = maxNumberSectionsSupported;
2050 numberChannelsSupportedForPositionBasedControl = maxNumberChannelsSupportedForPositionBasedControl;
2051 supportsDocumentation = reportToTCSupportsDocumentation;
2052 supportsTCGEOWithoutPositionBasedControl = reportToTCSupportsTCGEOWithoutPositionBasedControl;
2053 supportsTCGEOWithPositionBasedControl = reportToTCSupportsTCGEOWithPositionBasedControl;
2054 supportsPeerControlAssignment = reportToTCSupportsPeerControlAssignment;
2055 supportsImplementSectionControl = reportToTCSupportsImplementSectionControl;
2056 }
2057
2059 {
2060 if (newState != currentState)
2061 {
2062 stateMachineTimestamp_ms = SystemTiming::get_timestamp_ms();
2063 currentState = newState;
2064
2065 if (StateMachineState::Disconnected == newState)
2066 {
2067 clear_queues();
2068 }
2069 }
2070 }
2071
2072 void TaskControllerClient::set_state(StateMachineState newState, std::uint32_t timestamp)
2073 {
2074 stateMachineTimestamp_ms = timestamp;
2075 currentState = newState;
2076 }
2077
2079 {
2080 if (serverVersion < static_cast<std::uint8_t>(Version::SecondPublishedEdition))
2081 {
2082 if (nullptr == primaryVirtualTerminal)
2083 {
2084 languageCommandInterface.set_partner(nullptr); // TC might not reply and no VT specified, so just see if anyone knows.
2085 LOG_WARNING("[TC]: The TC is < version 4 but no VT was provided. Language data will be requested globally, which might not be ideal.");
2086 }
2087 else
2088 {
2090 LOG_DEBUG("[TC]: Using VT as the partner for language data, because the TC's version is less than 4.");
2091 }
2092 }
2093 }
2094
2096 {
2097#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
2098 for (;;)
2099 {
2100 if (shouldTerminate)
2101 {
2102 break;
2103 }
2104 update();
2105 std::this_thread::sleep_for(std::chrono::milliseconds(50));
2106 }
2107#endif
2108 }
2109
2114
2119
2124
2129
2131 {
2132 return maxServerBootTime_s;
2133 }
2134
2136 {
2137 return (0 != (static_cast<std::uint8_t>(option) & serverOptionsByte1));
2138 }
2139
2141 {
2142 Version retVal = Version::Unknown;
2143
2144 if (serverVersion <= static_cast<std::uint8_t>(Version::SecondPublishedEdition))
2145 {
2146 retVal = static_cast<Version>(serverVersion);
2147 }
2148 return retVal;
2149 }
2150
2151 void TaskControllerClient::on_value_changed_trigger(std::uint16_t elementNumber, std::uint16_t DDI)
2152 {
2153 ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false };
2154 LOCK_GUARD(Mutex, clientMutex);
2155
2156 requestData.ackRequested = false;
2157 requestData.elementNumber = elementNumber;
2158 requestData.ddi = DDI;
2159 requestData.processDataValue = 0;
2160 queuedValueRequests.push_back(requestData);
2161 }
2162
2164 {
2165 constexpr std::array<std::uint8_t, CAN_DATA_LENGTH> buffer = { static_cast<std::uint8_t>(ProcessDataCommands::TechnicalCapabilities) |
2166 (static_cast<std::uint8_t>(TechnicalDataMessageCommands::IdentifyTaskController) << 4),
2167 0xFF,
2168 0xFF,
2169 0xFF,
2170 0xFF,
2171 0xFF,
2172 0xFF,
2173 0xFF };
2174 return CANNetworkManager::CANNetwork.send_can_message(static_cast<std::uint32_t>(CANLibParameterGroupNumber::ProcessData),
2175 buffer.data(),
2178 nullptr);
2179 }
2180
2181} // namespace isobus
Defines some PGNs that are used in the library or are very common.
The main class that manages the ISOBUS stack including: callbacks, Name to Address management,...
A class that acts as a logging sink. The intent is that someone could make their own derived class of...
@ PriorityLowest7
The lowest priority.
std::uint32_t get_parameter_group_number() const
Returns the PGN encoded in the identifier.
A class that represents a generic CAN message of arbitrary length.
const std::vector< std::uint8_t > & get_data() const
Gets a reference to the data in the CAN message.
std::uint32_t get_data_length() const
Returns the length of the data in the CAN message.
std::uint32_t get_uint24_at(const std::uint32_t index, const ByteFormat format=ByteFormat::LittleEndian) const
Get a right-aligned 24-bit integer from the buffer (returned as a uint32_t) at a specific index....
std::shared_ptr< ControlFunction > get_source_control_function() const
Gets the source control function that the message is from.
std::uint8_t get_uint8_at(const std::uint32_t index) const
Get a 8-bit unsigned byte from the buffer at a specific index. A 8-bit unsigned byte can hold a value...
CANIdentifier get_identifier() const
Returns the identifier of the message.
void add_global_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent)
This is how you register a callback for any PGN destined for the global address (0xFF)
static CANNetworkManager CANNetwork
Static singleton of the one network manager. Use this to access stack functionality.
void remove_global_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent)
This is how you remove a callback for any PGN destined for the global address (0xFF)
bool send_can_message(std::uint32_t parameterGroupNumber, const std::uint8_t *dataBuffer, std::uint32_t dataLength, std::shared_ptr< InternalControlFunction > sourceControlFunction, std::shared_ptr< ControlFunction > destinationControlFunction=nullptr, CANIdentifier::CANPriority priority=CANIdentifier::CANPriority::PriorityDefault6, TransmitCompleteCallback txCompleteCallback=nullptr, void *parentPointer=nullptr, DataChunkCallback frameChunkCallback=nullptr)
This is the main way to send a CAN message of any length.
std::string get_language_code() const
Returns the commanded language code parsed from the last language command.
bool send_request_language_command() const
Sends a PGN request for the language command PGN to the interface's partner, or the global address de...
std::shared_ptr< PartneredControlFunction > get_partner() const
Returns the current partner being used by the interface.
void set_partner(std::shared_ptr< PartneredControlFunction > filteredControlFunction)
Changes the partner being used by the interface to a new partner.
bool get_initialized() const
Returns if initialize has been called yet.
std::uint32_t get_language_command_timestamp() const
Returns a timestamp (in ms) corresponding to when the interface last received a language command.
A class to manage a client connection to a ISOBUS field computer's task controller or data logger.
bool send_working_set_master() const
Sends the working set master message.
std::uint8_t serverNumberOfBoomsForSectionControl
When reported by the TC, this is the maximum number of section control booms that are supported.
bool request_task_controller_identification() const
Sends a broadcast request to TCs to identify themseleves.
std::string ddopStructureLabel
Stores a pre-parsed structure label, helps to avoid processing the whole DDOP during a CAN message ca...
std::shared_ptr< PartneredControlFunction > partnerControlFunction
The partner control function this client will send to.
TaskControllerClient(std::shared_ptr< PartneredControlFunction > partner, std::shared_ptr< InternalControlFunction > clientSource, std::shared_ptr< PartneredControlFunction > primaryVT)
The constructor for a TaskControllerClient.
void select_language_command_partner()
Sets the behavior of the language command interface based on the TC's reported version information.
std::shared_ptr< PartneredControlFunction > primaryVirtualTerminal
A pointer to the primary VT's control function. Used for TCs < version 4 and language command compati...
bool send_pdack(std::uint16_t elementNumber, std::uint16_t ddi) const
Sends a Process Data ACK.
std::uint8_t numberOfWorkingSetMembers
The number of working set members that will be reported in the working set master message.
bool get_supports_tcgeo_without_position_based_control() const
Returns if the client has been configured to report that it supports TC-GEO without position based co...
bool send_request_version_response() const
Sends the response to a request for version from the TC.
std::list< ProcessDataCallbackInfo > queuedValueRequests
A list of queued value requests that will be processed on the next update.
bool get_supports_documentation() const
Returns if the client has been configured to report that it supports documentation to the TC.
bool get_supports_tcgeo_with_position_based_control() const
Returns if the client has been configured to report that it supports TC-GEO with position based contr...
std::uint8_t serverOptionsByte1
The options specified in ISO 11783-10 that this TC, DL, or client meets (The definition of this byte ...
std::uint8_t maxServerBootTime_s
Maximum number of seconds from a power cycle to transmission of first �Task Controller Status message...
bool send_status() const
Sends the status message to the TC.
std::list< ProcessDataCallbackInfo > queuedValueCommands
A list of queued value commands that will be processed on the next update.
bool get_is_task_active() const
Returns if a task is active as indicated by the TC.
void remove_request_value_callback(RequestValueCommandCallback callback, void *parentPointer)
Removes the specified callback from the list of value request callbacks.
void set_common_config_items(std::uint8_t maxNumberBoomsSupported, std::uint8_t maxNumberSectionsSupported, std::uint8_t maxNumberChannelsSupportedForPositionBasedControl, bool reportToTCSupportsDocumentation, bool reportToTCSupportsTCGEOWithoutPositionBasedControl, bool reportToTCSupportsTCGEOWithPositionBasedControl, bool reportToTCSupportsPeerControlAssignment, bool reportToTCSupportsImplementSectionControl)
Sets the common items found in all versions of configure
bool send_value_command(std::uint16_t elementNumber, std::uint16_t ddi, std::int32_t value) const
Sends the value command message for a specific DDI/Element number combo.
std::uint32_t statusMessageTimestamp_ms
Timestamp corresponding to the last time we sent a status message to the TC.
std::uint8_t const * userSuppliedBinaryDDOP
Stores a client-provided DDOP if one was provided.
static void process_tx_callback(std::uint32_t parameterGroupNumber, std::uint32_t dataLength, std::shared_ptr< InternalControlFunction > sourceControlFunction, std::shared_ptr< ControlFunction > destinationControlFunction, bool successful, void *parentPointer)
The callback passed to the network manager's send function to know when a Tx is completed.
bool(*)(std::uint16_t elementNumber, std::uint16_t DDI, std::int32_t &processVariableValue, void *parentPointer) RequestValueCommandCallback
A callback for handling a value request command from the TC.
bool send_request_structure_label() const
Sends a request to the TC for its structure label.
void update()
The cyclic update function for this interface.
std::list< ProcessDataCallbackInfo > measurementTimeIntervalCommands
A list of measurement commands that will be processed on a time interval.
Mutex clientMutex
A general mutex to protect data in the worker thread against data accessed by the app or the network ...
bool get_is_initialized() const
Returns if the client has been initialized.
static bool process_internal_object_pool_upload_callback(std::uint32_t callbackIndex, std::uint32_t bytesOffset, std::uint32_t numberOfBytesNeeded, std::uint8_t *chunkBuffer, void *parentPointer)
The data callback passed to the network manger's send function for the transport layer messages.
bool enableStatusMessage
Enables sending the status message to the TC cyclically.
std::uint8_t tcStatusBitfield
The last received TC/DL status from the status message.
void add_value_command_callback(ValueCommandCallback callback, void *parentPointer)
Adds a callback that will be called when the TC commands a new value for one of your variables.
bool send_object_pool_activate() const
Sends the activate object pool message.
bool send_generic_process_data(std::uint8_t multiplexor) const
Sends a process data message with 1 mux byte and all 0xFFs as payload.
std::shared_ptr< PartneredControlFunction > get_partner_control_function() const
Returns the control function of the TC server with which this TC client communicates.
void on_value_changed_trigger(std::uint16_t elementNumber, std::uint16_t DDI)
Tells the TC client that a value was changed or the TC client needs to command a value to the TC serv...
bool supportsDocumentation
Determines if the client reports documentation support to the TC.
std::uint32_t serverStatusMessageTimestamp_ms
Timestamp corresponding to the last time we received a status message from the TC.
void process_queued_commands()
Processes queued TC requests and commands. Calls the user's callbacks if needed.
std::uint8_t get_connected_tc_number_sections_supported() const
Returns the number of sections that the connected TC supports for section control.
bool(*)(std::uint16_t elementNumber, std::uint16_t DDI, std::int32_t processVariableValue, void *parentPointer) ValueCommandCallback
A callback for handling a set value command from the TC.
LanguageCommandInterface languageCommandInterface
Used to determine the language and unit systems in use by the TC server.
std::uint8_t get_connected_tc_number_booms_supported() const
Returns the number of booms that the connected TC supports for section control.
void add_request_value_callback(RequestValueCommandCallback callback, void *parentPointer)
This adds a callback that will be called when the TC requests the value of one of your variables.
StateMachineState
Enumerates the different internal state machine states.
@ WaitForRequestVersionFromServer
Waiting to see if the TC will request our version (optional)
@ WaitForDeleteObjectPoolResponse
Waiting for a response to our request to delete our object pool off the TC.
@ RequestVersion
Requests the TC version and related data from the TC.
@ WaitForStructureLabelResponse
Client is waiting for the TC to respond to our request for its structure label.
@ SendRequestVersionResponse
Sending our response to the TC's request for out version information.
@ WaitForServerStatusMessage
Client is waiting to identify the TC via reception of a valid status message.
@ SendStatusMessage
Enables sending the status message.
@ RequestLocalizationLabel
Client is requesting the DDOP localization label the TC has for us (if any)
@ WaitForDDOPTransfer
The DDOP transfer in ongoing. Client is waiting for a callback from the transport layer.
@ SendWorkingSetMaster
Client initating communication with TC by sending the working set master message.
@ RequestLanguage
Client is requesting the language command PGN from the TC.
@ SendDeleteObjectPool
Client is sending a request to the TC to delete its current copy of our object pool.
@ DeactivateObjectPool
Client is shutting down and is therefore sending the deactivate object pool message.
@ WaitForObjectPoolActivateResponse
Client is waiting for a response to its request to activate the object pool.
@ SendRequestTransferObjectPool
Client is requesting to transfer the DDOP to the TC.
@ WaitForRequestTransferObjectPoolResponse
Waiting for a response to our request to transfer the DDOP to the TC.
@ SendObjectPoolActivate
Client is sending the activate object pool message.
@ RequestStructureLabel
Client is requesting the DDOP structure label that the TC has (if any)
@ WaitForObjectPoolDeactivateResponse
Client is waiting for a response to the deactivate object pool message.
@ WaitForObjectPoolTransferResponse
DDOP has transferred. Waiting for a response to our object pool transfer.
@ BeginTransferDDOP
Client is initiating the DDOP transfer.
@ WaitForLocalizationLabelResponse
Waiting for a response to our request for the localization label from the TC.
@ WaitForStartUpDelay
Client is waiting for the mandatory 6s startup delay.
@ WaitForLanguageResponse
Waiting for a response to our request for the language command PGN.
@ ProcessDDOP
Client is processing the DDOP into a binary DDOP and validating object IDs in the pool.
@ Disconnected
Not communicating with the TC.
@ WaitForRequestVersionResponse
Waiting for the TC to respond to a request for its version.
std::uint32_t stateMachineTimestamp_ms
Timestamp that tracks when the state machine last changed states (in milliseconds)
void initialize(bool spawnThread)
This function starts the state machine. Call this once you have created your DDOP,...
void configure(std::shared_ptr< DeviceDescriptorObjectPool > DDOP, std::uint8_t maxNumberBoomsSupported, std::uint8_t maxNumberSectionsSupported, std::uint8_t maxNumberChannelsSupportedForPositionBasedControl, bool reportToTCSupportsDocumentation, bool reportToTCSupportsTCGEOWithoutPositionBasedControl, bool reportToTCSupportsTCGEOWithPositionBasedControl, bool reportToTCSupportsPeerControlAssignment, bool reportToTCSupportsImplementSectionControl)
A convenient way to set all client options at once instead of calling the individual setters.
std::string previousStructureLabel
Stores the last structure label we used, helps to warn the user if they aren't updating the label pro...
ProcessDataCommands
Enumerates the different Process Data commands from ISO11783-10 Table B.1.
@ StatusMessage
Message is a Task Controller Status message.
@ MeasurementMaximumWithinThreshold
The client has to send the value of this data element to the TC or DL when the value is lower than th...
@ ProcessDataAcknowledge
Message is a Process Data Acknowledge (PDACK).
@ MeasurementMinimumWithinThreshold
The client has to send the value of this data element to the TC or DL when the value is higher than t...
@ DeviceDescriptor
Subcommand for the transfer and management of device descriptors.
@ Value
This command is used both to answer a request value command and to set the value of a process data en...
@ TechnicalCapabilities
Used for determining the technical capabilities of a TC, DL, or client.
@ MeasurementChangeThreshold
The client has to send the value of this data element to the TC or DL when the value change is higher...
@ RequestValue
The value of the data entity specified by the data dictionary identifier is requested.
@ MeasurementTimeInterval
The process data value is the time interval for sending the data element specified by the data dictio...
@ SetValueAndAcknowledge
This command is used to set the value of a process data entity and request a reception acknowledgemen...
std::vector< std::uint8_t > generatedBinaryDDOP
Stores the DDOP in binary form after it has been generated.
static void process_rx_message(const CANMessage &message, void *parentPointer)
Processes a CAN message destined for any TC client.
bool get_connected_tc_option_supported(ServerOptions option) const
Returns if the connected TC supports a certain option.
std::list< ProcessDataCallbackInfo > measurementMinimumThresholdCommands
A list of measurement commands that will be processed when the value drops below a threshold.
std::uint8_t get_number_booms_supported() const
Returns the previously configured number of booms supported by the client.
std::uint8_t serverVersion
The detected version of the TC Server.
std::uint8_t get_connected_tc_max_boot_time() const
Returns the maximum boot time in seconds reported by the connected TC.
std::shared_ptr< DeviceDescriptorObjectPool > clientDDOP
Stores the DDOP for upload to the TC (if needed)
void remove_value_command_callback(ValueCommandCallback callback, void *parentPointer)
Removes the specified callback from the list of value command callbacks.
std::list< ProcessDataCallbackInfo > measurementOnChangeThresholdCommands
A list of measurement commands that will be processed when the value changes by the specified amount.
Version
Enumerates the different task controller versions.
@ SecondPublishedEdition
The version of the second edition published as the final draft International Standard(E2....
bool send_delete_object_pool() const
Sends the delete object pool command to the TC.
DDOPUploadType ddopUploadMode
Determines if DDOPs get generated or raw uploaded.
std::uint32_t languageCommandWaitingTimestamp_ms
Timestamp used to determine when to give up on waiting for a language command response.
std::uint8_t get_connected_tc_number_channels_supported() const
Returns the number of channels that the connected TC supports for position control.
bool send_object_pool_deactivate() const
Sends the deactivate object pool message.
StateMachineState get_state() const
Returns the current state machine state.
bool supportsTCGEOWithoutPositionBasedControl
Determines if the client reports TC-GEO without position control capability to the TC.
std::uint8_t get_number_sections_supported() const
Returns the previously configured number of section supported by the client.
TechnicalDataMessageCommands
Enumerates the subcommands within the technical data message group.
@ ParameterRequestVersion
The Request Version message allows the TC, DL, and the client to determine the ISO 11783-10 version o...
@ ParameterVersion
The Version message is sent in response to the request version message and contains the ISO 11783-10 ...
@ IdentifyTaskController
Upon receipt of this message, the TC shall display, for a period of 3 s, the TC Number.
bool send_request_object_pool_transfer() const
Sends a request to the TC indicating we wish to transfer an object pool.
std::vector< ValueCommandCallbackInfo > valueCommandsCallbacks
A list of callbacks that will be called when the TC sets a process data value.
std::uint8_t serverNumberOfSectionsForSectionControl
When reported by the TC, this is the maximum number of sections that are supported (or 0xFF for versi...
std::shared_ptr< std::vector< std::uint8_t > > userSuppliedVectorDDOP
Stores a client-provided DDOP if one was provided.
void process_queued_threshold_commands()
Processes measurement threshold/interval commands.
static constexpr std::uint32_t SIX_SECOND_TIMEOUT_MS
The startup delay time defined in the standard.
static constexpr std::uint16_t TWO_SECOND_TIMEOUT_MS
Used for sending the status message to the TC.
bool initialized
Tracks the initialization state of the interface instance.
std::uint8_t numberBoomsSupported
Stores the number of booms this client supports for section control.
bool shouldReuploadAfterDDOPDeletion
Used to determine how the state machine should progress when updating a DDOP.
void terminate()
Terminates the client and joins the worker thread if applicable.
std::vector< RequestValueCommandCallbackInfo > requestValueCallbacks
A list of callbacks that will be called when the TC requests a process data value.
std::uint8_t get_number_channels_supported_for_position_based_control() const
Returns the previously configured number of channels supported for position based control.
bool shouldTerminate
This variable tells the worker thread to exit.
bool supportsImplementSectionControl
Determines if the client reports implement section control capability to the TC.
void restart()
Calling this function will reset the task controller client's connection with the TC server,...
StateMachineState currentState
Tracks the internal state machine's current state.
std::uint8_t serverNumberOfChannelsForPositionBasedControl
When reported by the TC, this is the maximum number of individual control channels that is supported.
ServerOptions
Enumerates the bits stored in our version data that we send to the TC when handshaking.
std::shared_ptr< InternalControlFunction > get_internal_control_function() const
Returns the internal control function being used by the interface to send messages.
bool get_supports_implement_section_control() const
Returns if the client has been configured to report that it supports implement section control to the...
std::list< ProcessDataCallbackInfo > measurementMaximumThresholdCommands
A list of measurement commands that will be processed when the value above a threshold.
void process_labels_from_ddop()
Searches the DDOP for a device object and stores that object's structure and localization labels.
bool send_request_localization_label() const
Sends a request to the TC for its localization label.
std::uint8_t numberSectionsSupported
Stores the number of sections this client supports for section control.
DeviceDescriptorCommands
Enumerates the subcommands within the device descriptor command message group.
@ ObjectPoolTransferResponse
Response to an object pool transfer.
@ LocalizationLabel
Sent by the TC or DL to inform the client about the availability of the requested localization versio...
@ ObjectPoolTransfer
Enables the client to transfer (part of) the device descriptor object pool to the TC.
@ RequestLocalizationLabel
Allows the client to determine the availability of the requested device descriptor localization.
@ RequestObjectPoolTransfer
The Request Object-pool Transfer message allows the client to determine whether it is allowed to tran...
@ RequestObjectPoolTransferResponse
Sent in response to Request Object-pool Transfer message.
@ ObjectPoolActivateDeactivateResponse
sent by a client to complete its connection procedure to a TC or DL or to disconnect from a TC or DL.
@ ObjectPoolDelete
This is a message to delete the device descriptor object pool for the client that sends this message.
@ StructureLabel
The Structure Label message is sent by the TC or DL to inform the client about the availability of th...
@ RequestStructureLabel
Allows the client to determine the availability of the requested device descriptor structure.
@ ObjectPoolDeleteResponse
TC response to a Object-pool Delete message.
@ ObjectPoolActivateDeactivate
sent by a client to complete its connection procedure to a TC or DL or to disconnect from a TC or DL.
std::shared_ptr< InternalControlFunction > myControlFunction
The internal control function the client uses to send from.
Version get_connected_tc_version() const
Returns the version of the connected task controller.
void clear_queues()
Clears all queued TC commands and responses.
bool get_was_ddop_supplied() const
Checks if a DDOP was provided via one of the configure functions.
bool get_supports_peer_control_assignment() const
Returns if the client has been configured to report that it supports peer control assignment to the T...
std::uint32_t userSuppliedBinaryDDOPSize_bytes
The number of bytes in the user provided binary DDOP (if one was provided)
std::thread * workerThread
The worker thread that updates this interface.
bool get_is_connected() const
Check whether the client is connected to the TC server.
@ ProgramaticallyGenerated
Using the AgIsoStack++ DeviceDescriptorObjectPool class.
@ UserProvidedBinaryPointer
Using a raw pointer to a binary DDOP.
@ UserProvidedVector
Uses a vector of bytes that comprise a binary DDOP.
std::array< std::uint8_t, 7 > ddopLocalizationLabel
Stores a pre-parsed localization label, helps to avoid processing the whole DDOP during a CAN message...
std::uint8_t numberChannelsSupportedForPositionBasedControl
Stores the number of channels this client supports for position based control.
~TaskControllerClient()
Destructor for the client.
bool reupload_device_descriptor_object_pool(std::shared_ptr< std::vector< std::uint8_t > > binaryDDOP)
If the TC client is connected to a TC, calling this function will cause the TC client interface to de...
bool supportsPeerControlAssignment
Determines if the client reports peer control assignment capability to the TC.
void set_state(StateMachineState newState)
Changes the internal state machine state and updates the associated timestamp.
bool supportsTCGEOWithPositionBasedControl
Determines if the client reports TC-GEO with position control capability to the TC.
bool send_version_request() const
Sends the version request message to the TC.
void worker_thread_function()
The worker thread will execute this function when it runs, if applicable.
static constexpr std::size_t MAX_STRUCTURE_AND_LOCALIZATION_LABEL_LENGTH
Defines the max length of the device structure label and device localization label (in bytes)
A class to manage a client connection to a ISOBUS field computer's task controller.
@ Device
The root object. Each device shall have one single Device.
This namespace encompasses all of the ISO11783 stack's functionality to reduce global namespace pollu...
constexpr std::uint8_t CAN_DATA_LENGTH
The length of a classical CAN frame.
AcknowledgementType
The types of acknowledgement that can be sent in the Ack PGN.
@ Negative
"NACK" Indicates the request was not completed or we do not support the PGN
Stores data related to requests and commands from the TC.
std::uint16_t elementNumber
The element number for the command.
bool ackRequested
Stores if the TC used the mux that also requires a PDACK.
bool operator==(const ProcessDataCallbackInfo &obj) const
Allows easy comparison of callback data.
std::int32_t lastValue
Used for measurement commands to store timestamp or previous values.
std::int32_t processDataValue
The value of the value set command.
Stores a TC value command callback along with its parent pointer.
bool operator==(const RequestValueCommandCallbackInfo &obj) const
Allows easy comparison of callback data.
Stores a TC value command callback along with its parent pointer.
void * parent
The parent pointer, generic context value.
bool operator==(const ValueCommandCallbackInfo &obj) const
Allows easy comparison of callback data.