Spectral-BLDC 0.1__tar.gz

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,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,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: Spectral_BLDC
3
+ Version: 0.1
4
+ Summary: Python module for Spectral BLDC motor controllers
5
+ Author: Source robotics (Petar Crnjak)
6
+ Author-email: <info@source-robotics.com>
7
+ Keywords: python,BLDC,CANBUS,Robot,Source robotics,robotics
8
+ Classifier: Development Status :: 1 - Planning
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: Unix
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+
16
+
17
+ # Spectral-BLDC
18
+ Python lib for controlling spectral BLDC controllers.
@@ -0,0 +1,2 @@
1
+ # Spectral-BLDC
2
+ Python lib for controlling spectral BLDC controllers.
@@ -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,558 @@
1
+ import can
2
+ import CAN_utils as util
3
+ import logging
4
+ from collections import namedtuple
5
+
6
+ logging.basicConfig(level = logging.DEBUG,
7
+ format='%(asctime)s.%(msecs)03d %(levelname)s:\t%(message)s',
8
+ datefmt='%H:%M:%S'
9
+ )
10
+
11
+ #logging.disable(logging.DEBUG)
12
+
13
+ CanMessage = namedtuple("CanMessage", ["node_id", "command_id", "error_bit"])
14
+
15
+
16
+ class CanCommunication:
17
+ def __init__(self, bustype='slcan', channel='COM41', bitrate=1000000):
18
+ self.bustype=bustype
19
+ self.channel=channel
20
+ self.bitrate=bitrate
21
+ self.bus = can.interface.Bus(bustype = self.bustype, channel = self.channel, bitrate = self.bitrate)
22
+
23
+ def send_can_message(self, message_id, data=b'', remote_frame=False):
24
+ message = can.Message(arbitration_id=message_id, data=data, is_remote_frame=remote_frame, is_extended_id=False)
25
+ self.bus.send(message)
26
+
27
+ def receive_can_messages(self,timeout = None):
28
+ self.timeout = timeout
29
+ message = self.bus.recv(timeout = self.timeout)
30
+ if message is not None:
31
+ node_id,command_id,error_bit = util.extract_from_can_id(message.arbitration_id)
32
+ can_message = CanMessage(node_id, command_id, error_bit)
33
+ else:
34
+ return None,None
35
+
36
+ return message, can_message
37
+
38
+
39
+
40
+ class SpectralCAN:
41
+
42
+ SEND_PD_GAINS_ID = 16
43
+ SEND_CURRENT_GAINS_ID = 17
44
+ SEND_VELOCITY_GAINS_ID = 18
45
+ SEND_POSITION_GAINS_ID = 19
46
+ SEND_LIMITS_ID = 20
47
+ SEND_KT_ID = 22
48
+ SEND_CAN_ID = 11
49
+ SEND_ESTOP_ID = 0
50
+ SEND_IDLE_ID = 12
51
+ SEND_SAVE_CONFIG_ID = 13
52
+ SEND_RESET_ID = 14
53
+ SEND_CLEAR_ERROR_ID = 1
54
+ SEND_WATCHDOG_ID = 15
55
+ SEND_HEARTBEAT_SETUP_ID = 30
56
+ SEND_CYCLIC_COMMAND_ID = 29
57
+
58
+ SEND_RESPOND_TEMPERATURE_ID = 23
59
+ SEND_RESPOND_DEVICE_INFO_ID = 25
60
+ SEND_RESPOND_ENCODER_DATA_ID = 28
61
+ SEND_RESPOND_VOLTAGE_ID = 24
62
+ SEND_RESPOND_STATE_OF_ERRORS_ID = 26
63
+ SEND_RESPOND_IQ_DATA_ID = 27
64
+ SEND_RESPOND_PING_ID = 10
65
+
66
+ SEND_DATA_PACK_1_ID = 2
67
+ SEND_DATA_PACK_2_ID = 6
68
+ SEND_DATA_PACK_3_ID = 8
69
+ SEND_DATA_PACK_PD_ID = 4
70
+
71
+ SEND_GRIPPER_DATA_PACK_ID = 61
72
+ SEND_GRIPPER_CALIBRATE_ID = 62
73
+
74
+ RESPOND_HEARTBEAT_ID = 9
75
+ RESPOND_DATA_PACK_1_ID = 3
76
+ RESPOND_DATA_PACK_2_ID = 5
77
+ RESPOND_DATA_PACK_3_ID = 7
78
+ RESPOND_GRIPPER_DATA_PACK_ID = 60
79
+
80
+
81
+ def __init__(self, node_id, communication):
82
+ self.node_id = node_id
83
+ self.communication = communication
84
+
85
+ self.temperature = None
86
+ self.position = None
87
+ self.speed = None
88
+ self.current = None
89
+ self.voltage = None
90
+ self.heartbeat = None
91
+
92
+ self.gripper_position = None
93
+ self.gripper_speed = None
94
+ self.gripper_current = None
95
+ self.gripper_activated = None
96
+ self.gripper_action_status = None
97
+ self.gripper_object_detection = None
98
+ self.gripper_temperature_error = None
99
+ self.gripper_timeout_error = None
100
+ self.gripper_estop_error = None
101
+ self.gripper_calibrated = None
102
+
103
+ self.error_bit = None
104
+ self.error_bit = None
105
+ self.error = None
106
+ self.temperature_error = None
107
+ self.encoder_error = None
108
+ self.vbus_error = None
109
+ self.driver_error = None
110
+ self.velocity_error = None
111
+ self.current_error = None
112
+ self.estop_error = None
113
+ self.calibrated = None
114
+ self.activated = None
115
+ self.watchdog_error = None
116
+
117
+ self.ping = None
118
+ self.timestamp = None
119
+
120
+ self.HARDWARE_VERSION = None
121
+ self.BATCH_DATE = None
122
+ self.SOFTWARE_VERSION = None
123
+ self.SERIAL_NUMBER = None
124
+
125
+ self.DLC_warrning = None
126
+
127
+
128
+ # Configuration variables
129
+ self._velocity_limit = 0
130
+
131
+ def Set_2_none(self):
132
+ self.temperature = None
133
+ self.position = None
134
+ self.speed = None
135
+ self.current = None
136
+ self.voltage = None
137
+ self.heartbeat = None
138
+
139
+ self.error_bit = None
140
+ self.error = None
141
+ self.temperature_error = None
142
+ self.encoder_error = None
143
+ self.vbus_error = None
144
+ self.driver_error = None
145
+ self.velocity_error = None
146
+ self.current_error = None
147
+ self.estop_error = None
148
+ self.calibrated = None
149
+ self.activated = None
150
+
151
+ self.ping = None
152
+ self.timestamp = None
153
+
154
+ self.HARDWARE_VERSION = None
155
+ self.BATCH_DATE = None
156
+ self.SOFTWARE_VERSION = None
157
+ self.SERIAL_NUMBER = None
158
+
159
+ self.DLC_warrning = None
160
+
161
+
162
+ @property
163
+ def velocity_limit(self):
164
+ return self._velocity_limit
165
+
166
+ @velocity_limit.setter
167
+ def velocity_limit(self, value):
168
+ # Ensure the value is within acceptable range or add additional validation logic
169
+ self._velocity_limit = value
170
+
171
+
172
+ def UnpackData(self,message,UnpackedMessageID):
173
+
174
+ #logging.debug(message.data)
175
+ #logging.debug(UnpackedMessageID.node_id)
176
+ self.Set_2_none()
177
+
178
+ self.timestamp = message.timestamp
179
+ match UnpackedMessageID.command_id:
180
+
181
+ case self.RESPOND_HEARTBEAT_ID:
182
+ self.heartbeat = 1
183
+ logging.debug("Got heartbeat")
184
+
185
+
186
+ case self.RESPOND_DATA_PACK_1_ID:
187
+ if message.dlc == 8:
188
+ self.position = util.fuse_3_bytes(b'\x00' + message.data[0:3])
189
+ self.speed = util.fuse_3_bytes(b'\x00' + message.data[3:6])
190
+ self.current = util.fuse_2_bytes(b'\x00' + b'\x00' + message.data[6:8])
191
+ else:
192
+ self.DLC_warrning = 1
193
+ logging.debug(f"Encoder position ticks: {self.position}")
194
+ logging.debug(f"Encoder speed: {self.speed}")
195
+ logging.debug(f"current is: {self.current}")
196
+
197
+
198
+
199
+ case self.RESPOND_DATA_PACK_2_ID:
200
+ None
201
+
202
+ case self.RESPOND_DATA_PACK_3_ID:
203
+ None
204
+
205
+
206
+ case self.RESPOND_GRIPPER_DATA_PACK_ID:
207
+ if message.dlc == 4:
208
+ self.gripper_position = message.data[0]
209
+ #self.gripper_speed = message.data[1]
210
+ self.gripper_current = util.fuse_2_bytes(b'\x00' + b'\x00' + message.data[1:3])
211
+ byte1 = util.split_2_bitfield(message.data[3])
212
+ self.gripper_activated = byte1[0]
213
+ self.gripper_action_status = byte1[1]
214
+ self.gripper_object_detection = util.combine_bits(byte1[2],byte1[3])
215
+ self.gripper_temperature_error = byte1[4]
216
+ self.gripper_timeout_error = byte1[5]
217
+ self.gripper_estop_error = byte1[6]
218
+ self.gripper_calibrated = byte1[7]
219
+ else:
220
+ self.DLC_warrning = 1
221
+
222
+ logging.debug("Got gripper data")
223
+ logging.debug(f"Position is: {self.gripper_position}")
224
+ logging.debug(f"Current is: {self.gripper_current}")
225
+ logging.debug(f"Activated is: {self.gripper_activated}")
226
+ logging.debug(f"Action status is: {self.gripper_action_status}")
227
+ logging.debug(f"Object detection is: {self.gripper_object_detection}")
228
+ logging.debug(f"Temperature error is: {self.gripper_temperature_error}")
229
+ logging.debug(f"Timeout error is: {self.gripper_timeout_error}")
230
+ logging.debug(f"Estop error is: {self.gripper_estop_error}")
231
+ logging.debug(f"Calibrated is: {self.gripper_calibrated}")
232
+
233
+
234
+
235
+
236
+ # Radi
237
+ case self.SEND_RESPOND_TEMPERATURE_ID:
238
+ if message.dlc == 2:
239
+ self.temperature = util.fuse_2_bytes(b'\x00'+ b'\x00' + message.data[0:2])
240
+ else:
241
+ self.DLC_warrning = 1
242
+ logging.debug(f"Temperature is: {self.temperature}")
243
+
244
+
245
+ # Radi
246
+ case self.SEND_RESPOND_DEVICE_INFO_ID:
247
+ if message.dlc == 7:
248
+ self.HARDWARE_VERSION = message.data[0]
249
+ self.BATCH_DATE = message.data[1]
250
+ self.SOFTWARE_VERSION = message.data[2]
251
+ self.SERIAL_NUMBER = util.fuse_4_bytes(message.data[3:7])
252
+ else:
253
+ self.DLC_warrning = 1
254
+
255
+ logging.debug(f"Hardware version is: {self.HARDWARE_VERSION}")
256
+ logging.debug(f"Batch date is: {self.BATCH_DATE}")
257
+ logging.debug(f"Software version is: {self.SOFTWARE_VERSION}")
258
+ logging.debug(f"Serial number is: {self.SERIAL_NUMBER}")
259
+
260
+
261
+ #Radi
262
+ case self.SEND_RESPOND_ENCODER_DATA_ID:
263
+ if message.dlc == 8:
264
+ self.position = util.fuse_4_bytes(message.data[0:4])
265
+ self.speed = util.fuse_4_bytes(message.data[4:8])
266
+ else:
267
+ self.DLC_warrning = 1
268
+ logging.debug(f"Encoder position ticks: {self.position}")
269
+ logging.debug(f"Encoder speed: {self.speed}")
270
+
271
+ # Radi
272
+ case self.SEND_RESPOND_VOLTAGE_ID:
273
+ if message.dlc == 2:
274
+ self.voltage = util.fuse_2_bytes(b'\x00'+ b'\x00' + message.data[0:2])
275
+ else:
276
+ self.DLC_warrning = 1
277
+ logging.debug(f"Voltage is: {self.voltage}")
278
+
279
+ # Radi
280
+ case self.SEND_RESPOND_STATE_OF_ERRORS_ID:
281
+ if message.dlc == 2:
282
+ byte1 = util.split_2_bitfield(message.data[0])
283
+ byte2 = util.split_2_bitfield(message.data[1])
284
+ self.error = byte1[0]
285
+ self.temperature_error = byte1[1]
286
+ self.encoder_error = byte1[2]
287
+ self.vbus_error = byte1[3]
288
+ self.driver_error = byte1[4]
289
+ self.velocity_error = byte1[5]
290
+ self.current_error = byte1[6]
291
+ self.estop_error = byte1[7]
292
+ self.calibrated = byte2[0]
293
+ self.activated = byte2[1]
294
+ self.watchdog_error = byte2[2]
295
+
296
+
297
+ else:
298
+ self.DLC_warrning = 1
299
+
300
+ logging.debug(f"Error is: {self.error}")
301
+ logging.debug(f"Temperature rror is: {self.temperature_error}")
302
+ logging.debug(f"Encoder error is: {self.encoder_error}")
303
+ logging.debug(f"Vbus error is: {self.vbus_error}")
304
+ logging.debug(f"Driver error is: {self.driver_error}")
305
+ logging.debug(f"Velocity error is: {self.velocity_error}")
306
+ logging.debug(f"Current error is: {self.current_error}")
307
+ logging.debug(f"Estop error is: {self.estop_error}")
308
+ logging.debug(f"Watchdog error is: {self.watchdog_error}")
309
+ logging.debug(f"Calibrated is: {self.calibrated}")
310
+ logging.debug(f"Activated is: {self.activated}")
311
+
312
+
313
+ # Radi
314
+ case self.SEND_RESPOND_IQ_DATA_ID:
315
+ if message.dlc == 2:
316
+ self.current = util.fuse_2_bytes(b'\x00'+ b'\x00' + message.data[0:2])
317
+ else:
318
+ self.DLC_warrning = 1
319
+ logging.debug(f"Current is: {self.current}")
320
+
321
+ # Radi
322
+ case self.SEND_RESPOND_PING_ID:
323
+ self.ping = 1
324
+ logging.debug(f"Ping is: {self.ping}")
325
+
326
+
327
+ def Send_data_pack_1(self,Position = None,Speed = None, Current = None):
328
+
329
+ if(Position != None and Speed != None and Current != None):
330
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_1_ID,0)
331
+ _position = util.split_2_3_bytes(Position)
332
+ _speed = util.split_2_3_bytes(Speed)
333
+ _current = util.split_2_2_bytes(Current)
334
+ combined = _position + _speed + _current
335
+ _combined = bytearray(combined)
336
+ self.communication.send_can_message(message_id = _canid, data = _combined)
337
+ elif(Position == None and Speed != None and Current != None):
338
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_1_ID,0)
339
+ _speed = util.split_2_3_bytes(Speed)
340
+ _current = util.split_2_2_bytes(Current)
341
+ combined = _speed + _current
342
+ _combined = bytearray(combined)
343
+ self.communication.send_can_message(message_id = _canid, data = _combined)
344
+ elif(Position == None and Speed == None and Current != None):
345
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_1_ID,0)
346
+ _current = util.split_2_2_bytes(Current)
347
+ combined = _current
348
+ _combined = bytearray(combined)
349
+ self.communication.send_can_message(message_id = _canid, data = _combined)
350
+ else:
351
+ logging.debug("Invalid command")
352
+
353
+
354
+ logging.debug("Send data pack 1")
355
+
356
+
357
+
358
+
359
+ def Send_data_pack_2():
360
+ None
361
+
362
+ def Send_data_pack_3():
363
+ None
364
+
365
+ def Send_data_pack_PD(self,Position = 0,Speed = 0, Current = 0):
366
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_DATA_PACK_PD_ID,0)
367
+ _position = util.split_2_3_bytes(Position)
368
+ _speed = util.split_2_3_bytes(Speed)
369
+ _current = util.split_2_2_bytes(Current)
370
+ combined = _position + _speed + _current
371
+ _combined = bytearray(combined)
372
+ self.communication.send_can_message(message_id = _canid, data = _combined)
373
+
374
+ logging.debug("Send data pack PD")
375
+
376
+ def Send_gripper_data_pack(self,position_ = None,speed_ = None,current_ = None,activate_ = None,action_ = None,ESTOP_ = None,Release_dir_ = None):
377
+
378
+ if(position_ != None and speed_!= None and current_ != None and activate_ != None and action_ != None and ESTOP_ != None and Release_dir_ != None):
379
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_GRIPPER_DATA_PACK_ID,0)
380
+ _current_ = util.split_2_2_bytes(current_)
381
+ bitfield_list = [activate_,action_,ESTOP_,Release_dir_,0,0,0,0]
382
+ fused = util.fuse_bitfield_2_bytearray(bitfield_list)
383
+ _combined = bytearray(bytes([position_]) + bytes([speed_]) + _current_ + fused)
384
+ self.communication.send_can_message(message_id = _canid, data = _combined)
385
+ logging.debug("Send gripper data pack 1")
386
+ else:
387
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_GRIPPER_DATA_PACK_ID,0)
388
+ self.communication.send_can_message(message_id = _canid)
389
+
390
+
391
+ def Send_gripper_calibrate(self):
392
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_GRIPPER_CALIBRATE_ID,0)
393
+ self.communication.send_can_message(message_id = _canid)
394
+ logging.debug("Send gripper calibrate")
395
+
396
+
397
+ # Radi
398
+ def Send_PD_Gains(self,KP,KD):
399
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_PD_GAINS_ID,0)
400
+ _KP = util.float_to_bytes(KP)
401
+ _KD = util.float_to_bytes(KD)
402
+ combined = _KP + _KD
403
+ _combined = bytearray(combined)
404
+ self.communication.send_can_message(message_id = _canid, data = _combined)
405
+ logging.debug("Send PD gains")
406
+
407
+ # Radi
408
+ def Send_Current_Gains(self,KPIQ,KIIQ):
409
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_CURRENT_GAINS_ID,0)
410
+ _KPIQ = util.float_to_bytes(KPIQ)
411
+ _KIIQ = util.float_to_bytes(KIIQ)
412
+ combined = _KPIQ + _KIIQ
413
+ _combined = bytearray(combined)
414
+ self.communication.send_can_message(message_id = _canid, data = _combined)
415
+ logging.debug("Send current gains")
416
+
417
+ # Radi
418
+ def Send_Velocity_Gains(self,KPV,KIV):
419
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_VELOCITY_GAINS_ID,0)
420
+ _KPV = util.float_to_bytes(KPV)
421
+ _KIV = util.float_to_bytes(KIV)
422
+ combined = _KPV + _KIV
423
+ _combined = bytearray(combined)
424
+ self.communication.send_can_message(message_id = _canid, data = _combined)
425
+ logging.debug("Send velocity gains")
426
+
427
+ # Radi
428
+ def Send_Position_Gains(self,KPP):
429
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_POSITION_GAINS_ID,0)
430
+ _KPP = util.float_to_bytes(KPP)
431
+ combined = _KPP
432
+ _combined = bytearray(combined)
433
+ self.communication.send_can_message(message_id = _canid, data = _combined)
434
+ logging.debug("Send new position gains")
435
+
436
+ # Radi
437
+ def Send_Limits(self,Velocity_limit,Current_limit):
438
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_LIMITS_ID,0)
439
+ _v_limit = util.float_to_bytes(Velocity_limit)
440
+ _c_limit = util.float_to_bytes(Current_limit)
441
+ combined = _v_limit + _c_limit
442
+ _combined = bytearray(combined)
443
+ self.communication.send_can_message(message_id = _canid, data = _combined)
444
+ logging.debug("Send new velocity and current limits")
445
+
446
+ # Radi
447
+ def Send_Kt(self,Kt):
448
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_KT_ID,0)
449
+ _Kt = util.float_to_bytes(Kt)
450
+ combined = _Kt
451
+ _combined = bytearray(combined)
452
+ self.communication.send_can_message(message_id = _canid, data = _combined)
453
+ logging.debug("Send new Kt")
454
+
455
+ # Radi čudno
456
+ def Send_CAN_ID(self,_CAN_ID):
457
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_CAN_ID,0) # Node id, command id, error bit ( 0 )
458
+ _data = _CAN_ID
459
+ self.communication.send_can_message(message_id = _canid, data = [_data])
460
+ logging.debug("Send new CAN ID")
461
+
462
+ # Radi
463
+ def Send_ESTOP(self):
464
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_ESTOP_ID,0)
465
+ self.communication.send_can_message(message_id = _canid)
466
+ logging.debug("Send ESTOP")
467
+
468
+ #Radi
469
+ def Send_Idle(self):
470
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_IDLE_ID,0)
471
+ self.communication.send_can_message(message_id = _canid)
472
+ logging.debug("Send Idle")
473
+
474
+ # Radi
475
+ def Send_Save_config(self):
476
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_SAVE_CONFIG_ID,0)
477
+ self.communication.send_can_message(message_id = _canid)
478
+ logging.debug("Send Save config")
479
+
480
+ # Radi
481
+ def Send_Reset(self):
482
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESET_ID,0)
483
+ self.communication.send_can_message(message_id = _canid)
484
+ logging.debug("Send Reset")
485
+
486
+ # Radi
487
+ def Send_Clear_Error(self):
488
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_CLEAR_ERROR_ID,0)
489
+ self.communication.send_can_message(message_id = _canid)
490
+ logging.debug("Send Clear Error")
491
+
492
+ # Radi
493
+ def Send_Watchdog(self,_rate,_action = 'Idle'):
494
+ if _action == 'Idle':
495
+ _action_byte = bytes([0x00])
496
+ elif _action == 'Hold':
497
+ _action_byte = bytes([0x01])
498
+ elif _action == 'Brake':
499
+ _action_byte = bytes([0x02])
500
+
501
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_WATCHDOG_ID,0)
502
+ watch_time = util.split_2_4_bytes(_rate)
503
+ _combined = bytearray(watch_time + _action_byte)
504
+ self.communication.send_can_message(message_id = _canid, data = _combined)
505
+
506
+ # Radi
507
+ def Send_Heartbeat_Setup(self,_rate):
508
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_HEARTBEAT_SETUP_ID,0)
509
+ Heart_rate = util.split_2_4_bytes(_rate)
510
+ _combined = bytearray(Heart_rate)
511
+ self.communication.send_can_message(message_id = _canid, data = _combined)
512
+
513
+ # TODO
514
+ def Send_Cyclic_command(self,ID):
515
+ None
516
+
517
+ # The DLC value does not necessarily define the number of bytes of data in a message.
518
+ # 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.
519
+
520
+ def Send_Respond_Temperature(self):
521
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_TEMPERATURE_ID,0)
522
+ self.communication.send_can_message(message_id = _canid, remote_frame=True)
523
+ logging.debug("Request temperature")
524
+
525
+ def Send_Respond_Voltage(self):
526
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_VOLTAGE_ID,0)
527
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
528
+ logging.debug("Request voltage")
529
+
530
+ def Send_Respond_Device_Info(self):
531
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_DEVICE_INFO_ID,0)
532
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
533
+ logging.debug("Request device info")
534
+
535
+ def Send_Respond_State_of_Errors(self):
536
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_STATE_OF_ERRORS_ID,0)
537
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
538
+ logging.debug("Request state of all errors")
539
+
540
+ def Send_Respond_Iq_data(self):
541
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_IQ_DATA_ID,0)
542
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
543
+ logging.debug("Request Iq data")
544
+
545
+ def Send_Respond_Encoder_data(self):
546
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_ENCODER_DATA_ID,0)
547
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
548
+ logging.debug("Request encoder data")
549
+
550
+ def Send_Respond_Ping(self):
551
+ _canid = util.combine_2_can_id(self.node_id,self.SEND_RESPOND_PING_ID,0)
552
+ self.communication.send_can_message(message_id = _canid, remote_frame = True)
553
+ logging.debug("Request ping")
554
+
555
+
556
+
557
+ if __name__ == "__main__":
558
+ None
@@ -0,0 +1 @@
1
+ __all__ = ["CAN_utils", "Spectral_BLDC"]
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: Spectral-BLDC
3
+ Version: 0.1
4
+ Summary: Python module for Spectral BLDC motor controllers
5
+ Author: Source robotics (Petar Crnjak)
6
+ Author-email: <info@source-robotics.com>
7
+ Keywords: python,BLDC,CANBUS,Robot,Source robotics,robotics
8
+ Classifier: Development Status :: 1 - Planning
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: Unix
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+
16
+
17
+ # Spectral-BLDC
18
+ Python lib for controlling spectral BLDC controllers.
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ Spectral_BLDC/CAN_utils.py
5
+ Spectral_BLDC/Spectral_BLDC.py
6
+ Spectral_BLDC/__init__.py
7
+ Spectral_BLDC.egg-info/PKG-INFO
8
+ Spectral_BLDC.egg-info/SOURCES.txt
9
+ Spectral_BLDC.egg-info/dependency_links.txt
10
+ Spectral_BLDC.egg-info/requires.txt
11
+ Spectral_BLDC.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ python-can
@@ -0,0 +1 @@
1
+ Spectral_BLDC
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ from setuptools import setup, find_packages
2
+ import codecs
3
+ import os
4
+
5
+ here = os.path.abspath(os.path.dirname(__file__))
6
+
7
+ with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh:
8
+ long_description = "\n" + fh.read()
9
+
10
+ VERSION = '0.01'
11
+ DESCRIPTION = 'Python module for Spectral BLDC motor controllers'
12
+
13
+ # Setting up
14
+ setup(
15
+ name="Spectral_BLDC",
16
+ version=VERSION,
17
+ author="Source robotics (Petar Crnjak)",
18
+ author_email="<info@source-robotics.com>",
19
+ description=DESCRIPTION,
20
+ long_description_content_type="text/markdown",
21
+ long_description=long_description,
22
+ packages=find_packages(),
23
+ install_requires=['python-can'],
24
+ keywords=['python', 'BLDC', 'CANBUS', 'Robot', 'Source robotics', 'robotics'],
25
+ classifiers=[
26
+ "Development Status :: 1 - Planning",
27
+ "Intended Audience :: Developers",
28
+ "Programming Language :: Python :: 3",
29
+ "Operating System :: Unix",
30
+ "Operating System :: Microsoft :: Windows",
31
+ ]
32
+ )