HandsON-BuildHat-API 1.0__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.
- HandsON_BuildHat_API/SpikePI.py +324 -0
- HandsON_BuildHat_API/__init__.py +4 -0
- handson_buildhat_api-1.0.dist-info/METADATA +49 -0
- handson_buildhat_api-1.0.dist-info/RECORD +7 -0
- handson_buildhat_api-1.0.dist-info/WHEEL +5 -0
- handson_buildhat_api-1.0.dist-info/licenses/LICENSE +9 -0
- handson_buildhat_api-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Encoding : UTF-8
|
|
2
|
+
# Date : 2025-04-30
|
|
3
|
+
# Production : HandsON Technology Co., Ltd
|
|
4
|
+
# Author : HaNeul Jung (caffeine.reload@gmail.com)
|
|
5
|
+
|
|
6
|
+
# All rights to the code, excluding third-party libraries used herein, are reserved by HandsON Technology Co., Ltd.
|
|
7
|
+
|
|
8
|
+
from serial import Serial
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
class _ConstModuleID:
|
|
12
|
+
'''
|
|
13
|
+
모듈 ID 모음
|
|
14
|
+
'''
|
|
15
|
+
COLOR = 0x3D
|
|
16
|
+
DISTANCE = 0x3E
|
|
17
|
+
FORCE = 0x3F
|
|
18
|
+
|
|
19
|
+
SMALL_MOTOR = 0x41
|
|
20
|
+
MEDIUM_MOTOR = 0x30
|
|
21
|
+
LARGE_MOTOR = 0x31
|
|
22
|
+
|
|
23
|
+
class _ConstValue:
|
|
24
|
+
'''
|
|
25
|
+
기타 상수 모음
|
|
26
|
+
'''
|
|
27
|
+
COMMAND = 0b1000 << 4
|
|
28
|
+
GET_DATA = 0b1001 << 4
|
|
29
|
+
ALL_PORT = 0b1111
|
|
30
|
+
|
|
31
|
+
MOTOR_PWM_MODE = 0
|
|
32
|
+
MOTOR_ABS_MODE = 1
|
|
33
|
+
MOTOR_REL_MODE = 2
|
|
34
|
+
|
|
35
|
+
class _PORT:
|
|
36
|
+
PORT = {
|
|
37
|
+
'A':0, 'B':1, 'C':2, 'D':3, 'E':4, 'F':5
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class _COLOR:
|
|
41
|
+
COLOR = {
|
|
42
|
+
0:'black', 1:'violet', 3:'blue', 4:'cyan',
|
|
43
|
+
5:'green', 7:'yellow', 9:'red', 10:'white',
|
|
44
|
+
255:'None'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class _Availability:
|
|
48
|
+
'''
|
|
49
|
+
내부용 시리얼 통신의 체크섬을 확인 및 체크섬을 만드는 함수를 제공합니다.
|
|
50
|
+
'''
|
|
51
|
+
@staticmethod
|
|
52
|
+
def make_checksum(byte:bytes) -> bytes:
|
|
53
|
+
'''
|
|
54
|
+
입력 받은 바이트를 기반으로 체크섬을 만들고 반환합니다.
|
|
55
|
+
'''
|
|
56
|
+
sum = 0
|
|
57
|
+
for i in byte: sum ^= i
|
|
58
|
+
return bytes([sum])
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def checksum_check(byte:bytes) -> bool:
|
|
62
|
+
'''
|
|
63
|
+
바이트열의 체크섬을 확인하고 불 값을 반환합니다.
|
|
64
|
+
'''
|
|
65
|
+
sum = 0
|
|
66
|
+
for i in byte[:-1]: sum ^= i
|
|
67
|
+
try:
|
|
68
|
+
if sum == byte[-1]: return True
|
|
69
|
+
else: return False
|
|
70
|
+
except IndexError:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
class _SerialManager:
|
|
74
|
+
'''
|
|
75
|
+
내부용 시리얼 버스 객체, **외부 접근 절대 금지**.
|
|
76
|
+
'''
|
|
77
|
+
_serial_port = (
|
|
78
|
+
'/dev/ttyTHS1', # Jetson Orin Nano
|
|
79
|
+
'/dev/ttyAMA0', # Raspberry PI 5
|
|
80
|
+
'/dev/ttyS0' # Raspberry PI 4
|
|
81
|
+
)
|
|
82
|
+
serial = None
|
|
83
|
+
|
|
84
|
+
for i in _serial_port:
|
|
85
|
+
try: serial = Serial(port=i, baudrate=115200, timeout=0.1)
|
|
86
|
+
except: pass
|
|
87
|
+
else: break
|
|
88
|
+
|
|
89
|
+
if serial == None:
|
|
90
|
+
raise Exception('사용 가능한 시리얼 포트를 찾을 수 없습니다.')
|
|
91
|
+
|
|
92
|
+
class _ModuleParent:
|
|
93
|
+
def _get_data(self, byte:bytes) -> bytes:
|
|
94
|
+
while True:
|
|
95
|
+
self._send_data(byte)
|
|
96
|
+
data = _SerialManager.serial.read(16)
|
|
97
|
+
|
|
98
|
+
if data == b'': continue
|
|
99
|
+
|
|
100
|
+
if _Availability.checksum_check(data): return data
|
|
101
|
+
|
|
102
|
+
def _send_data(self, byte:bytes) -> None:
|
|
103
|
+
com = byte
|
|
104
|
+
for i in range(0, 7-len(com)): com += b'\x00'
|
|
105
|
+
com += _Availability.make_checksum(com)
|
|
106
|
+
time.sleep(0.005)
|
|
107
|
+
_SerialManager.serial.write(com)
|
|
108
|
+
|
|
109
|
+
def _module_check(self, port:int, ID:tuple) -> None:
|
|
110
|
+
command = [_ConstValue.GET_DATA + _ConstValue.ALL_PORT]
|
|
111
|
+
if not self._get_data(byte=bytes(command))[port + 1] in ID:
|
|
112
|
+
raise Exception('해당 포트에 연결된 모듈이 다르거나 없습니다.')
|
|
113
|
+
|
|
114
|
+
class ColorSensor(_ModuleParent):
|
|
115
|
+
def __init__(self, port:str):
|
|
116
|
+
self._PORT = _PORT.PORT[port]
|
|
117
|
+
self._module_check(self._PORT, (_ConstModuleID.COLOR, ))
|
|
118
|
+
|
|
119
|
+
def get_color(self) -> str:
|
|
120
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
121
|
+
data = self._get_data(bytes(command))
|
|
122
|
+
return _COLOR.COLOR[data[1]]
|
|
123
|
+
|
|
124
|
+
def get_reflected_light(self) -> int:
|
|
125
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
126
|
+
data = self._get_data(bytes(command))
|
|
127
|
+
return data[2]
|
|
128
|
+
|
|
129
|
+
def get_rgb_intensity(self) -> tuple:
|
|
130
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
131
|
+
data = self._get_data(bytes(command))
|
|
132
|
+
return (data[3] + (data[4] << 8), data[5] + (data[6] << 8), data[7] + (data[8] << 8))
|
|
133
|
+
|
|
134
|
+
def get_red(self) -> int:
|
|
135
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
136
|
+
data = self._get_data(bytes(command))
|
|
137
|
+
return data[3] + (data[4] << 8)
|
|
138
|
+
|
|
139
|
+
def get_green(self) -> int:
|
|
140
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
141
|
+
data = self._get_data(bytes(command))
|
|
142
|
+
return data[5] + (data[6] << 8)
|
|
143
|
+
|
|
144
|
+
def get_blue(self) -> int:
|
|
145
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
146
|
+
data = self._get_data(bytes(command))
|
|
147
|
+
return data[7] + (data[8] << 8)
|
|
148
|
+
|
|
149
|
+
class DistanceSensor(_ModuleParent):
|
|
150
|
+
def __init__(self, port:str):
|
|
151
|
+
self._PORT = _PORT.PORT[port]
|
|
152
|
+
self._module_check(self._PORT, (_ConstModuleID.DISTANCE, ))
|
|
153
|
+
|
|
154
|
+
def get_distance_cm(self) -> int:
|
|
155
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
156
|
+
data = self._get_data(bytes(command))
|
|
157
|
+
return (data[1] + (data[2] << 8)) // 10
|
|
158
|
+
|
|
159
|
+
class ForceSensor(_ModuleParent):
|
|
160
|
+
def __init__(self, port:str):
|
|
161
|
+
self._PORT = _PORT.PORT[port]
|
|
162
|
+
self._module_check(self._PORT, (_ConstModuleID.FORCE, ))
|
|
163
|
+
|
|
164
|
+
def is_pressed(self) -> bool:
|
|
165
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
166
|
+
data = self._get_data(bytes(command))
|
|
167
|
+
if data[2] == 1: return True
|
|
168
|
+
else: return False
|
|
169
|
+
|
|
170
|
+
def get_force_percentage(self) -> int:
|
|
171
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
172
|
+
data = self._get_data(bytes(command))
|
|
173
|
+
return data[1]
|
|
174
|
+
|
|
175
|
+
class Motor(_ModuleParent):
|
|
176
|
+
def __init__(self, port:str):
|
|
177
|
+
self._PORT = _PORT.PORT[port]
|
|
178
|
+
self._module_check(self._PORT, (_ConstModuleID.SMALL_MOTOR, _ConstModuleID.MEDIUM_MOTOR, _ConstModuleID.LARGE_MOTOR))
|
|
179
|
+
|
|
180
|
+
def stop(self) -> None:
|
|
181
|
+
com = bytes((_ConstValue.COMMAND + self._PORT, _ConstValue.MOTOR_PWM_MODE, 0, 0))
|
|
182
|
+
self._send_data(com)
|
|
183
|
+
|
|
184
|
+
def start(self, speed:int) -> None:
|
|
185
|
+
if not speed in range(-100, 101):
|
|
186
|
+
raise Exception('모터의 속도는 ~100 ~ 100 사이의 정수만 입력 가능합니다.')
|
|
187
|
+
|
|
188
|
+
if speed > 0: sw = 1
|
|
189
|
+
else: sw = 0
|
|
190
|
+
|
|
191
|
+
com = bytes((_ConstValue.COMMAND + self._PORT, _ConstValue.MOTOR_PWM_MODE, sw, abs(speed)))
|
|
192
|
+
self._send_data(com)
|
|
193
|
+
|
|
194
|
+
def run_to_position(self, degrees, speed) -> None:
|
|
195
|
+
if not speed in range(0, 101):
|
|
196
|
+
raise Exception('모터의 속도는 0 ~ 100 사이의 정수만 입력 가능합니다.')
|
|
197
|
+
if not degrees in range(-180, 181):
|
|
198
|
+
raise Exception('모터의 절대 각도는 -180 ~ 180 사이의 정수만 입력 가능합니다.')
|
|
199
|
+
|
|
200
|
+
if degrees >= 0: target = degrees
|
|
201
|
+
else: target = degrees & 0xFFFF
|
|
202
|
+
|
|
203
|
+
com = bytes((_ConstValue.COMMAND + self._PORT, _ConstValue.MOTOR_ABS_MODE, speed, (target & 0xFF), (target >> 8)))
|
|
204
|
+
self._send_data(com)
|
|
205
|
+
|
|
206
|
+
def run_for_degrees(self, degrees, speed) -> None:
|
|
207
|
+
if not speed in range(0, 101):
|
|
208
|
+
raise Exception('모터의 속도는 0 ~ 100 사이의 정수만 입력 가능합니다.')
|
|
209
|
+
|
|
210
|
+
if not degrees in range(-2147483647, 2147483647):
|
|
211
|
+
raise Exception('모터의 동작 각도는 -2147483647 ~ 2147483647 사이의 값만 입력 가능합니다.')
|
|
212
|
+
|
|
213
|
+
if degrees >= 0: target = degrees
|
|
214
|
+
else: target = degrees & 0xFFFFFFFF
|
|
215
|
+
|
|
216
|
+
com = bytes((_ConstValue.COMMAND + self._PORT, _ConstValue.MOTOR_REL_MODE, speed, (target & 0xFF), (target >> 8) & 0xFF, (target >> 16) & 0xFF, (target >> 24) & 0xFF))
|
|
217
|
+
self._send_data(com)
|
|
218
|
+
|
|
219
|
+
def get_speed(self) -> int:
|
|
220
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
221
|
+
data = self._get_data(bytes(command))
|
|
222
|
+
if data[1] >> 7 == 0: return data[1]
|
|
223
|
+
else: return -(0xFF - data[1])
|
|
224
|
+
|
|
225
|
+
def get_position(self) -> int:
|
|
226
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
227
|
+
data = self._get_data(bytes(command))
|
|
228
|
+
if data[7] >> 7 == 0: return data[6] + (data[7] << 8)
|
|
229
|
+
else: return -(0xFFFF - (data[6] + (data[7] << 8) - 1))
|
|
230
|
+
|
|
231
|
+
def get_degrees_counted(self) -> int:
|
|
232
|
+
command = [_ConstValue.GET_DATA + self._PORT]
|
|
233
|
+
data = self._get_data(bytes(command))
|
|
234
|
+
if data[5] >> 7 == 0: return data[2] + (data[3] << 8) + (data[4] << 16) + (data[5] << 24)
|
|
235
|
+
else: return -(0xFFFFFFFF - (data[2] + (data[3] << 8) + (data[4] << 16) + (data[5] << 24) - 1))
|
|
236
|
+
|
|
237
|
+
class MotorPair(_ModuleParent):
|
|
238
|
+
def __init__(self, left_motor, right_motor):
|
|
239
|
+
self._PORTs = (_PORT.PORT[left_motor], _PORT.PORT[right_motor])
|
|
240
|
+
for i in self._PORTs: self._module_check(i, (_ConstModuleID.LARGE_MOTOR, _ConstModuleID.MEDIUM_MOTOR, _ConstModuleID.SMALL_MOTOR))
|
|
241
|
+
|
|
242
|
+
def stop(self) -> None:
|
|
243
|
+
for i in self._PORTs:
|
|
244
|
+
com = bytes((_ConstValue.COMMAND + i, _ConstValue.MOTOR_PWM_MODE, 0, 0))
|
|
245
|
+
self._send_data(com)
|
|
246
|
+
|
|
247
|
+
def start(self, steering, speed) -> None:
|
|
248
|
+
if not steering in range(-100, 101):
|
|
249
|
+
raise Exception('조향 값은 -100 ~ 100 사이의 값만 입력 가능합니다.')
|
|
250
|
+
if not speed in range(-100, 101):
|
|
251
|
+
raise Exception('속도 값은 -100 ~ 100 사이의 값만 입력 가능합니다.')
|
|
252
|
+
|
|
253
|
+
out = (
|
|
254
|
+
int((speed + (speed * (steering / 100))) * 1),
|
|
255
|
+
int((speed - (speed * (steering / 100))) * -1)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
for motor, speed in zip(self._PORTs, out):
|
|
259
|
+
if speed > 0: sw = 1
|
|
260
|
+
else: sw = 0
|
|
261
|
+
|
|
262
|
+
com = bytes((_ConstValue.COMMAND + motor, _ConstValue.MOTOR_PWM_MODE, sw, abs(speed)))
|
|
263
|
+
self._send_data(com)
|
|
264
|
+
|
|
265
|
+
def move(self, degrees, steering, speed) -> None:
|
|
266
|
+
if not steering in range(-100, 101):
|
|
267
|
+
raise Exception('조향 값은 -100 ~ 100 사이의 값만 입력 가능합니다.')
|
|
268
|
+
if not speed in range(0, 101):
|
|
269
|
+
raise Exception('속도 값은 0 ~ 100 사이의 값만 입력 가능합니다.')
|
|
270
|
+
if not degrees in range(-2147483647, 2147483647):
|
|
271
|
+
raise Exception('모터의 동작 각도는 -2147483647 ~ 2147483647 사이의 값만 입력 가능합니다.')
|
|
272
|
+
|
|
273
|
+
out = (
|
|
274
|
+
int(speed + (speed * (steering / 100))),
|
|
275
|
+
int(speed - (speed * (steering / 100)))
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
for motor, speed, count in zip(self._PORTs, out, (0, 1)):
|
|
279
|
+
|
|
280
|
+
if degrees >= 0: target = degrees
|
|
281
|
+
else: target = degrees & 0xFFFFFFFF
|
|
282
|
+
|
|
283
|
+
if count == 1: target = ~target
|
|
284
|
+
|
|
285
|
+
com = bytes((_ConstValue.COMMAND + motor, _ConstValue.MOTOR_REL_MODE, speed, (target & 0xFF), (target >> 8) & 0xFF, (target >> 16) & 0xFF, (target >> 24) & 0xFF))
|
|
286
|
+
self._send_data(com)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def move_tank(self, degrees, left_speed, right_speed) -> None:
|
|
290
|
+
if not left_speed in range(-100, 101) or not right_speed in range(-100, 101):
|
|
291
|
+
raise Exception('조향 값은 -100 ~ 100 사이의 값만 입력 가능합니다.')
|
|
292
|
+
if not degrees in range(-2147483647, 2147483647):
|
|
293
|
+
raise Exception('모터의 동작 각도는 -2147483647 ~ 2147483647 사이의 값만 입력 가능합니다.')
|
|
294
|
+
|
|
295
|
+
out = (
|
|
296
|
+
int(left_speed * 1),
|
|
297
|
+
int(right_speed * -1)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
for motor, speed, count in zip(self._PORTs, out, (0, 1)):
|
|
301
|
+
|
|
302
|
+
if degrees >= 0: target = degrees
|
|
303
|
+
else: target = degrees & 0xFFFFFFFF
|
|
304
|
+
|
|
305
|
+
if count == 1: target = ~target
|
|
306
|
+
|
|
307
|
+
com = bytes((_ConstValue.COMMAND + motor, _ConstValue.MOTOR_REL_MODE, speed, (target & 0xFF), (target >> 8) & 0xFF, (target >> 16) & 0xFF, (target >> 24) & 0xFF))
|
|
308
|
+
self._send_data(com)
|
|
309
|
+
|
|
310
|
+
def start_tank(self, left_speed, right_speed) -> None:
|
|
311
|
+
if not left_speed in range(-100, 101) or not right_speed in range(-100, 101):
|
|
312
|
+
raise Exception('조향 값은 -100 ~ 100 사이의 값만 입력 가능합니다.')
|
|
313
|
+
|
|
314
|
+
out = (
|
|
315
|
+
int(left_speed * 1),
|
|
316
|
+
int(right_speed * -1)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
for motor, speed in zip(self._PORTs, out):
|
|
320
|
+
if speed > 0: sw = 1
|
|
321
|
+
else: sw = 0
|
|
322
|
+
|
|
323
|
+
com = bytes((_ConstValue.COMMAND + motor, _ConstValue.MOTOR_PWM_MODE, sw, abs(speed)))
|
|
324
|
+
self._send_data(com)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: HandsON-BuildHat-API
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: A third-party API for controlling LEGO SPIKE devices
|
|
5
|
+
Author-email: HandsON Technology <caffeine.reload@gmail.com>
|
|
6
|
+
License: Proprietary - Personal/Educational Use Only
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourname/HandsON-BuildHat-API
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pyserial>=3.5
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
HandsON Build Hat API
|
|
18
|
+
=======
|
|
19
|
+
**Production** : HandsON Technology co., ltd.
|
|
20
|
+
**Author** : HaNeul Jung (caffeine.reload@gmail.com)
|
|
21
|
+
**Last Update** : 2025-06-07
|
|
22
|
+
|
|
23
|
+
## 개요
|
|
24
|
+
제작된 빌드햇의 제어에 필요한 API 라이브러리
|
|
25
|
+
|
|
26
|
+
## 구동 환경
|
|
27
|
+
### 컨트롤러
|
|
28
|
+
|이름|설명|
|
|
29
|
+
|:--|:--|
|
|
30
|
+
|Raspberry PI 4, 5|정상 동작 확인|
|
|
31
|
+
|Jetson Orin Nano, Nano|정상 동작 확인 (하드웨어 구조가 라즈베리파이 버전과 다름)|
|
|
32
|
+
|
|
33
|
+
### 종속성
|
|
34
|
+
- python3-serial
|
|
35
|
+
|
|
36
|
+
## 예제
|
|
37
|
+
모든 API는 기존 Spike Legacy와 문법적으로 동일
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from SpikePI import ColorSensor, Motor
|
|
41
|
+
|
|
42
|
+
motor = Motor('A')
|
|
43
|
+
colorsensor = ColorSensor('B')
|
|
44
|
+
|
|
45
|
+
reflected = colorsensor.get_reflected_light()
|
|
46
|
+
motor.start(100)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
자세한 문법은 Lego Spike Legacy 앱에서 확인
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
HandsON_BuildHat_API/SpikePI.py,sha256=L4zmkYZxM1uZjmCVoax0lZ8E9XsBipPLWlbdjQsCsIc,11884
|
|
2
|
+
HandsON_BuildHat_API/__init__.py,sha256=nAmCRGKB6DwSg84Qv3N27Ay34fySDrYj6p3RUMFRImk,162
|
|
3
|
+
handson_buildhat_api-1.0.dist-info/licenses/LICENSE,sha256=xkedyvUIiuPGSXgx2D3uwqfpQ1LLkovfT0zPSAdK6Jg,442
|
|
4
|
+
handson_buildhat_api-1.0.dist-info/METADATA,sha256=k04oav7-OD3I0q4Zcer7oxyXljoGFxnxCKzqeQdbTzA,1367
|
|
5
|
+
handson_buildhat_api-1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
handson_buildhat_api-1.0.dist-info/top_level.txt,sha256=lZyPncSVT9fytRRmtXmZ5bIeg_Xlfmt8-_50B5ib5jU,21
|
|
7
|
+
handson_buildhat_api-1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2025 HandsON Technology co., ltd.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is provided for personal and educational use only.
|
|
6
|
+
You are granted permission to use this software *as-is* without modification.
|
|
7
|
+
You may not copy, modify, distribute, sublicense, or use this software in any commercial context without explicit written permission from the copyright holder.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
HandsON_BuildHat_API
|