Spectral-BLDC 1.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ import struct
2
+
3
+
4
+ int_to_3_bytes = struct.Struct('>I').pack # BIG endian order
5
+
6
+ # Fuses 3 bytes to 1 signed int
7
+ def fuse_3_bytes(var_in):
8
+ value = struct.unpack(">I", bytearray(var_in))[0] # converts bytes array to int
9
+
10
+ # convert to negative number if it is negative
11
+ if value >= 1<<23:
12
+ value -= 1<<24
13
+
14
+ return value
15
+
16
+ # Fuses 2 bytes to 1 signed int
17
+ def fuse_2_bytes(var_in):
18
+ value = struct.unpack(">I", bytearray(var_in))[0] # converts bytes array to int
19
+
20
+ # convert to negative number if it is negative
21
+ if value >= 1<<15:
22
+ value -= 1<<16
23
+
24
+ return value
25
+
26
+ # Fuses 4 bytes to 1 signed int
27
+ def fuse_4_bytes(bytes_array):
28
+ # Unpack 4 bytes into an unsigned integer
29
+ value = struct.unpack(">I", bytearray(bytes_array))[0]
30
+ # Convert to a negative number if it is negative
31
+ if value & 0x80000000:
32
+ value -= 1 << 32
33
+
34
+ return value
35
+
36
+ # Fuses 4 bytes to 1 32bit float value
37
+ def fuse_float_bytes(bytes_array):
38
+ # Unpack 4 bytes into a float using IEEE 754 single-precision format (32 bits)
39
+ float_value = struct.unpack('>f', bytearray(bytes_array))[0]
40
+ return float_value
41
+
42
+
43
+ # Split data to 3 bytes
44
+ def split_2_3_bytes(var_in):
45
+ y = int_to_3_bytes(var_in & 0xFFFFFF) # converts my int value to bytes array
46
+ return y[1:4]
47
+
48
+ # Splits data to 2 bytes
49
+ def split_2_2_bytes(var_in):
50
+ return struct.pack(">H", var_in & 0xFFFF)
51
+
52
+ # Splits int data to 4 bytes
53
+ def split_2_4_bytes(var_in):
54
+ return struct.pack(">I", var_in & 0xFFFFFFFF)
55
+
56
+ # Splits float data to 4 bytes
57
+ def float_to_bytes(float_value):
58
+ # Pack the float value into 4 bytes using IEEE 754 single-precision format (32 bits)
59
+ return struct.pack('>f', float_value)
60
+
61
+ def extract_from_can_id(can_id):
62
+ # Extracting ID2 (first 4 MSB)
63
+ id2 = (can_id >> 7) & 0xF
64
+
65
+ # Extracting CAN Command (next 6 bits)
66
+ can_command = (can_id >> 1) & 0x3F
67
+
68
+ # Extracting Error Bit (last bit)
69
+ error_bit = can_id & 0x1
70
+
71
+ return id2, can_command, error_bit
72
+
73
+
74
+ def combine_2_can_id(id2, can_command, error_bit):
75
+ # Combine components into an 11-bit CAN ID
76
+ can_id = 0
77
+
78
+ # Add ID2 (first 4 MSB)
79
+ can_id |= (id2 & 0xF) << 7
80
+
81
+ # Add CAN Command (next 6 bits)
82
+ can_id |= (can_command & 0x3F) << 1
83
+
84
+ # Add Error Bit (last bit)
85
+ can_id |= (error_bit & 0x1)
86
+
87
+ return can_id
88
+
89
+ # Fuse bitfield list to byte
90
+ def fuse_bitfield_2_bytearray(var_in):
91
+ number = 0
92
+ for b in var_in:
93
+ number = (2 * number) + b
94
+ return bytes([number])
95
+
96
+ # Splits byte to bitfield list
97
+ def split_2_bitfield(var_in):
98
+ #return [var_in >> i & 1 for i in range(7,-1,-1)]
99
+ return [(var_in >> i) & 1 for i in range(7, -1, -1)]
100
+
101
+ # Combine 2 bits into int
102
+ def combine_bits(bit1, bit2):
103
+ # Ensure that bit1 and bit2 are either 0 or 1
104
+ bit1 = 1 if bit1 else 0
105
+ bit2 = 1 if bit2 else 0
106
+
107
+ # Combine the bits using bitwise OR
108
+ result = (bit1 << 1) | bit2
109
+ return result
110
+
111
+ if __name__ == "__main__":
112
+ # Packing data to send on can bus
113
+ pos_ = -150
114
+ speed_ = -187
115
+ torque_ = -3047
116
+ a = split_2_3_bytes(pos_)
117
+ b = split_2_3_bytes(speed_)
118
+ c = split_2_2_bytes(torque_)
119
+ print(a)
120
+ print(b)
121
+ print(c)
122
+
123
+ # Unpacking data we receive from can bus
124
+ message = bytearray(b'\xff\xffj\xff\xffE\x0b\xe7')
125
+ print(message)
126
+ position = fuse_3_bytes(b'\x00' + message[0:3])
127
+ speed = fuse_3_bytes( b'\x00' + message[3:6])
128
+ torque = fuse_2_bytes(b'\x00'+ b'\x00' + message[6:8])
129
+ print(position)
130
+ print(speed)
131
+ print(torque)
132
+
133
+
134
+
135
+
@@ -0,0 +1,621 @@
1
+ import can
2
+ from .import CAN_utils as util
3
+ import logging
4
+ from collections import namedtuple
5
+ import struct
6
+
7
+ """
8
+ logging.basicConfig(level = logging.DEBUG,
9
+ format='%(asctime)s.%(msecs)03d %(levelname)s:\t%(message)s',
10
+ datefmt='%H:%M:%S'
11
+ )
12
+
13
+ logging.disable(logging.DEBUG)
14
+ """
15
+
16
+ CanMessage = namedtuple("CanMessage", ["node_id", "command_id", "error_bit"])
17
+
18
+
19
+ class CanCommunication:
20
+ def __init__(self, bustype='slcan', channel='COM41', bitrate=1000000):
21
+ self.bustype=bustype
22
+ self.channel=channel
23
+ self.bitrate=bitrate
24
+ if self.channel != None:
25
+ self.bus = can.interface.Bus(bustype = self.bustype, channel = self.channel, bitrate = self.bitrate)
26
+
27
+ def send_can_message(self, message_id, data=b'', remote_frame=False):
28
+ message = can.Message(arbitration_id=message_id, data=data, is_remote_frame=remote_frame, is_extended_id=False)
29
+ try:
30
+ self.bus.send(message)
31
+ except can.CanOperationError as e:
32
+ None#return False
33
+ except Exception as e:
34
+ None#return False
35
+
36
+ def receive_can_messages(self,timeout = None):
37
+ self.timeout = timeout
38
+ message = self.bus.recv(timeout = self.timeout)
39
+ if message is not None:
40
+ node_id,command_id,error_bit = util.extract_from_can_id(message.arbitration_id)
41
+ can_message = CanMessage(node_id, command_id, error_bit)
42
+ else:
43
+ return None,None
44
+
45
+ return message, can_message
46
+
47
+
48
+
49
+ class SpectralCAN:
50
+
51
+ SEND_PD_GAINS_ID = 16
52
+ SEND_CURRENT_GAINS_ID = 17
53
+ SEND_VELOCITY_GAINS_ID = 18
54
+ SEND_POSITION_GAINS_ID = 19
55
+ SEND_LIMITS_ID = 20
56
+ SEND_KT_ID = 22
57
+ SEND_CAN_ID = 11
58
+ SEND_ESTOP_ID = 0
59
+ SEND_IDLE_ID = 12
60
+ SEND_SAVE_CONFIG_ID = 13
61
+ SEND_RESET_ID = 14
62
+ SEND_CLEAR_ERROR_ID = 1
63
+ SEND_WATCHDOG_ID = 15
64
+ SEND_HEARTBEAT_SETUP_ID = 30
65
+ SEND_CYCLIC_COMMAND_ID = 29
66
+
67
+ SEND_RESPOND_TEMPERATURE_ID = 23
68
+ SEND_RESPOND_DEVICE_INFO_ID = 25
69
+ SEND_RESPOND_ENCODER_DATA_ID = 28
70
+ SEND_RESPOND_VOLTAGE_ID = 24
71
+ SEND_RESPOND_STATE_OF_ERRORS_ID = 26
72
+ SEND_RESPOND_IQ_DATA_ID = 27
73
+ SEND_RESPOND_PING_ID = 10
74
+
75
+ SEND_DATA_PACK_1_ID = 2
76
+ SEND_DATA_PACK_2_ID = 6
77
+ SEND_DATA_PACK_3_ID = 8
78
+ SEND_DATA_PACK_PD_ID = 4
79
+ SEND_HALL_INDEX = 31
80
+ SEND_RESPOND_KT_ID = 33
81
+
82
+ SEND_GRIPPER_DATA_PACK_ID = 61
83
+ SEND_GRIPPER_CALIBRATE_ID = 62
84
+
85
+ RESPOND_HEARTBEAT_ID = 9
86
+ RESPOND_DATA_PACK_1_ID = 3
87
+ RESPOND_DATA_PACK_2_ID = 5
88
+ RESPOND_DATA_PACK_3_ID = 7
89
+ RESPOND_GRIPPER_DATA_PACK_ID = 60
90
+ RESPOND_DATA_HALL = 32
91
+
92
+
93
+
94
+ def __init__(self, node_id, communication):
95
+ self.node_id = node_id
96
+ self.communication = communication
97
+
98
+ self.temperature = None
99
+ self.position = None
100
+ self.speed = None
101
+ self.current = None
102
+ self.voltage = None
103
+ self.heartbeat = None
104
+
105
+ self.gripper_position = None
106
+ self.gripper_speed = None
107
+ self.gripper_current = None
108
+ self.gripper_activated = None
109
+ self.gripper_action_status = None
110
+ self.gripper_object_detection = None
111
+ self.gripper_temperature_error = None
112
+ self.gripper_timeout_error = None
113
+ self.gripper_estop_error = None
114
+ self.gripper_calibrated = None
115
+
116
+ self.error_bit = None
117
+ self.error_bit = None
118
+ self.error = None
119
+ self.temperature_error = None
120
+ self.encoder_error = None
121
+ self.vbus_error = None
122
+ self.driver_error = None
123
+ self.velocity_error = None
124
+ self.current_error = None
125
+ self.estop_error = None
126
+ self.calibrated = None
127
+ self.activated = None
128
+ self.watchdog_error = None
129
+ self.HALL_trigger = None
130
+ self.additional_pin2 = None
131
+ self.hall_index = None
132
+ self.Kt = None
133
+
134
+ self.ping = None
135
+ self.timestamp = None
136
+
137
+ self.HARDWARE_VERSION = None
138
+ self.BATCH_DATE = None
139
+ self.SOFTWARE_VERSION = None
140
+ self.SERIAL_NUMBER = None
141
+
142
+ self.DLC_warrning = None
143
+
144
+
145
+ # Configuration variables
146
+ self._velocity_limit = 0
147
+
148
+ def Set_2_none(self):
149
+ self.temperature = None
150
+ self.position = None
151
+ self.speed = None
152
+ self.current = None
153
+ self.voltage = None
154
+ self.heartbeat = None
155
+
156
+ self.gripper_position = None
157
+ self.gripper_speed = None
158
+ self.gripper_current = None
159
+ self.gripper_activated = None
160
+ self.gripper_action_status = None
161
+ self.gripper_object_detection = None
162
+ self.gripper_temperature_error = None
163
+ self.gripper_timeout_error = None
164
+ self.gripper_estop_error = None
165
+ self.gripper_calibrated = None
166
+
167
+ self.error_bit = None
168
+ self.error = None
169
+ self.temperature_error = None
170
+ self.encoder_error = None
171
+ self.vbus_error = None
172
+ self.driver_error = None
173
+ self.velocity_error = None
174
+ self.current_error = None
175
+ self.estop_error = None
176
+ self.calibrated = None
177
+ self.activated = None
178
+ self.HALL_trigger = None
179
+ self.additional_pin2 = None
180
+ self.hall_index = None
181
+ self.Kt = None
182
+ self.ping = None
183
+ self.timestamp = None
184
+
185
+ self.HARDWARE_VERSION = None
186
+ self.BATCH_DATE = None
187
+ self.SOFTWARE_VERSION = None
188
+ self.SERIAL_NUMBER = None
189
+
190
+ self.DLC_warrning = None
191
+
192
+
193
+ @property
194
+ def velocity_limit(self):
195
+ return self._velocity_limit
196
+
197
+ @velocity_limit.setter
198
+ def velocity_limit(self, value):
199
+ # Ensure the value is within acceptable range or add additional validation logic
200
+ self._velocity_limit = value
201
+
202
+
203
+ def UnpackData(self,message,UnpackedMessageID):
204
+
205
+ #logging.debug(message.data)
206
+ #logging.debug(UnpackedMessageID.node_id)
207
+ self.Set_2_none()
208
+
209
+ self.timestamp = message.timestamp
210
+ match UnpackedMessageID.command_id:
211
+
212
+ case self.RESPOND_HEARTBEAT_ID:
213
+ self.heartbeat = 1
214
+ #logging.debug("Got heartbeat")
215
+
216
+
217
+ case self.RESPOND_DATA_PACK_1_ID:
218
+ if message.dlc == 8:
219
+ self.position = util.fuse_3_bytes(b'\x00' + message.data[0:3])
220
+ self.speed = util.fuse_3_bytes(b'\x00' + message.data[3:6])
221
+ self.current = util.fuse_2_bytes(b'\x00' + b'\x00' + message.data[6:8])
222
+ else:
223
+ self.DLC_warrning = 1
224
+ #logging.debug(f"Encoder position ticks: {self.position}")
225
+ #logging.debug(f"Encoder speed: {self.speed}")
226
+ #logging.debug(f"current is: {self.current}")
227
+
228
+
229
+
230
+ case self.RESPOND_DATA_PACK_2_ID:
231
+ None
232
+
233
+ case self.RESPOND_DATA_PACK_3_ID:
234
+ None
235
+
236
+ case self.RESPOND_DATA_HALL:
237
+ if message.dlc == 4:
238
+ self.position = util.fuse_3_bytes(b'\x00' + message.data[0:3])
239
+ byte1 = util.split_2_bitfield(message.data[3])
240
+ self.HALL_trigger = byte1[0]
241
+ self.additional_pin2 = byte1[1]
242
+ self.hall_index = byte1[2] # new: edge detected flag
243
+ else:
244
+ self.DLC_warrning = 1
245
+
246
+
247
+ case self.RESPOND_GRIPPER_DATA_PACK_ID:
248
+ if message.dlc == 4:
249
+ self.gripper_position = message.data[0]
250
+ #self.gripper_speed = message.data[1]
251
+ self.gripper_current = util.fuse_2_bytes(b'\x00' + b'\x00' + message.data[1:3])
252
+ byte1 = util.split_2_bitfield(message.data[3])
253
+ self.gripper_activated = byte1[0]
254
+ self.gripper_action_status = byte1[1]
255
+ self.gripper_object_detection = util.combine_bits(byte1[2],byte1[3])
256
+ self.gripper_temperature_error = byte1[4]
257
+ self.gripper_timeout_error = byte1[5]
258
+ self.gripper_estop_error = byte1[6]
259
+ self.gripper_calibrated = byte1[7]
260
+ else:
261
+ self.DLC_warrning = 1
262
+
263
+ #logging.debug("Got gripper data")
264
+ #logging.debug(f"Position is: {self.gripper_position}")
265
+ #logging.debug(f"Current is: {self.gripper_current}")
266
+ #logging.debug(f"Activated is: {self.gripper_activated}")
267
+ #logging.debug(f"Action status is: {self.gripper_action_status}")
268
+ #logging.debug(f"Object detection is: {self.gripper_object_detection}")
269
+ #logging.debug(f"Temperature error is: {self.gripper_temperature_error}")
270
+ #logging.debug(f"Timeout error is: {self.gripper_timeout_error}")
271
+ #logging.debug(f"Estop error is: {self.gripper_estop_error}")
272
+ #logging.debug(f"Calibrated is: {self.gripper_calibrated}")
273
+
274
+
275
+
276
+
277
+ # Radi
278
+ case self.SEND_RESPOND_TEMPERATURE_ID:
279
+ if message.dlc == 2:
280
+ self.temperature = util.fuse_2_bytes(b'\x00'+ b'\x00' + message.data[0:2])
281
+ else:
282
+ self.DLC_warrning = 1
283
+ #logging.debug(f"Temperature is: {self.temperature}")
284
+
285
+
286
+ # Radi
287
+ case self.SEND_RESPOND_DEVICE_INFO_ID:
288
+ if message.dlc == 7:
289
+ self.HARDWARE_VERSION = message.data[0]
290
+ self.BATCH_DATE = message.data[1]
291
+ self.SOFTWARE_VERSION = message.data[2]
292
+ self.SERIAL_NUMBER = util.fuse_4_bytes(message.data[3:7])
293
+ else:
294
+ self.DLC_warrning = 1
295
+
296
+ #logging.debug(f"Hardware version is: {self.HARDWARE_VERSION}")
297
+ #logging.debug(f"Batch date is: {self.BATCH_DATE}")
298
+ #logging.debug(f"Software version is: {self.SOFTWARE_VERSION}")
299
+ #logging.debug(f"Serial number is: {self.SERIAL_NUMBER}")
300
+
301
+
302
+ #Radi
303
+ case self.SEND_RESPOND_ENCODER_DATA_ID:
304
+ if message.dlc == 8:
305
+ self.position = util.fuse_4_bytes(message.data[0:4])
306
+ self.speed = util.fuse_4_bytes(message.data[4:8])
307
+ else:
308
+ self.DLC_warrning = 1
309
+ #logging.debug(f"Encoder position ticks: {self.position}")
310
+ #logging.debug(f"Encoder speed: {self.speed}")
311
+
312
+ # Radi
313
+ case self.SEND_RESPOND_VOLTAGE_ID:
314
+ if message.dlc == 2:
315
+ self.voltage = util.fuse_2_bytes(b'\x00'+ b'\x00' + message.data[0:2])
316
+ else:
317
+ self.DLC_warrning = 1
318
+ #logging.debug(f"Voltage is: {self.voltage}")
319
+
320
+
321
+ case self.SEND_RESPOND_KT_ID:
322
+ if message.dlc == 4:
323
+ self.Kt = struct.unpack('>f', bytes(message.data[0:4]))[0]
324
+ else:
325
+ self.DLC_warrning = 1
326
+
327
+ # Radi
328
+ case self.SEND_RESPOND_STATE_OF_ERRORS_ID:
329
+ if message.dlc == 2:
330
+ byte1 = util.split_2_bitfield(message.data[0])
331
+ byte2 = util.split_2_bitfield(message.data[1])
332
+ self.error = byte1[0]
333
+ self.temperature_error = byte1[1]
334
+ self.encoder_error = byte1[2]
335
+ self.vbus_error = byte1[3]
336
+ self.driver_error = byte1[4]
337
+ self.velocity_error = byte1[5]
338
+ self.current_error = byte1[6]
339
+ self.estop_error = byte1[7]
340
+ self.calibrated = byte2[0]
341
+ self.activated = byte2[1]
342
+ self.watchdog_error = byte2[2]
343
+
344
+
345
+ else:
346
+ self.DLC_warrning = 1
347
+
348
+ #logging.debug(f"Error is: {self.error}")
349
+ #logging.debug(f"Temperature rror is: {self.temperature_error}")
350
+ #logging.debug(f"Encoder error is: {self.encoder_error}")
351
+ #logging.debug(f"Vbus error is: {self.vbus_error}")
352
+ #logging.debug(f"Driver error is: {self.driver_error}")
353
+ #logging.debug(f"Velocity error is: {self.velocity_error}")
354
+ #logging.debug(f"Current error is: {self.current_error}")
355
+ #logging.debug(f"Estop error is: {self.estop_error}")
356
+ #logging.debug(f"Watchdog error is: {self.watchdog_error}")
357
+ #logging.debug(f"Calibrated is: {self.calibrated}")
358
+ #logging.debug(f"Activated is: {self.activated}")
359
+
360
+
361
+ # Radi
362
+ case self.SEND_RESPOND_IQ_DATA_ID:
363
+ if message.dlc == 2:
364
+ self.current = util.fuse_2_bytes(b'\x00'+ b'\x00' + message.data[0:2])
365
+ else:
366
+ self.DLC_warrning = 1
367
+ #logging.debug(f"Current is: {self.current}")
368
+
369
+ # Radi
370
+ case self.SEND_RESPOND_PING_ID:
371
+ self.ping = 1
372
+ #logging.debug(f"Ping is: {self.ping}")
373
+
374
+
375
+ def Send_data_pack_1(self,Position = None,Speed = None, Current = None):
376
+
377
+ if(Position != None and Speed != None and Current != None):
378
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_1_ID,0)
379
+ _position = util.split_2_3_bytes(Position)
380
+ _speed = util.split_2_3_bytes(Speed)
381
+ _current = util.split_2_2_bytes(Current)
382
+ combined = _position + _speed + _current
383
+ _combined = bytearray(combined)
384
+ self.communication.send_can_message(message_id = _canid, data = _combined)
385
+ elif(Position == None and Speed != None and Current != None):
386
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_1_ID,0)
387
+ _speed = util.split_2_3_bytes(Speed)
388
+ _current = util.split_2_2_bytes(Current)
389
+ combined = _speed + _current
390
+ _combined = bytearray(combined)
391
+ self.communication.send_can_message(message_id = _canid, data = _combined)
392
+ elif(Position == None and Speed == None and Current != None):
393
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_1_ID,0)
394
+ _current = util.split_2_2_bytes(Current)
395
+ combined = _current
396
+ _combined = bytearray(combined)
397
+ self.communication.send_can_message(message_id = _canid, data = _combined)
398
+ else:
399
+ #None
400
+ logging.debug("Invalid command")
401
+
402
+
403
+ #logging.debug("Send data pack 1")
404
+
405
+
406
+
407
+
408
+ def Send_data_pack_2():
409
+ None
410
+
411
+ def Send_data_pack_3():
412
+ None
413
+
414
+ def Send_data_pack_PD(self,Position = 0,Speed = 0, Current = 0):
415
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_PD_ID,0)
416
+ _position = util.split_2_3_bytes(Position)
417
+ _speed = util.split_2_3_bytes(Speed)
418
+ _current = util.split_2_2_bytes(Current)
419
+ combined = _position + _speed + _current
420
+ _combined = bytearray(combined)
421
+ self.communication.send_can_message(message_id = _canid, data = _combined)
422
+
423
+ #logging.debug("Send data pack PD")
424
+
425
+ def Send_data_pack_HALL(self,Speed = 0, trigger_value = 0):
426
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_HALL_INDEX,0)
427
+ _speed = util.split_2_3_bytes(Speed)
428
+ # trigger_value sent as raw byte: 0=low level, 1=high level, 2=any edge
429
+ combined = _speed + bytearray([trigger_value])
430
+ self.communication.send_can_message(message_id=_canid, data=combined)
431
+
432
+ #logging.debug("Send data pack HALL")
433
+
434
+ def Send_gripper_data_pack(self,position_ = None,speed_ = None,current_ = None,activate_ = None,action_ = None,ESTOP_ = None,Release_dir_ = None):
435
+
436
+ if(position_ != None and speed_!= None and current_ != None and activate_ != None and action_ != None and ESTOP_ != None and Release_dir_ != None):
437
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_GRIPPER_DATA_PACK_ID,0)
438
+ _current_ = util.split_2_2_bytes(current_)
439
+ bitfield_list = [activate_,action_,ESTOP_,Release_dir_,0,0,0,0]
440
+ fused = util.fuse_bitfield_2_bytearray(bitfield_list)
441
+ _combined = bytearray(bytes([position_]) + bytes([speed_]) + _current_ + fused)
442
+ self.communication.send_can_message(message_id = _canid, data = _combined)
443
+ #logging.debug("Send gripper data pack 1")
444
+ else:
445
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_GRIPPER_DATA_PACK_ID,0)
446
+ self.communication.send_can_message(message_id = _canid)
447
+
448
+
449
+ def Send_gripper_calibrate(self):
450
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_GRIPPER_CALIBRATE_ID,0)
451
+ self.communication.send_can_message(message_id = _canid)
452
+ #logging.debug("Send gripper calibrate")
453
+
454
+
455
+ # Radi
456
+ def Send_PD_Gains(self,KP,KD):
457
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_PD_GAINS_ID,0)
458
+ _KP = util.float_to_bytes(KP)
459
+ _KD = util.float_to_bytes(KD)
460
+ combined = _KP + _KD
461
+ _combined = bytearray(combined)
462
+ self.communication.send_can_message(message_id = _canid, data = _combined)
463
+ #logging.debug("Send PD gains")
464
+
465
+ # Radi
466
+ def Send_Current_Gains(self,KPIQ,KIIQ):
467
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_CURRENT_GAINS_ID,0)
468
+ _KPIQ = util.float_to_bytes(KPIQ)
469
+ _KIIQ = util.float_to_bytes(KIIQ)
470
+ combined = _KPIQ + _KIIQ
471
+ _combined = bytearray(combined)
472
+ self.communication.send_can_message(message_id = _canid, data = _combined)
473
+ #logging.debug("Send current gains")
474
+
475
+ # Radi
476
+ def Send_Velocity_Gains(self,KPV,KIV):
477
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_VELOCITY_GAINS_ID,0)
478
+ _KPV = util.float_to_bytes(KPV)
479
+ _KIV = util.float_to_bytes(KIV)
480
+ combined = _KPV + _KIV
481
+ _combined = bytearray(combined)
482
+ self.communication.send_can_message(message_id = _canid, data = _combined)
483
+ #logging.debug("Send velocity gains")
484
+
485
+ # Radi
486
+ def Send_Position_Gains(self,KPP):
487
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_POSITION_GAINS_ID,0)
488
+ _KPP = util.float_to_bytes(KPP)
489
+ combined = _KPP
490
+ _combined = bytearray(combined)
491
+ self.communication.send_can_message(message_id = _canid, data = _combined)
492
+ #logging.debug("Send new position gains")
493
+
494
+ # Radi
495
+ def Send_Limits(self,Velocity_limit,Current_limit):
496
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_LIMITS_ID,0)
497
+ _v_limit = util.float_to_bytes(Velocity_limit)
498
+ _c_limit = util.float_to_bytes(Current_limit)
499
+ combined = _v_limit + _c_limit
500
+ _combined = bytearray(combined)
501
+ self.communication.send_can_message(message_id = _canid, data = _combined)
502
+ #logging.debug("Send new velocity and current limits")
503
+
504
+ # Radi
505
+ def Send_Kt(self,Kt):
506
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_KT_ID,0)
507
+ _Kt = util.float_to_bytes(Kt)
508
+ combined = _Kt
509
+ _combined = bytearray(combined)
510
+ self.communication.send_can_message(message_id = _canid, data = _combined)
511
+ #logging.debug("Send new Kt")
512
+
513
+ # Radi čudno
514
+ def Send_CAN_ID(self,_CAN_ID):
515
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_CAN_ID,0) # Node id, command id, error bit ( 0 )
516
+ _data = _CAN_ID
517
+ self.communication.send_can_message(message_id = _canid, data = [_data])
518
+ #logging.debug("Send new CAN ID")
519
+
520
+ # Radi
521
+ def Send_ESTOP(self):
522
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_ESTOP_ID,0)
523
+ self.communication.send_can_message(message_id = _canid)
524
+ #logging.debug("Send ESTOP")
525
+
526
+ #Radi
527
+ def Send_Idle(self):
528
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_IDLE_ID,0)
529
+ self.communication.send_can_message(message_id = _canid)
530
+ #logging.debug("Send Idle")
531
+
532
+ # Radi
533
+ def Send_Save_config(self):
534
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_SAVE_CONFIG_ID,0)
535
+ self.communication.send_can_message(message_id = _canid)
536
+ #logging.debug("Send Save config")
537
+
538
+ # Radi
539
+ def Send_Reset(self):
540
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESET_ID,0)
541
+ self.communication.send_can_message(message_id = _canid)
542
+ #logging.debug("Send Reset")
543
+
544
+ # Radi
545
+ def Send_Clear_Error(self):
546
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_CLEAR_ERROR_ID,0)
547
+ self.communication.send_can_message(message_id = _canid)
548
+ #logging.debug("Send Clear Error")
549
+
550
+ # Radi
551
+ def Send_Watchdog(self,_rate,_action = 'Idle'):
552
+ if _action == 'Idle':
553
+ _action_byte = bytes([0x00])
554
+ elif _action == 'Hold':
555
+ _action_byte = bytes([0x01])
556
+ elif _action == 'Brake':
557
+ _action_byte = bytes([0x02])
558
+
559
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_WATCHDOG_ID,0)
560
+ watch_time = util.split_2_4_bytes(_rate)
561
+ _combined = bytearray(watch_time + _action_byte)
562
+ self.communication.send_can_message(message_id = _canid, data = _combined)
563
+
564
+ # Radi
565
+ def Send_Heartbeat_Setup(self,_rate):
566
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_HEARTBEAT_SETUP_ID,0)
567
+ Heart_rate = util.split_2_4_bytes(_rate)
568
+ _combined = bytearray(Heart_rate)
569
+ self.communication.send_can_message(message_id = _canid, data = _combined)
570
+
571
+ # TODO
572
+ def Send_Cyclic_command(self,ID):
573
+ None
574
+
575
+ # The DLC value does not necessarily define the number of bytes of data in a message.
576
+ # Its purpose varies depending on the frame type - for data frames it represents the amount of data contained in the message, in remote frames it represents the amount of data being requested.
577
+
578
+ def Send_Respond_Temperature(self):
579
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_TEMPERATURE_ID,0)
580
+ self.communication.send_can_message(message_id = _canid, remote_frame=True)
581
+ #logging.debug("Request temperature")
582
+
583
+ def Send_Respond_Voltage(self):
584
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_VOLTAGE_ID,0)
585
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
586
+ #logging.debug("Request voltage")
587
+
588
+ def Send_Respond_Device_Info(self):
589
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_DEVICE_INFO_ID,0)
590
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
591
+ #logging.debug("Request device info")
592
+
593
+ def Send_Respond_State_of_Errors(self):
594
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_STATE_OF_ERRORS_ID,0)
595
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
596
+ #logging.debug("Request state of all errors")
597
+
598
+ def Send_Respond_Iq_data(self):
599
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_IQ_DATA_ID,0)
600
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
601
+ #logging.debug("Request Iq data")
602
+
603
+ def Send_Respond_Encoder_data(self):
604
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_ENCODER_DATA_ID,0)
605
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
606
+ #logging.debug("Request encoder data")
607
+
608
+ def Send_Respond_Ping(self):
609
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_PING_ID,0)
610
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
611
+ #logging.debug("Request ping")
612
+
613
+ def Send_Respond_Kt(self):
614
+ _canid = util.combine_2_can_id(self.node_id, self.SEND_RESPOND_KT_ID, 0)
615
+ self.communication.send_can_message(message_id=_canid, remote_frame=True)
616
+ #logging.debug("Request Kt")
617
+
618
+
619
+
620
+ if __name__ == "__main__":
621
+ None
@@ -0,0 +1,4 @@
1
+ from .Spectral_BLDC import CanCommunication, SpectralCAN
2
+ from .import CAN_utils
3
+
4
+ __all__ = ["CAN_utils", "Spectral_BLDC"]
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: Spectral_BLDC
3
+ Version: 1.19
4
+ Summary: Python module for Spectral BLDC motor controllers
5
+ Author: Source robotics (Petar Crnjak)
6
+ Author-email: <info@source-robotics.com>
7
+ License: MIT
8
+ Project-URL: Documentation, https://source-robotics.github.io/PAROL-docs/
9
+ Project-URL: Source, https://source-robotics.github.io/PAROL-docs/
10
+ Keywords: python,BLDC,CANBUS,Robot,Source robotics,robotics
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Operating System :: Unix
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: python-can
20
+ Requires-Dist: pyserial
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: keywords
27
+ Dynamic: license
28
+ Dynamic: license-file
29
+ Dynamic: project-url
30
+ Dynamic: requires-dist
31
+ Dynamic: summary
32
+
33
+
34
+ # Spectral-BLDC
35
+
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) ![Issues](https://img.shields.io/github/issues/PCrnjak/Spectral-BLDC-Python) ![release](https://img.shields.io/github/v/release/PCrnjak/Spectral-BLDC-Python)
37
+
38
+ Python lib for controlling [spectral BLDC](https://github.com/PCrnjak/Spectral-Micro-BLDC-controller/blob/main/README.md) controllers and [SSG-48 gripper](https://github.com/PCrnjak/SSG-48-adaptive-electric-gripper) over CAN bus.
39
+ For more info about this API and all available commands check [DOCS](https://source-robotics.github.io/Spectral-BLDC-docs/apage7_can/)
40
+
41
+ # How to install
42
+
43
+ pip install Spectral-BLDC
44
+
45
+ # Basic example
46
+
47
+
48
+ ``` py
49
+ import Spectral_BLDC as Spectral
50
+ import time
51
+
52
+
53
+ Communication1 = Spectral.CanCommunication(bustype='slcan', channel='COM41', bitrate=1000000)
54
+ Motor1 = Spectral.SpectralCAN(node_id=0, communication=Communication1)
55
+
56
+ while True:
57
+
58
+ Motor1.Send_Respond_Encoder_data()
59
+
60
+ message, UnpackedMessageID = Communication1.receive_can_messages(timeout=0.2)
61
+
62
+ if message is not None:
63
+
64
+ Motor1.UnpackData(message,UnpackedMessageID)
65
+ print(f"Motor position is: {Motor1.position}")
66
+ print(f"Motor speed is: {Motor1.speed}")
67
+
68
+ else:
69
+ print("No message after timeout period!")
70
+ print("")
71
+ time.sleep(1 )
72
+ ```
73
+
74
+
75
+ # More examples
76
+
77
+ Check out the [Examples folder](https://github.com/PCrnjak/Spectral-BLDC-Python/tree/main/examples) for more examples!
78
+ Available examples:
79
+ * Send_respond_1
80
+ * Get_encoder_data
81
+ * SSG48_gripper_test
82
+
83
+
84
+
85
+ This project is entirely open-source and free for all to use. Any support, whether through donations or advice, is greatly appreciated. Thank you!
86
+
87
+ [![General badge](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/PCrnjak?locale.x=en_US)
88
+ [![General badge](https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white)](https://www.patreon.com/PCrnjak)
@@ -0,0 +1,8 @@
1
+ Spectral_BLDC/CAN_utils.py,sha256=AFi9vhcVQlBiUwi4M6jTuJG4cVx94CAxvXGCfpiVbqQ,3649
2
+ Spectral_BLDC/Spectral_BLDC.py,sha256=h786AuE6lLapKgeyRfxQLLckwTiYV_cv3RD-93aXGAw,24768
3
+ Spectral_BLDC/__init__.py,sha256=OhIvpuQNYke_kwPnDVqcOapFRW669kNRaMf2DJ0_faE,126
4
+ spectral_bldc-1.19.dist-info/licenses/LICENSE,sha256=yauL0_2mxmTslgz6-SDQFkSWymp_sw2_xx8bK_UxUmM,1090
5
+ spectral_bldc-1.19.dist-info/METADATA,sha256=pQH5OhGI6pYM-_u9ou5jFyb_uO0e1PXjxk6eLyfFMms,3203
6
+ spectral_bldc-1.19.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ spectral_bldc-1.19.dist-info/top_level.txt,sha256=hASH15Q_Hv6M0bbhy35EWqUxXSFBNh60i4Dw7lfKQJQ,14
8
+ spectral_bldc-1.19.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Petar Crnjak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ Spectral_BLDC