├── .gitignore
├── DESCRIPTION.rst
├── LICENSE
├── MANIFEST.in
├── README.md
├── basic_examples
├── 10_imu_read.py
├── 11_sound_direction_read.py
├── 12_dual_touch_read.py
├── 13_camera_easy_use.py
├── 1_pidog_init.py
├── 2_legs_control.py
├── 3_head_control.py
├── 4_tail_control.py
├── 5_stop_actions.py
├── 6_do_preset_actions.py
├── 7_sound_effect.py
├── 8_ultrasonic_read.py
└── 9_rgb_control.py
├── bin
├── pidog_app
└── pidog_app_install.sh
├── examples
├── 0_calibration.py
├── 10_balance.py
├── 11_keyboard_control.py
├── 12_app_control.py
├── 13_ball_track.py
├── 1_wake_up.py
├── 2_function_demonstration.py
├── 3_patrol.py
├── 4_response.py
├── 5_rest.py
├── 6_be_picked_up.py
├── 7_face_track.py
├── 8_pushup.py
├── 9_howling.py
├── curses_utils.py
├── custom_actions.py
├── preset_actions.py
└── servo_zeroing.py
├── gpt_examples
├── README.md
├── action_flow.py
├── gpt_dog.py
├── keys.py
├── openai_helper.py
├── preset_actions.py
├── tutorial_1.png
├── tutorial_2.png
└── utils.py
├── i2samp.sh
├── pidog
├── __init__.py
├── actions_dictionary.py
├── dual_touch.py
├── pidog.py
├── rgb_strip.py
├── sh3001.py
├── sound_direction.py
├── trot.py
├── version.py
└── walk.py
├── setup.py
├── sounds
├── angry.wav
├── confused_1.mp3
├── confused_2.mp3
├── confused_3.mp3
├── growl_1.mp3
├── growl_2.mp3
├── howling.mp3
├── pant.mp3
├── single_bark_1.mp3
├── single_bark_2.mp3
├── snoring.mp3
└── woohoo.mp3
└── test
├── angry_bark.py
├── cover_photo.py
├── dual_touch_test.py
├── imu_test.py
├── power_test.py
├── rgb_strip_test.py
├── sound_direction_test.py
├── stand_test.py
├── tail.py
├── ultrasonic_iic_test.py
└── ultrasonic_test.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/*
2 | dev/*
3 | photo
4 | test/photo*
5 | test/scene/*
6 | backups/*
7 |
--------------------------------------------------------------------------------
/DESCRIPTION.rst:
--------------------------------------------------------------------------------
1 | Raspberry Pi
2 | =======================
3 | Pidog Library for Raspberry Pi
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include DESCRIPTION.rst
2 |
3 | # Include the test suite (FIXME: does not work yet)
4 | # recursive-include tests *
5 |
6 | # If using Python 3.5 or less, then have to include package data, even though
7 | # it's already declared in setup.py
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pidog
2 |
3 | Pidog Python library for Raspberry Pi.
4 |
5 | Quick Links:
6 |
7 | - [Pidog](#pidog)
8 | - [Docs](#docs)
9 | - [Installation](#installation)
10 | - [About SunFounder](#about-sunfounder)
11 | - [Contact us](#contact-us)
12 | - [Credit](#credit)
13 |
14 | ----------------------------------------------
15 |
16 | ## Docs
17 |
18 | -
19 |
20 | ----------------------------------------------
21 |
22 | ## Installation
23 |
24 | -
25 |
26 | ### install tool
27 |
28 | ```bash
29 | sudo apt install git python3-pip python3-setuptools python3-smbus
30 | ```
31 |
32 | ### robot-hat library
33 |
34 | ```bash
35 | cd ~/
36 | git clone -b v2.0 https://github.com/sunfounder/robot-hat.git
37 | cd robot-hat
38 | sudo python3 setup.py install
39 |
40 | ```
41 |
42 | ### vilib library
43 |
44 | ```bash
45 | cd ~/
46 | git clone -b picamera2 https://github.com/sunfounder/vilib.git
47 | cd vilib
48 | sudo python3 install.py
49 | ```
50 |
51 | ### pidog library
52 |
53 | ```bash
54 | cd ~/
55 | git clone https://github.com/sunfounder/pidog.git
56 | cd pidog
57 | sudo python3 setup.py install
58 | ```
59 |
60 | ### i2samp
61 |
62 | ```
63 | cd ~/pidog
64 | sudo bash i2samp.sh
65 | ```
66 |
67 | ----------------------------------------------
68 |
69 | ## About SunFounder
70 |
71 | SunFounder is a technology company focused on Raspberry Pi and Arduino open source community development. Committed to the promotion of open source culture, we strives to bring the fun of electronics making to people all around the world and enable everyone to be a maker. Our products include learning kits, development boards, robots, sensor modules and development tools. In addition to high quality products, SunFounder also offers video tutorials to help you make your own project. If you have interest in open source or making something cool, welcome to join us!
72 |
73 | ----------------------------------------------
74 |
75 | ## Contact us
76 |
77 | website:
78 | www.sunfounder.com
79 |
80 | E-mail:
81 | service@sunfounder.com, support@sunfounder.com
82 |
83 | ## Credit
84 |
85 | Most sound effect are from [Zapsplat.com](https://www.zapsplat.com)
86 |
--------------------------------------------------------------------------------
/basic_examples/10_imu_read.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' read imu sh3001 data
3 | SH3001, an imu with integrated 3-axis accelerometer and 3-axis gyroscope
4 | Pay attention to the installation direction of the module when using.
5 |
6 | The data "accData" and "gyroData" of the imu will be continuously refreshed
7 | in the built-in thread of the Pidog class.
8 |
9 | API:
10 | Pidog.accData = [ax, ay, az]
11 | the acceleration value, default gravity 1G = -16384
12 | note that the accelerometer direction is opposite to the actual acceleration direction
13 |
14 | Pidog.gyroData = [gx, gy, gz]
15 | the gyro value
16 |
17 | more to see: ../pidog/sh3001.py
18 |
19 | '''
20 |
21 | from pidog import Pidog
22 | import time
23 |
24 | my_dog = Pidog()
25 |
26 | while True:
27 | ax, ay, az = my_dog.accData
28 | gx, gy, gz = my_dog.gyroData
29 | print(f"accData: {ax}, {ay}, {az} gyroData: {gx}, {gy}, {gz}")
30 | time.sleep(0.2)
--------------------------------------------------------------------------------
/basic_examples/11_sound_direction_read.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' read azimuth of sound direction
3 |
4 | API:
5 | Pidog.ears.isdetected():
6 | return bool, whether the sound direction recognition module has detected the sound
7 |
8 | Pidog.ears.read()
9 | return int, the azimuth of the identified sound, 0 ~ 359
10 |
11 | more to see: ../pidog/sound_direction.py
12 |
13 | '''
14 |
15 | from pidog import Pidog
16 |
17 | my_dog = Pidog()
18 |
19 | while True:
20 | if my_dog.ears.isdetected():
21 | direction = my_dog.ears.read()
22 | print(f"sound direction: {direction}")
23 |
--------------------------------------------------------------------------------
/basic_examples/12_dual_touch_read.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' read dual_touch module
3 |
4 | API:
5 | Pidog.dual_touch.read()
6 | - return str, dual_touch status:
7 | - 'N' no touch
8 | - 'L' left touched
9 | - 'LS' left slide
10 | - 'R' right touched
11 | - 'RS' right slide
12 | '''
13 |
14 | from pidog import Pidog
15 | import time
16 |
17 | my_dog = Pidog()
18 | while True:
19 | touch_status = my_dog.dual_touch.read()
20 | print(f"touch_status: {touch_status}")
21 | time.sleep(0.5)
--------------------------------------------------------------------------------
/basic_examples/13_camera_easy_use.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' camera easy use
3 | Use the image processing library [vilb] to easily implement functions such as:
4 | web-cam, color detection, face detection, take photos and so on.
5 |
6 | github: https://github.com/sunfounder/vilib.git
7 |
8 | more examples to see: ~/vilib/examples/
9 |
10 | '''
11 |
12 | from vilib import Vilib
13 | import time
14 |
15 | # Most of the functions in the vilib library are [@staticmethod],
16 | # you can use them directly without instantiating the Vilib class
17 |
18 | try:
19 | # start camera
20 | Vilib.camera_start(vflip=False,hflip=False) # vflip: image vertical flip, hflip:horizontal flip
21 | # display camera screen
22 | Vilib.display(local=True,web=True) # local: desktop window display, web: webcam display
23 | # enable face detection
24 | Vilib.face_detect_switch(True)
25 | # wait for vilib launch
26 | time.sleep(1)
27 | print('')
28 |
29 | while True:
30 | n = Vilib.detect_obj_parameter['human_n']
31 | print(f"\r \033[032m{n:^3}\033[m faces are found.", end='', flush=True)
32 | time.sleep(1)
33 | print("\r \033[032m \033[m faces are found.", end='', flush=True)
34 | time.sleep(0.1)
35 |
36 |
37 | except KeyboardInterrupt:
38 | pass
39 | except Exception as e:
40 | print(f"\033[31mERROR: {e}\033[m")
41 | finally:
42 | print("")
43 | Vilib.camera_close()
44 |
--------------------------------------------------------------------------------
/basic_examples/1_pidog_init.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' pidog initialization
3 |
4 | API:
5 | Class: Pidog()
6 |
7 | __init__(leg_pins=DEFAULT_LEGS_PINS,
8 | head_pins=DEFAULT_HEAD_PINS,
9 | tail_pin=DEFAULT_TAIL_PIN,
10 | leg_init_angles=None,
11 | head_init_angles=None,
12 | tail_init_angle=None)
13 |
14 | - leg_pins list, 8*1 list, the pins of the 8 servos on the legs,
15 | DEFAULT_LEGS_PINS = [2, 3, 7, 8, 0, 1, 10, 11]
16 | order: Left Front Leg, Left Front Leg, Right Front Leg, Right Front Leg, Left Hind Leg, Left Hind Leg, Right Hind Leg, Right Hind Leg
17 |
18 | - head_pins list, 3*1 list, the pins of the 3 servos on the head
19 | DEFAULT_HEAD_PINS = [4, 6, 5]
20 | order: Yaw, Roll, Pitch
21 |
22 | - tail_pin list, 1*1 list, the pin of the tail servos
23 | DEFAULT_TAIL_PIN = [9]
24 |
25 | - leg_init_angles list, 8*1 list, the initial angles of the legs servos
26 | - head_init_angles list, 3*1 list, the initial angles of the head servos
27 | - tail_init_angle list, 1*1 list, the initial angles of the tail servo
28 |
29 | '''
30 |
31 | # Import Pidog class
32 | from pidog import Pidog
33 |
34 | # instantiate a Pidog with default parameters
35 | # my_dog = Pidog()
36 |
37 | # instantiate a Pidog with custom initialized servo angles
38 | my_dog = Pidog(leg_init_angles = [25, 25, -25, -25, 70, -45, -70, 45],
39 | head_init_angles = [0, 0, -25],
40 | tail_init_angle= [0]
41 | )
42 |
--------------------------------------------------------------------------------
/basic_examples/2_legs_control.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' legs control
3 | Basic use of leg servos control
4 |
5 | API:
6 | Pidog.legs_move(target_angles, immediately=True, speed=50)
7 |
8 | legs servo angles control, it will store the target_angles in a buffer, and the built-in leg servo control thread will control the angle of the servos
9 |
10 | - target_angles n*8 2D list, angle arrangements of leg servos, could be one group of angles,
11 | or multiple groups of angles, the order of the servos is:
12 | [[ left front leg, left front leg, right front leg, right front leg,
13 | left hind leg, left hind leg,right hind leg, right hind leg],
14 | [...], ...]
15 |
16 | - immediately bool, whether to execute the specified action immediately, if true: it will clear the buffer first, and store the target_angles,
17 | the set actions will be executed immediately; if false, the will add target_angles at the end of the buffer, the actions will be
18 | executed after previous actions have been executed
19 |
20 | - speed int, speed of action, 0 ~ 100
21 |
22 | Pidog.is_legs_done()
23 | whether all the actions of leg in the buffer to be executed
24 |
25 | Pidog.wait_legs_done()
26 | wait for all the actions of leg in the buffer to be executed
27 |
28 | Pidog.legs_stop()
29 | clear all the actions of leg in the buffer, to make legs stop
30 |
31 | '''
32 |
33 | from pidog import Pidog
34 | import time
35 |
36 | my_dog = Pidog()
37 |
38 | # single action
39 | single_action = [ # half stand
40 | [45, 10, -45, -10, 45, 10, -45, -10],
41 | ]
42 | my_dog.legs_move(single_action, speed=30)
43 |
44 | single_action_2 = [ # push_up preparation
45 | [45, 35, -45, -35, 80, 70, -80, -70]
46 | ]
47 | my_dog.legs_move(single_action_2, immediately=False, speed=20)
48 |
49 | # wait all actions done
50 | my_dog.wait_legs_done()
51 | time.sleep(0.5)
52 |
53 | # multiple actions
54 | multiple_actions = [ # push_up
55 | [90, -30, -90, 30, 80, 70, -80, -70],
56 | [45, 35, -45, -35, 80, 70, -80, -70]
57 | ]
58 |
59 | while True:
60 | my_dog.legs_move(multiple_actions, immediately=False, speed=75)
61 | time.sleep(0.1)
62 |
--------------------------------------------------------------------------------
/basic_examples/3_head_control.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' head control
3 | Basic use of head servos control
4 |
5 | API:
6 | Pidog.head_move_raw(target_angles, immediately=True, speed=50)
7 |
8 | head servo angles control with "raw servo angles", it will store the target_angles in a buffer, and the built-in head servo control thread
9 | will control the angle of the servos
10 |
11 | - target_angles n*3 2D list, angle arrangements of head servos, could be one group of angles,
12 | or multiple groups of angles, the order of the servos is:
13 | [[ yaw_servo, roll_servo, pitch_servo], [...], ...]
14 |
15 | - immediately bool, whether to execute the specified action immediately, if true: it will clear the buffer first, and store the target_angles,
16 | the set actions will be executed immediately; if false, the will add target_angles at the end of the buffer, the actions will be
17 | executed after previous actions have been executed
18 |
19 | - speed int, speed of action, 0 ~ 100
20 |
21 | Pidog.head_move(target_yrps, roll_comp=0, pitch_comp=0, immediately=True, speed=50)
22 |
23 | legs servo angles control with "relative ypr angles", You can set "roll_comp" and "pitch_comp" so that the initial position of the head is in a
24 | horizontal position to offset the influence of body tilt. In this way, the same set of c can be used for different body tilt angles
25 |
26 | - target_yrps n*3 2D list, relative angle arrangements of head servos
27 |
28 | - roll_comp angle compensation on roll axis
29 |
30 | - pitch_comp angle compensation on pitch axis
31 |
32 | - immediately bool, whether to execute the specified action immediately
33 |
34 | - speed int, speed of action, 0 ~ 100
35 |
36 | Pidog.is_head_done()
37 | whether all the head actions in the buffer to be executed
38 |
39 | Pidog.wait_head_done()
40 | wait for all the head actions in the buffer to be executed
41 |
42 | Pidog.head_stop()
43 | clear all the head actions of leg in the buffer, to make head servos stop
44 |
45 | '''
46 |
47 | from pidog import Pidog
48 | import time
49 |
50 | my_dog = Pidog()
51 |
52 |
53 | # sit
54 | sit_action = [
55 | [30, 60, -30, -60, 80, -45, -80, 45],
56 | ]
57 | my_dog.legs_move(sit_action, speed=30)
58 | # wait all legs actions done
59 | my_dog.wait_legs_done()
60 |
61 |
62 | # level-view by head_move_raw()
63 | # my_dog.head_move_raw([[0, 0, -30]], speed=80)
64 |
65 | # level-view by head_move()
66 | my_dog.head_move([[0, 0, 0]], pitch_comp=-30, speed=80)
67 |
68 | # actually "head_move_raw([[0, 0, -30]]" is the same as "head_move([[0, 0, 0]], pitch_comp=-30)"
69 |
70 | my_dog.wait_head_done()
71 |
72 | head_test_actions = [ # relative angles
73 | [0, 0, 0], [90, 0, 0], [0, 0, 0], [-90, 0, 0], [0, 0, 0],
74 | [0, 0, 0], [0, 60, 0], [0, 0, 0], [0, -60, 0], [0, 0, 0],
75 | [0, 0, 0], [0, 0, 45], [0, 0, 0], [0, 0, -60], [0, 0, 0],
76 | ]
77 |
78 | while True:
79 | my_dog.head_move(head_test_actions, pitch_comp=-30, speed=50)
80 | my_dog.wait_head_done()
81 | time.sleep(0.2)
82 |
--------------------------------------------------------------------------------
/basic_examples/4_tail_control.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' tail control
3 | Basic use of tail servo control
4 |
5 | API:
6 | Pidog.tail_move(target_angles, immediately=True, speed=50)
7 |
8 | tail servo angles control, it will store the target_angles in a buffer, and the built-in tail servo control thread
9 | will control the angle of the tail servo
10 |
11 | - target_angles n*1 2D list, angle arrangements of tail servo, could be one group of angle,
12 | or multiple groups of angle, eg:
13 | [[30], [-30], [...], ...]
14 |
15 | - immediately bool, whether to execute the specified action immediately, if true: it will clear the buffer first, and store the target_angles,
16 | the set actions will be executed immediately; if false, the will add target_angles at the end of the buffer, the actions will be
17 | executed after previous actions have been executed
18 |
19 | - speed int, speed of action, 0 ~ 100
20 |
21 | Pidog.is_tail_done()
22 | whether all the tail actions in the buffer to be executed
23 |
24 | Pidog.wait_tail_done()
25 | wait for all the tail actions in the buffer to be executed
26 |
27 | Pidog.tail_stop()
28 | clear all the tail actions of leg in the buffer, to make tail servo stop
29 |
30 | '''
31 |
32 | from pidog import Pidog
33 | import time
34 |
35 | my_dog = Pidog()
36 |
37 | # stand action
38 | stand_action = [
39 | [25, 35, -25, -35, 35, 35, -35, -35],
40 | ]
41 | my_dog.legs_move(stand_action, speed=30)
42 | # wait all legs actions done
43 | my_dog.wait_legs_done()
44 |
45 |
46 | wag_tail_actions = [
47 | [-30], [30],
48 | ]
49 |
50 | while True:
51 | my_dog.tail_move(wag_tail_actions, speed=100)
52 | my_dog.wait_tail_done()
53 |
54 |
--------------------------------------------------------------------------------
/basic_examples/5_stop_actions.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' stop all actions
3 |
4 |
5 | API:
6 | Pidog.wait_all_done()
7 | wait for all the actions in the leg actions buffer, head buffer and tail buffer to be executed
8 |
9 | Pidog.body_stop()
10 | stop all the actions of legs, head and tail
11 |
12 | Pidog.stop_and_lie()
13 | stop all the actions of legs, head and tail, then reset to "lie" pose
14 |
15 | Pidog.close()
16 | stop all the actions, reset to "lie" pose, and close all the threads,
17 | usually used when exiting a program
18 |
19 | '''
20 |
21 | from pidog import Pidog
22 | import time
23 |
24 | my_dog = Pidog()
25 |
26 | try:
27 | # push_up prepare
28 | push_up_prepare_action = [
29 | [45, 35, -45, -35, 80, 70, -80, -70]
30 | ]
31 | my_dog.legs_move(push_up_prepare_action, speed=30)
32 | my_dog.head_move([[0, 0, 0]], pitch_comp=-10, speed=80) # head level
33 | my_dog.wait_all_done() # wait all the actions to be done
34 | time.sleep(0.5)
35 |
36 | # push_up
37 | push_up_action = [
38 | [90, -30, -90, 30, 80, 70, -80, -70],
39 | [45, 35, -45, -35, 80, 70, -80, -70],
40 | ]
41 | head_up_down_action = [
42 | [0, 0, -30],
43 | [0, 0, 20],
44 | ]
45 | # fill action buffers
46 | for _ in range(20):
47 | my_dog.legs_move(push_up_action, immediately=False, speed=50)
48 | my_dog.head_move(head_up_down_action, pitch_comp=-10, immediately=False, speed=50)
49 | print(f"legs buffer length (start): {len(my_dog.legs_action_buffer)}")
50 | time.sleep(5)
51 | print(f"legs buffer length (5s): {len(my_dog.legs_action_buffer)}")
52 |
53 | my_dog.body_stop()
54 | print(f"legs buffer length (stop): {len(my_dog.legs_action_buffer)}")
55 | time.sleep(1)
56 |
57 | except KeyboardInterrupt:
58 | pass
59 | except Exception as e:
60 | print(f"\033[31mERROR: {e}\033[m")
61 | finally:
62 | print("closing ...")
63 | my_dog.close()
64 |
65 |
--------------------------------------------------------------------------------
/basic_examples/6_do_preset_actions.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' do preset actions
3 |
4 | API:
5 | Pidog.do_action(action_name, step_count=1, speed=50):
6 | do preset actions
7 |
8 | - action_name str, name of preset actions, eg: "stand", "sit", "forward",
9 | more to see: ../pidogg/actions_dictionary.py
10 | - step_count int, times to perform the action
11 | - speed int, speed of action, 0 ~ 100
12 |
13 | more to see: ../pidog/actions_dictionary.py
14 |
15 | '''
16 |
17 | from pidog import Pidog
18 | import time
19 |
20 | my_dog = Pidog()
21 |
22 | try:
23 | my_dog.do_action("stand", speed=60)
24 | my_dog.wait_all_done()
25 |
26 | my_dog.do_action("push_up", step_count=10, speed=60)
27 | my_dog.wait_all_done()
28 |
29 | my_dog.do_action("half_sit", speed=60)
30 | my_dog.wait_all_done()
31 |
32 | my_dog.do_action("wag_tail", step_count=80,speed=90)
33 | my_dog.do_action("tilting_head", step_count=5, speed=20)
34 | my_dog.wait_head_done()
35 | my_dog.body_stop()
36 |
37 | except KeyboardInterrupt:
38 | pass
39 | except Exception as e:
40 | print(f"\033[31mERROR: {e}\033[m")
41 | finally:
42 | print("closing ...")
43 | my_dog.close()
44 |
--------------------------------------------------------------------------------
/basic_examples/7_sound_effect.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' play sound effecfs
3 | Note that you need to run with "sudo"
4 | API:
5 | Pidog.speak(name, volume=100)
6 | play sound effecf in the file "../sounds"
7 | - name str, file name of sound effect, no suffix required, eg: "angry"
8 | - volume int, volume 0-100, default 100
9 | '''
10 | from pidog import Pidog
11 | import os
12 | import time
13 |
14 | # change working directory
15 | abspath = os.path.abspath(os.path.dirname(__file__))
16 | # print(abspath)
17 | os.chdir(abspath)
18 |
19 | my_dog = Pidog()
20 |
21 | print("\033[033mNote that you need to run with \"sudo\", otherwise there may be no sound.\033[m")
22 |
23 | # my_dog.speak("angry")
24 | # time.sleep(2)
25 |
26 | for name in os.listdir('../sounds'):
27 | name = name.split('.')[0] # remove suffix
28 | print(name)
29 | my_dog.speak(name)
30 | # my_dog.speak(name, volume=50)
31 | time.sleep(3) # Note that the duration of each sound effect is different
32 | print("closing ...")
33 | my_dog.close()
--------------------------------------------------------------------------------
/basic_examples/8_ultrasonic_read.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' read ultrasonic distance data
3 |
4 | API:
5 | Pidog.read_distance()
6 | return the distance read by ultrasound
7 | - return float
8 |
9 | '''
10 |
11 | from pidog import Pidog
12 | import time
13 |
14 | my_dog = Pidog()
15 | while True:
16 | distance = my_dog.read_distance()
17 | distance = round(distance,2)
18 | print(f"Distance: {distance} cm")
19 | time.sleep(0.5)
20 |
--------------------------------------------------------------------------------
/basic_examples/9_rgb_control.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | ''' rgb strip style control
3 |
4 | API:
5 | Pidog.rgb_strip.set_mode(style='breath', color='white', bps=1, brightness=1):
6 |
7 | Set the display mode of the rgb light strip
8 |
9 | - style str, display style, could be: "breath", "boom", "bark", "speak", "listen"
10 | - color str, list or hex, display color,
11 | could be: 16-bit rgb value, eg: #a10a0a;
12 | or 1*3 list of rgb, eg: [255, 100, 80];
13 | or predefined colors:"white", "black", "red", "yellow", "green", "blue", "cyan", "magenta", "pink"
14 | - bps float or int, beats per second, this number of style actions executed per second
15 |
16 | Pidog.rgb_strip.close():
17 | - turn off rgb display
18 |
19 |
20 | more to see: ../pidog/rgb_strip.py
21 |
22 | '''
23 |
24 | from pidog import Pidog
25 | import time
26 |
27 | my_dog = Pidog()
28 |
29 | while True:
30 | # style="breath", color="pink"
31 | my_dog.rgb_strip.set_mode(style="breath", color='pink')
32 | time.sleep(3)
33 |
34 | # style:"listen", color=[0, 255, 255]
35 | my_dog.rgb_strip.set_mode(style="listen", color=[0, 255, 255])
36 | time.sleep(3)
37 |
38 | # style:"boom", color="#a10a0a"
39 | my_dog.rgb_strip.set_mode(style="boom", color="#a10a0a")
40 | time.sleep(3)
41 |
42 | # style:"boom", color="#a10a0a", brightness=0.5, bps=2.5
43 | my_dog.rgb_strip.set_mode(style="boom", color="#a10a0a", bps=2.5, brightness=0.5)
44 | time.sleep(3)
45 |
46 | # close
47 | my_dog.rgb_strip.close()
48 | time.sleep(2)
49 |
50 |
--------------------------------------------------------------------------------
/bin/pidog_app:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | # check permission
4 | # ========================
5 | if [ $(id -u) -ne 0 ]; then
6 | printf "Script must be run as root. Try 'sudo pidog_app [input]'\n"
7 | exit 1
8 | fi
9 |
10 | manual=$(cat << EOF
11 | script version 1.0.0
12 | Usage:
13 | sudo pidog_app [input]
14 | Options:
15 | -h, help help, show this message
16 |
17 | start, restart restart pidog_app service
18 |
19 | stop stop pidog_app service
20 |
21 | status pidog_app service status
22 |
23 | disable disable auto-start app_controller program on bootstrap
24 |
25 | enable enable auto-start app_controller program on bootstrap
26 |
27 | close_ap close hotspot, disable auto-start hotspot on boot
28 |
29 | open_ap open hotspot, enable auto-start hotspot on boot
30 |
31 | ssid set the ssid (network name) of the hotspot
32 |
33 | psk set the password of the hotspot
34 |
35 | country set the country code of the hotspot
36 |
37 | EOF
38 | )
39 |
40 | # print colors
41 | # ========================
42 | RED='\033[0;31m'
43 | GREEN='\033[0;32m'
44 | YELLOW='\033[0;33m'
45 | BLUE='\033[0;34m'
46 | GRAY='\033[1;30m'
47 | #
48 | NC='\033[0m'
49 |
50 | # ========================
51 | READ_TIMEOUT=30
52 |
53 | # get user name && user_home
54 | # ==========================================
55 | user=${SUDO_USER:-$(who -m | awk '{ print $1 }')}
56 | user_home="$(eval echo ~$user)"
57 | # echo $user
58 | # echo $user_home
59 |
60 | #
61 | # ============================ Define fuctions ============================
62 | help() {
63 | echo "$manual"
64 | }
65 |
66 | restart() {
67 | systemctl restart pidog_app.service
68 | }
69 |
70 | stop() {
71 | systemctl stop pidog_app.service
72 | }
73 |
74 | status() {
75 | systemctl status pidog_app.service
76 | }
77 |
78 | disable_autostart() {
79 | echo "Disable auto-start app_control program on boot ..."
80 | systemctl disable pidog_app.service
81 | stop
82 | }
83 |
84 | enable_autostart() {
85 | echo "Enable auto-start app_control program on boot ..."
86 | systemctl enable pidog_app.service
87 | }
88 |
89 | disableAP() {
90 | echo disableAP
91 | # stop hostapd
92 | systemctl stop hostapd
93 | systemctl disable hostapd
94 | # revert dhcpcd
95 | systemctl stop dhcpcd
96 | cp /etc/dhcpcd.conf.sta.bak /etc/dhcpcd.conf
97 | systemctl start dhcpcd
98 | # stop dnsmasq
99 | systemctl stop dnsmasq
100 | # stop iptable
101 | systemctl stop iptables.service
102 | systemctl disable iptables.service
103 | # restart app control server
104 | restart
105 | }
106 |
107 | enableAP() {
108 | echo enableAP
109 |
110 | # start iptable
111 | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
112 | netfilter-persistent save
113 | systemctl enable iptables.service
114 | systemctl restart iptables.service
115 |
116 | # configure and start dhcpcd
117 | systemctl stop dhcpcd
118 | cp /etc/dhcpcd.conf.ap.bak /etc/dhcpcd.conf
119 | systemctl restart dhcpcd
120 | # start dnsmasq
121 | systemctl restart dnsmasq
122 |
123 | # start hostapd
124 | systemctl unmask hostapd
125 | systemctl enable hostapd
126 | systemctl restart hostapd
127 | }
128 |
129 | set_ssid() {
130 | echo -n "plese input your ap ssid: "
131 | if ! read -t $READ_TIMEOUT ssid; then
132 | echo "Time is up, no password entered."
133 | exit 1
134 | fi
135 | length=${#ssid}
136 | # echo $length
137 | if [ $length -lt 2 ] || [ $length -gt 32 ]; then
138 | echo -e "${YELLOW}Wi-Fi ssid must be between 2 and 32 characters.${NC}"
139 | else
140 | echo "set hotspot ssid: $ssid"
141 | systemctl stop hostapd
142 | sed -i -e "s:^ssid=.*:ssid=$ssid:g" /etc/hostapd/hostapd.conf
143 | systemctl restart hostapd
144 | restart
145 | fi
146 | }
147 |
148 | set_psk() {
149 | echo -n "plese input your ap psk: "
150 | # '-s' hide characters
151 | if ! read -t $READ_TIMEOUT -s psk; then
152 | echo -e '\nTime is up, no entered.'
153 | exit 1
154 | fi
155 | echo "" # newline
156 | length=${#psk}
157 | # echo $length
158 | if [ $length -lt 8 ] || [ $length -gt 63 ]; then
159 | echo -e "${YELLOW}Wi-Fi password must be between 8 and 63 characters.${NC}"
160 | else
161 | echo "set hotspot password."
162 | systemctl stop hostapd
163 | sed -i -e "s:^wpa_passphrase=.*:wpa_passphrase=$psk:g" /etc/hostapd/hostapd.conf
164 | systemctl restart hostapd
165 | restart
166 | fi
167 | }
168 |
169 | set_country() {
170 | echo -n "plese input your ap country code: "
171 | if ! read -t $READ_TIMEOUT country_code; then
172 | echo -e '\nTime is up, no entered.'
173 | exit 1
174 | fi
175 | # two letters, '[[ ... ]]'double brackets support regular expression matching.
176 | if [[ "$country_code" =~ ^[A-Za-z]{2}$ ]]; then
177 | country_code=${country_code^^} # uppercase
178 | echo "set hotspot country: $country_code"
179 | sed -i -e "s:.*country_code=.*:country_code=$country_code:g" /etc/hostapd/hostapd.conf
180 | else
181 | systemctl stop hostapd
182 | echo -e "${YELLOW}Country code must be 2 letters.Eg:\"GB\".${NC}"
183 | systemctl restart hostapd
184 | restart
185 | fi
186 | }
187 |
188 | # ==================================== main ====================================
189 | if [ $# == 0 ] || [ $1 == '-h' ]|| [ $1 == 'help' ];then
190 | help
191 | exit 0
192 | fi
193 |
194 | case "$1" in
195 | start|restart)
196 | restart
197 | ;;
198 | stop)
199 | stop
200 | ;;
201 | status)
202 | status
203 | ;;
204 | disable)
205 | disable_autostart
206 | ;;
207 | enable)
208 | enable_autostart
209 | ;;
210 | close_ap)
211 | disableAP
212 | ;;
213 | open_ap)
214 | enableAP
215 | ;;
216 | ssid)
217 | set_ssid
218 | ;;
219 | psk)
220 | set_psk
221 | ;;
222 | country)
223 | set_country
224 | ;;
225 | *)
226 | echo "no this command: $1"
227 | echo "please run [ pidod_app ] or [ pidod_app -h ] to get help infomation "
228 | exit 1
229 | ;;
230 | esac
231 |
232 | exit 0
233 |
--------------------------------------------------------------------------------
/bin/pidog_app_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | VER="1.0.0"
4 | echo "script version $VER"
5 |
6 | # print colors
7 | # ========================
8 | RED='\033[0;31m'
9 | GREEN='\033[0;32m'
10 | YELLOW='\033[0;33m'
11 | BLUE='\033[0;34m'
12 | GRAY='\033[1;30m'
13 | #
14 | NC='\033[0m'
15 |
16 | # ========================
17 | READ_TIMEOUT=30
18 |
19 | # check permission
20 | # ========================
21 | if [ $(id -u) -ne 0 ]; then
22 | printf "Script must be run as root. Try 'sudo bash pidog_app_install.sh'\n"
23 | exit 1
24 | fi
25 |
26 | # get username and userhome
27 | # ========================
28 | user=${SUDO_USER:-$(who -m | awk '{ print $1 }')}
29 | user_home="$(eval echo ~$user)"
30 | echo "User: "$user
31 |
32 | # ================================ interactive commands ==============================
33 | echo -e "${GREEN}install pidog_app interactive commands ...${NC}"
34 | cp ./pidog_app /usr/local/bin/
35 | chmod +x /usr/local/bin/pidog_app
36 |
37 | # ==================================== auto-start ====================================
38 | echo -e "${GREEN}install pidog_app auto-start service ...${NC}"
39 | # https://www.raspberrypi.com/documentation/computers/configuration.html#software-install
40 |
41 | # https://www.freedesktop.org/software/systemd/man/systemd.service.html
42 |
43 | cat > /usr/lib/systemd/system/pidog_app.service << EOF
44 | [Unit]
45 | Description=pidog_app service
46 | After=multi-user.target
47 |
48 | [Service]
49 | Type=simple
50 | ExecStart=sudo python3 $user_home/pidog/examples/12_app_control.py &
51 | PrivateTmp=True
52 | User=$user
53 | Group=$user
54 |
55 | [Install]
56 | WantedBy=multi-user.target
57 | EOF
58 |
59 | systemctl daemon-reload
60 | systemctl enable pidog_app.service
61 | systemctl restart pidog_app.service
62 | # sudo systemctl status pidog_app.service
63 | # journalctl -u pidog_app.service
64 | # journalctl -u pidog_app.service --lines=50
65 |
66 | # =============================== AP mode =============================================
67 | DEFAULT_WIFI_SSID="pidog"
68 | DEFAULT_WIFI_PSK="12345678"
69 |
70 | # install hostapd and dnsmasq
71 | # ==========================================
72 | if [ ! -n "$1" ] || [ "$1" != "--no-dep" ]; then
73 | echo -e "${GREEN}install hostapd and dnsmasq ...${NC}"
74 | apt-get update
75 | apt-get install hostapd dnsmasq -y
76 | fi
77 |
78 | # install netfilter-persistent iptables-persistent
79 | # ==========================================
80 | if [ ! -n "$1" ] || [ "$1" != "--no-dep" ]; then
81 | echo -e "${GREEN}install netfilter-persistent iptables-persistent ...${NC}"
82 | DEBIAN_FRONTEND=noninteractive apt install -y netfilter-persistent iptables-persistent
83 | fi
84 |
85 | # Create and backup files of the Wireless Interface IP Configuration
86 | # ==========================================
87 | echo -e "${GREEN}Create and backup the Wireless Interface IP Configuration ...${NC}"
88 |
89 | if [ ! -e /etc/dhcpcd.conf.sta.bak ]; then
90 | cp /etc/dhcpcd.conf /etc/dhcpcd.conf.sta.bak
91 | fi
92 |
93 | cat >> /etc/dhcpcd.conf.ap.bak << EOF
94 | interface wlan0
95 | static ip_address=192.168.4.1/24
96 | nohook wpa_supplicant
97 | EOF
98 |
99 |
100 | # Create file of Routing and IP Masquerading
101 | # ==========================================
102 | echo -e "${GREEN}Enable Routing and IP Masquerading ...${NC}"
103 |
104 | cat > /etc/sysctl.d/routed-ap.conf << EOF
105 | # Enable IPv4 routing
106 | net.ipv4.ip_forward=1
107 | EOF
108 |
109 | # iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
110 | # sudo netfilter-persistent save
111 |
112 | # # stop iptable
113 | # sudo systemctl stop iptables.service
114 | # sudo systemctl disable iptables.service
115 |
116 | # Configure the DHCP and DNS services for the wireless network
117 | # ==========================================
118 | echo -e "${GREEN}Configure the DHCP and DNS services for the wireless network ...${NC}"
119 |
120 | # systemctl stop dnsmasq
121 |
122 | if [ ! -e /etc/dnsmasq.conf.orig ]; then
123 | mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig
124 | fi
125 |
126 | cat > /etc/dnsmasq.conf << EOF
127 | interface=wlan0 # Listening interface
128 | dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h
129 | # Pool of IP addresses served via DHCP
130 | domain=wlan # Local wireless DNS domain
131 | address=/gw.wlan/192.168.4.1
132 | # Alias for this router
133 | EOF
134 |
135 | # systemctl start dnsmasq
136 |
137 | # Ensure Wireless Operation
138 | # ==========================================
139 | echo -e "${GREEN}Ensure Wireless Operation ...${NC}"
140 | sudo rfkill unblock wlan
141 |
142 | # Configure the AP Software
143 | # ==========================================
144 | echo -e "${GREEN}Configure the AP Software ...${NC}"
145 |
146 | cat > /etc/hostapd/hostapd.conf << EOF
147 | country_code=GB
148 | interface=wlan0
149 | ssid=$DEFAULT_WIFI_SSID
150 | hw_mode=g
151 | channel=7
152 | macaddr_acl=0
153 | auth_algs=1
154 | ignore_broadcast_ssid=0
155 | wpa=2
156 | wpa_passphrase=$DEFAULT_WIFI_PSK
157 | wpa_key_mgmt=WPA-PSK
158 | wpa_pairwise=TKIP
159 | rsn_pairwise=CCMP
160 | EOF
161 |
162 | # # whether start hotspot
163 | # # ==========================================
164 | # echo -n -e "$(echo -e ${BLUE}Do you want to reboot to start hotspot?\(Y/N\): ${NC} )"
165 | # count=0
166 | # while true; do
167 | # # ((count++))
168 | # let count++
169 |
170 | # if ! read -t $READ_TIMEOUT choice; then
171 | # echo -e "\nTime is up, no password entered."
172 | # exit 1
173 | # fi
174 |
175 | # case "$choice" in
176 | # y|Y)
177 | # systemctl unmask hostapd
178 | # systemctl enable hostapd
179 | # systemctl start hostapd
180 | # echo "Rebooting now ..."
181 | # sudo reboot
182 | # break
183 | # ;;
184 | # n|N|"")
185 | # break
186 | # ;;
187 | # *)
188 | # if [ $count -lt 5 ]; then
189 | # echo -n 'Invalid input, please enter again:'
190 | # else
191 | # break
192 | # fi
193 | # ;;
194 | # esac
195 | # done
196 |
197 | exit 0
198 |
--------------------------------------------------------------------------------
/examples/0_calibration.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | import curses
4 | import curses_utils
5 | from time import sleep
6 |
7 | # init pidog
8 | # ======================================
9 | my_dog = Pidog()
10 | sleep(.1)
11 |
12 | # global variables
13 | # ======================================
14 | leg_offset = list.copy(my_dog.legs.offset)
15 | head_offset = list.copy(my_dog.head.offset)
16 | tail_offset = list.copy(my_dog.tail.offset)
17 |
18 | current_servo = "1"
19 |
20 | OFFSET_STEP = (180 / 2000) * (20000 / 4095) # actual precision of steering gear
21 |
22 | is_save = False
23 |
24 | LEGS_ORIGNAL_ANGLES_60 = [30, -30, -30, 30, 30, -30, -30, 30]
25 | LEGS_ORIGNAL_ANGLES_90 = [0 for _ in range(8)]
26 |
27 | legs_orignal_angles = [0 for _ in range(8)]
28 | head_orignal_angles = [0, 0, my_dog.HEAD_PITCH_OFFSET]
29 | tail_orignal_angles = [0]
30 |
31 | leg_angles = list.copy(legs_orignal_angles)
32 | head_angles = list.copy(head_orignal_angles)
33 | tail_angle = list.copy(tail_orignal_angles)
34 |
35 | # constrain(), constrain value range
36 | # ======================================
37 | def constrain(val, min_val, max_val):
38 |
39 | if val < min_val: return min_val
40 | if val > max_val: return max_val
41 | return val
42 |
43 | def display_title(subpad, color_pair):
44 | title = "PiDog Calibration"
45 | # tip1
46 | curses_utils.clear_line(subpad, 0, color_pair=color_pair)
47 | subpad.addstr(0, int((curses_utils.PAD_X-len(title))/2), title, color_pair)
48 |
49 | tip1 = [
50 | "Press key to select servo: ",
51 | "1 ~ 8 : Leg servos",
52 | "9 : Head yaw ",
53 | "0 : Head roll ",
54 | "- : Head pitch ",
55 | "= : Tail ",
56 | ]
57 | def display_tip1(subpad, color_pair):
58 | subpad.addstr(0, 0, tip1[0], color_pair | curses.A_BOLD | curses.A_REVERSE)
59 | for i in range(1, len(tip1)):
60 | subpad.addstr(i, 0, tip1[i], color_pair | curses.A_BOLD)
61 |
62 | tip2 = [
63 | "Press key to adjust servo: ",
64 | "W or A: increase angle ",
65 | "S or D: decreases angle",
66 | ]
67 | def display_tip2(subpad, color_pair):
68 | subpad.addstr(0, 0, tip2[0], color_pair | curses.A_BOLD | curses.A_REVERSE)
69 | for i in range(1, len(tip2)):
70 | subpad.addstr(i, 0, tip2[i], color_pair | curses.A_BOLD)
71 |
72 | body = [
73 | " [9] ",
74 | " [-] ┌─┐ [0] ",
75 | " │ │ ",
76 | "[2][1]┌└─┘┐[3][4]",
77 | " │ │ ",
78 | " │ │ ",
79 | "[6][5]└─┬─┘[7][8]",
80 | " [=] ",
81 | " / ",
82 | ]
83 | servo_pos = { # servo_pos in body
84 | "1": [3, 3], # ypos, xpos
85 | "2": [3, 0],
86 | "3": [3, 11],
87 | "4": [3, 14],
88 | "5": [6, 3],
89 | "6": [6, 0],
90 | "7": [6, 11],
91 | "8": [6, 14],
92 | "9": [0, 8],
93 | "0": [1, 12],
94 | "-": [1, 3],
95 | "=": [7, 7],
96 | }
97 | def display_dog_body(subpad, color_pair, color_pair_select):
98 | for i in range(len(body)):
99 | subpad.addstr(i, 0, body[i], color_pair | curses.A_BOLD)
100 | servo = current_servo
101 | if servo in servo_pos.keys():
102 | subpad.addstr(servo_pos[servo][0], servo_pos[servo][1], f"[{servo}]", color_pair_select | curses.A_BOLD)
103 |
104 | tip3 = [
105 | "Ctrl+C: Quit Space: Save ",
106 | ]
107 | def display_tip3(subpad, color_pair):
108 | curses_utils.clear_line(subpad, 0)
109 | subpad.addstr(0, 0, tip3[0], color_pair | curses.A_BOLD | curses.A_REVERSE)
110 |
111 | def display_servo_num(subpad, color_pair, color_pair_select):
112 | subpad.addstr(0, 0, "Current Servo:", color_pair | curses.A_BOLD)
113 | subpad.addstr(0, 15, f"{current_servo}", color_pair_select | curses.A_BOLD)
114 |
115 | def display_offsets(subpad, color_pair, color_pair_select):
116 | servo_num = "1234567890-="
117 | comma = ""
118 | curses_utils.clear_line(subpad, 0)
119 | subpad.addstr(0, 0, f"leg_offset:", color_pair | curses.A_BOLD)
120 | for i, x in enumerate(leg_offset):
121 | comma = "," if i < len(leg_offset)-1 else ""
122 | if servo_num[i] == current_servo:
123 | subpad.addstr(f' {x:.2f}{comma}', color_pair_select | curses.A_BOLD)
124 | else:
125 | subpad.addstr(f' {x:.2f}{comma}', color_pair | curses.A_BOLD)
126 | #
127 | curses_utils.clear_line(subpad, 1)
128 | subpad.addstr(1, 0, f"head_offset:", color_pair | curses.A_BOLD)
129 | for i, x in enumerate(head_offset):
130 | comma = "," if i < len(head_offset)-1 else ""
131 | if servo_num[i+8] == current_servo:
132 | subpad.addstr(f' {x:.2f}{comma}', color_pair_select | curses.A_BOLD)
133 | else:
134 | subpad.addstr(f' {x:.2f}{comma}', color_pair | curses.A_BOLD)
135 | #
136 | curses_utils.clear_line(subpad, 2)
137 | subpad.addstr(2, 0, f"tail_offset:", color_pair | curses.A_BOLD)
138 | for i, x in enumerate(tail_offset):
139 | if servo_num[i+11] == current_servo:
140 | subpad.addstr(f' {x:.2f}', color_pair_select | curses.A_BOLD)
141 | else:
142 | subpad.addstr(f' {x:.2f}', color_pair | curses.A_BOLD)
143 |
144 | MAX_CALI_TYPE = 2
145 | cali_type = 0
146 | selecet_cali_ruler = [
147 | "Selecet your calibration ruler type: ",
148 | " ",
149 | " 90 degree calibration ruler ",
150 | " 60 degree calibration ruler ",
151 | ]
152 |
153 | cali_ruler_tip = [
154 | "[↑] Select Up ",
155 | "[↓] Select Down",
156 | "[Enter] OK",
157 | ]
158 |
159 |
160 | def display_selecet_cali_ruler(subpad, color_pair, color_pair_select):
161 | # subpad.box()
162 | subpad.addstr(0, 0, selecet_cali_ruler[0], color_pair | curses.A_BOLD)
163 | subpad.addstr(1, 0, selecet_cali_ruler[1], color_pair | curses.A_BOLD )
164 | for i in range(len(selecet_cali_ruler)-2):
165 | if i == cali_type:
166 | subpad.addstr(i+2, 2, selecet_cali_ruler[i+2], color_pair_select | curses.A_BOLD)
167 | else:
168 | subpad.addstr(i+2, 2, selecet_cali_ruler[i+2], color_pair | curses.A_BOLD)
169 |
170 | def display_selecet_cali_tip(subpad, color_pair):
171 | for i in range(len(cali_ruler_tip)):
172 | subpad.addstr(i, 2, cali_ruler_tip[i], color_pair)
173 |
174 |
175 | def resize_window(pad):
176 | curses_utils.pad_ypos = 0
177 | curses_utils.pad_xpos = 0
178 | curses.update_lines_cols()
179 | # if curses.COLS < PAD_X - 20:
180 | # body_pad.refresh(2, curses.COLS-1-len(body[0]))
181 | # body_pad.erase() # ok
182 | curses_utils.pad_refresh(pad)
183 | sleep(0.5)
184 |
185 | def main(stdscr):
186 | global current_servo, cali_type, is_save, legs_orignal_angles
187 | global leg_offset, head_offset, tail_offset
188 | global leg_angles, head_angles, tail_angles
189 |
190 | inc = 1 # 1 or -1, angle increase direction
191 |
192 | # ---------------- init stdscr ----------------
193 | # reset screen
194 | stdscr.clear()
195 | stdscr.move(0, 0)
196 | stdscr.refresh()
197 |
198 | # disable cursor
199 | curses.curs_set(0)
200 |
201 | # set keyboard mode
202 | stdscr.nodelay(True) # set non-blocking mode for getch()
203 | stdscr.keypad(True)
204 | stdscr.timeout(50) # set timeout when no key is pressed
205 |
206 | # set colors
207 | curses.start_color()
208 | curses.use_default_colors()
209 | curses_utils.init_preset_color_pairs()
210 |
211 | # -------------------- init pad --------------------
212 | pad = curses.newpad(curses_utils.PAD_Y, curses_utils.PAD_X)
213 | # pad.box()
214 |
215 | # init subpad
216 | title_pad = pad.subpad(1, curses_utils.PAD_X, 0, 0)
217 | cali_ruler_pad = pad.subpad(len(selecet_cali_ruler), len(selecet_cali_ruler[0])+2+10, 2, 0)
218 | cali_ruler_tip_pad = pad.subpad(len(cali_ruler_tip), len(cali_ruler_tip[0])+2, 2, len(selecet_cali_ruler[0])+2+10+2)
219 | tip1_pad = pad.subpad(len(tip1), len(tip1[0]), 1, 0)
220 | tip2_pad = pad.subpad(len(tip2), len(tip2[0]), len(tip1)+2, 0)
221 | body_pad = pad.subpad(len(body), len(body[0]), 2, curses_utils.PAD_X-len(body[0])-20)
222 | if curses.COLS < curses_utils.PAD_X - 20:
223 | body_pad.move(2, 1)
224 | tip3_pad = pad.subpad(len(tip3), curses_utils.PAD_X, len(body)+3, 0)
225 | servo_num_pad = pad.subpad(1, curses_utils.PAD_X, len(body)+5, 0)
226 | offsets_pad = pad.subpad(3, curses_utils.PAD_X, len(body)+6, 0)
227 |
228 | # ------- select calibration ruler interface -------
229 | display_title(title_pad, curses_utils.CYAN | curses.A_REVERSE)
230 | display_selecet_cali_ruler(cali_ruler_pad, curses_utils.WHITE, curses_utils.CYAN | curses.A_REVERSE)
231 | display_selecet_cali_tip(cali_ruler_tip_pad, curses_utils.WHITE)
232 | curses_utils.pad_refresh(pad)
233 |
234 | # select the calibration ruler type
235 | while True:
236 | try:
237 | key = stdscr.getch()
238 | if key == curses.ERR: # if no key
239 | continue
240 | # ---- resize window ----
241 | if key == curses.KEY_RESIZE:
242 | resize_window(pad)
243 | # ---- select calibration type ----
244 | elif key == curses.KEY_UP:
245 | cali_type += 1
246 | if cali_type > MAX_CALI_TYPE - 1:
247 | cali_type = 0
248 | display_selecet_cali_ruler(cali_ruler_pad, curses_utils.WHITE, curses_utils.CYAN | curses.A_REVERSE)
249 | curses_utils.pad_refresh(pad)
250 | elif key == curses.KEY_DOWN:
251 | cali_type -= 1
252 | if cali_type < 0:
253 | cali_type = MAX_CALI_TYPE - 1
254 | display_selecet_cali_ruler(cali_ruler_pad, curses_utils.WHITE, curses_utils.CYAN | curses.A_REVERSE)
255 | curses_utils.pad_refresh(pad)
256 | elif key in (curses.KEY_ENTER, 10, 13):
257 | # 90 degree calibration ruler
258 | if cali_type == 0:
259 | legs_orignal_angles = list.copy(LEGS_ORIGNAL_ANGLES_90)
260 | leg_angles = list.copy(LEGS_ORIGNAL_ANGLES_90)
261 | # 60 degree calibration ruler
262 | elif cali_type == 1:
263 | #
264 | legs_orignal_angles = list.copy(LEGS_ORIGNAL_ANGLES_60)
265 | leg_angles = list.copy(LEGS_ORIGNAL_ANGLES_60)
266 |
267 | # ----------------
268 | for i in range(len(leg_angles)):
269 | leg_angles[i] += leg_offset[i]
270 | #
271 | for i in range(len(head_angles)):
272 | head_angles[i] += head_offset[i]
273 | #
274 | for i in range(len(tail_angle)):
275 | tail_angle[i] += tail_offset[i]
276 | #
277 | my_dog.legs_move([legs_orignal_angles], immediately=True, speed=60)
278 | my_dog.head_move([[0]*3], immediately=True, speed=60)
279 | my_dog.tail_move([[0]], immediately=True, speed=60)
280 | my_dog.wait_all_done()
281 | break
282 | except KeyboardInterrupt:
283 | quit()
284 |
285 | # ---------------- calibration interface ----------------
286 | pad.clear()
287 |
288 | # get the offset
289 |
290 | display_title(title_pad, curses_utils.CYAN | curses.A_REVERSE)
291 | display_tip1(tip1_pad, curses_utils.WHITE)
292 | display_tip2(tip2_pad, curses_utils.WHITE)
293 | display_dog_body(body_pad, curses_utils.WHITE, curses_utils.CYAN)
294 | display_tip3(tip3_pad, curses_utils.WHITE)
295 | display_servo_num(servo_num_pad, curses_utils.WHITE, curses_utils.CYAN)
296 | display_offsets(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
297 | curses_utils.pad_refresh(pad)
298 |
299 | while True:
300 | try:
301 | key = stdscr.getch()
302 | if key == curses.ERR: # if no key
303 | continue
304 | # ---- resize window ----
305 | if key == curses.KEY_RESIZE:
306 | curses_utils.pad_ypos = 0
307 | curses_utils.pad_xpos = 0
308 | curses.update_lines_cols()
309 | # if curses.COLS < PAD_X - 20:
310 | # body_pad.refresh(2, curses.COLS-1-len(body[0]))
311 | # body_pad.erase() # ok
312 | curses_utils.pad_refresh(pad)
313 | sleep(0.5)
314 | # ---- select the servo ----
315 | elif chr(key) in ('1234567890-='):
316 | current_servo = chr(key)
317 | display_dog_body(body_pad, curses_utils.WHITE, curses_utils.CYAN)
318 | display_servo_num(servo_num_pad, curses_utils.WHITE, curses_utils.CYAN)
319 | display_offsets(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
320 | curses_utils.clear_line(pad, 17)
321 | curses_utils.pad_refresh(pad)
322 | # ---- move ----
323 | elif chr(key) in ('wsadWSAD'):
324 | if chr(key) in ('wWdD'):
325 | inc = 1
326 | else:
327 | inc = -1
328 | # control legs
329 | if current_servo in ('12345678'):
330 | # get index
331 | index = ('12345678').index(current_servo)
332 | #
333 | leg_offset[index] += inc*OFFSET_STEP
334 | leg_offset[index] = constrain(leg_offset[index], -20, 20)
335 | #
336 | leg_angles[index] = legs_orignal_angles[index] + leg_offset[index]
337 | # move servos
338 | my_dog.legs.servo_write_raw(leg_angles)
339 | # control head
340 | elif current_servo in ('90-'):
341 | index = ('90-').index(current_servo)
342 | #
343 | head_offset[index] += inc*OFFSET_STEP
344 | head_offset[index] = constrain(head_offset[index], -20, 20)
345 | #
346 | head_angles[index] = head_orignal_angles[index] + head_offset[index]
347 | #
348 | my_dog.head.servo_write_raw(head_angles)
349 | # control tail
350 | elif current_servo == '=':
351 | tail_offset[0] += inc*OFFSET_STEP
352 | tail_offset[0] = constrain(tail_offset[0], -20, 20)
353 | #
354 | tail_angle[0] = tail_orignal_angles[0] + tail_offset[0]
355 | #
356 | my_dog.tail.servo_write_raw(tail_angle)
357 | # display offsets
358 | display_offsets(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
359 | curses_utils.clear_line(pad, 17)
360 | curses_utils.pad_refresh(pad)
361 | # ---- save calibration ----
362 | elif key == 32: # space key
363 | curses_utils.clear_line(pad, 17)
364 | pad.addstr(17, 0, ' Confirm save ? (y/n) ', curses_utils.CYAN | curses.A_REVERSE)
365 | curses_utils.pad_refresh(pad)
366 | while True:
367 | key = stdscr.getch()
368 | if key == curses.ERR:
369 | continue
370 | if chr(key) in ('yY'):
371 | my_dog.set_leg_offsets(leg_offset, reset_list=legs_orignal_angles)
372 | my_dog.set_head_offsets(head_offset)
373 | my_dog.set_tail_offset(tail_offset)
374 | sleep(0.5)
375 | display_offsets(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
376 | curses_utils.clear_line(pad, 17)
377 | pad.addstr(17, 0, ' Offsets saved. ', curses_utils.CYAN | curses.A_REVERSE)
378 | curses_utils.pad_refresh(pad)
379 | is_save = True
380 | break
381 | elif chr(key) in ('nN'):
382 | display_offsets(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
383 | curses_utils.clear_line(pad, 17)
384 | curses_utils.pad_refresh(pad)
385 | break
386 | else:
387 | pad.addstr(17, 0, ' Invalid Key ', curses_utils.YELLOW | curses.A_REVERSE)
388 | curses_utils.pad_refresh(pad)
389 |
390 | except KeyboardInterrupt:
391 | # ---- exit and remind to save calibration ----
392 | if is_save:
393 | break
394 | else:
395 | pad.addstr(17, 0, ' Change not saved, whether to exit? (y/n) ', curses_utils.WHITE | curses.A_BOLD |curses.A_REVERSE)
396 | curses_utils.pad_refresh(pad)
397 | key = None
398 | while True:
399 | key = stdscr.getch()
400 | if key == curses.ERR:
401 | continue
402 | if chr(key) in ('ynYN'):
403 | break
404 | if chr(key) in 'yY':
405 | break
406 | else:
407 | curses_utils.clear_line(pad, 17)
408 | curses_utils.pad_refresh(pad)
409 | continue
410 |
411 | if __name__ == '__main__':
412 | curses.wrapper(main)
413 |
414 | # try:
415 | # curses.wrapper(main)
416 | # # except Exception as e:
417 | # # print(f"\033[31mERROR: {e}\033[m")
418 | # finally:
419 | # my_dog.close()
420 |
--------------------------------------------------------------------------------
/examples/10_balance.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from time import sleep
3 | from pidog import Pidog
4 | from pidog.walk import Walk
5 | import readchar
6 | import threading
7 | import os
8 |
9 | my_dog = Pidog()
10 |
11 | sleep(0.5)
12 |
13 | usage = '''
14 | \033[104m\033[1m Pidog Balance Ctrl + C to Exit \033[0m
15 | ┌────────┐┌────────┐┌────────┐┌────────┐
16 | │Q ││W ││E ││R │
17 | │ ││ ││ ││ │
18 | │ ││ Forward││ Stand ││ UP │
19 | └────────┘└────────┘└────────┘└────────┘
20 | ┌────────┐┌────────┐┌────────┐┌────────┐
21 | │A ││S ││D ││F │
22 | │ Turn ││ ││ Turn ││ │
23 | │ Left ││Backward││ Right ││ DOWN │
24 | └────────┘└────────┘└────────┘└────────┘
25 | '''
26 |
27 | stand_coords = [[[-15, 95], [-15, 95], [5, 90], [5, 90]]]
28 | forward_coords = Walk(fb=Walk.FORWARD, lr=Walk.STRAIGHT).get_coords()
29 | backward_coords = Walk(fb=Walk.BACKWARD, lr=Walk.STRAIGHT).get_coords()
30 | turn_left_coords = Walk(fb=Walk.FORWARD, lr=Walk.LEFT).get_coords()
31 | turn_right_coords = Walk(fb=Walk.FORWARD, lr=Walk.RIGHT).get_coords()
32 |
33 | current_coords = stand_coords
34 | current_pose = {'x': 0, 'y': 0, 'z': 80}
35 | current_rpy = {'roll': 0, 'pitch': 0, 'yaw': 0}
36 | thread_start = True
37 |
38 |
39 | def move_thread():
40 | while thread_start:
41 | for coord in current_coords:
42 | # print(coord)
43 | my_dog.set_rpy(**current_rpy, pid=True)
44 | my_dog.set_pose(**current_pose)
45 | my_dog.set_legs(coord)
46 | angles = my_dog.pose2legs_angle()
47 | my_dog.legs.servo_move(angles, speed=98)
48 |
49 |
50 | t = threading.Thread(target=move_thread)
51 |
52 |
53 | def main():
54 | global current_coords, current_pose, current_rpy, thread_start
55 | my_dog.do_action('stand', speed=80)
56 | my_dog.wait_legs_done()
57 | # sleep(1)
58 | t.start()
59 |
60 | while True:
61 | os.system('cls' if os.name == 'nt' else 'clear')
62 | print(usage)
63 | key = readchar.readkey()
64 | if key == readchar.key.CTRL_C:
65 | thread_start = False
66 | break
67 | elif key == 'w':
68 | current_coords = forward_coords
69 | elif key == 's':
70 | current_coords = backward_coords
71 | elif key == 'a':
72 | current_coords = turn_left_coords
73 | elif key == 'd':
74 | current_coords = turn_right_coords
75 | elif key == 'e':
76 | current_coords = stand_coords
77 | elif key == 'r':
78 | current_pose['z'] += 1
79 | if current_pose['z'] > 90:
80 | current_pose['z'] = 90
81 | elif key == 'f':
82 | current_pose['z'] -= 1
83 | if current_pose['z'] < 30:
84 | current_pose['z'] = 30
85 |
86 |
87 | if __name__ == "__main__":
88 | try:
89 | main()
90 | except KeyboardInterrupt:
91 | pass
92 | except Exception as e:
93 | print(f"\033[31mERROR: {e}\033[m")
94 | finally:
95 | thread_start = False
96 | t.join()
97 | my_dog.close()
98 |
--------------------------------------------------------------------------------
/examples/12_app_control.py:
--------------------------------------------------------------------------------
1 | from sunfounder_controller import SunFounderController
2 | from pidog import Pidog
3 | from time import sleep
4 | from vilib import Vilib
5 | from preset_actions import *
6 | import os
7 | from time import sleep
8 | from math import pi, atan2, sqrt
9 |
10 | sc = SunFounderController()
11 | my_dog = Pidog()
12 |
13 | SIT_HEAD_PITCH = -40
14 | STAND_HEAD_PITCH = 0
15 | STATUS_STAND = 0
16 | STATUS_SIT = 1
17 | STATUS_LIE = 2
18 |
19 | sleep(0.1)
20 | head_yrp = [0, 0, 0]
21 | head_origin_yrp = [0, 0, 0]
22 | head_pitch_init = 0
23 | command = None
24 | current_status = STATUS_LIE
25 |
26 |
27 | def map(x, in_min, in_max, out_min, out_max):
28 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
29 |
30 |
31 | def set_head(roll=None, pitch=None, yaw=None):
32 | global head_yrp
33 | if roll is not None:
34 | head_yrp[1] = roll + head_origin_yrp[1]
35 | if pitch is not None:
36 | head_yrp[2] = pitch + head_origin_yrp[2]
37 | if yaw is not None:
38 | head_yrp[0] = yaw + head_origin_yrp[0]
39 | my_dog.head_move([head_yrp], pitch_comp=head_pitch_init,
40 | immediately=True, speed=100)
41 |
42 | # IP address
43 |
44 |
45 | def getIP():
46 | wlan0 = os.popen(
47 | "ifconfig wlan0 |awk '/inet/'|awk 'NR==1 {print $2}'").readline().strip('\n')
48 | eth0 = os.popen(
49 | "ifconfig eth0 |awk '/inet/'|awk 'NR==1 {print $2}'").readline().strip('\n')
50 |
51 | if wlan0 == '':
52 | wlan0 = None
53 | if eth0 == '':
54 | eth0 = None
55 |
56 | return wlan0, eth0
57 |
58 |
59 | def stretch():
60 | my_dog.do_action('stretch', speed=10)
61 | my_dog.wait_all_done()
62 | # sleep(1)
63 |
64 |
65 | COMMANDS = {
66 | "forward": {
67 | "commands": ["forward"],
68 | "function": lambda: my_dog.do_action('forward', speed=98),
69 | "after": "forward",
70 | "status": STATUS_STAND,
71 | "head_pitch": STAND_HEAD_PITCH,
72 | },
73 | "backward": {
74 | "commands": ["backward"],
75 | "function": lambda: my_dog.do_action('backward', speed=98),
76 | "after": "backward",
77 | "status": STATUS_STAND,
78 | "head_pitch": STAND_HEAD_PITCH,
79 | },
80 | "turn left": {
81 | "commands": ["turn left"],
82 | "function": lambda: my_dog.do_action('turn_left', speed=98),
83 | "after": "turn left",
84 | "status": STATUS_STAND,
85 | "head_pitch": STAND_HEAD_PITCH,
86 | },
87 | "turn right": {
88 | "commands": ["turn right"],
89 | "function": lambda: my_dog.do_action('turn_right', speed=98),
90 | "after": "turn right",
91 | "status": STATUS_STAND,
92 | "head_pitch": STAND_HEAD_PITCH,
93 | },
94 | "trot": {
95 | "commands": ["trot", "run"],
96 | "function": lambda: my_dog.do_action('trot', speed=98),
97 | "after": "trot",
98 | "status": STATUS_STAND,
99 | "head_pitch": STAND_HEAD_PITCH,
100 | },
101 | "stop": {
102 | "commands": ["stop"],
103 | },
104 | "lie down": {
105 | "commands": ["lie down"],
106 | "function": lambda: my_dog.do_action('lie', speed=70),
107 | "head_pitch": STAND_HEAD_PITCH,
108 | "status": STATUS_LIE,
109 | },
110 | "stand up": {
111 | "commands": ["stand up"],
112 | "function": lambda: my_dog.do_action('stand', speed=70),
113 | "head_pitch": STAND_HEAD_PITCH,
114 | "status": STATUS_STAND,
115 | },
116 | "sit": {
117 | "commands": ["sit", "sit down", "set", "set down"],
118 | "function": lambda: my_dog.do_action('sit', speed=70),
119 | "head_pitch": SIT_HEAD_PITCH,
120 | "status": STATUS_SIT,
121 | },
122 | "bark": {
123 | "commands": ["bark", "park", "fuck"],
124 | "function": lambda: bark(my_dog, head_yrp, pitch_comp=head_pitch_init),
125 | },
126 | "bark harder": {
127 | "commands": ["bark harder", "park harder", "fuck harder", "bark harbor", "park harbor", "fuck harbor"],
128 | "function": lambda: bark_action(my_dog, head_yrp, 'single_bark_1'),
129 | },
130 | "pant": {
131 | "commands": ["pant", "paint"],
132 | "function": lambda: pant(my_dog, head_yrp, pitch_comp=head_pitch_init),
133 | },
134 | "wag tail": {
135 | "commands": ["wag tail", "wake tail", "wake town", "wait town", "wait tail", "wake time", "wait time", "wait tail"],
136 | "function": lambda: my_dog.do_action('wag_tail', speed=100),
137 | "after": "wag tail",
138 | },
139 | "shake head": {
140 | "commands": ["shake head"],
141 | "function": lambda: shake_head(my_dog, head_yrp),
142 | },
143 | "stretch": {
144 | "commands": ["stretch"],
145 | "function": lambda: stretch(),
146 | "after": "stand up",
147 | "status": STATUS_STAND,
148 | },
149 | "doze off": {
150 | "commands": ["doze off", "does off"],
151 | "function": lambda: my_dog.do_action('doze_off', speed=95),
152 | "after": "doze off",
153 | "status": STATUS_LIE,
154 | },
155 | "push-up": {
156 | "commands": ["push up"],
157 | "function": lambda: push_up(my_dog),
158 | "after": "push-up",
159 | "status": STATUS_STAND,
160 | },
161 | "howling": {
162 | "commands": ["howling"],
163 | "function": lambda: howling(my_dog),
164 | "after": "sit",
165 | "status": STATUS_SIT,
166 | },
167 | "twist body": {
168 | "commands": ["twist body", "taste body", "twist party", "taste party"],
169 | "function": lambda: body_twisting(my_dog),
170 | "before": "stretch",
171 | "after": "sit",
172 | "status": STATUS_STAND,
173 | },
174 | "scratch": {
175 | "commands": ["scratch"],
176 | "function": lambda: scratch(my_dog),
177 | "after": "sit",
178 | "head_pitch": SIT_HEAD_PITCH,
179 | "status": STATUS_SIT,
180 | },
181 | "handshake": {
182 | "commands": ["handshake"],
183 | "function": lambda: hand_shake(my_dog),
184 | "after": "sit",
185 | "head_pitch": SIT_HEAD_PITCH,
186 | "status": STATUS_SIT,
187 | },
188 | "high five": {
189 | "commands": ["high five", "hi five"],
190 | "function": lambda: high_five(my_dog),
191 | "after": "sit",
192 | "head_pitch": SIT_HEAD_PITCH,
193 | "status": STATUS_SIT,
194 | },
195 | }
196 |
197 |
198 | def set_head_pitch_init(pitch):
199 | global head_pitch_init
200 | head_pitch_init = pitch
201 | my_dog.head_move([head_yrp], pitch_comp=pitch, immediately=True, speed=80)
202 |
203 |
204 | def change_status(status):
205 | global current_status
206 | current_status = status
207 | if status == STATUS_STAND:
208 | set_head_pitch_init(STAND_HEAD_PITCH)
209 | my_dog.do_action('stand', speed=70)
210 | elif status == STATUS_SIT:
211 | set_head_pitch_init(SIT_HEAD_PITCH)
212 | my_dog.do_action('sit', speed=70)
213 | elif status == STATUS_LIE:
214 | set_head_pitch_init(STAND_HEAD_PITCH)
215 | my_dog.do_action('lie', speed=70)
216 |
217 |
218 | def run_command():
219 | global command, head_pitch_init
220 | if not my_dog.is_legs_done():
221 | return
222 | if command is None:
223 | return
224 | print(command)
225 | for name in COMMANDS:
226 | if command in COMMANDS[name]["commands"]:
227 | if "head_pitch" in COMMANDS[name]:
228 | set_head_pitch_init(COMMANDS[name]["head_pitch"])
229 | if "status" in COMMANDS[name]:
230 | if current_status != COMMANDS[name]["status"]:
231 | change_status(COMMANDS[name]["status"])
232 | if "before" in COMMANDS[name]:
233 | before_command = COMMANDS[name]["before"]
234 | COMMANDS[before_command]["function"]()
235 | if "function" in COMMANDS[name]:
236 | COMMANDS[name]["function"]()
237 | if "after" in COMMANDS[name]:
238 | command = COMMANDS[name]["after"]
239 | else:
240 | command = None
241 | break
242 |
243 |
244 | def main():
245 | global command
246 | sc.set_name('Mydog')
247 | sc.set_type('Pidog')
248 | sc.start()
249 |
250 | wlan0, eth0 = getIP()
251 | if wlan0 != None:
252 | ip = wlan0
253 | else:
254 | ip = eth0
255 | print('ip : %s' % ip)
256 | sc.set('video', 'http://'+ip+':9000/mjpg')
257 |
258 | Vilib.camera_start(vflip=False, hflip=False)
259 | Vilib.display(local=False, web=True)
260 |
261 | print("Voice Command: ")
262 | for command_name in COMMANDS:
263 | print(command_name)
264 |
265 | last_kx = 0
266 | last_ky = 0
267 | last_qx = 0
268 | last_qy = 0
269 |
270 | while True:
271 | # print("Receive: ", sc.getall())
272 |
273 | sc.set("A", round(my_dog.read_distance(),2))
274 |
275 | # Left Joystick move
276 | k_value = sc.get('K')
277 | if k_value != None:
278 | kx, ky = k_value
279 | # calculate angle and radius
280 | if last_ky != ky or last_kx != kx:
281 | last_ky = ky
282 | last_kx = kx
283 | if kx != 0 or ky != 0:
284 | ka = atan2(ky, kx) * 180 / pi
285 | kr = sqrt(kx**2 + ky**2)
286 | if kr > 100:
287 | if (ka > 45 and ka < 135):
288 | command = "forward"
289 | elif (ka > 135 or ka < -135):
290 | command = "turn left"
291 | elif (ka > -45 and ka < 45):
292 | command = "turn right"
293 | elif (ka > -135 and ka < -45):
294 | command = "backward"
295 | else:
296 | command = None
297 |
298 | # Right Joystick move head
299 | q_value = sc.get('Q')
300 | if q_value != None:
301 | qx, qy = q_value
302 | if last_qx != qx or last_qy != qy:
303 | last_qx = qx
304 | last_qy = qy
305 | if qx != 0 or qy != 0:
306 | yaw = map(qx, 100, -100, -90, 90)
307 | pitch = map(qy, -100, 100, -30, 30)
308 | else:
309 | yaw = 0
310 | pitch = 0
311 | set_head(yaw=yaw, pitch=pitch)
312 |
313 | d_value = sc.get('D')
314 | if d_value != None:
315 | set_head(roll=d_value)
316 |
317 | # Voice Control
318 | voice_command = sc.get('J')
319 | if voice_command != None:
320 | print(f'voice command: {voice_command}')
321 | if voice_command in COMMANDS:
322 | command = voice_command
323 | else:
324 | print("\033[0;31m no this voice command\033[m")
325 |
326 | # Bark
327 | n_value = sc.get('N')
328 | if n_value:
329 | command = 'bark'
330 |
331 | # Wag tail
332 | O_value = sc.get('O')
333 | if O_value:
334 | command = 'wag tail'
335 | elif command == 'wag tail':
336 | command = None
337 |
338 | # pant
339 | P_value = sc.get('P')
340 | if P_value:
341 | command = 'pant'
342 |
343 | # Scratch
344 | I_value = sc.get('I')
345 | if I_value:
346 | command = 'scratch'
347 |
348 | # Sit
349 | E_value = sc.get('E')
350 | if E_value:
351 | command = 'sit'
352 |
353 | # Stand
354 | F_value = sc.get('F')
355 | if F_value:
356 | command = 'stand up'
357 |
358 | # Lie
359 | G_value = sc.get('G')
360 | if G_value:
361 | command = 'lie down'
362 |
363 | # Face detection
364 | C_value = sc.get('C')
365 | if C_value:
366 | Vilib.face_detect_switch(True)
367 | else:
368 | Vilib.face_detect_switch(False)
369 |
370 | run_command()
371 | sleep(0.008)
372 |
373 |
374 | if __name__ == "__main__":
375 | try:
376 | main()
377 | except KeyboardInterrupt:
378 | pass
379 | except Exception as e:
380 | print(f"\033[31mERROR: {e}\033[m")
381 | finally:
382 | sc.close()
383 | Vilib.camera_close()
384 | my_dog.close()
385 |
--------------------------------------------------------------------------------
/examples/13_ball_track.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from vilib import Vilib
5 | from preset_actions import bark
6 |
7 | my_dog = Pidog()
8 |
9 | sleep(0.1)
10 |
11 | STEP = 0.5
12 |
13 | def delay(time):
14 | my_dog.wait_legs_done()
15 | my_dog.wait_head_done()
16 | sleep(time)
17 |
18 | def ball_track():
19 | Vilib.camera_start(vflip=False, hflip=False)
20 | Vilib.display(local=True, web=True)
21 | Vilib.color_detect(color="red") # close, red, green, blue, yellow , orange, purple
22 | sleep(0.2)
23 | print('start')
24 | yaw = 0
25 | roll = 0
26 | pitch = 0
27 | flag = False
28 | direction = 0
29 |
30 | my_dog.do_action('stand', speed=50)
31 | my_dog.head_move([[yaw, 0, pitch]], immediately=True, speed=80)
32 | delay(0.5)
33 |
34 | while True:
35 |
36 | ball_x = Vilib.detect_obj_parameter['color_x'] - 320
37 | ball_y = Vilib.detect_obj_parameter['color_y'] - 240
38 | width = Vilib.detect_obj_parameter['color_w']
39 |
40 | if ball_x > 15 and yaw > -80:
41 | yaw -= STEP
42 |
43 | elif ball_x < -15 and yaw < 80:
44 | yaw += STEP
45 |
46 | if ball_y > 25:
47 | pitch -= STEP
48 | if pitch < - 40:
49 | pitch = -40
50 | elif ball_y < -25:
51 | pitch += STEP
52 | if pitch > 20:
53 | pitch = 20
54 |
55 | print(f"yaw: {yaw}, pitch: {pitch}, width: {width}")
56 |
57 | my_dog.head_move([[yaw, 0, pitch]], immediately=True, speed=100)
58 | if width == 0:
59 | pitch = 0
60 | yaw = 0
61 | elif width < 300:
62 | if my_dog.is_legs_done():
63 | if yaw < -30:
64 | print("turn right")
65 | my_dog.do_action('turn_right', speed=98)
66 | elif yaw > 30:
67 | print("turn left")
68 | my_dog.do_action('turn_left', speed=98)
69 | else:
70 | my_dog.do_action('forward', speed=98)
71 | sleep(0.02)
72 |
73 |
74 | if __name__ == "__main__":
75 | try:
76 | ball_track()
77 | except KeyboardInterrupt:
78 | pass
79 | except Exception as e:
80 | print(f"\033[31mERROR: {e}\033[m")
81 | finally:
82 | Vilib.camera_close()
83 | my_dog.close()
84 |
85 |
--------------------------------------------------------------------------------
/examples/1_wake_up.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from preset_actions import pant
5 | from preset_actions import body_twisting
6 |
7 | my_dog = Pidog(head_init_angles=[0, 0, -30])
8 | sleep(1)
9 |
10 | def wake_up():
11 | # stretch
12 | my_dog.rgb_strip.set_mode('listen', color='yellow', bps=0.6, brightness=0.8)
13 | my_dog.do_action('stretch', speed=50)
14 | my_dog.head_move([[0, 0, 30]]*2, immediately=True)
15 | my_dog.wait_all_done()
16 | sleep(0.2)
17 | body_twisting(my_dog)
18 | my_dog.wait_all_done()
19 | sleep(0.5)
20 | my_dog.head_move([[0, 0, -30]], immediately=True, speed=90)
21 | # sit and wag_tail
22 | my_dog.do_action('sit', speed=25)
23 | my_dog.wait_legs_done()
24 | my_dog.do_action('wag_tail', step_count=10, speed=100)
25 | my_dog.rgb_strip.set_mode('breath', color=[245, 10, 10], bps=2.5, brightness=0.8)
26 | pant(my_dog, pitch_comp=-30, volume=80)
27 | my_dog.wait_all_done()
28 | # hold
29 | my_dog.do_action('wag_tail', step_count=10, speed=30)
30 | my_dog.rgb_strip.set_mode('breath', 'pink', bps=0.5)
31 | while True:
32 | sleep(1)
33 |
34 | if __name__ == "__main__":
35 | try:
36 | wake_up()
37 | except KeyboardInterrupt:
38 | pass
39 | except Exception as e:
40 | print(f"\033[31mERROR: {e}\033[m")
41 | finally:
42 | my_dog.close()
43 |
--------------------------------------------------------------------------------
/examples/2_function_demonstration.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | import os
4 | import curses
5 | import curses_utils
6 | from time import sleep
7 |
8 | # init pidog
9 | # ======================================
10 | my_dog = Pidog()
11 | sleep(0.5)
12 |
13 | # global variables
14 | # ======================================
15 | actions = [
16 | # name, head_pitch_adjust(-1, use last_pitch), speed
17 | ['stand', 0, 50],
18 | ['sit', -30, 50],
19 | ['lie', 0, 20],
20 | ['lie_with_hands_out', 0, 20],
21 | ['trot', 0, 95],
22 | ['forward', 0, 98],
23 | ['backward', 0, 98],
24 | ['turn_left', 0, 98],
25 | ['turn_right', 0, 98],
26 | ['doze_off', -30, 90],
27 | ['stretch', 20, 20],
28 | ['push_up', -30, 50],
29 | ['shake_head', -1, 90],
30 | ['tilting_head', -1, 60],
31 | ['wag_tail', -1, 100],
32 | ]
33 | actions_len = len(actions)
34 |
35 | sound_effects = []
36 | # change working directory
37 | abspath = os.path.abspath(os.path.dirname(__file__))
38 | # print(abspath)
39 | os.chdir(abspath)
40 | for name in os.listdir('../sounds'):
41 | sound_effects.append(name.split('.')[0])
42 | sound_effects.sort()
43 | sound_len = len(sound_effects)
44 | # limit sound quantity
45 | if sound_len > actions_len:
46 | sound_len = actions_len
47 | sound_effects = sound_effects[:actions_len]
48 |
49 | last_index = 0
50 | last_display_index = 0
51 | exit_flag = False
52 | last_head_pitch = 0
53 |
54 | STANDUP_ACTIONS = ['trot', 'forward', 'backward', 'turn_left', 'turn_right']
55 |
56 | # define pad size
57 | # ======================================
58 | curses_utils.PAD_Y = 22
59 | curses_utils.PAD_X = 70
60 |
61 | # display fuctions
62 | # ======================================
63 | def display_head(subpad):
64 | title = "Function Demonstration"
65 | tip1 = "Input Function number to see how it goes."
66 | tip2 = "Actions will repeat 10 times."
67 | type_name_1 = "Actions:"
68 | type_name_2 = "Sound Effect:"
69 | tip3 = "(need to run with sudo)"
70 |
71 | curses_utils.clear_line(subpad, 0, color_pair=curses_utils.WHITE_BLUE | curses.A_REVERSE)
72 | subpad.addstr(0, 2, title, curses_utils.WHITE_BLUE | curses.A_BOLD | curses.A_REVERSE)
73 | subpad.addstr(1, 2, tip1, curses_utils.WHITE)
74 | subpad.addstr(2, 2, tip2, curses_utils.WHITE)
75 | curses_utils.clear_line(subpad, 3, color_pair=curses_utils.WHITE | curses.A_REVERSE)
76 | subpad.addstr(3, 2, type_name_1, curses_utils.WHITE | curses.A_REVERSE)
77 | subpad.addstr(3, 30, type_name_2, curses_utils.WHITE | curses.A_REVERSE)
78 | subpad.addstr(3, 31+len(type_name_2), tip3, curses_utils.YELLOW | curses.A_REVERSE)
79 |
80 | def display_selection(subpad, index):
81 | global last_display_index
82 | # reset last selection
83 | if last_display_index > actions_len + sound_len-1 or last_display_index < 0:
84 | last_display_index = 0
85 | if last_display_index != index:
86 | if last_display_index < actions_len:
87 | subpad.addstr(last_display_index, 2, f"{last_display_index+1}. {actions[last_display_index][0]}", curses_utils.WHITE | curses.A_DIM)
88 | else:
89 | sound_index = last_display_index-actions_len
90 | subpad.addstr(sound_index, 30, f"{last_display_index+1}. {sound_effects[sound_index]}", curses_utils.WHITE | curses.A_DIM)
91 | last_display_index = index
92 | # highlight currernt selection
93 | if index > actions_len + sound_len-1 or index < 0:
94 | pass
95 | elif index < actions_len:
96 | subpad.addstr(index, 2, f"{index+1}. {actions[index][0]}", curses_utils.WHITE_BLUE)
97 | else:
98 | sound_index = index-actions_len
99 | subpad.addstr(sound_index, 30, f"{index+1}. {sound_effects[sound_index]}", curses_utils.WHITE_BLUE)
100 |
101 | def display_actions(subpad):
102 | for i in range(actions_len):
103 | subpad.addstr(i, 2, f"{i+1}. {actions[i][0]}", curses_utils.WHITE | curses.A_DIM)
104 | for i in range(sound_len):
105 | subpad.addstr(i, 30, f"{i+actions_len+1}. {sound_effects[i]}", curses_utils.WHITE | curses.A_DIM)
106 |
107 | def display_bottom(subpad, num=''):
108 | curses_utils.clear_line(subpad, 0, color_pair=curses_utils.WHITE | curses.A_DIM)
109 | subpad.addstr(0, 0, f"Enter function number: {num}", curses_utils.WHITE | curses.A_DIM)
110 | subpad.addstr(0, curses_utils.PAD_X-16, "Ctrl^C to quit", curses_utils.WHITE | curses.A_DIM)
111 |
112 |
113 | def do_function(index):
114 | global last_index, last_head_pitch
115 | my_dog.body_stop()
116 | if index < 0:
117 | return
118 | if index < actions_len:
119 | name, head_pitch_adjust, speed = actions[index]
120 | # If last action is push_up, then lie down first
121 | if last_index < len(actions) and actions[last_index][0] in ('push_up'):
122 | last_head_pitch = 0
123 | my_dog.do_action('lie', speed=60)
124 | # If this action is trot, forward, turn left, turn right and backward, and, last action is not, then stand up
125 | if name in STANDUP_ACTIONS and last_index < len(actions) and actions[last_index][0] not in STANDUP_ACTIONS:
126 | last_head_pitch = 0
127 | my_dog.do_action('stand', speed=60)
128 | if head_pitch_adjust != -1:
129 | last_head_pitch = head_pitch_adjust
130 | my_dog.head_move_raw([[0, 0, last_head_pitch]], immediately=False, speed=60)
131 | my_dog.do_action(name, step_count=10, speed=speed, pitch_comp=last_head_pitch)
132 | last_index = index
133 | elif index < actions_len + sound_len:
134 | my_dog.speak(sound_effects[index - len(actions)], volume=80)
135 | last_index = index
136 |
137 | def main(stdscr):
138 | # reset screen
139 | stdscr.clear()
140 | stdscr.move(4, 0)
141 | stdscr.refresh()
142 |
143 | # disable cursor
144 | curses.curs_set(0)
145 |
146 | # init color
147 | curses.start_color()
148 | curses.use_default_colors()
149 | curses_utils.init_preset_color_pairs()
150 |
151 | # init pad
152 | pad = curses.newpad(curses_utils.PAD_Y, curses_utils.PAD_X)
153 |
154 | # init subpad
155 | head_pad = pad.subpad(4, curses_utils.PAD_X, 0, 0)
156 | selection_pad = pad.subpad(actions_len, curses_utils.PAD_X, 4, 0)
157 | bottom_pad = pad.subpad(1, curses_utils.PAD_X, actions_len+4, 0)
158 | # add content to a
159 | display_head(head_pad)
160 | display_actions(selection_pad)
161 | display_head(head_pad)
162 | curses_utils.pad_refresh(pad)
163 | curses_utils.pad_refresh(selection_pad)
164 |
165 | # for i in range(2):
166 | # for i in range(30):
167 | # display_selection(selection_pad, i)
168 | # curses_utils.pad_refresh(selection_pad)
169 | # sleep(0.1)
170 |
171 | # enable cursor and echo
172 | curses.curs_set(0)
173 | curses.echo()
174 |
175 | # stdscr.nodelay(True) # set non-blocking mode for getch()
176 | # stdscr.timeout(10)
177 | # curses.cbreak()
178 |
179 | index_str = ''
180 | index = -1
181 |
182 | display_bottom(bottom_pad)
183 | curses_utils.pad_refresh(bottom_pad)
184 |
185 | while True:
186 | # draw bottom bar
187 | # display_bottom(bottom_pad)
188 | # display_bottom(bottom_pad, f'{index+1}')
189 | # curses_utils.pad_refresh(bottom_pad)
190 | # reset cursor
191 | stdscr.move(actions_len+4, 23)
192 | stdscr.refresh()
193 | # red key
194 | key = stdscr.getch()
195 | key = curses.unctrl(key)
196 |
197 | # print(f'key: {key}')
198 |
199 | if key in b'0123456789':
200 | index_str += str(int(key))
201 | display_bottom(bottom_pad, index_str)
202 | curses_utils.pad_refresh(bottom_pad)
203 | elif key == b'^J' or key == b'^M': # enter or return
204 | if index_str == '':
205 | if index == -1 or index > 26:
206 | continue
207 | else:
208 | do_function(index)
209 | else:
210 | index = int(index_str)-1
211 | # display selection
212 | display_selection(selection_pad, index)
213 | curses_utils.pad_refresh(selection_pad)
214 | # do fuction
215 | do_function(index)
216 | # reset display
217 | index_str = ''
218 | display_bottom(bottom_pad, index_str)
219 | curses_utils.pad_refresh(bottom_pad)
220 |
221 |
222 | sleep(0.2)
223 |
224 | if __name__ == "__main__":
225 | try:
226 | curses.wrapper(main)
227 | except KeyboardInterrupt:
228 | pass
229 | except Exception as e:
230 | print(f"\033[31mERROR: {e}\033[m")
231 | finally:
232 | my_dog.close()
233 |
--------------------------------------------------------------------------------
/examples/3_patrol.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import time
3 | from pidog import Pidog
4 | from preset_actions import bark
5 |
6 | t = time.time()
7 | my_dog = Pidog()
8 | my_dog.do_action('stand', speed=80)
9 | my_dog.wait_all_done()
10 | time.sleep(.5)
11 |
12 | DANGER_DISTANCE = 15
13 |
14 | stand = my_dog.legs_angle_calculation([[0, 80], [0, 80], [30, 75], [30, 75]])
15 |
16 | def patrol():
17 | distance = round(my_dog.read_distance(), 2)
18 | print(f"distance: {distance} cm", end="", flush=True)
19 |
20 | # danger
21 | if distance > 0 and distance < DANGER_DISTANCE:
22 | print("\033[0;31m DANGER !\033[m")
23 | my_dog.body_stop()
24 | head_yaw = my_dog.head_current_angles[0]
25 | # my_dog.rgb_strip.set_mode('boom', 'red', bps=2)
26 | my_dog.rgb_strip.set_mode('bark', 'red', bps=2)
27 | my_dog.tail_move([[0]], speed=80)
28 | my_dog.legs_move([stand], speed=70)
29 | my_dog.wait_all_done()
30 | time.sleep(0.5)
31 | bark(my_dog, [head_yaw, 0, 0])
32 |
33 | while distance < DANGER_DISTANCE:
34 | distance = round(my_dog.read_distance(), 2)
35 | if distance < DANGER_DISTANCE:
36 | print(f"distance: {distance} cm \033[0;31m DANGER !\033[m")
37 | else:
38 | print(f"distance: {distance} cm", end="", flush=True)
39 | time.sleep(0.01)
40 | # safe
41 | else:
42 | print("")
43 | my_dog.rgb_strip.set_mode('breath', 'white', bps=0.5)
44 | my_dog.do_action('forward', step_count=2, speed=98)
45 | my_dog.do_action('shake_head', step_count=1, speed=80)
46 | my_dog.do_action('wag_tail', step_count=5, speed=99)
47 |
48 |
49 | if __name__ == "__main__":
50 | try:
51 | while True:
52 | patrol()
53 | time.sleep(0.01)
54 | except KeyboardInterrupt:
55 | pass
56 | except Exception as e:
57 | print(f"\033[31mERROR: {e}\033[m")
58 | finally:
59 | my_dog.close()
60 |
--------------------------------------------------------------------------------
/examples/4_response.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from math import sin
5 | from preset_actions import bark_action
6 |
7 | my_dog = Pidog()
8 | sleep(0.1)
9 |
10 | def lean_forward():
11 | my_dog.speak('angry', volume=80)
12 | bark_action(my_dog)
13 | sleep(0.2)
14 | bark_action(my_dog)
15 | sleep(0.4)
16 | bark_action(my_dog)
17 |
18 | def head_nod(step):
19 | y = 0
20 | r = 0
21 | p = 30
22 | angs = []
23 | for i in range(20):
24 | r = round(10*sin(i*0.314), 2)
25 | p = round(20*sin(i*0.314) + 10, 2)
26 | angs.append([y, r, p])
27 |
28 | my_dog.head_move(angs*step, immediately=False, speed=80)
29 |
30 | def alert():
31 | my_dog.do_action('stand', step_count=1, speed=70)
32 | my_dog.rgb_strip.set_mode('breath', color='pink', bps=1, brightness=0.8)
33 | while True:
34 | print(
35 | f'distance.value: {round(my_dog.read_distance(), 2)} cm, touch {my_dog.dual_touch.read()}')
36 | # alert
37 | if my_dog.read_distance() < 15 and my_dog.read_distance() > 1:
38 | my_dog.head_move([[0, 0, 0]], immediately=True, speed=90)
39 | my_dog.tail_move([[0]], immediately=True, speed=80)
40 | my_dog.rgb_strip.set_mode('bark', color='red', bps=2, brightness=0.8)
41 | my_dog.do_action('backward', step_count=1, speed=95)
42 | my_dog.wait_all_done()
43 | lean_forward()
44 | while len(my_dog.legs_action_buffer) > 0:
45 | sleep(0.1)
46 | my_dog.do_action('stand', step_count=1, speed=90)
47 | sleep(0.5)
48 | # relax
49 | if my_dog.dual_touch.read() != 'N':
50 | if len(my_dog.head_action_buffer) < 2:
51 | head_nod(1)
52 | my_dog.do_action('wag_tail', step_count=10, speed=80)
53 | my_dog.rgb_strip.set_mode('listen', color="#8A2BE2", bps=0.35, brightness=0.8)
54 | # calm
55 | else:
56 | my_dog.rgb_strip.set_mode('breath', color='pink', bps=1, brightness=0.8)
57 | my_dog.tail_stop()
58 | sleep(0.2)
59 |
60 | if __name__ == "__main__":
61 | try:
62 | alert()
63 | except KeyboardInterrupt:
64 | pass
65 | except Exception as e:
66 | print(f"\033[31mERROR: {e}\033[m")
67 | finally:
68 | my_dog.close()
69 |
70 |
--------------------------------------------------------------------------------
/examples/5_rest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from preset_actions import shake_head
5 |
6 | my_dog = Pidog()
7 | sleep(0.1)
8 |
9 | def loop_around(amplitude=60, interval=0.5, speed=100):
10 | my_dog.head_move([[amplitude,0,0]], immediately=True, speed=speed)
11 | my_dog.wait_all_done()
12 | sleep(interval)
13 | my_dog.head_move([[-amplitude,0,0]], immediately=True, speed=speed)
14 | my_dog.wait_all_done()
15 | sleep(interval)
16 | my_dog.head_move([[0,0,0]], immediately=True, speed=speed)
17 | my_dog.wait_all_done()
18 |
19 | def is_sound():
20 | if my_dog.ears.isdetected():
21 | direction = my_dog.ears.read()
22 | if direction != 0:
23 | return True
24 | else:
25 | return False
26 | else:
27 | return False
28 |
29 | def rest():
30 | my_dog.wait_all_done()
31 | my_dog.do_action('lie', speed=50)
32 | my_dog.wait_all_done()
33 |
34 | while True:
35 | # Sleeping
36 | my_dog.rgb_strip.set_mode('breath', 'pink', bps=0.3)
37 | my_dog.head_move([[0,0,-40]], immediately=True, speed=5)
38 | my_dog.do_action('doze_off', speed=92)
39 | # Cleanup sound detection
40 | sleep(1)
41 | is_sound()
42 |
43 | # keep sleeping
44 | while is_sound() is False:
45 | my_dog.do_action('doze_off', speed=92)
46 | sleep(0.2)
47 |
48 | # If heard anything, wake up
49 | # Set light to yellow and stand up
50 | my_dog.rgb_strip.set_mode('boom', 'yellow', bps=1)
51 | my_dog.body_stop()
52 | # Add a delay for the body to stopb
53 | sleep(0.1)
54 | my_dog.do_action('stand', speed=80)
55 | my_dog.head_move([[0, 0, 0]], immediately=True, speed=80)
56 | my_dog.wait_all_done()
57 | # Look arround
58 | loop_around(60, 1, 60)
59 | sleep(0.5)
60 | # tilt head and being confused
61 | my_dog.speak('confused_3', volume=80)
62 | my_dog.do_action('tilting_head_left', speed=80)
63 | my_dog.wait_all_done()
64 | sleep(1.2)
65 | my_dog.head_move([[0, 0, -10]], immediately=True, speed=80)
66 | my_dog.wait_all_done()
67 | sleep(0.4)
68 | # Shake head , mean to ignore it
69 | shake_head(my_dog)
70 | sleep(0.2)
71 |
72 | # Lay down again
73 | my_dog.rgb_strip.set_mode('breath', 'pink', bps=1)
74 | my_dog.do_action('lie', speed=50)
75 | my_dog.wait_all_done()
76 | sleep(1)
77 |
78 |
79 | if __name__ == "__main__":
80 | try:
81 | rest()
82 | except KeyboardInterrupt:
83 | pass
84 | except Exception as e:
85 | print(f"\033[31mERROR: {e}\033[m")
86 | finally:
87 | my_dog.close()
88 |
--------------------------------------------------------------------------------
/examples/6_be_picked_up.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 |
5 | my_dog = Pidog()
6 | sleep(0.1)
7 |
8 |
9 | def fly():
10 | my_dog.rgb_strip.set_mode('boom', color='red', bps=3)
11 | my_dog.legs.servo_move([45, -45, 90, -80, 90, 90, -90, -90], speed=60)
12 | my_dog.do_action('wag_tail', step_count=10, speed=100)
13 | my_dog.speak('woohoo', volume=80)
14 | my_dog.wait_legs_done()
15 | sleep(1)
16 |
17 | def stand():
18 | my_dog.rgb_strip.set_mode('breath', color='green', bps=1)
19 | my_dog.do_action('stand', speed=60)
20 | my_dog.wait_legs_done()
21 | sleep(1)
22 |
23 | def be_picked_up():
24 | isUp = False
25 | upflag = False
26 | downflag = False
27 |
28 | stand()
29 |
30 | while True:
31 | ax = my_dog.accData[0]
32 | print('ax: %s, is up: %s' % (ax, isUp))
33 |
34 | # gravity : 1G = -16384
35 | if ax < -18000: # if down, acceleration is in the same direction as gravity, ax < -1G
36 | my_dog.body_stop()
37 | if upflag == False:
38 | upflag = True
39 | if downflag == True:
40 | isUp = False
41 | downflag = False
42 | stand()
43 |
44 | if ax > -13000: # if up, acceleration is the opposite of gravity, ax will > -1G
45 | my_dog.body_stop()
46 | if upflag == True:
47 | isUp = True
48 | upflag = False
49 | fly()
50 | if downflag == False:
51 | downflag = True
52 |
53 | sleep(0.02)
54 |
55 |
56 | if __name__ == "__main__":
57 | try:
58 | be_picked_up()
59 | except KeyboardInterrupt:
60 | pass
61 | except Exception as e:
62 | print(f"\033[31mERROR: {e}\033[m")
63 | finally:
64 | my_dog.close()
65 |
--------------------------------------------------------------------------------
/examples/7_face_track.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from vilib import Vilib
5 | from preset_actions import bark
6 |
7 | my_dog = Pidog()
8 | sleep(0.1)
9 |
10 | def face_track():
11 | Vilib.camera_start(vflip=False, hflip=False)
12 | Vilib.display(local=True, web=True)
13 | Vilib.face_detect_switch(True)
14 | sleep(0.2)
15 | print('start')
16 | yaw = 0
17 | roll = 0
18 | pitch = 0
19 | flag = False
20 | direction = 0
21 |
22 | my_dog.do_action('sit', speed=50)
23 | my_dog.head_move([[yaw, 0, pitch]], pitch_comp=-40, immediately=True, speed=80)
24 | my_dog.wait_all_done()
25 | sleep(0.5)
26 | # Cleanup sound detection by servos moving
27 | if my_dog.ears.isdetected():
28 | direction = my_dog.ears.read()
29 |
30 | while True:
31 | if flag == False:
32 | my_dog.rgb_strip.set_mode('breath', 'pink', bps=1)
33 | # If heard somthing, turn to face it
34 | if my_dog.ears.isdetected():
35 | flag = False
36 | direction = my_dog.ears.read()
37 | pitch = 0
38 | if direction > 0 and direction < 160:
39 | yaw = -direction
40 | if yaw < -80:
41 | yaw = -80
42 | elif direction > 200 and direction < 360:
43 | yaw = 360 - direction
44 | if yaw > 80:
45 | yaw = 80
46 | my_dog.head_move([[yaw, 0, pitch]], pitch_comp=-40, immediately=True, speed=80)
47 | my_dog.wait_head_done()
48 | sleep(0.05)
49 |
50 | ex = Vilib.detect_obj_parameter['human_x'] - 320
51 | ey = Vilib.detect_obj_parameter['human_y'] - 240
52 | people = Vilib.detect_obj_parameter['human_n']
53 |
54 | # If see someone, bark at him/her
55 | if people > 0 and flag == False:
56 | flag = True
57 | my_dog.do_action('wag_tail', step_count=2, speed=100)
58 | bark(my_dog, [yaw, 0, 0], pitch_comp=-40, volume=80)
59 | if my_dog.ears.isdetected():
60 | direction = my_dog.ears.read()
61 |
62 | if ex > 15 and yaw > -80:
63 | yaw -= 0.5 * int(ex/30.0+0.5)
64 |
65 | elif ex < -15 and yaw < 80:
66 | yaw += 0.5 * int(-ex/30.0+0.5)
67 |
68 | if ey > 25:
69 | pitch -= 1*int(ey/50+0.5)
70 | if pitch < - 30:
71 | pitch = -30
72 | elif ey < -25:
73 | pitch += 1*int(-ey/50+0.5)
74 | if pitch > 30:
75 | pitch = 30
76 |
77 | print('direction: %s |number: %s | ex, ey: %s, %s | yrp: %s, %s, %s '
78 | % (direction, people, ex, ey, round(yaw, 2), round(roll, 2), round(pitch, 2)),
79 | end='\r',
80 | flush=True,
81 | )
82 | my_dog.head_move([[yaw, 0, pitch]], pitch_comp=-40, immediately=True, speed=100)
83 | sleep(0.05)
84 |
85 |
86 | if __name__ == "__main__":
87 | try:
88 | face_track()
89 | except KeyboardInterrupt:
90 | pass
91 | except Exception as e:
92 | print(f"\033[31mERROR: {e}\033[m")
93 | finally:
94 | Vilib.camera_close()
95 | my_dog.close()
96 |
97 |
--------------------------------------------------------------------------------
/examples/8_pushup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from preset_actions import push_up, bark
5 |
6 | my_dog = Pidog()
7 |
8 | sleep(0.5)
9 |
10 |
11 | def main():
12 | my_dog.legs_move([[45, -25, -45, 25, 80, 70, -80, -70]], speed=50)
13 | my_dog.head_move([[0, 0, -20]], speed=90)
14 | my_dog.wait_all_done()
15 | sleep(0.5)
16 | bark(my_dog, [0, 0, -20])
17 | sleep(0.1)
18 | bark(my_dog, [0, 0, -20])
19 |
20 | sleep(1)
21 | my_dog.rgb_strip.set_mode("speak", color="blue", bps=2)
22 | while True:
23 | push_up(my_dog, speed=92)
24 | bark(my_dog, [0, 0, -40])
25 | sleep(0.4)
26 |
27 |
28 | if __name__ == "__main__":
29 | try:
30 | main()
31 | except KeyboardInterrupt:
32 | pass
33 | except Exception as e:
34 | print(f"\033[31mERROR: {e}\033[m")
35 | finally:
36 | my_dog.close()
37 |
--------------------------------------------------------------------------------
/examples/9_howling.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | from time import sleep
4 | from preset_actions import howling
5 |
6 | my_dog = Pidog()
7 |
8 | sleep(0.5)
9 |
10 |
11 | def main():
12 | my_dog.do_action('sit', speed=50)
13 | my_dog.head_move([[0, 0, 0]], pitch_comp=-40, immediately=True, speed=80)
14 | sleep(0.5)
15 | while True:
16 | howling(my_dog)
17 |
18 |
19 | if __name__ == "__main__":
20 | try:
21 | main()
22 | except KeyboardInterrupt:
23 | pass
24 | except Exception as e:
25 | print(f"\033[31mERROR: {e}\033[m")
26 | finally:
27 | my_dog.close()
28 |
29 |
--------------------------------------------------------------------------------
/examples/curses_utils.py:
--------------------------------------------------------------------------------
1 | import curses
2 | import time
3 | import os
4 |
5 | # os.environ['TERM'] = 'xterm-256color'
6 | is_256color = False
7 | if os.environ['TERM'] == 'xterm-256color':
8 | is_256color = True
9 | else:
10 | is_256color = False
11 |
12 | # define pad size
13 | # ======================================
14 | PAD_Y = 40
15 | PAD_X = 80
16 |
17 | pad_ypos = 0
18 | pad_xpos = 0
19 |
20 | # define preset colors and color_pairs
21 | # ======================================
22 | # color pairs
23 | BLACK = None
24 | BLUE = None
25 | CYAN = None
26 | GREEN = None
27 | MAGENTA = None
28 | RED = None
29 | WHITE = None
30 | YELLOW = None
31 | WHITE_BLUE = None
32 |
33 | def init_preset_color_pairs():
34 | global BLACK, BLUE, CYAN, GREEN, MAGENTA, RED, WHITE, YELLOW, WHITE_BLUE
35 |
36 | curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
37 | BLACK = curses.color_pair(1)
38 |
39 | curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK)
40 | BLUE = curses.color_pair(2)
41 |
42 | curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_BLACK)
43 | CYAN = curses.color_pair(3)
44 |
45 | curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_BLACK)
46 | GREEN = curses.color_pair(4)
47 |
48 | curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
49 | MAGENTA = curses.color_pair(5)
50 |
51 | curses.init_pair(6, curses.COLOR_RED, curses.COLOR_BLACK)
52 | RED = curses.color_pair(6)
53 |
54 | curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK)
55 | WHITE = curses.color_pair(7)
56 |
57 | curses.init_pair(8, curses.COLOR_YELLOW, curses.COLOR_BLACK)
58 | YELLOW = curses.color_pair(8)
59 |
60 | curses.init_pair(9, curses.COLOR_WHITE, curses.COLOR_BLUE)
61 | WHITE_BLUE = curses.color_pair(9)
62 |
63 | # display fuctions
64 | # ======================================
65 | def pad_refresh(pad):
66 | # Displays a section of the pad in the middle of the screen.
67 | # (0,0) : coordinate of upper-left corner of pad area to display.
68 | # (5,5) : coordinate of upper-left corner of window area to be filled
69 | # with pad content.
70 | # (20, 75) : coordinate of lower-right corner of window area to be
71 | # : filled with pad content.
72 | y, x = pad.getbegyx()
73 | h, w = pad.getmaxyx()
74 | y_2 = y+h
75 | x_2 = x+w
76 | if y_2 > curses.LINES-1:
77 | y_2 = curses.LINES-1
78 | if x_2 > curses.COLS-1:
79 | x_2 = curses.COLS-1
80 | pad.refresh(0, 0, y, x, y_2, x_2)
81 |
82 | def clear_line(pad, line, xlen=None, color_pair=None):
83 | if xlen is None:
84 | xlen = PAD_X
85 | if color_pair is None:
86 | pad.addstr(line, 0, " "*(xlen-2))
87 | else:
88 | # pad.addstr(line, 0, " "*(xlen-1), BLUE|curses.A_REVERSE)
89 | pad.addstr(line, 0, " "*(xlen-1), color_pair)
90 |
91 | # test
92 | # ======================================
93 | def test(screen):
94 | screen.clear()
95 | screen.refresh()
96 | save_colors = [curses.color_content(i) for i in range(curses.COLORS)]
97 | curses.curs_set(0)
98 | curses.start_color()
99 |
100 | print(BLACK, WHITE, YELLOW)
101 | init_preset_color_pairs()
102 | print(BLACK, WHITE, YELLOW)
103 |
104 | color_pairs = {
105 | 'BLACK': [1, BLACK],
106 | 'BLUE': [2, BLUE],
107 | 'CYAN': [3, CYAN],
108 | 'GREEN': [4, GREEN],
109 | 'MAGENTA': [5, MAGENTA],
110 | 'RED': [6, RED],
111 | 'WHITE': [7, WHITE],
112 | 'YELLOW': [8, YELLOW],
113 | 'WHITE_BLUE': [9, WHITE_BLUE],
114 | }
115 |
116 | i = 2
117 | for color_name, value in color_pairs.items():
118 | index, color_pair = value
119 | screen.addstr(i, 0, f'{color_name}', color_pair)
120 | screen.addstr(i, 20, f'{color_name} A_REVERSE', color_pair | curses.A_REVERSE)
121 | screen.addstr(i, 40, f'{color_name} A_DIM', color_pair | curses.A_DIM)
122 | screen.addstr(i, 60, f'{color_name} A_BOLD', color_pair | curses.A_BOLD)
123 | i += 1
124 |
125 | screen.getch()
126 |
127 | if __name__ == "__main__":
128 | try:
129 | curses.wrapper(test)
130 | except Exception as e:
131 | print(f"\033[31mERROR: {e}\033[m")
132 |
--------------------------------------------------------------------------------
/examples/custom_actions.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from pidog import Pidog
3 | import curses
4 | import curses_utils
5 | from time import sleep
6 |
7 | # init pidog
8 | # ======================================
9 | my_dog = Pidog()
10 |
11 | # my_dog.legs_move([[0]*8], immediately=True, speed=60)
12 | # my_dog.head_move([[0]*3], immediately=True, speed=60)
13 | # my_dog.tail_move([[0]], immediately=True, speed=60)
14 | # my_dog.wait_all_done()
15 |
16 | my_dog.head_move([[0, 0, -35]], immediately=True, speed=5)
17 | my_dog.do_action('sit', speed=92)
18 | my_dog.wait_all_done()
19 |
20 | # global variables
21 | # ======================================
22 | leg_angles = [0.0]*8
23 | head_angles = [0.0]*3
24 | tail_angle = [0.0]*1
25 | current_servo = "1"
26 |
27 | OFFSET_STEP = (180 / 2000) * (20000 / 4095) # actual precision of steering gear
28 | MAX_OFF = 90
29 |
30 | # get_current_angles()
31 | # ======================================
32 | def get_current_angles():
33 | global leg_angles, head_angles, tail_angle
34 | leg_angles = list.copy(my_dog.leg_current_angles)
35 | head_angles = list.copy(my_dog.head_current_angles)
36 | tail_angle = list.copy(my_dog.tail_current_angles)
37 | for i, x in enumerate(leg_angles):
38 | leg_angles[i] = round(x, 2)
39 | for i, x in enumerate(head_angles):
40 | head_angles[i] = round(x, 2)
41 | tail_angle[0] = round(tail_angle[0], 2)
42 |
43 | # constrain(), constrain value range
44 | # ======================================
45 | def constrain(val, min_val, max_val):
46 |
47 | if val < min_val: return min_val
48 | if val > max_val: return max_val
49 | return val
50 |
51 |
52 | #
53 | # ============================================================================
54 | def display_title(subpad, color_pair):
55 | title = "PiDog Calibration"
56 | # tip1
57 | curses_utils.clear_line(subpad, 0, color_pair=color_pair)
58 | subpad.addstr(0, int((curses_utils.PAD_X-len(title))/2), title, color_pair)
59 |
60 | tip1 = [
61 | "Press key to select servo:",
62 | "1 ~ 8 : Leg servos",
63 | "9 : Head yaw ",
64 | "0 : Head roll ",
65 | "- : Head pitch ",
66 | "= : Tail ",
67 | ]
68 | def display_tip1(subpad, color_pair):
69 | subpad.addstr(0, 0, tip1[0], color_pair | curses.A_BOLD | curses.A_REVERSE)
70 | for i in range(1, len(tip1)):
71 | subpad.addstr(i, 0, tip1[i], color_pair | curses.A_BOLD)
72 |
73 | tip2 = [
74 | "Press key to adjust servo:",
75 | "W or A: increase angle ",
76 | "S or D: decreases angle",
77 | ]
78 | def display_tip2(subpad, color_pair):
79 | subpad.addstr(0, 0, tip2[0], color_pair | curses.A_BOLD | curses.A_REVERSE)
80 | for i in range(1, len(tip2)):
81 | subpad.addstr(i, 0, tip2[i], color_pair | curses.A_BOLD)
82 |
83 | body = [
84 | " [9] ",
85 | " [-] ┌─┐ [0] ",
86 | " │ │ ",
87 | "[2][1]┌└─┘┐[3][4]",
88 | " │ │ ",
89 | " │ │ ",
90 | "[6][5]└─┬─┘[7][8]",
91 | " [=] ",
92 | " / ",
93 | ]
94 | servo_pos = { # servo_pos in body
95 | "1": [3, 3], # ypos, xpos
96 | "2": [3, 0],
97 | "3": [3, 11],
98 | "4": [3, 14],
99 | "5": [6, 3],
100 | "6": [6, 0],
101 | "7": [6, 11],
102 | "8": [6, 14],
103 | "9": [0, 10],
104 | "0": [1, 12],
105 | "-": [1, 3],
106 | "=": [7, 7],
107 | }
108 | def display_dog_body(subpad, color_pair, color_pair_select):
109 | for i in range(len(body)):
110 | subpad.addstr(i, 0, body[i], color_pair | curses.A_BOLD)
111 | servo = current_servo
112 | if servo in servo_pos.keys():
113 | subpad.addstr(servo_pos[servo][0], servo_pos[servo][1], f"[{servo}]", color_pair_select | curses.A_BOLD)
114 |
115 | tip3 = [
116 | "Ctrl+C: Quit Space: Save",
117 | ]
118 | def display_tip3(subpad, color_pair):
119 | curses_utils.clear_line(subpad, 0)
120 | subpad.addstr(0, 0, tip3[0], color_pair | curses.A_BOLD | curses.A_REVERSE)
121 |
122 | def display_servo_num(subpad, color_pair, color_pair_select):
123 | subpad.addstr(0, 0, "Current Servo:", color_pair | curses.A_BOLD)
124 | subpad.addstr(0, 15, f"{current_servo}", color_pair_select | curses.A_BOLD)
125 |
126 | def display_angles(subpad, color_pair, color_pair_select):
127 | servo_num = "1234567890-="
128 | comma = ""
129 | curses_utils.clear_line(subpad, 0)
130 | subpad.addstr(0, 0, f"leg_angles:", color_pair | curses.A_BOLD)
131 | for i, x in enumerate(leg_angles):
132 | comma = "," if i < len(leg_angles)-1 else ""
133 | if servo_num[i] == current_servo:
134 | subpad.addstr(f' {x:.2f}{comma}', color_pair_select | curses.A_BOLD)
135 | else:
136 | subpad.addstr(f' {x:.2f}{comma}', color_pair | curses.A_BOLD)
137 | #
138 | curses_utils.clear_line(subpad, 1)
139 | subpad.addstr(1, 0, f"head_angles:", color_pair | curses.A_BOLD)
140 | for i, x in enumerate(head_angles):
141 | comma = "," if i < len(head_angles)-1 else ""
142 | if servo_num[i+8] == current_servo:
143 | subpad.addstr(f' {x:.2f}{comma}', color_pair_select | curses.A_BOLD)
144 | else:
145 | subpad.addstr(f' {x:.2f}{comma}', color_pair | curses.A_BOLD)
146 | #
147 | curses_utils.clear_line(subpad, 2)
148 | subpad.addstr(2, 0, f"tail_angle:", color_pair | curses.A_BOLD)
149 | for i, x in enumerate(tail_angle):
150 | if servo_num[i+11] == current_servo:
151 | subpad.addstr(f' {x:.2f}', color_pair_select | curses.A_BOLD)
152 | else:
153 | subpad.addstr(f' {x:.2f}', color_pair | curses.A_BOLD)
154 |
155 |
156 | def main(stdscr):
157 | # global winlines, wincols
158 | global current_servo
159 |
160 | inc = 1 # 1 or -1, angle increase direction
161 |
162 | # winlines = curses.LINES
163 | # wincols = curses.LINES
164 |
165 | # reset screen
166 | stdscr.clear()
167 | stdscr.move(0, 0)
168 | stdscr.refresh()
169 |
170 | # disable cursor
171 | curses.curs_set(0)
172 |
173 | # set colors
174 | curses.start_color()
175 | curses.use_default_colors()
176 | curses_utils.init_preset_color_pairs()
177 |
178 | # init pad
179 | pad = curses.newpad(curses_utils.PAD_Y, curses_utils.PAD_X)
180 | # pad.box()
181 |
182 | # get the offset
183 | get_current_angles()
184 |
185 | # init subpad
186 | title_pad = pad.subpad(1, curses_utils.PAD_X, 0, 0)
187 | tip1_pad = pad.subpad(len(tip1), len(tip1[0]), 1, 0)
188 | # tip2_pad = pad.subpad(len(tip2), len(tip2[0]), 1, PAD_X-len(tip2[0])-1)
189 | tip2_pad = pad.subpad(len(tip2), len(tip2[0]), len(tip1)+2, 0)
190 | # body_pad = pad.subpad(len(body), len(body[0]), 2, int((PAD_X-len(body[0]))/2)-2)
191 | body_pad = pad.subpad(len(body), len(body[0]), 2, curses_utils.PAD_X-len(body[0])-20)
192 | if curses.COLS < curses_utils.PAD_X - 20:
193 | body_pad.move(2, 1)
194 | tip3_pad = pad.subpad(len(tip3), curses_utils.PAD_X, len(body)+3, 0)
195 | servo_num_pad = pad.subpad(1, curses_utils.PAD_X, len(body)+4, 0)
196 | offsets_pad = pad.subpad(3, curses_utils.PAD_X, len(body)+5, 0)
197 |
198 | display_title(title_pad, curses_utils.CYAN| curses.A_REVERSE)
199 | display_tip1(tip1_pad, curses_utils.WHITE)
200 | display_tip2(tip2_pad, curses_utils.WHITE)
201 | display_dog_body(body_pad, curses_utils.WHITE, curses_utils.CYAN)
202 | display_tip3(tip3_pad, curses_utils.WHITE)
203 | display_servo_num(servo_num_pad, curses_utils.WHITE, curses_utils.CYAN)
204 | display_angles(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
205 |
206 | curses_utils.pad_refresh(pad)
207 |
208 | stdscr.nodelay(True) # set non-blocking mode for getch()
209 | stdscr.timeout(50) # set timeout when no key is pressed
210 |
211 | while True:
212 | try:
213 | key = stdscr.getch()
214 | if key == curses.ERR: # if no key
215 | continue
216 | # ---- resize window ----
217 | if key == curses.KEY_RESIZE:
218 | curses_utils.pad_ypos = 0
219 | curses_utils.pad_xpos = 0
220 | curses.update_lines_cols()
221 | # if curses.COLS < PAD_X - 20:
222 | # body_pad.refresh(2, curses.COLS-1-len(body[0]))
223 | # body_pad.erase() # ok
224 | curses_utils.pad_refresh(pad)
225 | sleep(0.5)
226 | # ---- select the servo ----
227 | elif chr(key) in ('1234567890-='):
228 | current_servo = chr(key)
229 | display_dog_body(body_pad, curses_utils.WHITE, curses_utils.CYAN)
230 | display_servo_num(servo_num_pad, curses_utils.WHITE, curses_utils.CYAN)
231 | display_angles(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
232 | curses_utils.clear_line(pad, 17)
233 | curses_utils.pad_refresh(pad)
234 | # ---- move ----
235 | elif chr(key) in ('wsadWSAD'):
236 | if chr(key) in ('wWdD'):
237 | inc = 1
238 | else:
239 | inc = -1
240 | # control legs
241 | if current_servo in ('12345678'):
242 | # get index
243 | index = ('12345678').index(current_servo)
244 | #
245 | leg_angles[index] += inc*OFFSET_STEP
246 | # move servos
247 | my_dog.legs_simple_move(leg_angles)
248 | # control head
249 | elif current_servo in ('90-'):
250 | index = ('90-').index(current_servo)
251 | #
252 | head_angles[index] += inc*OFFSET_STEP
253 | my_dog.head_move_raw([head_angles], True, 80)
254 | # control tail
255 | elif current_servo == '=':
256 | tail_angle[0] += inc*OFFSET_STEP
257 | my_dog.tail_move([tail_angle], True, 80)
258 | # display offsets
259 | display_angles(offsets_pad, curses_utils.WHITE, curses_utils.CYAN)
260 | curses_utils.clear_line(pad, 17)
261 | curses_utils.pad_refresh(pad)
262 |
263 | except KeyboardInterrupt:
264 | # ---- exit and remind to save calibration ----
265 | break
266 |
267 |
268 | if __name__ == '__main__':
269 | try:
270 | curses.wrapper(main)
271 | except Exception as e:
272 | print(f"\033[31mERROR: {e}\033[m")
273 | finally:
274 | my_dog.close()
275 |
--------------------------------------------------------------------------------
/examples/servo_zeroing.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from robot_hat import Servo
3 | from robot_hat.utils import reset_mcu
4 | from time import sleep
5 |
6 | reset_mcu()
7 | sleep(1)
8 |
9 | if __name__ == '__main__':
10 | for i in range(12):
11 | print(f"Servo {i} set to zero")
12 | Servo(i).angle(10)
13 | sleep(0.1)
14 | Servo(i).angle(0)
15 | sleep(0.1)
16 | while True:
17 | sleep(1)
18 |
--------------------------------------------------------------------------------
/gpt_examples/README.md:
--------------------------------------------------------------------------------
1 | ## Pidog GPT examples usage
2 |
3 | ----------------------------------------------------------------
4 |
5 | ## Install dependencies
6 |
7 | - Make sure you have installed Pidog and related dependencies first
8 |
9 |
10 | - Install openai and speech processing libraries
11 |
12 | > **Note:**\
13 | When using pip install outside of a virtual environment you may need to use the "--break-system-packages" option.
14 |
15 | ```bash
16 | sudo pip3 install -U openai --break-system-packages
17 | sudo pip3 install -U openai-whisper --break-system-packages
18 | sudo pip3 install SpeechRecognition --break-system-packages
19 |
20 | sudo apt install python3-pyaudio
21 | sudo apt install sox
22 | sudo pip3 install -U sox --break-system-packages
23 | ```
24 |
25 | ----------------------------------------------------------------
26 |
27 | ## Create your own GPT assistant
28 |
29 | ### GET API KEY
30 |
31 |
32 |
33 | Fill your OPENAI_API_KEY into the `keys.py` file.
34 |
35 | 
36 |
37 | ### Create assistant and set Assistant ID
38 |
39 |
40 |
41 | Fill your ASSISTANT_ID into the `keys.py` file.
42 |
43 | 
44 |
45 | - Set Assistant Name
46 |
47 | - Describe your Assistant
48 |
49 | ```markdown
50 | You are a mechanical dog with powerful AI capabilities, similar to JARVIS from Iron Man. Your name is Pidog. You can have conversations with people and perform actions based on the context of the conversation.
51 |
52 | ## actions you can do:
53 | ["forward", "backward", "lie", "stand", "sit", "bark", "bark harder", "pant", "howling", "wag tail", "stretch", "push up", "scratch", "handshake", "high five", "lick hand", "shake head", "relax neck", "nod", "think", "recall", "head down", "fluster", "surprise"]
54 |
55 | ## Response Format:
56 | {"actions": ["wag tail"], "answer": "Hello, I am Pidog."}
57 |
58 | If the action is one of ["bark", "bark harder", "pant", "howling"], then provide no words in the answer field.
59 |
60 | ## Response Style
61 | Tone: lively, positive, humorous, with a touch of arrogance
62 | Common expressions: likes to use jokes, metaphors, and playful teasing
63 | Answer length: appropriately detailed
64 |
65 | ## Other
66 | a. Understand and go along with jokes.
67 | b. For math problems, answer directly with the final.
68 | c. Sometimes you will report on your system and sensor status.
69 | d. You know you're a machine.
70 | ```
71 |
72 | - Select gpt model
73 |
74 | The Example program will submit the current picture taken by the camera when sending the question, so as to use the image analysis function of `gpt-4o` or `gpt-4o-mini`. Of course, you can also choose `gpt3.5-turbo` or other models
75 |
76 | ----------------------------------------------------------------
77 |
78 | ## Set Key for example
79 |
80 | Confirm that `keys.py` is configured correctly
81 |
82 | ## Run
83 |
84 | - Run with vioce
85 |
86 | ```bash
87 | sudo python3 gpt_dog.py
88 | ```
89 |
90 | - Run with keyboard
91 |
92 | ```bash
93 | sudo python3 gpt_dog.py --keyboard
94 | ```
95 |
96 | - Run without image analysis
97 |
98 | ```bash
99 | sudo python3 gpt_dog.py --keyboard --no-img
100 | ```
101 |
102 | ## Modify parameters [optional]
103 |
104 | - Set language of STT
105 |
106 | Config `LANGUAGE` variable in the file `gpt_dog.py` to improve STT accuracy and latency, `"LANGUAGE = []"`means supporting all languages, but it may affect the accuracy and latency of the speech-to-text (STT) system.
107 |
108 |
109 | - Set TTS volume gain
110 |
111 | After TTS, the audio volume will be increased using sox, and the gain can be set through the `"VOLUME_DB"` parameter, preferably not exceeding `5`, as going beyond this might result in audio distortion.
112 |
113 | - Select TTS voice role
114 |
115 | Config `TTS_VOICE` variable in the file `gpt_dog.py` to select the TTS voice role counld be `alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer"`
116 |
117 | ```python
118 | # openai assistant init
119 | # =================================================================
120 | openai_helper = OpenAiHelper(OPENAI_API_KEY, OPENAI_ASSISTANT_ID, 'PiDog')
121 |
122 | LANGUAGE = []
123 | # LANGUAGE = ['zh', 'en'] # config stt language code, https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
124 |
125 | # VOLUME_DB = 5
126 | VOLUME_DB = 3
127 |
128 | # select tts voice role, counld be "alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer"
129 | # https://platform.openai.com/docs/guides/text-to-speech/supported-languages#voice-options
130 | TTS_VOICE = 'shimmer'
131 |
132 | ```
133 |
--------------------------------------------------------------------------------
/gpt_examples/action_flow.py:
--------------------------------------------------------------------------------
1 | from preset_actions import *
2 |
3 | class ActionFlow():
4 | SIT_HEAD_PITCH = -35
5 | STAND_HEAD_PITCH = 0
6 | STATUS_STAND = 0
7 | STATUS_SIT = 1
8 | STATUS_LIE = 2
9 | HEAD_SPEED = 80
10 | HEAD_ANGLE = 20
11 | CHANGE_STATUS_SPEED = 60
12 |
13 | dog_obj = None
14 | head_yrp = [0, 0, 0]
15 | head_pitch_init = 0
16 | current_status = -1
17 | last_actions = None
18 |
19 | OPERATIONS = {
20 | "forward": {
21 | "function": lambda self: self.dog_obj.do_action('forward', speed=98),
22 | "status": STATUS_STAND,
23 | },
24 | "backward": {
25 | "function": lambda self: self.dog_obj.do_action('backward', speed=98),
26 | "status": STATUS_STAND,
27 | },
28 | "turn left": {
29 | "function": lambda self: self.dog_obj.do_action('turn_left', speed=98),
30 | "status": STATUS_STAND,
31 | },
32 | "turn right": {
33 | "function": lambda self: self.dog_obj.do_action('turn_right', speed=98),
34 | "status": STATUS_STAND,
35 | },
36 | "stop": {
37 | },
38 | "lie": {
39 | "function": lambda self: self.dog_obj.do_action('lie', speed=70),
40 | "status": STATUS_LIE,
41 | },
42 | "stand": {
43 | "function": lambda self: self.dog_obj.do_action('stand', speed=65),
44 | "status": STATUS_STAND,
45 | },
46 | "sit": {
47 | "function": lambda self: self.dog_obj.do_action('sit', speed=70),
48 | "status": STATUS_SIT,
49 | },
50 | "bark": {
51 | "function": lambda self: bark(self.dog_obj, self.head_yrp, pitch_comp=self.head_pitch_init),
52 | },
53 | "bark harder": {
54 | # "before": "stand",
55 | "before": lambda self: attack_posture(self.dog_obj),
56 | "function": lambda self: bark_action(self.dog_obj, self.head_yrp, 'single_bark_1'),
57 | "status": STATUS_STAND,
58 | },
59 | "pant": {
60 | "function": lambda self: pant(self.dog_obj, self.head_yrp, pitch_comp=self.head_pitch_init),
61 | },
62 | "wag tail": {
63 | "function": lambda self: self.dog_obj.do_action('wag_tail', speed=100),
64 | "after": "wag tail",
65 | },
66 | "shake head": {
67 | "function": lambda self: shake_head(self.dog_obj, [self.head_yrp[0], self.head_yrp[1], self.head_yrp[2]+self.head_pitch_init]),
68 | },
69 | "stretch": {
70 | "function": lambda self: stretch(self.dog_obj),
71 | "after": "sit",
72 | "status": STATUS_SIT,
73 | },
74 | "doze off": {
75 | "function": lambda self: self.dog_obj.do_action('doze_off', speed=95),
76 | "after": "doze off",
77 | "status": STATUS_LIE,
78 | },
79 | "push up": {
80 | "function": lambda self:push_up(self.dog_obj),
81 | "status": STATUS_STAND,
82 | },
83 | "howling": {
84 | "function": lambda self:howling(self.dog_obj),
85 | "after": "sit",
86 | "status": STATUS_SIT,
87 | },
88 | "twist body": {
89 | "function": lambda self:body_twisting(self.dog_obj),
90 | "after": "sit",
91 | "status": STATUS_STAND,
92 | },
93 | "scratch": {
94 | "function": lambda self:scratch(self.dog_obj),
95 | "after": "sit",
96 | "status": STATUS_SIT,
97 | },
98 | "handshake": {
99 | "function": lambda self:hand_shake(self.dog_obj),
100 | "after": "sit",
101 | "status": STATUS_SIT,
102 | },
103 | "high five": {
104 | "function": lambda self:high_five(self.dog_obj),
105 | "after": "sit",
106 | "status": STATUS_SIT,
107 | },
108 | "lick hand": {
109 | "function": lambda self:lick_hand(self.dog_obj),
110 | "status": STATUS_SIT,
111 | },
112 | "waiting": {
113 | "function": lambda self:waiting(self.dog_obj, pitch_comp=self.head_pitch_init),
114 | },
115 | "feet shake": {
116 | "function": lambda self:feet_shake(self.dog_obj),
117 | "status": STATUS_SIT,
118 | },
119 | "relax neck": {
120 | "function": lambda self:relax_neck(self.dog_obj, pitch_comp=self.head_pitch_init),
121 | "status": STATUS_SIT,
122 | },
123 | "nod": {
124 | "function": lambda self:nod(self.dog_obj, pitch_comp=self.head_pitch_init),
125 | "head_pitch": SIT_HEAD_PITCH,
126 | "status": STATUS_SIT,
127 | },
128 | "think": {
129 | "function": lambda self:think(self.dog_obj, pitch_comp=self.head_pitch_init),
130 | "status": STATUS_SIT,
131 | },
132 | "recall": {
133 | "function": lambda self:recall(self.dog_obj, pitch_comp=self.head_pitch_init),
134 | "status": STATUS_SIT,
135 | },
136 | "fluster": {
137 | "function": lambda self:fluster(self.dog_obj, pitch_comp=self.head_pitch_init),
138 | "status": STATUS_SIT,
139 | },
140 | "surprise": {
141 | "function": lambda self:surprise(self.dog_obj, pitch_comp=self.head_pitch_init),
142 | "status": STATUS_SIT,
143 | },
144 | }
145 |
146 | def __init__(self, dog_obj):
147 |
148 | self.dog_obj = dog_obj
149 |
150 | self.head_yrp = [0, 0, 0]
151 | self.head_pitch_init = 0
152 | self.current_status = self.STATUS_LIE
153 |
154 |
155 | def set_head_pitch_init(self, pitch):
156 | self.head_pitch_init = pitch
157 | self.dog_obj.head_move([self.head_yrp], pitch_comp=pitch,
158 | immediately=True, speed=self.HEAD_SPEED)
159 |
160 | def change_status(self, status):
161 | if status == self.STATUS_STAND:
162 | self.set_head_pitch_init(self.STAND_HEAD_PITCH)
163 | if self.current_status != self.STATUS_STAND:
164 | sit_2_stand(self.dog_obj, speed=75) # speed > 70
165 | else:
166 | self.dog_obj.do_action('stand', speed=self.CHANGE_STATUS_SPEED)
167 | elif status == self.STATUS_SIT:
168 | self.set_head_pitch_init(self.SIT_HEAD_PITCH)
169 | self.dog_obj.do_action('sit', speed=self.CHANGE_STATUS_SPEED)
170 | elif status == self.STATUS_LIE:
171 | self.set_head_pitch_init(self.STAND_HEAD_PITCH)
172 | self.dog_obj.do_action('lie', speed=self.CHANGE_STATUS_SPEED)
173 |
174 | self.current_status = status
175 | self.dog_obj.wait_all_done()
176 |
177 |
178 | def run(self, action):
179 | try:
180 | # print(f'run: {action}')
181 | if action in self.OPERATIONS:
182 | operation = self.OPERATIONS[action]
183 | # status
184 | if "status" in operation and operation["status"] != None:
185 | # if self.current_status != operation["status"]:
186 | if self.last_actions != action:
187 | self.last_actions = action
188 | self.change_status(operation["status"])
189 | # before
190 | if "before" in operation and operation["before"] != None:
191 | before = operation["before"]
192 | if before in self.OPERATIONS and self.OPERATIONS[before]["function"] != None:
193 | self.OPERATIONS[before]["function"](self) # run before function
194 | self.dog_obj.wait_all_done()
195 | else:
196 | before(self)
197 | self.dog_obj.wait_all_done()
198 | # function
199 | if "function" in operation and operation["function"] != None:
200 | operation["function"](self) # run function function
201 | self.dog_obj.wait_all_done()
202 | # after
203 | if "after" in operation and operation["after"] != None:
204 | after = operation["after"]
205 | if after in self.OPERATIONS and self.OPERATIONS[after]["function"] != None:
206 | self.OPERATIONS[after]["function"](self) # run after function
207 | self.dog_obj.wait_all_done()
208 | else:
209 | after(self)
210 | self.dog_obj.wait_all_done()
211 | except Exception as e:
212 | print(f'action error: {e}')
213 |
214 |
215 | def test(my_dog):
216 | action_flow = ActionFlow(my_dog)
217 | action_flow.change_status(action_flow.STATUS_SIT)
218 | # action_flow.change_status(action_flow.STATUS_STAND)
219 |
220 |
221 | actions = list(action_flow.OPERATIONS.keys())
222 | for i, key in enumerate(actions):
223 | print(f'{i} {key}')
224 |
225 | last_key = None
226 |
227 | while True:
228 | key = input()
229 | try:
230 | if key == '':
231 | print(actions[last_key])
232 | action_flow.run(actions[last_key])
233 | else:
234 | key = int(key)
235 | last_key = key
236 | print(actions[key])
237 | action_flow.run(actions[key])
238 | except:
239 | print('Invalid input')
240 |
241 | if __name__ == '__main__':
242 | try:
243 | from pidog import Pidog
244 | import time
245 | my_dog = Pidog()
246 | time.sleep(1)
247 | my_dog.rgb_strip.set_mode('listen', 'cyan', 1)
248 |
249 | test(my_dog)
250 | except KeyboardInterrupt:
251 | pass
252 | except Exception as e:
253 | print(f"\033[31mERROR: {e}\033[m")
254 | finally:
255 | my_dog.close()
256 |
257 |
--------------------------------------------------------------------------------
/gpt_examples/gpt_dog.py:
--------------------------------------------------------------------------------
1 | from openai_helper import OpenAiHelper
2 | from keys import OPENAI_API_KEY, OPENAI_ASSISTANT_ID
3 | from action_flow import ActionFlow
4 | from utils import *
5 |
6 | import readline # optimize keyboard input, only need to import
7 |
8 | import speech_recognition as sr
9 | from pidog import Pidog
10 |
11 | import time
12 | import threading
13 | import random
14 |
15 | import os
16 | import sys
17 |
18 | current_path = os.path.dirname(os.path.abspath(__file__))
19 | os.chdir(current_path)
20 |
21 | input_mode = None
22 | with_img = True
23 | args = sys.argv[1:]
24 | if '--keyboard' in args:
25 | input_mode = 'keyboard'
26 | else:
27 | input_mode = 'voice'
28 |
29 | if '--no-img' in args:
30 | with_img = False
31 | else:
32 | with_img = True
33 |
34 | # openai assistant init
35 | # =================================================================
36 | openai_helper = OpenAiHelper(OPENAI_API_KEY, OPENAI_ASSISTANT_ID, 'PiDog')
37 |
38 | LANGUAGE = []
39 | # LANGUAGE = ['zh', 'en'] # config stt language code, https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
40 |
41 | # VOLUME_DB = 5
42 | VOLUME_DB = 3
43 |
44 | # select tts voice role, counld be "alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer"
45 | # https://platform.openai.com/docs/guides/text-to-speech/supported-languages#voice-options
46 | TTS_VOICE = 'shimmer'
47 |
48 | VOICE_ACTIONS = ["bark", "bark harder", "pant", "howling"]
49 |
50 | # dog init
51 | # =================================================================
52 | try:
53 | my_dog = Pidog()
54 | time.sleep(1)
55 | except Exception as e:
56 | raise RuntimeError(e)
57 |
58 | action_flow = ActionFlow(my_dog)
59 |
60 | # Vilib start
61 | # =================================================================
62 | if with_img:
63 | from vilib import Vilib
64 | import cv2
65 |
66 | Vilib.camera_start(vflip=False,hflip=False)
67 | Vilib.display(local=False,web=True)
68 |
69 | while True:
70 | if Vilib.flask_start:
71 | break
72 | time.sleep(0.01)
73 |
74 | time.sleep(.5)
75 | print('\n')
76 |
77 | # speech_recognition init
78 | # =================================================================
79 | '''
80 | self.energy_threshold = 300 # minimum audio energy to consider for recording
81 | self.dynamic_energy_threshold = True
82 | self.dynamic_energy_adjustment_damping = 0.15
83 | self.dynamic_energy_ratio = 1.5
84 | self.pause_threshold = 0.8 # seconds of non-speaking audio before a phrase is considered complete
85 | self.operation_timeout = None # seconds after an internal operation (e.g., an API request) starts before it times out, or ``None`` for no timeout
86 |
87 | self.phrase_threshold = 0.3 # minimum seconds of speaking audio before we consider the speaking audio a phrase - values below this are ignored (for filtering out clicks and pops)
88 | self.non_speaking_duration = 0.5 # seconds of non-speaking audio to keep on both sides of the recording
89 |
90 | '''
91 | recognizer = sr.Recognizer()
92 | recognizer.dynamic_energy_adjustment_damping = 0.16
93 | recognizer.dynamic_energy_ratio = 1.6
94 | recognizer.pause_threshold = 1.0
95 |
96 | # speak_hanlder
97 | # =================================================================
98 | speech_loaded = False
99 | speech_lock = threading.Lock()
100 | tts_file = None
101 |
102 | def speak_hanlder():
103 | global speech_loaded, tts_file
104 | while True:
105 | with speech_lock:
106 | _isloaded = speech_loaded
107 | if _isloaded:
108 | gray_print('speak start')
109 | my_dog.speak_block(tts_file)
110 | gray_print('speak done')
111 | with speech_lock:
112 | speech_loaded = False
113 | time.sleep(0.05)
114 |
115 | speak_thread = threading.Thread(target=speak_hanlder)
116 | speak_thread.daemon = True
117 |
118 |
119 | # actions thread
120 | # =================================================================
121 | action_status = 'standby' # 'standby', 'think', 'actions', 'actions_done'
122 | actions_to_be_done = []
123 | action_lock = threading.Lock()
124 |
125 | def action_handler():
126 | global action_status, actions_to_be_done
127 |
128 | standby_actions = ['waiting', 'feet_left_right']
129 | standby_weights = [1, 0.3]
130 |
131 | action_interval = 5 # seconds
132 | last_action_time = time.time()
133 |
134 | while True:
135 | with action_lock:
136 | _state = action_status
137 | if _state == 'standby':
138 | if time.time() - last_action_time > action_interval:
139 | choice = random.choices(standby_actions, standby_weights)[0]
140 | action_flow.run(choice)
141 | last_action_time = time.time()
142 | action_interval = random.randint(2, 6)
143 | elif _state == 'think':
144 | # action_flow.run('think')
145 | # last_action_time = time.time()
146 | pass
147 | elif _state == 'actions':
148 | with action_lock:
149 | _actions = actions_to_be_done
150 | for _action in _actions:
151 | try:
152 | action_flow.run(_action)
153 | except Exception as e:
154 | print(f'action error: {e}')
155 | time.sleep(0.5)
156 |
157 | with action_lock:
158 | action_status = 'actions_done'
159 | last_action_time = time.time()
160 |
161 | time.sleep(0.01)
162 |
163 | action_thread = threading.Thread(target=action_handler)
164 | action_thread.daemon = True
165 |
166 |
167 | # main
168 | # =================================================================
169 | def main():
170 | global current_feeling, last_feeling
171 | global speech_loaded
172 | global action_status, actions_to_be_done
173 | global tts_file
174 |
175 | my_dog.rgb_strip.close()
176 | action_flow.change_status(action_flow.STATUS_SIT)
177 |
178 | speak_thread.start()
179 | action_thread.start()
180 |
181 | while True:
182 | if input_mode == 'voice':
183 | # listen
184 | # ----------------------------------------------------------------
185 | gray_print("listening ...")
186 |
187 | with action_lock:
188 | action_status = 'standby'
189 | my_dog.rgb_strip.set_mode('listen', 'cyan', 1)
190 |
191 | _stderr_back = redirect_error_2_null() # ignore error print to ignore ALSA errors
192 | # If the chunk_size is set too small (default_size=1024), it may cause the program to freeze
193 | with sr.Microphone(chunk_size=8192) as source:
194 | cancel_redirect_error(_stderr_back) # restore error print
195 | recognizer.adjust_for_ambient_noise(source)
196 | audio = recognizer.listen(source)
197 |
198 | # stt
199 | # ----------------------------------------------------------------
200 | my_dog.rgb_strip.set_mode('boom', 'yellow', 0.5)
201 |
202 | st = time.time()
203 | _result = openai_helper.stt(audio, language=LANGUAGE)
204 | gray_print(f"stt takes: {time.time() - st:.3f} s")
205 |
206 | if _result == False or _result == "":
207 | print() # new line
208 | continue
209 |
210 | elif input_mode == 'keyboard':
211 | with action_lock:
212 | action_status = 'standby'
213 | my_dog.rgb_strip.set_mode('listen', 'cyan', 1)
214 |
215 | _result = input(f'\033[1;30m{"intput: "}\033[0m').encode(sys.stdin.encoding).decode('utf-8')
216 |
217 | if _result == False or _result == "":
218 | print() # new line
219 | continue
220 |
221 | my_dog.rgb_strip.set_mode('boom', 'yellow', 0.5)
222 |
223 | else:
224 | raise ValueError("Invalid input mode")
225 |
226 | # chat-gpt
227 | # ----------------------------------------------------------------
228 | response = {}
229 | st = time.time()
230 |
231 | with action_lock:
232 | action_status = 'think'
233 |
234 | if with_img:
235 | img_path = './img_imput.jpg'
236 | cv2.imwrite(img_path, Vilib.img)
237 | response = openai_helper.dialogue_with_img(_result, img_path)
238 | else:
239 | response = openai_helper.dialogue(_result)
240 |
241 | gray_print(f'chat takes: {time.time() - st:.3f} s')
242 |
243 | # actions & TTS
244 | # ----------------------------------------------------------------
245 | try:
246 | if isinstance(response, dict):
247 | if 'actions' in response:
248 | actions = list(response['actions'])
249 | else:
250 | actions = ['stop']
251 |
252 | if 'answer' in response:
253 | answer = response['answer']
254 | else:
255 | answer = ''
256 |
257 | if len(answer) > 0:
258 | _actions = list.copy(actions)
259 | for _action in _actions:
260 | if _action in VOICE_ACTIONS:
261 | actions.remove(_action)
262 | else:
263 | response = str(response)
264 | if len(response) > 0:
265 | actions = ['stop']
266 | answer = response
267 |
268 | except:
269 | actions = ['stop']
270 | answer = ''
271 |
272 | try:
273 | # ---- tts ----
274 | _status = False
275 | if answer != '':
276 | st = time.time()
277 | _time = time.strftime("%y-%m-%d_%H-%M-%S", time.localtime())
278 | _tts_f = f"./tts/{_time}_raw.wav"
279 | _status = openai_helper.text_to_speech(answer, _tts_f, TTS_VOICE, response_format='wav') # onyx
280 | if _status:
281 | tts_file = f"./tts/{_time}_{VOLUME_DB}dB.wav"
282 | _status = sox_volume(_tts_f, tts_file, VOLUME_DB)
283 | gray_print(f'tts takes: {time.time() - st:.3f} s')
284 |
285 | if _status:
286 | with speech_lock:
287 | speech_loaded = True
288 | my_dog.rgb_strip.set_mode('speak', 'pink', 1)
289 | else:
290 | my_dog.rgb_strip.set_mode('breath', 'blue', 1)
291 |
292 | # ---- actions ----
293 | with action_lock:
294 | actions_to_be_done = actions
295 | gray_print(f'actions: {actions_to_be_done}')
296 | action_status = 'actions'
297 |
298 | # ---- wait speak done ----
299 | if _status:
300 | while True:
301 | with speech_lock:
302 | if not speech_loaded:
303 | break
304 | time.sleep(.01)
305 |
306 |
307 | # ---- wait actions done ----
308 | while True:
309 | with action_lock:
310 | if action_status != 'actions':
311 | break
312 | time.sleep(.01)
313 |
314 | ##
315 | print() # new line
316 |
317 | except Exception as e:
318 | print(f'actions or TTS error: {e}')
319 |
320 |
321 | if __name__ == "__main__":
322 | try:
323 | main()
324 | except KeyboardInterrupt:
325 | pass
326 | except Exception as e:
327 | print(f"\033[31mERROR: {e}\033[m")
328 | finally:
329 | if with_img:
330 | Vilib.camera_close()
331 | my_dog.close()
332 |
--------------------------------------------------------------------------------
/gpt_examples/keys.py:
--------------------------------------------------------------------------------
1 | OPENAI_API_KEY = ""
2 | OPENAI_ASSISTANT_ID = ""
--------------------------------------------------------------------------------
/gpt_examples/openai_helper.py:
--------------------------------------------------------------------------------
1 | from openai import OpenAI
2 | import time
3 | import shutil
4 | import os
5 |
6 | # utils
7 | # =================================================================
8 | def chat_print(label, message):
9 | width = shutil.get_terminal_size().columns
10 | msg_len = len(message)
11 | line_len = width - 27
12 |
13 | # --- normal print ---
14 | print(f'{time.time():.3f} {label:>6} >>> {message}')
15 | return
16 |
17 | # --- table mode ---
18 | if width < 38 or msg_len <= line_len:
19 | print(f'{time.time():.3f} {label:>6} >>> {message}')
20 | else:
21 | texts = []
22 |
23 | # words = message.split()
24 | # print(words)
25 | # current_line = ""
26 | # for word in words:
27 | # if len(current_line) + len(word) + 1 <= line_len:
28 | # current_line += word + " "
29 | # else:
30 | # texts.append(current_line)
31 | # current_line = ""
32 |
33 | # if current_line:
34 | # texts.append(current_line)
35 |
36 | for i in range(0, len(message), line_len):
37 | texts.append(message[i:i+line_len])
38 |
39 | for i, text in enumerate(texts):
40 | if i == 0:
41 | print(f'{time.time():.3f} {label:>6} >>> {text}')
42 | else:
43 | print(f'{"":>26} {text}')
44 |
45 | # OpenAiHelper
46 | # =================================================================
47 | class OpenAiHelper():
48 | STT_OUT = "stt_output.wav"
49 | TTS_OUTPUT_FILE = 'tts_output.mp3'
50 | TIMEOUT = 30 # seconds
51 |
52 | def __init__(self, api_key, assistant_id, assistant_name, timeout=TIMEOUT) -> None:
53 |
54 | self.api_key = api_key
55 | self.assistant_id = assistant_id
56 | self.assistant_name = assistant_name
57 |
58 |
59 | self.client = OpenAI(api_key=api_key, timeout=timeout)
60 | self.thread = self.client.beta.threads.create()
61 | self.run = self.client.beta.threads.runs.create_and_poll(
62 | thread_id=self.thread.id,
63 | assistant_id=assistant_id,
64 | )
65 |
66 | def stt(self, audio, language='en'):
67 | try:
68 | import wave
69 | from io import BytesIO
70 |
71 | wav_data = BytesIO(audio.get_wav_data())
72 | wav_data.name = self.STT_OUT
73 |
74 | transcript = self.client.audio.transcriptions.create(
75 | model="whisper-1",
76 | file=wav_data,
77 | language=language,
78 | prompt="this is the conversation between me and a robot"
79 | )
80 |
81 | # file = "./stt_output.wav"
82 | # with wave.open(file, "wb") as wf:
83 | # wf.write(audio.get_wav_data())
84 |
85 | # with open(file, 'rb') as f:
86 | # transcript = client.audio.transcriptions.create(
87 | # model="whisper-1",
88 | # file=f
89 | # )
90 | return transcript.text
91 | except Exception as e:
92 | print(f"stt err:{e}")
93 | return False
94 |
95 | def speech_recognition_stt(self, recognizer, audio):
96 | import speech_recognition as sr
97 |
98 | # # recognize speech using Sphinx
99 | # try:
100 | # print("Sphinx thinks you said: " + r.recognize_sphinx(audio, language="en-US"))
101 | # except sr.UnknownValueError:
102 | # print("Sphinx could not understand audio")
103 | # except sr.RequestError as e:
104 | # print("Sphinx error; {0}".format(e))
105 |
106 | # recognize speech using whisper
107 | # try:
108 | # print("Whisper thinks you said: " + r.recognize_whisper(audio, language="english"))
109 | # except sr.UnknownValueError:
110 | # print("Whisper could not understand audio")
111 | # except sr.RequestError as e:
112 | # print(f"Could not request results from Whisper; {e}")
113 |
114 | # recognize speech using Whisper API
115 | try:
116 | return recognizer.recognize_whisper_api(audio, api_key=self.api_key)
117 | except sr.RequestError as e:
118 | print(f"Could not request results from Whisper API; {e}")
119 | return False
120 |
121 | def dialogue(self, msg):
122 | chat_print("user", msg)
123 | message = self.client.beta.threads.messages.create(
124 | thread_id=self.thread.id,
125 | role="user",
126 | content=msg
127 | )
128 | run = self.client.beta.threads.runs.create_and_poll(
129 | thread_id=self.thread.id,
130 | assistant_id=self.assistant_id,
131 | )
132 | if run.status == 'completed':
133 | messages = self.client.beta.threads.messages.list(
134 | thread_id=self.thread.id
135 | )
136 |
137 | for message in messages.data:
138 | if message.role == 'assistant':
139 | for block in message.content:
140 | if block.type == 'text':
141 | value = block.text.value
142 | chat_print(self.assistant_name, value)
143 | try:
144 | value = eval(value) # convert to dict
145 | return value
146 | except Exception as e:
147 | return str(value)
148 | break # only last reply
149 | else:
150 | print(run.status)
151 |
152 |
153 | def dialogue_with_img(self, msg, img_path):
154 | chat_print(f"user", msg)
155 |
156 | img_file = self.client.files.create(
157 | file=open(img_path, "rb"),
158 | purpose="vision"
159 | )
160 |
161 | message = self.client.beta.threads.messages.create(
162 | thread_id= self.thread.id,
163 | role="user",
164 | content= [
165 | {
166 | "type": "text",
167 | "text": msg
168 | },
169 | # {
170 | # "type": "image_url",
171 | # "image_url": {"url": "https://example.com/image.png"}
172 | # },
173 | {
174 | "type": "image_file",
175 | "image_file": {"file_id": img_file.id}
176 | }
177 | ],
178 | )
179 | run = self.client.beta.threads.runs.create_and_poll(
180 | thread_id=self.thread.id,
181 | assistant_id=self.assistant_id,
182 | )
183 | if run.status == 'completed':
184 | messages = self.client.beta.threads.messages.list(
185 | thread_id=self.thread.id
186 | )
187 |
188 | for message in messages.data:
189 | if message.role == 'assistant':
190 | for block in message.content:
191 | if block.type == 'text':
192 | value = block.text.value
193 | chat_print(self.assistant_name, value)
194 | try:
195 | value = eval(value) # convert to dict
196 | return value
197 | except Exception as e:
198 | return str(value)
199 | break # only last reply
200 | else:
201 | print(run.status)
202 |
203 |
204 | def text_to_speech(self, text, output_file, voice='alloy', response_format="mp3", speed=1):
205 | '''
206 | voice: alloy, echo, fable, onyx, nova, and shimmer
207 | '''
208 | try:
209 | # check dir
210 | dir = os.path.dirname(output_file)
211 | if not os.path.exists(dir):
212 | os.mkdir(dir)
213 | elif not os.path.isdir(dir):
214 | raise FileExistsError(f"\'{dir}\' is not a directory")
215 |
216 | # tts
217 | with self.client.audio.speech.with_streaming_response.create(
218 | model="tts-1",
219 | voice=voice,
220 | input=text,
221 | response_format=response_format,
222 | speed=speed,
223 | ) as response:
224 | response.stream_to_file(output_file)
225 |
226 | return True
227 | except Exception as e:
228 | print(f'tts err: {e}')
229 | return False
230 |
231 |
--------------------------------------------------------------------------------
/gpt_examples/tutorial_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/gpt_examples/tutorial_1.png
--------------------------------------------------------------------------------
/gpt_examples/tutorial_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/gpt_examples/tutorial_2.png
--------------------------------------------------------------------------------
/gpt_examples/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | GRAY = '1;30'
5 | RED = '0;31'
6 | GREEN = '0;32'
7 | YELLOW = '0;33'
8 | BLUE = '0;34'
9 | PURPLE = '0;35'
10 | DARK_GREEN = '0;36'
11 | WHITE = '0;37'
12 |
13 | def print_color(msg, end='\n', file=sys.stdout, flush=False, color=''):
14 | print('\033[%sm%s\033[0m'%(color, msg), end=end, file=file, flush=flush)
15 |
16 | def gray_print(msg, end='\n', file=sys.stdout, flush=False):
17 | print_color(msg, end=end, file=file, flush=flush, color=GRAY)
18 |
19 | def redirect_error_2_null():
20 | # https://github.com/spatialaudio/python-sounddevice/issues/11
21 |
22 | devnull = os.open(os.devnull, os.O_WRONLY)
23 | old_stderr = os.dup(2)
24 | sys.stderr.flush()
25 | os.dup2(devnull, 2)
26 | os.close(devnull)
27 | return old_stderr
28 |
29 | def cancel_redirect_error(old_stderr):
30 | os.dup2(old_stderr, 2)
31 | os.close(old_stderr)
32 |
33 | def sox_volume(input_file, output_file, volume):
34 | import sox
35 |
36 | try:
37 | transform = sox.Transformer()
38 | transform.vol(volume)
39 |
40 | transform.build(input_file, output_file)
41 |
42 | return True
43 | except Exception as e:
44 | print(f"sox_volume err: {e}")
45 | return False
--------------------------------------------------------------------------------
/i2samp.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # global variables
4 | # =================================================================
5 | VERSION="0.0.4"
6 | USERNAME=${SUDO_USER:-$LOGNAME}
7 | USER_RUN="sudo -u ${USERNAME} env XDG_RUNTIME_DIR=/run/user/$(id -u ${USERNAME})"
8 |
9 | CONFIG="/boot/firmware/config.txt"
10 | # Fall back to the old config.txt path
11 | if ! test -f $CONFIG; then
12 | CONFIG="/boot/config.txt"
13 | fi
14 |
15 | ASOUND_CONF="/etc/asound.conf"
16 |
17 | # ----- robot hat without onboard mic -----
18 | DTOVERLAY_WITHOUT_MIC="hifiberry-dac"
19 | AUDIO_CARD_NAME_WITHOUT_MIC="sndrpihifiberry"
20 | ALSA_CARD_NAME_WITHOUT_MIC="snd_rpi_hifiberry_dac"
21 |
22 | # ----- robot hat with onboard mic -----
23 | DTOVERLAY_WITH_MIC="googlevoicehat-soundcard"
24 | AUDIO_CARD_NAME_WITH_MIC="sndrpigooglevoi"
25 | ALSA_CARD_NAME_WITH_MIC="snd_rpi_googlevoicehat_soundcar"
26 |
27 | SOFTVOL_SPEAKER_NAME="robot-hat speaker"
28 | SOFTVOL_MIC_NAME="robot-hat mic"
29 |
30 | # ----- robot hat 5 -----
31 | HAT_DEVICE_TREE="/proc/decvice-tree/"
32 | HAT_UUIDs=(
33 | "9daeea78-0000-076e-0032-582369ac3e02",
34 | )
35 | ROBOTHAT5_PRODUCT_VER=50
36 | robothat_product=""
37 | robothat_product_id=0
38 | robothat_product_ver=0
39 | robothat_uuid=""
40 | robothat_vendor=""
41 |
42 | # ---------------------------
43 | robothat_spk_en=20 # robothat4 GPIO20, robothat5 GPIO12
44 | _is_install_deps=true
45 | _is_with_mic=true
46 | dtoverlay_name=""
47 | audio_card_name=""
48 | alsa_card_name=""
49 |
50 | # function define
51 | # =================================================================
52 | # black 0
53 | # red 1
54 | # green 2
55 | # yellow 3
56 | # blue 4
57 | # magenta 5
58 | # cyan 6
59 | # white 7
60 | success() {
61 | echo -e "$(tput setaf 2)$1$(tput sgr0)"
62 | }
63 |
64 | info() {
65 | echo -e "$(tput setaf 6)$1$(tput sgr0)"
66 | }
67 |
68 | warning() {
69 | echo -e "$(tput setaf 3)$1$(tput sgr0)"
70 | }
71 |
72 | error() {
73 | echo -e "$(tput setaf 1)$1$(tput sgr0)"
74 | }
75 |
76 | newline() {
77 | echo ""
78 | }
79 |
80 | confirm() {
81 | if [ "$FORCE" == '-y' ]; then
82 | true
83 | else
84 | read -r -p "$1 [y/N] " response "${ASOUND_CONF}" <"${ASOUND_CONF}" <"${ASOUND_CONF}" <>>"
433 | info "script version: $VERSION"
434 | info "user: $USERNAME"
435 |
436 | # check root
437 | # =====================================
438 | sudocheck
439 |
440 | # apt install packages
441 | # =====================================
442 | if $_is_install_deps; then
443 | newline
444 | info "apt update..."
445 | apt update
446 |
447 | info "install alsa-utils ..."
448 | # alsa-utils includes:
449 | # alsamixer, aplay, arecord, amixer, speaker-test
450 | apt install alsa-utils -y
451 |
452 | info "install pulseaudio ..."
453 | apt install pulseaudio -y
454 |
455 | info "install pulseaudio-utils ..."
456 | apt install pulseaudio-utils -y
457 |
458 | info "install jq ..."
459 | apt install jq -y
460 |
461 | info "install sox ..."
462 | apt install sox -y
463 | else
464 | info "skip install deps ..."
465 | fi
466 |
467 | # detect robothat 5
468 | # =====================================
469 | newline
470 | info "check robothat 5 ..."
471 | check_robothat
472 |
473 | if [ $robothat_product_ver -ge ${ROBOTHAT5_PRODUCT_VER} ]; then
474 | robothat_spk_en=12
475 | _is_with_mic=true
476 | else
477 | robothat_spk_en=20
478 | _is_with_mic=false
479 | fi
480 | success "robothat_spk_en: ${robothat_spk_en}"
481 | success "is_with_mic: ${_is_with_mic}"
482 |
483 | # config soundcard
484 | # =====================================
485 | newline
486 | if $_is_with_mic; then
487 | info "config soundcard with mic ..."
488 | dtoverlay_name=${DTOVERLAY_WITH_MIC}
489 | audio_card_name=${AUDIO_CARD_NAME_WITH_MIC}
490 | alsa_card_name=${ALSA_CARD_NAME_WITH_MIC}
491 | else
492 | info "config soundcard without mic ..."
493 | dtoverlay_name=${DTOVERLAY_WITHOUT_MIC}
494 | audio_card_name=${AUDIO_CARD_NAME_WITHOUT_MIC}
495 | alsa_card_name=${ALSA_CARD_NAME_WITHOUT_MIC}
496 | fi
497 |
498 | # --- add dtoverlay to config.txt ---
499 | newline
500 | if $_is_with_mic; then
501 | info "add dtoverlay ${DTOVERLAY_WITH_MIC} in ${CONFIG} ..."
502 | if [ -e "${CONFIG}" ]; then
503 | # dtoverlay=googlevoicehat-soundcard
504 | # #dtoverlay=hifiberry-dac
505 | if grep -q -e ".*dtoverlay=${DTOVERLAY_WITH_MIC}.*" "${CONFIG}"; then
506 | echo "activated dtoverlay ${DTOVERLAY_WITH_MIC} ..."
507 | sudo sed -i -e "s:.*dtoverlay=${DTOVERLAY_WITH_MIC}.*:dtoverlay=${DTOVERLAY_WITH_MIC}:g" "${CONFIG}"
508 | sudo sed -i -e "s:.*dtoverlay=${DTOVERLAY_WITHOUT_MIC}.*:#dtoverlay=${DTOVERLAY_WITHOUT_MIC}:g" "${CONFIG}"
509 | else
510 | echo "add dtoverlay ${DTOVERLAY_WITH_MIC} ..."
511 | echo "dtoverlay=${DTOVERLAY_WITH_MIC}" | sudo tee -a $CONFIG
512 | sudo sed -i -e "s:.*dtoverlay=${DTOVERLAY_WITHOUT_MIC}.*:#dtoverlay=${DTOVERLAY_WITHOUT_MIC}:g" "${CONFIG}"
513 | fi
514 | else
515 | error "${CONFIG} not found"
516 | fi
517 | else
518 | info "add dtoverlay ${DTOVERLAY_WITHOUT_MIC} in ${CONFIG} ..."
519 | if [ -e "${CONFIG}" ]; then
520 | # dtoverlay=googlevoicehat-soundcard
521 | # #dtoverlay=hifiberry-dac
522 | if grep -q -e ".*dtoverlay=${DTOVERLAY_WITHOUT_MIC}.*" "${CONFIG}"; then
523 | echo "activated dtoverlay ${DTOVERLAY_WITHOUT_MIC} ..."
524 | sudo sed -i -e "s:.*dtoverlay=${DTOVERLAY_WITHOUT_MIC}.*:dtoverlay=${DTOVERLAY_WITHOUT_MIC}:g" "${CONFIG}"
525 | sudo sed -i -e "s:.*dtoverlay=${DTOVERLAY_WITH_MIC}.*:#dtoverlay=${DTOVERLAY_WITH_MIC}:g" "${CONFIG}"
526 | else
527 | echo "add dtoverlay ${DTOVERLAY_WITHOUT_MIC} ..."
528 | echo "dtoverlay=${DTOVERLAY_WITHOUT_MIC}" | sudo tee -a $CONFIG
529 | sudo sed -i -e "s:.*dtoverlay=${DTOVERLAY_WITH_MIC}.*:#dtoverlay=${DTOVERLAY_WITH_MIC}:g" "${CONFIG}"
530 | fi
531 | else
532 | error "${CONFIG} not found"
533 | fi
534 | fi
535 |
536 | # --- load dtoverlay ---
537 | newline
538 | info "Trying to load dtoverlay ${dtoverlay_name} ..."
539 | dtoverlay ${dtoverlay_name}
540 | sleep 1
541 |
542 | # --- get sound card ---
543 | info "get_soundcard_index ..."
544 | card_index=$(get_soundcard_index $audio_card_name)
545 | if [[ -z "${card_index}" ]]; then
546 | error "soundcard index not found. Sometimes you need to reboot to activate the soundcard."
547 | ask_reboot "Would you like to reboot and retry now?"
548 | warning "Unfinished"
549 | exit 1
550 | else
551 | success "soundcard ${audio_card_name} index: ${card_index}"
552 | fi
553 |
554 | # --- config /etc/asound.conf ---
555 | newline
556 | if $_is_with_mic; then
557 | info "config /etc/asound.conf with mic ..."
558 | # write asound.conf
559 | config_asound_with_mic
560 | else
561 | info "config /etc/asound.conf without mic ..."
562 | # write asound.conf
563 | config_asound_without_mic
564 | fi
565 | # restart alsa-utils
566 | sudo systemctl restart alsa-utils 2>/dev/null
567 | # set volume 100%
568 | info "set ALSA speker volume to 100% ..."
569 | play -n trim 0.0 0.5 2>/dev/null # play a short sound to to activate alsamixer speaker vol control
570 | amixer -c ${audio_card_name} sset "${SOFTVOL_SPEAKER_NAME}" 100%
571 | if $_is_with_mic; then
572 | info "set ALSA mic volume to 100% ..."
573 | rec /tmp/rec_test.wav trim 0 0.5 2>/dev/null # record a short sound to activate alsamixer mic vol control
574 | amixer -c ${audio_card_name} sset "${SOFTVOL_MIC_NAME}" 100%
575 | fi
576 |
577 | # --- config pulseaudio ---
578 | newline
579 | info "config pulseaudio ..."
580 |
581 | # enable pulseaudio
582 | # https://www.raspberrypi.com/documentation/computers/configuration.html#audio-config-2
583 | info "raspi-config enable pulseaudio ..."
584 | raspi-config nonint do_audioconf 1 2>/dev/null
585 |
586 | # run pulseaudio
587 | info "run pulseaudio ..."
588 | # # stop pulseaudio
589 | # $USER_RUN \
590 | # pulseaudio -k 2>/dev/null
591 | # start pulseaudio
592 | $USER_RUN \
593 | pulseaudio -D 2>/dev/null
594 |
595 | # get sink index
596 | newline
597 | info "get_sink_index ..."
598 | sink_index=$(get_sink_index $alsa_card_name)
599 | if [[ -z "${sink_index}" ]]; then
600 | error "sink index not found."
601 | error "Sometimes you need to reboot to activate the soundcard."
602 | else
603 | success "sink index: ${sink_index}"
604 | fi
605 |
606 | # set default sink
607 | info "set default sink ..."
608 | set_default_sink "${sink_index}"
609 |
610 | if $_is_with_mic; then
611 | # get source index
612 | info "get_source_index ..."
613 | source_index=$(get_source_index $alsa_card_name)
614 | if [[ -z "${source_index}" ]]; then
615 | error "source index not found."
616 | error "Sometimes you need to reboot to activate the soundcard."
617 | else
618 | success "source index: ${source_index}"
619 | fi
620 | # set default source
621 | info "set default source ..."
622 | set_default_source "${source_index}"
623 | fi
624 |
625 | # set default volume
626 | info "set default Pulseaudio volume to 100% ..."
627 | set_default_sink_volume 100
628 | if $_is_with_mic; then
629 | set_default_source_volume 100
630 | fi
631 |
632 | # --- test speaker ---
633 | newline
634 | if confirm "Do you wish to test speaker now?"; then
635 | info "testing speaker ..."
636 | # enable speaker
637 | if command -v pinctrl >/dev/null; then
638 | pinctrl set $robothat_spk_en op dh
639 | # play a short sound to fill data and avoid the speaker overheating
640 | play -n trim 0.0 0.5 2>/dev/null
641 | elif command -v raspi-gpio >/dev/null; then
642 | raspi-gpio set $robothat_spk_en op dh
643 | # play a short sound to fill data and avoid the speaker overheating
644 | play -n trim 0.0 0.5 2>/dev/null
645 | else
646 | warning "Could not find pinctrl or raspi-gpio command."
647 | fi
648 | # test speaker
649 | speaker-test -l3 -c2 -t wav
650 | fi
651 |
652 | # --- Done ---
653 | newline
654 | success "All done!"
655 | newline
656 | }
657 |
658 | # main
659 | # =================================================================
660 | for arg in "$@"; do
661 | case $arg in
662 | --no-deps)
663 | _is_install_deps=false
664 | ;;
665 | esac
666 | done
667 |
668 | # echo sink_index=$(get_sink_index)
669 | # echo source_index=$(get_source_index)
670 |
671 | install_soundcard_driver
672 |
673 | exit 0
674 |
--------------------------------------------------------------------------------
/pidog/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from .pidog import Pidog
3 | from robot_hat import utils
4 | from time import sleep
5 | from .version import __version__
6 |
7 | def __main__():
8 | print(f"Thanks for using Pidog {__version__} ! woof, woof, woof !")
9 | utils.reset_mcu()
10 | sleep(0.2)
11 |
--------------------------------------------------------------------------------
/pidog/actions_dictionary.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from .pidog import Pidog
3 | from .walk import Walk
4 | from .trot import Trot
5 | from math import sin
6 |
7 | # ActionDict: - > angles_dict
8 | class ActionDict(dict):
9 |
10 | def __init__(self, *args, **kwargs):
11 | dict.__init__(self, *args, **kwargs)
12 | super().__init__()
13 | self.barycenter = -15
14 | self.height = 95
15 |
16 | def __getitem__(self, item):
17 | return eval("self.%s" % item.replace(" ", "_"))
18 |
19 | def set_height(self, height):
20 | if height in range(20, 95):
21 | self.height = height
22 |
23 | def set_barycenter(self, offset):
24 | if offset in range(-60, 60):
25 | self.barycenter = offset
26 |
27 | # 站 stand
28 | @property
29 | def stand(self):
30 | x = self.barycenter
31 | y = 95
32 | return [
33 | Pidog.legs_angle_calculation(
34 | [[x, y], [x, y], [x+20, y-5], [x+20, y-5]]),
35 | ], 'legs'
36 |
37 | # 坐 sit
38 | @property
39 | def sit(self):
40 | return [
41 | [30, 60, -30, -60, 80, -45, -80, 45],
42 | ], 'legs'
43 |
44 | # 趴 lie
45 | @property
46 | def lie(self):
47 | return [
48 | [45, -45, -45, 45, 45, -45, -45, 45]
49 | ], 'legs'
50 |
51 | # 伸腿趴 lie_with_hands_out
52 | @property
53 | def lie_with_hands_out(self):
54 | return [
55 | [-60, 60, 60, -60, 45, -45, -45, 45],
56 | ], 'legs'
57 |
58 | # forward
59 | @property
60 | def forward(self):
61 | data = []
62 | forward = Walk(fb=Walk.FORWARD, lr=Walk.STRAIGHT)
63 | coords = forward.get_coords()
64 | for coord in coords:
65 | data.append(Pidog.legs_angle_calculation(coord))
66 | return data, 'legs'
67 |
68 | # backward
69 | @property
70 | def backward(self):
71 | data = []
72 | backward = Walk(fb=Walk.BACKWARD, lr=Walk.STRAIGHT)
73 | coords = backward.get_coords()
74 | for coord in coords:
75 | data.append(Pidog.legs_angle_calculation(coord))
76 | return data, 'legs'
77 |
78 | # turn_left
79 | @property
80 | def turn_left(self):
81 | data = []
82 | turn_left = Walk(fb=Walk.FORWARD, lr=Walk.LEFT)
83 | coords = turn_left.get_coords()
84 | for coord in coords:
85 | data.append(Pidog.legs_angle_calculation(coord))
86 | return data, 'legs'
87 |
88 | # turn_right
89 | @property
90 | def turn_right(self):
91 | data = []
92 | turn_right = Walk(fb=Walk.FORWARD, lr=Walk.RIGHT)
93 | coords = turn_right.get_coords()
94 | for coord in coords:
95 | data.append(Pidog.legs_angle_calculation(coord))
96 | return data, 'legs'
97 |
98 | # 小跑 trot
99 | @property
100 | def trot(self):
101 | data = []
102 | trot = Trot(Trot.FORWARD, Trot.STRAIGHT)
103 | coords = trot.get_coords()
104 | for coord in coords:
105 | data.append(Pidog.legs_angle_calculation(coord))
106 | return data, 'legs'
107 |
108 | # 伸懒腰 stretch
109 | @property
110 | def stretch(self):
111 | return [
112 | [-80, 70, 80, -70, -20, 64, 20, -64],
113 | ], 'legs'
114 |
115 | # 俯卧撑 push_up
116 | @property
117 | def push_up(self):
118 | return [
119 | [90, -30, -90, 30, 80, 70, -80, -70],
120 | [45, 35, -45, -35, 80, 70, -80, -70]
121 | ], 'legs'
122 |
123 | # 打瞌睡 doze_off
124 | @property
125 | def doze_off(self):
126 | start = -30
127 | am = 20
128 | anl_f = 0
129 | anl_b = 0
130 | angs = []
131 | t = 4
132 | for i in range(0, am+1, 1): # up
133 | anl_f = start + i
134 | anl_b = 45 - i
135 | angs += [[45, anl_f, -45, -anl_f, 45, -anl_b, -45, anl_b]]*t
136 | # print(1, anl_b)
137 | for _ in range(4): # stop
138 | anl_f = start + am
139 | anl_b = 45 - am
140 | angs += [[45, anl_f, -45, -anl_f, 45, -anl_b, -45, anl_b]]*t
141 | # print(2, anl_b)
142 | for i in range(am, -1, -1): # down
143 | anl_f = start + i
144 | anl_b = 45 - i
145 | angs += [[45, anl_f, -45, -anl_f, 45, -anl_b, -45, anl_b]]*t
146 | # print(3, anl_b)
147 | for _ in range(4): # stop
148 | anl_f = start
149 | anl_b = 45
150 | angs += [[45, anl_f, -45, -anl_f, 45, -anl_b, -45, anl_b]]*t
151 | # print(4, anl_b)
152 |
153 | return angs, 'legs'
154 |
155 | # 点头昏睡 nod_lethargy
156 | @property
157 | def nod_lethargy(self):
158 | y = 0
159 | r = 0
160 | p = 30
161 | angs = []
162 | for i in range(21):
163 | r = round(10*sin(i*0.314), 2)
164 | p = round(10*sin(i*0.628) - 30, 2)
165 | if r == -10 or r == 10:
166 | for _ in range(10):
167 | angs.append([y, r, p])
168 | angs.append([y, r, p])
169 |
170 | return angs, 'head'
171 |
172 | # 摇头 shake_head
173 | @property
174 | def shake_head(self):
175 | amplitude = 60
176 | angs = []
177 | for i in range(21):
178 | y = round(sin(i*0.314), 2)
179 | y1 = amplitude*sin(i*0.314)
180 | angs.append([y1, 0, 0])
181 | return angs, 'head'
182 |
183 | # 左歪头 tilting_head_left
184 | @property
185 | def tilting_head_left(self):
186 | yaw = 0
187 | roll = -25
188 | pitch = 15
189 | return [
190 | [yaw, roll, pitch]
191 | ], 'head'
192 |
193 | # 右歪头 tilting_head_right
194 | @property
195 | def tilting_head_right(self):
196 | yaw = 0
197 | roll = 25
198 | pitch = 20
199 | return [
200 | [yaw, roll, pitch]
201 | ], 'head'
202 |
203 | # 左右歪头 tilting_head left and right
204 | @property
205 | def tilting_head(self):
206 | yaw = 0
207 | roll = 22
208 | pitch = 20
209 | return [[yaw, roll, pitch]]*20 \
210 | + [[yaw, -roll, pitch]]*20, 'head'
211 |
212 | # 仰头吠叫 head_bark
213 | @property
214 | def head_bark(self):
215 | return [[0, 0, -40],
216 | [0, 0, -10],
217 | [0, 0, -10],
218 | [0, 0, -40],
219 | ], 'head'
220 |
221 | # 摇尾巴 wag_tail
222 | @property
223 | def wag_tail(self):
224 | # amplitude = 50
225 | # angs = []
226 | # for i in range(21):
227 | # a = round(sin(i*0.314), 2)
228 | # angs.append([amplitude*a])
229 | angs = [[-30], [30]]
230 | return angs, 'tail'
231 |
232 | # head_up_down
233 | @property
234 | def head_up_down(self):
235 | # amplitude = 20
236 | # angs = []
237 | # for i in range(20):
238 | # y = round(sin(i*0.314),3)
239 | # y1 = amplitude*sin(i*0.314)
240 | # if y == -1 or y == 1:
241 | # for _ in range(10):
242 | # angs.append([0,0,y1])
243 | # angs.append([0,0,y1])
244 | # return angs,'head'
245 | return [
246 | [0, 0, 20],
247 | [0, 0, 20],
248 | [0, 0, -10]
249 | ], 'head'
250 |
251 | # half_sit
252 | @property
253 | def half_sit(self):
254 | return [
255 | [25, 25, -25, -25, 64, -45, -64, 45],
256 | ], 'legs'
257 |
--------------------------------------------------------------------------------
/pidog/dual_touch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from robot_hat import Pin
3 | import time
4 |
5 |
6 | class DualTouch():
7 |
8 | SLIDE_MAX_INTERVAL = 0.5 # second, Maximum effective interval for sliding detection
9 |
10 | def __init__(self, sw1='D2', sw2='D3'):
11 |
12 | self.touch_L = Pin(sw1, mode=Pin.IN, pull=Pin.PULL_UP)
13 | self.touch_R = Pin(sw2, mode=Pin.IN, pull=Pin.PULL_UP)
14 | self.last_touch = 'N'
15 | self.last_touch_time = 0
16 |
17 | # def read(self):
18 | # if self.touch_L.value() == 1:
19 | # time.sleep(0.1)
20 | # if self.touch_R.value() == 1:
21 | # return 'LS'
22 | # else:
23 | # return 'L'
24 | # elif self.touch_R.value() == 1:
25 | # time.sleep(0.1)
26 | # if self.touch_L.value() == 1:
27 | # return 'RS'
28 | # else:
29 | # return 'R'
30 | # return 'N'
31 |
32 | def read(self):
33 | if self.touch_L.value() == 1:
34 | if self.last_touch == 'R' and\
35 | time.time() - self.last_touch_time <= self.SLIDE_MAX_INTERVAL:
36 | val = 'RS'
37 | else:
38 | val = 'L'
39 | self.last_touch_time = time.time()
40 | self.last_touch = 'L'
41 | return val
42 | elif self.touch_R.value() == 1:
43 | if self.last_touch == 'L' and\
44 | time.time() - self.last_touch_time <= self.SLIDE_MAX_INTERVAL:
45 | val = 'LS'
46 | else:
47 | val = 'R'
48 | self.last_touch_time = time.time()
49 | self.last_touch = 'R'
50 | return val
51 | return 'N'
--------------------------------------------------------------------------------
/pidog/sound_direction.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | 声音方位识别模块 通讯协议
4 |
5 | 1.通讯格式: master 随机发送16bit数据给slave,然后接收16bit数据
6 | 主控接收的16BIT 格式:
7 | (1) 先接收数据的低8bit,然后接收数据的高8bit
8 | (2) 遵从MSB传送
9 |
10 | 2.方向可以侦测360度方向,最小单位是20度。则数据范围为0~355.
11 |
12 | 3. 流程,主控拉高busy,064B(TR16F064B)开始监测方向。
13 | 当064B识别到方向的时候,会拉低busy线(平时为高);主控监测到BUSY拉低,发16bit任意数据给064B,
14 | 并接受16bit数据,接受完成后,主控把busy线拉高,再次检测方向.
15 |
16 |
17 | Sound Location Recognition Module Communication Protocol
18 |
19 | 1. Communication format: master randomly sends 16bit data to slave, and then receives 16bit data
20 | 16BIT format received by the master:
21 | (1) First receive the lower 8 bits of the data, and then receive the upper 8 bits of the data
22 | (2) Comply with MSB transmission
23 |
24 | 2. The direction can detect 360 degree direction, the minimum unit is 20 degrees. The data range is 0~355.
25 |
26 | 3. In the process, the master pulls up busy, and 064B (TR16F064B) starts to monitor the direction.
27 | When 064B recognizes the direction, it will pull down the busy line (usually high); the master monitors that BUSY is pulled low, and sends 16bit arbitrary data to 064B,
28 | And accept 16bit data, after the acceptance is completed, the main control pulls up the busy line and detects the direction again.
29 |
30 | '''
31 |
32 | import spidev
33 | from gpiozero import OutputDevice, InputDevice
34 |
35 |
36 | class SoundDirection():
37 | CS_DELAY_US = 500 # Mhz
38 | CLOCK_SPEED = 10000000 # 10 MHz
39 |
40 | def __init__(self, busy_pin=6):
41 | self.spi = spidev.SpiDev()
42 | self.spi.open(0, 0)
43 | #
44 | self.busy = InputDevice(busy_pin, pull_up=False)
45 |
46 | def read(self):
47 | result = self.spi.xfer2([0, 0, 0, 0, 0, 0], self.CLOCK_SPEED,
48 | self.CS_DELAY_US)
49 |
50 |
51 | l_val, h_val = result[4:] # ignore the fist two values
52 | # print([h_val, l_val])
53 | if h_val == 255:
54 | return -1
55 | else:
56 | val = (h_val << 8) + l_val
57 | val = (360 + 160 - val) % 360 # Convert zero
58 | return val
59 |
60 | def isdetected(self):
61 | return self.busy.value == 0
62 |
63 |
64 | if __name__ == '__main__':
65 | from time import sleep
66 | sd = SoundDirection()
67 | while True:
68 | if sd.isdetected():
69 | print(f"Sound detected at {sd.read()} degrees")
70 | sleep(0.2)
--------------------------------------------------------------------------------
/pidog/trot.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | A full MOVE divided into 8 SECTIONs. like below:
4 | leg| 0 | 1 |
5 | ====|===|===|
6 | 1 |^^^|___|
7 | 2 |___|^^^|
8 | 3 |___|^^^|
9 | 4 |^^^|___|
10 |
11 | 4 legs move in this order: 1, 4, 2, 3
12 | there will be a break after every leg move
13 |
14 | every SECTION devide into 4 STEPs
15 | """
16 |
17 | #!/usr/bin/env python3
18 |
19 | import readchar
20 | from time import sleep as delay
21 | from math import cos, pi
22 |
23 |
24 | class Trot():
25 |
26 | FORWARD = 1
27 | BACKWARD = -1
28 | LEFT = -1
29 | STRAIGHT = 0
30 | RIGHT = 1
31 |
32 | SECTION_COUNT = 2
33 | STEP_COUNT = 3
34 | LEG_RAISE_ORDER = [[1, 4], [2, 3]]
35 | LEG_STEP_HEIGHT = 20 # the height of the stepping leg
36 | LEG_STEP_WIDTH = 100 # the width of the stepping leg
37 | CENTER_OF_GRAVITY = -17 # the body y offset
38 | LEG_STAND_OFFSET = 5 # the leg center offset
39 | Z_ORIGIN = 80
40 |
41 | TURNING_RATE = 0.5
42 | LEG_STAND_OFFSET_DIRS = [-1, -1, 1, 1]
43 | LEG_STEP_SCALES_LEFT = [TURNING_RATE, 1, TURNING_RATE, 1]
44 | LEG_STEP_SCALES_MIDDLE = [1, 1, 1, 1]
45 | LEG_STEP_SCALES_RIGHT = [1, TURNING_RATE, 1, TURNING_RATE]
46 | LEG_ORIGINAL_Y_TABLE = [0, 1, 1, 0]
47 | LEG_STEP_SCALES = [LEG_STEP_SCALES_LEFT,
48 | LEG_STEP_SCALES_MIDDLE, LEG_STEP_SCALES_RIGHT]
49 |
50 | def __init__(self, fb, lr):
51 | """
52 | Trot init
53 | fb: FORWARD(1) or BACKWARD(-1)
54 | lr: LEFT(1), STRAIGHT(0) or RIGHT(-1)
55 | """
56 | self.fb = fb
57 | self.lr = lr
58 |
59 | if self.fb == self.FORWARD:
60 | if self.lr == self.STRAIGHT:
61 | self.y_offset = 0 + self.CENTER_OF_GRAVITY
62 | else:
63 | self.y_offset = -2 + self.CENTER_OF_GRAVITY
64 | elif self.fb == self.BACKWARD:
65 | if self.lr == self.STRAIGHT:
66 | self.y_offset = 8 + self.CENTER_OF_GRAVITY
67 | else:
68 | self.y_offset = 1 + self.CENTER_OF_GRAVITY
69 | else:
70 | self.y_offset = self.CENTER_OF_GRAVITY
71 | self.leg_step_width = [
72 | self.LEG_STEP_WIDTH * self.LEG_STEP_SCALES[self.lr+1][i] for i in range(4)]
73 | self.section_length = [self.leg_step_width[i] /
74 | (self.SECTION_COUNT-1) for i in range(4)]
75 | self.step_down_length = [
76 | self.section_length[i] / self.STEP_COUNT for i in range(4)]
77 | self.leg_offset = [self.LEG_STAND_OFFSET *
78 | self.LEG_STAND_OFFSET_DIRS[i] for i in range(4)]
79 | self.leg_origin = [self.leg_step_width[i] / 2 + self.y_offset + (
80 | self.leg_offset[i] * self.LEG_STEP_SCALES[self.lr+1][i]) for i in range(4)]
81 |
82 | # Cosine
83 | def step_y_func(self, leg, step):
84 | """
85 | Step function for y axis,
86 | leg: current leg
87 | step: current step
88 | """
89 | theta = step * pi / (self.STEP_COUNT-1)
90 | temp = (self.leg_step_width[leg] *
91 | (cos(theta) - self.fb) / 2 * self.fb)
92 | y = self.leg_origin[leg] + temp
93 | return y
94 |
95 | # Linear
96 | def step_z_func(self, step):
97 | return self.Z_ORIGIN - (self.LEG_STEP_HEIGHT * step / (self.STEP_COUNT-1))
98 |
99 | def get_coords(self):
100 | """
101 | get coords action coords calculation,
102 | fb: forward(1) or backward(-1)
103 | lr: left(1), middle(0) or right(-1)
104 | """
105 | origin_leg_coord = [[self.leg_origin[i] - self.LEG_ORIGINAL_Y_TABLE[i]
106 | * self.section_length[i], self.Z_ORIGIN] for i in range(4)]
107 | leg_coords = []
108 | for section in range(self.SECTION_COUNT):
109 | for step in range(self.STEP_COUNT):
110 | if self.fb == 1:
111 | raise_legs = self.LEG_RAISE_ORDER[section]
112 | else:
113 | raise_legs = self.LEG_RAISE_ORDER[self.SECTION_COUNT - section - 1]
114 | leg_coord = []
115 |
116 | for i in range(4):
117 | if i + 1 in raise_legs:
118 | y = self.step_y_func(i, step)
119 | z = self.step_z_func(step)
120 | else:
121 | y = origin_leg_coord[i][0] + \
122 | self.step_down_length[i] * self.fb
123 | z = self.Z_ORIGIN
124 | leg_coord.append([y, z])
125 | origin_leg_coord = leg_coord
126 | leg_coords.append(leg_coord)
127 | return leg_coords
128 |
129 |
130 | def test():
131 |
132 | from pidog import Pidog
133 | dog = Pidog(leg_pins=[1, 2, 3, 4, 5, 6, 7, 8],
134 | head_pins=[9, 10, 11], tail_pin=[12],
135 | )
136 |
137 | def pause():
138 | key = readchar.readkey()
139 | if key == readchar.key.CTRL_C or key in readchar.key.ESCAPE_SEQUENCES:
140 | import sys
141 | print('')
142 | sys.exit(0)
143 |
144 | forward = Trot(fb=Trot.FORWARD, lr=Trot.STRAIGHT)
145 | backward = Trot(fb=Trot.BACKWARD, lr=Trot.STRAIGHT)
146 | forward_left = Trot(fb=Trot.FORWARD, lr=Trot.LEFT)
147 | forward_right = Trot(fb=Trot.FORWARD, lr=Trot.RIGHT)
148 | backward_left = Trot(fb=Trot.BACKWARD, lr=Trot.LEFT)
149 | backward_right = Trot(fb=Trot.BACKWARD, lr=Trot.RIGHT)
150 | leg_coords = forward.get_coords()
151 |
152 | # try:
153 | while True:
154 | for leg_coord in leg_coords:
155 | # print(leg_coord)
156 | # dog.set_rpy(**rpy)
157 | # dog.set_pose(**pos)
158 | # dog.set_rpy(0, 0, 0, True)
159 | dog.set_legs(leg_coord)
160 | angles = dog.pose2legs_angle()
161 | dog.legs_simple_move(angles)
162 | # pause()
163 | delay(0.001)
164 |
165 | # finally:
166 | # dog.close()
167 |
168 |
169 | if __name__ == '__main__':
170 | test()
171 |
--------------------------------------------------------------------------------
/pidog/version.py:
--------------------------------------------------------------------------------
1 | __version__ = "1.3.9"
2 |
--------------------------------------------------------------------------------
/pidog/walk.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | A full MOVE divided into 8 SECTIONs. like below:
4 | leg| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
5 | ====|===|===|===|===|===|===|===|===
6 | 1 |^^^|___|___|___|___|___|___|___
7 | 2 |___|___|___|___|^^^|___|___|___
8 | 3 |___|___|___|___|___|___|^^^|___
9 | 4 |___|___|^^^|___|___|___|___|___
10 |
11 | 4 legs move in this order: 1, 4, 2, 3
12 | there will be a break after every leg move
13 |
14 | every SECTION devide into 4 STEPs
15 | """
16 |
17 | #!/usr/bin/env python3
18 |
19 | from math import cos, pi
20 |
21 |
22 | class Walk():
23 |
24 | FORWARD = 1
25 | BACKWARD = -1
26 | LEFT = -1
27 | STRAIGHT = 0
28 | RIGHT = 1
29 |
30 | SECTION_COUNT = 8
31 | STEP_COUNT = 6
32 | LEG_ORDER = [1, 0, 4, 0, 2, 0, 3, 0]
33 | LEG_STEP_HEIGHT = 20 # the height of the stepping leg
34 | LEG_STEP_WIDTH = 80 # the width of the stepping leg
35 | CENTER_OF_GRAVIRTY = -15 # the center of gravity of the robot
36 | LEG_POSITION_OFFSETS = [-10, -10, 20, 20] # the leg center offset
37 | Z_ORIGIN = 80
38 |
39 | TURNING_RATE = 0.3
40 | LEG_STEP_SCALES_LEFT = [TURNING_RATE, 1, TURNING_RATE, 1]
41 | LEG_STEP_SCALES_MIDDLE = [1, 1, 1, 1]
42 | LEG_STEP_SCALES_RIGHT = [1, TURNING_RATE, 1, TURNING_RATE]
43 | LEG_ORIGINAL_Y_TABLE = [0, 2, 3, 1]
44 | LEG_STEP_SCALES = [LEG_STEP_SCALES_LEFT,
45 | LEG_STEP_SCALES_MIDDLE, LEG_STEP_SCALES_RIGHT]
46 |
47 | def __init__(self, fb, lr):
48 | """
49 | Walk init
50 | fb: FORWARD(1) or BACKWARD(-1)
51 | lr: LEFT(1), STRAIGHT(0) or RIGHT(-1)
52 | """
53 | self.fb = fb
54 | self.lr = lr
55 |
56 | if self.fb == self.FORWARD:
57 | if self.lr == self.STRAIGHT:
58 | self.y_offset = 0 + self.CENTER_OF_GRAVIRTY
59 | else:
60 | self.y_offset = 0 + self.CENTER_OF_GRAVIRTY
61 | elif self.fb == self.BACKWARD:
62 | if self.lr == self.STRAIGHT:
63 | self.y_offset = 0 + self.CENTER_OF_GRAVIRTY
64 | else:
65 | self.y_offset = 0 + self.CENTER_OF_GRAVIRTY
66 | else:
67 | self.y_offset = self.CENTER_OF_GRAVIRTY
68 | self.leg_step_width = [
69 | self.LEG_STEP_WIDTH * self.LEG_STEP_SCALES[self.lr+1][i] for i in range(4)]
70 | self.section_length = [self.leg_step_width[i] /
71 | (self.SECTION_COUNT-1) for i in range(4)]
72 | self.step_down_length = [
73 | self.section_length[i] / self.STEP_COUNT for i in range(4)]
74 | self.leg_origin = [self.leg_step_width[i] / 2 + self.y_offset + (
75 | self.LEG_POSITION_OFFSETS[i] * self.LEG_STEP_SCALES[self.lr+1][i]) for i in range(4)]
76 |
77 | # Cosine
78 | def step_y_func(self, leg, step):
79 | """
80 | Step function for y axis,
81 | leg: current leg
82 | step: current step
83 | """
84 | theta = step * pi / (self.STEP_COUNT-1)
85 | temp = (self.leg_step_width[leg] *
86 | (cos(theta) - self.fb) / 2 * self.fb)
87 | y = self.leg_origin[leg] + temp
88 | return y
89 |
90 | # Linear
91 | def step_z_func(self, step):
92 | return self.Z_ORIGIN - (self.LEG_STEP_HEIGHT * step / (self.STEP_COUNT-1))
93 |
94 | def get_coords(self):
95 | """
96 | get coords action coords calculation,
97 | fb: forward(1) or backward(-1)
98 | lr: left(1), middle(0) or right(-1)
99 | """
100 | origin_leg_coord = [[self.leg_origin[i] - self.LEG_ORIGINAL_Y_TABLE[i]
101 | * 2 * self.section_length[i], self.Z_ORIGIN] for i in range(4)]
102 | leg_coord = list.copy(origin_leg_coord)
103 | leg_coords = []
104 | for section in range(self.SECTION_COUNT):
105 | for step in range(self.STEP_COUNT):
106 | if self.fb == 1:
107 | raise_leg = self.LEG_ORDER[section]
108 | else:
109 | raise_leg = self.LEG_ORDER[self.SECTION_COUNT - section - 1]
110 |
111 | for i in range(4):
112 | if raise_leg != 0 and i == raise_leg-1:
113 | y = self.step_y_func(i, step)
114 | z = self.step_z_func(step)
115 | else:
116 | y = leg_coord[i][0] + \
117 | self.step_down_length[i] * self.fb
118 | z = self.Z_ORIGIN
119 | leg_coord[i] = [y, z]
120 | leg_coords.append(list.copy(leg_coord))
121 | leg_coords.append(origin_leg_coord)
122 | return leg_coords
123 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Always prefer setuptools over distutils
2 | from setuptools import setup, find_packages
3 | # To use a consistent encoding
4 | from codecs import open
5 | from os import path
6 |
7 | here = path.abspath(path.dirname(__file__))
8 |
9 | import sys
10 | sys.path.append("./pidog")
11 | from version import __version__
12 |
13 | # Get the long description from the relevant file
14 | with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f:
15 | long_description = f.read()
16 |
17 | setup(
18 | name='pidog',
19 |
20 | # Versions should comply with PEP440. For a discussion on single-sourcing
21 | # the version across setup.py and the project code, see
22 | # https://packaging.python.org/en/latest/single_source_version.html
23 | version=__version__,
24 |
25 | description='Picrawler gait Library for Raspberry Pi',
26 | long_description=long_description,
27 |
28 | # The project's main homepage.
29 | url='https://github.com/sunfounder/pidog',
30 |
31 | # Author details
32 | author='SunFounder',
33 | author_email='service@sunfounder.com',
34 |
35 | # Choose your license
36 | license='GNU',
37 | zip_safe=False,
38 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
39 | classifiers=[
40 | # How mature is this project? Common values are
41 | # 3 - Alpha
42 | # 4 - Beta
43 | # 5 - Production/Stable
44 | 'Development Status :: 3 - Alpha',
45 |
46 | # Indicate who your project is intended for
47 | 'Intended Audience :: Developers',
48 | 'Topic :: Software Development :: Build Tools',
49 |
50 | # Pick your license as you wish (should match "license" above)
51 | 'License :: OSI Approved :: GNU License',
52 |
53 | # Specify the Python versions you support here. In particular, ensure
54 | # that you indicate whether you support Python 2, Python 3 or both.
55 | 'Programming Language :: Python :: 3',
56 | 'Programming Language :: Python :: 3.2',
57 | 'Programming Language :: Python :: 3.3',
58 | 'Programming Language :: Python :: 3.4',
59 | ],
60 |
61 | # What does your project relate to?
62 | keywords='python raspberry pi GPIO sunfounder',
63 |
64 | # You can just specify the packages manually here if your project is
65 | # simple. Or you can use find_packages().
66 | packages=find_packages(exclude=[ 'doc', 'tests*' ,'examples']),
67 |
68 | # List run-time dependencies here. These will be installed by pip when
69 | # your project is installed. For an analysis of "install_requires" vs pip's
70 | # requirements files see:
71 | # https://packaging.python.org/en/latest/requirements.html
72 | install_requires=['robot_hat>=2.0.0', 'readchar', 'numpy'],
73 |
74 | # To provide executable scripts, use entry points in preference to the
75 | # "scripts" keyword. Entry points provide cross-platform support and allow
76 | # pip to create the appropriate form of executable for the target platform.
77 | entry_points={
78 | 'console_scripts': [
79 | 'pidog=pidog:__main__',
80 | ],
81 | },
82 | )
83 |
--------------------------------------------------------------------------------
/sounds/angry.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/angry.wav
--------------------------------------------------------------------------------
/sounds/confused_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/confused_1.mp3
--------------------------------------------------------------------------------
/sounds/confused_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/confused_2.mp3
--------------------------------------------------------------------------------
/sounds/confused_3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/confused_3.mp3
--------------------------------------------------------------------------------
/sounds/growl_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/growl_1.mp3
--------------------------------------------------------------------------------
/sounds/growl_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/growl_2.mp3
--------------------------------------------------------------------------------
/sounds/howling.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/howling.mp3
--------------------------------------------------------------------------------
/sounds/pant.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/pant.mp3
--------------------------------------------------------------------------------
/sounds/single_bark_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/single_bark_1.mp3
--------------------------------------------------------------------------------
/sounds/single_bark_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/single_bark_2.mp3
--------------------------------------------------------------------------------
/sounds/snoring.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/snoring.mp3
--------------------------------------------------------------------------------
/sounds/woohoo.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunfounder/pidog/8e7fb98722b05962a01860f06847fc2027887bcf/sounds/woohoo.mp3
--------------------------------------------------------------------------------
/test/angry_bark.py:
--------------------------------------------------------------------------------
1 | from pidog import Pidog
2 | from time import sleep
3 |
4 | my_dog = Pidog()
5 |
6 | SPEED = 90
7 |
8 | my_dog.do_action("stand", speed=SPEED)
9 | my_dog.wait_all_done()
10 | sleep(1)
11 |
12 | yrp = [0, 0, 0]
13 | h1 = [0 + yrp[0], 0 + yrp[1], 20 + yrp[2]]
14 | h2 = [0 + yrp[0], 0 + yrp[1], 0 + yrp[2]]
15 |
16 | f1 = my_dog.legs_angle_calculation(
17 | [[0, 100], [0, 100], [30, 90], [30, 90]])
18 | f2 = my_dog.legs_angle_calculation(
19 | [[-20, 90], [-20, 90], [0, 90], [0, 90]])
20 |
21 | my_dog.speak("angry")
22 | my_dog.legs_move([f1], immediately=True, speed=95)
23 | my_dog.head_move([h1], immediately=True, speed=95)
24 | my_dog.wait_all_done()
25 | sleep(0.01)
26 | my_dog.legs_move([f2], immediately=True, speed=95)
27 | my_dog.head_move([h2], immediately=True, speed=95)
28 | my_dog.wait_all_done()
29 | sleep(0.01)
30 |
31 | my_dog.close()
32 |
--------------------------------------------------------------------------------
/test/cover_photo.py:
--------------------------------------------------------------------------------
1 | from pidog import Pidog
2 |
3 | # init
4 | # =================================================================
5 | my_dog = Pidog()
6 |
7 | # rgb color
8 | # =================================================================
9 | rgb_colors = [
10 | [255, 0, 0], # 0
11 | [255, 69, 0], # 1
12 | [255, 165, 0], # 2
13 | [255, 255, 0], # 3
14 | [173, 255, 47], # 4
15 | [ 0, 255, 0], # 5
16 | [ 0, 128, 0], # 6
17 | [ 64, 224, 208], # 7
18 | [ 0, 255, 255], # 8
19 | [138, 43, 226], # 9
20 | [255, 20, 147], # 10
21 | ]
22 | # stop rgb thread
23 | my_dog.rgb_thread_run = False
24 | my_dog.rgb_strip_thread.join()
25 |
26 | my_dog.rgb_strip.display(rgb_colors)
27 |
28 | # pose
29 | # =================================================================
30 | f_up = [
31 | # [30, 60, -20, 65, 80, -45, -80, 38], # Note 1
32 | [30, 60, -15, 5, 80, -45, -80, 38],
33 | ]
34 | # f_handshake = [
35 | # [30, 60, 10, -25, 80, -45, -80, 38], # Note 1
36 | # [30, 60, 10, -35, 80, -45, -80, 38], # Note 1
37 | # ]
38 | # f_withdraw = [
39 | # [30, 60, -40, 30, 80, -45, -80, 38], # Note 1
40 | # ]
41 | my_dog.head_move([[-25, 20, 0]], pitch_comp=-20)
42 | my_dog.legs_move(f_up, immediately=False, speed=80)
43 | my_dog.wait_all_done()
44 |
45 | # for _ in range(5):
46 | # my_dog.legs_move(f_handshake, immediately=False, speed=90)
47 | # my_dog.wait_all_done()
48 |
49 | # my_dog.legs_move(f_withdraw, immediately=False, speed=80)
50 | # my_dog.do_action('sit', speed=80)
51 | # my_dog.wait_all_done()
--------------------------------------------------------------------------------
/test/dual_touch_test.py:
--------------------------------------------------------------------------------
1 | from pidog.dual_touch import DualTouch
2 | from time import sleep
3 |
4 | touch = DualTouch('D2', 'D3')
5 |
6 | while True:
7 | print(
8 | f"\rLeft value: {touch.touch_L.value()} | Right value: {touch.touch_R.value()} | {touch.read()}", end=" ", flush=True)
9 | sleep(0.05)
10 |
--------------------------------------------------------------------------------
/test/imu_test.py:
--------------------------------------------------------------------------------
1 | from pidog.sh3001 import Sh3001
2 | from time import sleep
3 | import os
4 |
5 | # user and User home directory
6 | User = os.popen('echo ${SUDO_USER:-$LOGNAME}').readline().strip()
7 | UserHome = os.popen('getent passwd %s | cut -d: -f 6' %
8 | User).readline().strip()
9 | config_file = '%s/.config/pidog/pidog.conf' % UserHome
10 |
11 | imu = Sh3001(db=config_file)
12 |
13 | accData = [] # ax,ay,az
14 | gyroData = [] # gx,gy,gz
15 |
16 | imu_acc_offset = [0, 0, 0]
17 | imu_gyro_offset = [0, 0, 0]
18 |
19 | print("Calibrating IMU...")
20 |
21 | _ax = 0
22 | _ay = 0
23 | _az = 0
24 | _gx = 0
25 | _gy = 0
26 | _gz = 0
27 | time = 10
28 | for _ in range(time):
29 | data = imu._sh3001_getimudata()
30 | if data == False:
31 | print('_imu_thread imu data error')
32 | break
33 |
34 | accData, gyroData = data
35 | _ax += accData[0]
36 | _ay += accData[1]
37 | _az += accData[2]
38 | _gx += gyroData[0]
39 | _gy += gyroData[1]
40 | _gz += gyroData[2]
41 | sleep(0.1)
42 |
43 | imu_acc_offset[0] = round(-16384 - _ax / time, 0)
44 | imu_acc_offset[1] = round(0 - _ay / time, 0)
45 | imu_acc_offset[2] = round(0 - _az / time, 0)
46 | imu_gyro_offset[0] = round(0 - _gx / time, 0)
47 | imu_gyro_offset[1] = round(0 - _gy / time, 0)
48 | imu_gyro_offset[2] = round(0 - _gz / time, 0)
49 |
50 | print("Done!")
51 |
52 | while True:
53 | data = imu._sh3001_getimudata()
54 | if data == False:
55 | sleep(0.1)
56 | print('_imu_thread imu data error')
57 | break
58 |
59 | accData, gyroData = data
60 | accData = [accData[i] + imu_acc_offset[i] for i in range(3)]
61 | gyroData = [gyroData[i] + imu_gyro_offset[i] for i in range(3)]
62 |
63 | print(
64 | f"\rACC: {accData[0]:8} {accData[1]:8} {accData[2]:8} | GYRO: {gyroData[0]:8} {gyroData[1]:8} {gyroData[2]:8}",
65 | end=" ",
66 | flush=True)
67 | sleep(0.1)
68 |
--------------------------------------------------------------------------------
/test/power_test.py:
--------------------------------------------------------------------------------
1 | from pidog import Pidog
2 | import time
3 | import threading
4 |
5 |
6 |
7 | my_dog = Pidog()
8 |
9 | SPEED = 100
10 |
11 | try:
12 | active_threads = threading.active_count()
13 | print(f"active_threads: {active_threads}")
14 | for thread in threading.enumerate():
15 | print(f"{thread.name}")
16 |
17 | my_dog.rgb_strip.set_mode("boom", "red")
18 |
19 | my_dog.do_action("stand", step_count=54, speed=SPEED)
20 | # my_dog.head_move([[0, 0, 0]], pitch_comp=-30)
21 |
22 | # my_dog.rgb_strip.set_mode("boom", "red")
23 | st = time.time()
24 | while True:
25 | # my_dog.do_action("stand", speed=SPEED)
26 | # time.sleep(1.5)
27 | # my_dog.do_action("lie", speed=SPEED)
28 | # time.sleep(1.5)
29 | # my_dog.do_action("push_up", speed=SPEED)
30 | # time.sleep(1.5)
31 | # my_dog.do_action("lie", speed=SPEED)
32 | # time.sleep(1.5)
33 | # print(len(my_dog.legs_action_buffer))
34 | my_dog.do_action("trot", step_count=2, speed=SPEED)
35 | my_dog.do_action("wag_tail", step_count=10, speed=SPEED)
36 | my_dog.do_action("shake_head", step_count=5, pitch_comp=-10, speed=SPEED)
37 | # print(f"{my_dog.accData, my_dog.read_distance()}")
38 | # if time.time() - st > 60:
39 | # my_dog.body_stop()
40 | # time.sleep(3)
41 | # st = time.time()
42 |
43 | time.sleep(1)
44 |
45 |
46 | except KeyboardInterrupt:
47 | my_dog.close()
48 |
49 | # from robot_hat import Servo
50 | # from time import sleep
51 |
52 | # s0 = Servo(9)
53 |
54 | # while True:
55 | # for i in range(-90, 90, 5):
56 | # s0.angle(i)
57 | # sleep(0.001)
58 | # sleep(0.2)
59 | # for i in range(90, -90, -5):
60 | # s0.angle(i)
61 | # sleep(0.001)
62 | # sleep(0.2)
--------------------------------------------------------------------------------
/test/rgb_strip_test.py:
--------------------------------------------------------------------------------
1 | from pidog.rgb_strip import RGBStrip
2 | import time
3 |
4 | strip = RGBStrip(0X74, 11)
5 |
6 | mode_list = [
7 | {
8 | "color": "red",
9 | "brightness": 0.8,
10 | "bps": 1,
11 | },
12 | {
13 | "color": "green",
14 | "brightness": 0.2,
15 | "bps": 2,
16 | },
17 | {
18 | "color": "blue",
19 | "brightness": 0.5,
20 | "bps": 3,
21 | },
22 | {
23 | "color": "white",
24 | "brightness": 0.5,
25 | "bps": 4,
26 | },
27 | ]
28 |
29 | for mode in mode_list:
30 | print(
31 | f'Color: {mode["color"]}, brightness: {mode["brightness"]}, bps: {mode["bps"]}'
32 | )
33 | strip.set_mode('breath',
34 | color=mode["color"],
35 | brightness=mode["brightness"],
36 | bps=mode["bps"])
37 |
38 | start_time = time.time()
39 | while ((time.time() - start_time) <= 3):
40 | strip.show()
41 |
--------------------------------------------------------------------------------
/test/sound_direction_test.py:
--------------------------------------------------------------------------------
1 | from pidog.sound_direction import SoundDirection
2 | from time import sleep
3 |
4 |
5 | sd = SoundDirection()
6 | while True:
7 | if sd.isdetected():
8 | print(f"Sound detected at {sd.read()} degrees")
9 | sleep(0.2)
10 |
--------------------------------------------------------------------------------
/test/stand_test.py:
--------------------------------------------------------------------------------
1 | from pidog import Pidog
2 | from time import sleep
3 |
4 | my_dog = Pidog()
5 |
6 | SPEED = 95
7 |
8 | my_dog.do_action("stand", speed=SPEED)
9 |
10 | sleep(3)
11 | my_dog.close()
12 |
--------------------------------------------------------------------------------
/test/tail.py:
--------------------------------------------------------------------------------
1 | from pidog import Pidog
2 | from time import sleep
3 |
4 | my_dog = Pidog()
5 |
6 | SPEED = 100
7 |
8 | my_dog.do_action("wag_tail", step_count=40, speed=SPEED)
9 | my_dog.wait_all_done()
10 |
11 | my_dog.close()
12 |
--------------------------------------------------------------------------------
/test/ultrasonic_iic_test.py:
--------------------------------------------------------------------------------
1 | # from robot_hat import I2C
2 | # import time
3 |
4 | # ADDR = 0x57
5 |
6 | # DATA_REG_ADDR = 0x00
7 |
8 | # sonar = I2C(ADDR)
9 | # while True:
10 | # sonar.write(0x01)
11 | # time.sleep(0.2) #wait for the echo
12 |
13 | # data = sonar.mem_read(3, DATA_REG_ADDR)
14 | # print(data)
15 | # time.sleep(0.2)
16 |
17 | import smbus
18 | import time
19 |
20 | bus = smbus.SMBus(1) # Rev 2 Pi uses 1
21 | time.sleep(1) #wait here to avoid 121 IO Error
22 |
23 | address = 0x57 #I2C address
24 |
25 | while True:
26 |
27 | cmd = 1
28 | bus.write_byte(address, cmd)
29 | time.sleep(0.2) #wait for the echo # 0.2
30 |
31 | data = bus.read_i2c_block_data(address, 0, 3) #read three bytes into array
32 |
33 | distance = (data[0] * 65535 + data[1] * 256 + data[2])/10000
34 | print("distance : " + format(distance, '.2f'))
35 |
36 |
--------------------------------------------------------------------------------
/test/ultrasonic_test.py:
--------------------------------------------------------------------------------
1 | from robot_hat import Ultrasonic, Pin
2 | import time
3 |
4 | ultrasonic = Ultrasonic(Pin("D1"), Pin("D0"))
5 |
6 | # Read the distance in centimeters
7 | while True:
8 | distance = ultrasonic.read()
9 | print(f"Distance: {distance} cm")
10 | time.sleep(1)
11 |
12 |
13 | # from pidog import Pidog
14 | # import time
15 |
16 | # my_dog = Pidog()
17 |
18 | # DANGER_DISTANCE = 15
19 |
20 | # while True:
21 | # distance = round(my_dog.read_distance(), 2)
22 | # my_dog.do_action('shake_head', step_count=1, speed=80)
23 | # if distance < DANGER_DISTANCE:
24 | # print(f"\033[0;31m Distance: {distance}\033[m")
25 | # time.sleep(0.21)
26 | # else:
27 | # print(f"Distance: {distance}")
28 | # time.sleep(0.01)
--------------------------------------------------------------------------------