diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 379ff1c6..abac9ed0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,8 @@ jobs: - name: Run unittests run: | - python -m unittest discover -s tests -p 'test_*.py' + PYTHONPATH=src python -m unittest discover -s src/tests -p 'test_*.py' -t src + PYTHONPATH=src python -m unittest discover -s src/modules -p 'test_*.py' -t src permissions: pull-requests: write diff --git a/3d_prints/v2/skeleton.stl b/3d_prints/archie/Archie - Body Skeleton.stl similarity index 100% rename from 3d_prints/v2/skeleton.stl rename to 3d_prints/archie/Archie - Body Skeleton.stl diff --git a/3d_prints/v2/back_case.stl b/3d_prints/archie/Archie - Case Back.stl similarity index 100% rename from 3d_prints/v2/back_case.stl rename to 3d_prints/archie/Archie - Case Back.stl diff --git a/3d_prints/v2/front_case.stl b/3d_prints/archie/Archie - Case Front.stl similarity index 100% rename from 3d_prints/v2/front_case.stl rename to 3d_prints/archie/Archie - Case Front.stl diff --git a/3d_prints/v2/left_foot.stl b/3d_prints/archie/Archie - Foot Left.stl similarity index 100% rename from 3d_prints/v2/left_foot.stl rename to 3d_prints/archie/Archie - Foot Left.stl diff --git a/3d_prints/v2/right_foot.stl b/3d_prints/archie/Archie - Foot Right.stl similarity index 100% rename from 3d_prints/v2/right_foot.stl rename to 3d_prints/archie/Archie - Foot Right.stl diff --git a/3d_prints/v2/head_bottom.stl b/3d_prints/archie/Archie - Head Bottom.stl similarity index 100% rename from 3d_prints/v2/head_bottom.stl rename to 3d_prints/archie/Archie - Head Bottom.stl diff --git a/3d_prints/v2/head_lid.stl b/3d_prints/archie/Archie - Head Lid.stl similarity index 100% rename from 3d_prints/v2/head_lid.stl rename to 3d_prints/archie/Archie - Head Lid.stl diff --git a/3d_prints/v2/head_top.stl b/3d_prints/archie/Archie - Head Top.stl similarity index 100% rename from 3d_prints/v2/head_top.stl rename to 3d_prints/archie/Archie - Head Top.stl diff --git a/3d_prints/v2/head_visor.stl b/3d_prints/archie/Archie - Head Visor.stl similarity index 100% rename from 3d_prints/v2/head_visor.stl rename to 3d_prints/archie/Archie - Head Visor.stl diff --git a/3d_prints/v2/2026 updates/Archie - Leg Lower.stl b/3d_prints/archie/Archie - Leg Lower.stl similarity index 100% rename from 3d_prints/v2/2026 updates/Archie - Leg Lower.stl rename to 3d_prints/archie/Archie - Leg Lower.stl diff --git a/3d_prints/v2/2026 updates/Archie - Leg Upper.stl b/3d_prints/archie/Archie - Leg Upper.stl similarity index 100% rename from 3d_prints/v2/2026 updates/Archie - Leg Upper.stl rename to 3d_prints/archie/Archie - Leg Upper.stl diff --git a/3d_prints/v2/2026 updates/Archie - Neck Tilt.stl b/3d_prints/archie/Archie - Neck Extension.stl similarity index 100% rename from 3d_prints/v2/2026 updates/Archie - Neck Tilt.stl rename to 3d_prints/archie/Archie - Neck Extension.stl diff --git a/3d_prints/v2/neck_servo_mount.stl b/3d_prints/archie/Archie - Neck Servo Mount.stl similarity index 100% rename from 3d_prints/v2/neck_servo_mount.stl rename to 3d_prints/archie/Archie - Neck Servo Mount.stl diff --git a/3d_prints/v2/neck_tilt_lower.stl b/3d_prints/archie/Archie - Neck Tilt Lower.stl similarity index 100% rename from 3d_prints/v2/neck_tilt_lower.stl rename to 3d_prints/archie/Archie - Neck Tilt Lower.stl diff --git a/3d_prints/v2/neck_tilt_mount.stl b/3d_prints/archie/Archie - Neck Tilt Mount.stl similarity index 100% rename from 3d_prints/v2/neck_tilt_mount.stl rename to 3d_prints/archie/Archie - Neck Tilt Mount.stl diff --git a/3d_prints/v2/neck_tilt_upper.stl b/3d_prints/archie/Archie - Neck Tilt Upper.stl similarity index 100% rename from 3d_prints/v2/neck_tilt_upper.stl rename to 3d_prints/archie/Archie - Neck Tilt Upper.stl diff --git a/3d_prints/v2/complete.png b/3d_prints/archie/Complete (Reference).png similarity index 100% rename from 3d_prints/v2/complete.png rename to 3d_prints/archie/Complete (Reference).png diff --git a/3d_prints/v2/complete.stl b/3d_prints/archie/Complete (Reference).stl similarity index 100% rename from 3d_prints/v2/complete.stl rename to 3d_prints/archie/Complete (Reference).stl diff --git a/3d_prints/v2/left_leg_lower.stl b/3d_prints/archie/archive/left_leg_lower.stl similarity index 100% rename from 3d_prints/v2/left_leg_lower.stl rename to 3d_prints/archie/archive/left_leg_lower.stl diff --git a/3d_prints/v2/left_leg_upper.stl b/3d_prints/archie/archive/left_leg_upper.stl similarity index 100% rename from 3d_prints/v2/left_leg_upper.stl rename to 3d_prints/archie/archive/left_leg_upper.stl diff --git a/3d_prints/v2/neck_extension.stl b/3d_prints/archie/archive/neck_extension.stl similarity index 100% rename from 3d_prints/v2/neck_extension.stl rename to 3d_prints/archie/archive/neck_extension.stl diff --git a/3d_prints/v2/right_leg_lower.stl b/3d_prints/archie/archive/right_leg_lower.stl similarity index 100% rename from 3d_prints/v2/right_leg_lower.stl rename to 3d_prints/archie/archive/right_leg_lower.stl diff --git a/3d_prints/v2/right_leg_upper.stl b/3d_prints/archie/archive/right_leg_upper.stl similarity index 100% rename from 3d_prints/v2/right_leg_upper.stl rename to 3d_prints/archie/archive/right_leg_upper.stl diff --git a/README.md b/README.md index cb0d1040..0a75f062 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -# Open Source, 3D Printable, Modular Bipedal Robot Project +# Open Source, 3D Printable, Modular Robot Project -The **Modular Bipedal Robot** project aims to educate and inspire individuals interested in robotics and electronics. This open-source initiative focuses on creating a fully autonomous companion robot with a variety of advanced features. +The **Modular Robot** project aims to educate and inspire individuals interested in robotics and electronics. This open-source initiative focuses on creating a fully autonomous companion robot with a variety of advanced features. ## Key Features -- **Bipedal Design**: The robot includes articulated legs for bipedal movement. - **Control Systems**: Utilizes Arduino and Raspberry Pi, managed through custom PCBs. - **Modular Body**: Configurable body components allow for easy customization and adaptability. - **Software Modules**: @@ -30,7 +29,7 @@ The **Modular Bipedal Robot** project aims to educate and inspire individuals in ## Project Background -The Modular Biped Robot Project is designed to provide a flexible and modular framework for robotics development using Python and C++ on the Raspberry Pi and Arduino platforms. It aims to enable developers, robotics enthusiasts, and curious individuals to experiment, create, and customize their own biped robots. With a range of features and functionalities and the option to add your own easily, the Modular Biped Robot Project offers an exciting opportunity to explore the world of robotics. +The Modular Robot Project is designed to provide a flexible and modular framework for robotics development using Python and C++ on the Raspberry Pi and Arduino platforms. It aims to enable developers, robotics enthusiasts, and curious individuals to experiment, create, and customize their own robots. With a range of features and functionalities and the option to add your own easily, the Modular Robot Project offers an exciting opportunity to explore the world of robotics. ## Modularity @@ -38,7 +37,7 @@ The open source framework is designed for flexibility, allowing users to easily ## Resources -- **Documentation**: For detailed information, visit the project’s GitHub wiki: [Modular Biped Documentation](https://github.com/makerforgetech/modular-biped/wiki) +- **Documentation**: For detailed information, visit the project’s GitHub wiki: [Modular Robot Documentation](https://github.com/makerforgetech/modular-biped/wiki) - **Code**: Check out the modular open source software on [GitHub](https://github.com/makerforgetech/modular-biped) - **YouTube Playlist**: Explore the development process through our build videos: [Watch on YouTube](https://www.youtube.com/watch?v=2DVJ5xxAuWY&list=PL_ua9QbuRTv6Kh8hiEXXVqywS8pklZraT) - **Community**: Have a question or want to show off your build? Join the communities on [GitHub](https://bit.ly/maker-forge-community) and [Discord](https://bit.ly/makerforge-community)! @@ -46,7 +45,7 @@ The open source framework is designed for flexibility, allowing users to easily ## A Note On Branches -The `main` branch is the latest stable release of the Modular Biped Robot project. This is compatible with the 'buddy' release, the latest supported release. +The `main` branch is the latest stable release of the Modular Robot project. This is compatible with the 'buddy' release, the latest supported release. The `develop` branch is the development branch and may contain experimental features and changes that are not yet stable. Please use the `main` branch for the most stable experience. This will eventually become the next release, 'cody'. To facilitate testing modules are disabled by default in the `develop` branch. Enable each as needed in the config yaml files to test. @@ -151,4 +150,64 @@ INFO: 01/30/2025 12:05:27 PM [Personality.random_neopixel_status:117] [Personali INFO: 01/30/2025 12:05:27 PM [PiTemperature.monitor:30] Temperature: 42.2°C INFO: 01/30/2025 12:05:28 PM [PiTemperature.monitor:30] Temperature: 42.2°C INFO: 01/30/2025 12:05:28 PM [Main] Loop ended +``` + +# Getting Started + +1. Clone the repository and navigate to the project directory. +2. Review the environment configuration files in the `environments` folder and create your own if needed. +3. Run install.sh to set up the necessary dependencies and configurations for your chosen environment. +4. Add any environment variables required to /home/$USER/.myenv (this file is sourced on startup to set environment variables for the session). +5. Test by running startup.sh to start the robot. You should see logs in the CLI and app.log file indicating that the robot is running. +6. Ctrl+C to stop the robot. +7. To enable autolaunch on boot, run `./installers/autolaunch.sh enable ` (defaults to laptop if no environment specified). To disable autolaunch, run `./installers/autolaunch.sh disable`. + +# Adding New Modules +To add a new module, create a new directory in the `src/modules` directory with the name of your module. + +Inside this directory, create a Python file with the same name as the directory. This file should contain a class that extends the BaseModule class. + +Implement the necessary methods and functionality for your module. + +- `__init__(self, config, messaging_service)`: Initialize your module and set up any necessary variables or connections. The `config` parameter contains the configuration for your module from the YAML +- `setup_messaging(self)`: Set up any necessary subscriptions to topics using the `subscribe` method from the BaseModule class. You can subscribe to other topics or system/loop/ for timed loops. See the wiki for more details on the messaging service and available topics. +- `loop(self)`: Implement the looping functionality of your module in this method. This method will be called repeatedly in the main loop of the robot. +- `__exit__(self)`: Clean up any resources or connections when the module is stopped. + +Create a configuration YAML file for your module in the new module's directory called `config.yml`. This file should include the class name of your module and any dependencies it requires, as well as any non-environment specific configuration. + + +```yaml +bno055: + class: BNO055 + config: + some_config: + dependencies: + python: + - adafruit-circuitpython-bno055 + - adafruit-blinka +``` + +Finally, add your module to the configuration YAML file for your environment to enable it. + +You may wish to include environment specific configuration in the environment YAML file, which will be passed to the module's init method via kwargs. + +```yaml +bus_servo: + enabled: true + config: + poses: + - legs_forward: {leg_r_tilt: 2806, leg_r_hip: 1989, leg_r_knee: 1349, leg_r_ankle: 2754, neck_pan: 1698, neck_tilt: 118, leg_l_tilt: 1619, leg_l_hip: 1028, leg_l_knee: 2634, leg_l_ankle: 1567} + instances: + - name: leg_r_tilt + model: ST3215 + id: 1 + range: [2511, 3944] + range_degrees: 125.9 + start: 2810 + - name: leg_r_hip + model: ST3215 + id: 3 + range: [0, 2063] + range_degrees: 181.3 ``` \ No newline at end of file diff --git a/arduino_sketch/.vscode/arduino.json b/arduino_sketch/.vscode/arduino.json deleted file mode 100644 index 11bbd436..00000000 --- a/arduino_sketch/.vscode/arduino.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "board": "arduino:avr:pro", - "port": "/dev/ttyUSB0", - "programmer": "avrispmkii", - "sketch": "arduino_sketch.ino", - "configuration": "cpu=16MHzatmega328" -} \ No newline at end of file diff --git a/arduino_sketch/.vscode/c_cpp_properties.json b/arduino_sketch/.vscode/c_cpp_properties.json deleted file mode 100644 index 9b3a81f8..00000000 --- a/arduino_sketch/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,539 +0,0 @@ -{ - "version": 4, - "configurations": [ - { - "name": "Arduino", - "compilerPath": "/home/dan/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-g++", - "compilerArgs": [ - "-w", - "-std=gnu++11", - "-fpermissive", - "-fno-exceptions", - "-ffunction-sections", - "-fdata-sections", - "-fno-threadsafe-statics", - "-Wno-error=narrowing" - ], - "intelliSenseMode": "gcc-x64", - "includePath": [ - "/home/dan/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino", - "/home/dan/.arduino15/packages/arduino/hardware/avr/1.8.6/variants/eightanaloginputs", - "/home/dan/Arduino/libraries/ServoEasing/src", - "/home/dan/Arduino/libraries/Servo/src", - "/home/dan/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/lib/gcc/avr/7.3.0/include", - "/home/dan/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/lib/gcc/avr/7.3.0/include-fixed", - "/home/dan/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/avr/include" - ], - "forcedInclude": [ - "/home/dan/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h" - ], - "cStandard": "c11", - "cppStandard": "c++11", - "defines": [ - "F_CPU=16000000L", - "ARDUINO=10607", - "ARDUINO_AVR_PRO", - "ARDUINO_ARCH_AVR", - "__DBL_MIN_EXP__=(-125)", - "__HQ_FBIT__=15", - "__cpp_attributes=200809", - "__UINT_LEAST16_MAX__=0xffffU", - "__ATOMIC_ACQUIRE=2", - "__SFRACT_IBIT__=0", - "__FLT_MIN__=1.17549435e-38F", - "__GCC_IEC_559_COMPLEX=0", - "__BUILTIN_AVR_SLEEP=1", - "__BUILTIN_AVR_COUNTLSULLK=1", - "__cpp_aggregate_nsdmi=201304", - "__BUILTIN_AVR_COUNTLSULLR=1", - "__UFRACT_MAX__=0XFFFFP-16UR", - "__UINT_LEAST8_TYPE__=unsigned char", - "__DQ_FBIT__=63", - "__INTMAX_C(c)=c ## LL", - "__ULFRACT_FBIT__=32", - "__SACCUM_EPSILON__=0x1P-7HK", - "__CHAR_BIT__=8", - "__USQ_IBIT__=0", - "__UINT8_MAX__=0xff", - "__ACCUM_FBIT__=15", - "__WINT_MAX__=0x7fff", - "__FLT32_MIN_EXP__=(-125)", - "__cpp_static_assert=200410", - "__USFRACT_FBIT__=8", - "__ORDER_LITTLE_ENDIAN__=1234", - "__SIZE_MAX__=0xffffU", - "__WCHAR_MAX__=0x7fff", - "__LACCUM_IBIT__=32", - "__DBL_DENORM_MIN__=double(1.40129846e-45L)", - "__GCC_ATOMIC_CHAR_LOCK_FREE=1", - "__GCC_IEC_559=0", - "__FLT_EVAL_METHOD__=0", - "__BUILTIN_AVR_LLKBITS=1", - "__cpp_binary_literals=201304", - "__LLACCUM_MAX__=0X7FFFFFFFFFFFFFFFP-47LLK", - "__GCC_ATOMIC_CHAR32_T_LOCK_FREE=1", - "__BUILTIN_AVR_HKBITS=1", - "__BUILTIN_AVR_BITSLLK=1", - "__FRACT_FBIT__=15", - "__BUILTIN_AVR_BITSLLR=1", - "__cpp_variadic_templates=200704", - "__UINT_FAST64_MAX__=0xffffffffffffffffULL", - "__SIG_ATOMIC_TYPE__=char", - "__BUILTIN_AVR_UHKBITS=1", - "__UACCUM_FBIT__=16", - "__DBL_MIN_10_EXP__=(-37)", - "__FINITE_MATH_ONLY__=0", - "__cpp_variable_templates=201304", - "__LFRACT_IBIT__=0", - "__GNUC_PATCHLEVEL__=0", - "__FLT32_HAS_DENORM__=1", - "__LFRACT_MAX__=0X7FFFFFFFP-31LR", - "__UINT_FAST8_MAX__=0xff", - "__has_include(STR)=__has_include__(STR)", - "__DEC64_MAX_EXP__=385", - "__INT8_C(c)=c", - "__INT_LEAST8_WIDTH__=8", - "__UINT_LEAST64_MAX__=0xffffffffffffffffULL", - "__SA_FBIT__=15", - "__SHRT_MAX__=0x7fff", - "__LDBL_MAX__=3.40282347e+38L", - "__FRACT_MAX__=0X7FFFP-15R", - "__UFRACT_FBIT__=16", - "__UFRACT_MIN__=0.0UR", - "__UINT_LEAST8_MAX__=0xff", - "__GCC_ATOMIC_BOOL_LOCK_FREE=1", - "__UINTMAX_TYPE__=long long unsigned int", - "__LLFRACT_EPSILON__=0x1P-63LLR", - "__BUILTIN_AVR_DELAY_CYCLES=1", - "__DEC32_EPSILON__=1E-6DF", - "__FLT_EVAL_METHOD_TS_18661_3__=0", - "__UINT32_MAX__=0xffffffffUL", - "__GXX_EXPERIMENTAL_CXX0X__=1", - "__ULFRACT_MAX__=0XFFFFFFFFP-32ULR", - "__TA_IBIT__=16", - "__LDBL_MAX_EXP__=128", - "__WINT_MIN__=(-__WINT_MAX__ - 1)", - "__INT_LEAST16_WIDTH__=16", - "__ULLFRACT_MIN__=0.0ULLR", - "__SCHAR_MAX__=0x7f", - "__WCHAR_MIN__=(-__WCHAR_MAX__ - 1)", - "__INT64_C(c)=c ## LL", - "__DBL_DIG__=6", - "__GCC_ATOMIC_POINTER_LOCK_FREE=1", - "__AVR_HAVE_SPH__=1", - "__LLACCUM_MIN__=(-0X1P15LLK-0X1P15LLK)", - "__BUILTIN_AVR_KBITS=1", - "__BUILTIN_AVR_ABSK=1", - "__BUILTIN_AVR_ABSR=1", - "__SIZEOF_INT__=2", - "__SIZEOF_POINTER__=2", - "__GCC_ATOMIC_CHAR16_T_LOCK_FREE=1", - "__USACCUM_IBIT__=8", - "__USER_LABEL_PREFIX__", - "__STDC_HOSTED__=1", - "__LDBL_HAS_INFINITY__=1", - "__LFRACT_MIN__=(-0.5LR-0.5LR)", - "__HA_IBIT__=8", - "__FLT32_DIG__=6", - "__TQ_IBIT__=0", - "__FLT_EPSILON__=1.19209290e-7F", - "__GXX_WEAK__=1", - "__SHRT_WIDTH__=16", - "__USFRACT_IBIT__=0", - "__LDBL_MIN__=1.17549435e-38L", - "__FRACT_MIN__=(-0.5R-0.5R)", - "__AVR_SFR_OFFSET__=0x20", - "__DEC32_MAX__=9.999999E96DF", - "__cpp_threadsafe_static_init=200806", - "__DA_IBIT__=32", - "__INT32_MAX__=0x7fffffffL", - "__UQQ_FBIT__=8", - "__INT_WIDTH__=16", - "__SIZEOF_LONG__=4", - "__UACCUM_MAX__=0XFFFFFFFFP-16UK", - "__UINT16_C(c)=c ## U", - "__PTRDIFF_WIDTH__=16", - "__DECIMAL_DIG__=9", - "__LFRACT_EPSILON__=0x1P-31LR", - "__AVR_2_BYTE_PC__=1", - "__ULFRACT_MIN__=0.0ULR", - "__INTMAX_WIDTH__=64", - "__has_include_next(STR)=__has_include_next__(STR)", - "__BUILTIN_AVR_ULLRBITS=1", - "__LDBL_HAS_QUIET_NAN__=1", - "__ULACCUM_IBIT__=32", - "__UACCUM_EPSILON__=0x1P-16UK", - "__BUILTIN_AVR_SEI=1", - "__GNUC__=7", - "__ULLACCUM_MAX__=0XFFFFFFFFFFFFFFFFP-48ULLK", - "__cpp_delegating_constructors=200604", - "__HQ_IBIT__=0", - "__BUILTIN_AVR_SWAP=1", - "__FLT_HAS_DENORM__=1", - "__SIZEOF_LONG_DOUBLE__=4", - "__BIGGEST_ALIGNMENT__=1", - "__STDC_UTF_16__=1", - "__UINT24_MAX__=16777215UL", - "__BUILTIN_AVR_NOP=1", - "__GNUC_STDC_INLINE__=1", - "__DQ_IBIT__=0", - "__FLT32_HAS_INFINITY__=1", - "__DBL_MAX__=double(3.40282347e+38L)", - "__ULFRACT_IBIT__=0", - "__cpp_raw_strings=200710", - "__INT_FAST32_MAX__=0x7fffffffL", - "__DBL_HAS_INFINITY__=1", - "__INT64_MAX__=0x7fffffffffffffffLL", - "__ACCUM_IBIT__=16", - "__DEC32_MIN_EXP__=(-94)", - "__BUILTIN_AVR_UKBITS=1", - "__INTPTR_WIDTH__=16", - "__BUILTIN_AVR_FMULSU=1", - "__LACCUM_MAX__=0X7FFFFFFFFFFFFFFFP-31LK", - "__INT_FAST16_TYPE__=int", - "__LDBL_HAS_DENORM__=1", - "__BUILTIN_AVR_BITSK=1", - "__BUILTIN_AVR_BITSR=1", - "__cplusplus=201402L", - "__cpp_ref_qualifiers=200710", - "__DEC128_MAX__=9.999999999999999999999999999999999E6144DL", - "__INT_LEAST32_MAX__=0x7fffffffL", - "__USING_SJLJ_EXCEPTIONS__=1", - "__DEC32_MIN__=1E-95DF", - "__ACCUM_MAX__=0X7FFFFFFFP-15K", - "__DEPRECATED=1", - "__cpp_rvalue_references=200610", - "__DBL_MAX_EXP__=128", - "__USACCUM_EPSILON__=0x1P-8UHK", - "__WCHAR_WIDTH__=16", - "__FLT32_MAX__=3.40282347e+38F32", - "__DEC128_EPSILON__=1E-33DL", - "__SFRACT_MAX__=0X7FP-7HR", - "__FRACT_IBIT__=0", - "__PTRDIFF_MAX__=0x7fff", - "__UACCUM_MIN__=0.0UK", - "__UACCUM_IBIT__=16", - "__BUILTIN_AVR_NOPS=1", - "__BUILTIN_AVR_WDR=1", - "__FLT32_HAS_QUIET_NAN__=1", - "__GNUG__=7", - "__LONG_LONG_MAX__=0x7fffffffffffffffLL", - "__SIZEOF_SIZE_T__=2", - "__ULACCUM_MAX__=0XFFFFFFFFFFFFFFFFP-32ULK", - "__cpp_rvalue_reference=200610", - "__cpp_nsdmi=200809", - "__SIZEOF_WINT_T__=2", - "__LONG_LONG_WIDTH__=64", - "__cpp_initializer_lists=200806", - "__FLT32_MAX_EXP__=128", - "__SA_IBIT__=16", - "__ULLACCUM_MIN__=0.0ULLK", - "__BUILTIN_AVR_ROUNDUHK=1", - "__BUILTIN_AVR_ROUNDUHR=1", - "__cpp_hex_float=201603", - "__GXX_ABI_VERSION=1011", - "__INT24_MAX__=8388607L", - "__UTA_FBIT__=48", - "__FLT_MIN_EXP__=(-125)", - "__USFRACT_MAX__=0XFFP-8UHR", - "__UFRACT_IBIT__=0", - "__BUILTIN_AVR_ROUNDFX=1", - "__BUILTIN_AVR_ROUNDULK=1", - "__BUILTIN_AVR_ROUNDULR=1", - "__cpp_lambdas=200907", - "__BUILTIN_AVR_COUNTLSLLK=1", - "__BUILTIN_AVR_COUNTLSLLR=1", - "__BUILTIN_AVR_ROUNDHK=1", - "__INT_FAST64_TYPE__=long long int", - "__BUILTIN_AVR_ROUNDHR=1", - "__DBL_MIN__=double(1.17549435e-38L)", - "__BUILTIN_AVR_COUNTLSK=1", - "__BUILTIN_AVR_ROUNDLK=1", - "__BUILTIN_AVR_COUNTLSR=1", - "__BUILTIN_AVR_ROUNDLR=1", - "__LACCUM_MIN__=(-0X1P31LK-0X1P31LK)", - "__ULLACCUM_FBIT__=48", - "__BUILTIN_AVR_LKBITS=1", - "__ULLFRACT_EPSILON__=0x1P-64ULLR", - "__DEC128_MIN__=1E-6143DL", - "__REGISTER_PREFIX__", - "__UINT16_MAX__=0xffffU", - "__DBL_HAS_DENORM__=1", - "__BUILTIN_AVR_ULKBITS=1", - "__ACCUM_MIN__=(-0X1P15K-0X1P15K)", - "__AVR_ARCH__=2", - "__SQ_IBIT__=0", - "__FLT32_MIN__=1.17549435e-38F32", - "__UINT8_TYPE__=unsigned char", - "__BUILTIN_AVR_ROUNDUK=1", - "__BUILTIN_AVR_ROUNDUR=1", - "__UHA_FBIT__=8", - "__NO_INLINE__=1", - "__SFRACT_MIN__=(-0.5HR-0.5HR)", - "__UTQ_FBIT__=128", - "__FLT_MANT_DIG__=24", - "__LDBL_DECIMAL_DIG__=9", - "__VERSION__=\"7.3.0\"", - "__UINT64_C(c)=c ## ULL", - "__ULLFRACT_FBIT__=64", - "__cpp_unicode_characters=200704", - "__FRACT_EPSILON__=0x1P-15R", - "__ULACCUM_MIN__=0.0ULK", - "__UDA_FBIT__=32", - "__cpp_decltype_auto=201304", - "__LLACCUM_EPSILON__=0x1P-47LLK", - "__GCC_ATOMIC_INT_LOCK_FREE=1", - "__FLT32_MANT_DIG__=24", - "__BUILTIN_AVR_BITSUHK=1", - "__BUILTIN_AVR_BITSUHR=1", - "__FLOAT_WORD_ORDER__=__ORDER_LITTLE_ENDIAN__", - "__USFRACT_MIN__=0.0UHR", - "__BUILTIN_AVR_BITSULK=1", - "__ULLACCUM_IBIT__=16", - "__BUILTIN_AVR_BITSULR=1", - "__UQQ_IBIT__=0", - "__BUILTIN_AVR_LLRBITS=1", - "__SCHAR_WIDTH__=8", - "__BUILTIN_AVR_BITSULLK=1", - "__BUILTIN_AVR_BITSULLR=1", - "__INT32_C(c)=c ## L", - "__DEC64_EPSILON__=1E-15DD", - "__ORDER_PDP_ENDIAN__=3412", - "__DEC128_MIN_EXP__=(-6142)", - "__UHQ_FBIT__=16", - "__LLACCUM_FBIT__=47", - "__FLT32_MAX_10_EXP__=38", - "__BUILTIN_AVR_ROUNDULLK=1", - "__BUILTIN_AVR_ROUNDULLR=1", - "__INT_FAST32_TYPE__=long int", - "__BUILTIN_AVR_HRBITS=1", - "__UINT_LEAST16_TYPE__=unsigned int", - "__BUILTIN_AVR_UHRBITS=1", - "__INT16_MAX__=0x7fff", - "__SIZE_TYPE__=unsigned int", - "__UINT64_MAX__=0xffffffffffffffffULL", - "__UDQ_FBIT__=64", - "__INT8_TYPE__=signed char", - "__cpp_digit_separators=201309", - "__ELF__=1", - "__ULFRACT_EPSILON__=0x1P-32ULR", - "__LLFRACT_FBIT__=63", - "__FLT_RADIX__=2", - "__INT_LEAST16_TYPE__=int", - "__BUILTIN_AVR_ABSFX=1", - "__LDBL_EPSILON__=1.19209290e-7L", - "__UINTMAX_C(c)=c ## ULL", - "__INT24_MIN__=(-__INT24_MAX__-1)", - "__SACCUM_MAX__=0X7FFFP-7HK", - "__BUILTIN_AVR_ABSHR=1", - "__SIG_ATOMIC_MAX__=0x7f", - "__GCC_ATOMIC_WCHAR_T_LOCK_FREE=1", - "__cpp_sized_deallocation=201309", - "__SIZEOF_PTRDIFF_T__=2", - "__AVR=1", - "__BUILTIN_AVR_ABSLK=1", - "__BUILTIN_AVR_ABSLR=1", - "__LACCUM_EPSILON__=0x1P-31LK", - "__DEC32_SUBNORMAL_MIN__=0.000001E-95DF", - "__INT_FAST16_MAX__=0x7fff", - "__UINT_FAST32_MAX__=0xffffffffUL", - "__UINT_LEAST64_TYPE__=long long unsigned int", - "__USACCUM_MAX__=0XFFFFP-8UHK", - "__SFRACT_EPSILON__=0x1P-7HR", - "__FLT_HAS_QUIET_NAN__=1", - "__FLT_MAX_10_EXP__=38", - "__LONG_MAX__=0x7fffffffL", - "__DEC128_SUBNORMAL_MIN__=0.000000000000000000000000000000001E-6143DL", - "__FLT_HAS_INFINITY__=1", - "__cpp_unicode_literals=200710", - "__USA_FBIT__=16", - "__UINT_FAST16_TYPE__=unsigned int", - "__DEC64_MAX__=9.999999999999999E384DD", - "__INT_FAST32_WIDTH__=32", - "__BUILTIN_AVR_RBITS=1", - "__CHAR16_TYPE__=unsigned int", - "__PRAGMA_REDEFINE_EXTNAME=1", - "__SIZE_WIDTH__=16", - "__INT_LEAST16_MAX__=0x7fff", - "__DEC64_MANT_DIG__=16", - "__UINT_LEAST32_MAX__=0xffffffffUL", - "__SACCUM_FBIT__=7", - "__FLT32_DENORM_MIN__=1.40129846e-45F32", - "__GCC_ATOMIC_LONG_LOCK_FREE=1", - "__SIG_ATOMIC_WIDTH__=8", - "__INT_LEAST64_TYPE__=long long int", - "__INT16_TYPE__=int", - "__INT_LEAST8_TYPE__=signed char", - "__SQ_FBIT__=31", - "__DEC32_MAX_EXP__=97", - "__INT_FAST8_MAX__=0x7f", - "__INTPTR_MAX__=0x7fff", - "__QQ_FBIT__=7", - "__cpp_range_based_for=200907", - "__UTA_IBIT__=16", - "__AVR_ERRATA_SKIP__=1", - "__FLT32_MIN_10_EXP__=(-37)", - "__LDBL_MANT_DIG__=24", - "__SFRACT_FBIT__=7", - "__SACCUM_MIN__=(-0X1P7HK-0X1P7HK)", - "__DBL_HAS_QUIET_NAN__=1", - "__SIG_ATOMIC_MIN__=(-__SIG_ATOMIC_MAX__ - 1)", - "AVR=1", - "__BUILTIN_AVR_FMULS=1", - "__cpp_return_type_deduction=201304", - "__INTPTR_TYPE__=int", - "__UINT16_TYPE__=unsigned int", - "__WCHAR_TYPE__=int", - "__SIZEOF_FLOAT__=4", - "__AVR__=1", - "__BUILTIN_AVR_INSERT_BITS=1", - "__USQ_FBIT__=32", - "__UINTPTR_MAX__=0xffffU", - "__INT_FAST64_WIDTH__=64", - "__DEC64_MIN_EXP__=(-382)", - "__cpp_decltype=200707", - "__FLT32_DECIMAL_DIG__=9", - "__INT_FAST64_MAX__=0x7fffffffffffffffLL", - "__GCC_ATOMIC_TEST_AND_SET_TRUEVAL=1", - "__FLT_DIG__=6", - "__UINT_FAST64_TYPE__=long long unsigned int", - "__BUILTIN_AVR_BITSHK=1", - "__BUILTIN_AVR_BITSHR=1", - "__INT_MAX__=0x7fff", - "__LACCUM_FBIT__=31", - "__USACCUM_MIN__=0.0UHK", - "__UHA_IBIT__=8", - "__INT64_TYPE__=long long int", - "__BUILTIN_AVR_BITSLK=1", - "__BUILTIN_AVR_BITSLR=1", - "__FLT_MAX_EXP__=128", - "__UTQ_IBIT__=0", - "__DBL_MANT_DIG__=24", - "__cpp_inheriting_constructors=201511", - "__BUILTIN_AVR_ULLKBITS=1", - "__INT_LEAST64_MAX__=0x7fffffffffffffffLL", - "__DEC64_MIN__=1E-383DD", - "__WINT_TYPE__=int", - "__UINT_LEAST32_TYPE__=long unsigned int", - "__SIZEOF_SHORT__=2", - "__ULLFRACT_IBIT__=0", - "__LDBL_MIN_EXP__=(-125)", - "__UDA_IBIT__=32", - "__WINT_WIDTH__=16", - "__INT_LEAST8_MAX__=0x7f", - "__LFRACT_FBIT__=31", - "__LDBL_MAX_10_EXP__=38", - "__ATOMIC_RELAXED=0", - "__DBL_EPSILON__=double(1.19209290e-7L)", - "__BUILTIN_AVR_BITSUK=1", - "__BUILTIN_AVR_BITSUR=1", - "__UINT8_C(c)=c", - "__INT_LEAST32_TYPE__=long int", - "__BUILTIN_AVR_URBITS=1", - "__SIZEOF_WCHAR_T__=2", - "__LLFRACT_MAX__=0X7FFFFFFFFFFFFFFFP-63LLR", - "__TQ_FBIT__=127", - "__INT_FAST8_TYPE__=signed char", - "__ULLACCUM_EPSILON__=0x1P-48ULLK", - "__BUILTIN_AVR_ROUNDK=1", - "__BUILTIN_AVR_ROUNDR=1", - "__UHQ_IBIT__=0", - "__LLACCUM_IBIT__=16", - "__FLT32_EPSILON__=1.19209290e-7F32", - "__DBL_DECIMAL_DIG__=9", - "__STDC_UTF_32__=1", - "__INT_FAST8_WIDTH__=8", - "__DEC_EVAL_METHOD__=2", - "__TA_FBIT__=47", - "__UDQ_IBIT__=0", - "__ORDER_BIG_ENDIAN__=4321", - "__cpp_runtime_arrays=198712", - "__WITH_AVRLIBC__=1", - "__UINT64_TYPE__=long long unsigned int", - "__ACCUM_EPSILON__=0x1P-15K", - "__UINT32_C(c)=c ## UL", - "__BUILTIN_AVR_COUNTLSUHK=1", - "__INTMAX_MAX__=0x7fffffffffffffffLL", - "__cpp_alias_templates=200704", - "__BUILTIN_AVR_COUNTLSUHR=1", - "__BYTE_ORDER__=__ORDER_LITTLE_ENDIAN__", - "__FLT_DENORM_MIN__=1.40129846e-45F", - "__LLFRACT_IBIT__=0", - "__INT8_MAX__=0x7f", - "__LONG_WIDTH__=32", - "__UINT_FAST32_TYPE__=long unsigned int", - "__CHAR32_TYPE__=long unsigned int", - "__BUILTIN_AVR_COUNTLSULK=1", - "__BUILTIN_AVR_COUNTLSULR=1", - "__FLT_MAX__=3.40282347e+38F", - "__cpp_constexpr=201304", - "__USACCUM_FBIT__=8", - "__BUILTIN_AVR_COUNTLSFX=1", - "__INT32_TYPE__=long int", - "__SIZEOF_DOUBLE__=4", - "__FLT_MIN_10_EXP__=(-37)", - "__UFRACT_EPSILON__=0x1P-16UR", - "__INT_LEAST32_WIDTH__=32", - "__BUILTIN_AVR_COUNTLSHK=1", - "__BUILTIN_AVR_COUNTLSHR=1", - "__INTMAX_TYPE__=long long int", - "__BUILTIN_AVR_ABSLLK=1", - "__BUILTIN_AVR_ABSLLR=1", - "__DEC128_MAX_EXP__=6145", - "__AVR_HAVE_16BIT_SP__=1", - "__ATOMIC_CONSUME=1", - "__GNUC_MINOR__=3", - "__INT_FAST16_WIDTH__=16", - "__UINTMAX_MAX__=0xffffffffffffffffULL", - "__DEC32_MANT_DIG__=7", - "__HA_FBIT__=7", - "__BUILTIN_AVR_COUNTLSLK=1", - "__BUILTIN_AVR_COUNTLSLR=1", - "__BUILTIN_AVR_CLI=1", - "__DBL_MAX_10_EXP__=38", - "__LDBL_DENORM_MIN__=1.40129846e-45L", - "__INT16_C(c)=c", - "__cpp_generic_lambdas=201304", - "__STDC__=1", - "__PTRDIFF_TYPE__=int", - "__LLFRACT_MIN__=(-0.5LLR-0.5LLR)", - "__BUILTIN_AVR_LRBITS=1", - "__ATOMIC_SEQ_CST=5", - "__DA_FBIT__=31", - "__UINT32_TYPE__=long unsigned int", - "__BUILTIN_AVR_ROUNDLLK=1", - "__UINTPTR_TYPE__=unsigned int", - "__BUILTIN_AVR_ROUNDLLR=1", - "__USA_IBIT__=16", - "__BUILTIN_AVR_ULRBITS=1", - "__DEC64_SUBNORMAL_MIN__=0.000000000000001E-383DD", - "__DEC128_MANT_DIG__=34", - "__LDBL_MIN_10_EXP__=(-37)", - "__BUILTIN_AVR_COUNTLSUK=1", - "__BUILTIN_AVR_COUNTLSUR=1", - "__SIZEOF_LONG_LONG__=8", - "__ULACCUM_EPSILON__=0x1P-32ULK", - "__cpp_user_defined_literals=200809", - "__SACCUM_IBIT__=8", - "__GCC_ATOMIC_LLONG_LOCK_FREE=1", - "__LDBL_DIG__=6", - "__FLT_DECIMAL_DIG__=9", - "__UINT_FAST16_MAX__=0xffffU", - "__GCC_ATOMIC_SHORT_LOCK_FREE=1", - "__BUILTIN_AVR_ABSHK=1", - "__BUILTIN_AVR_FLASH_SEGMENT=1", - "__INT_LEAST64_WIDTH__=64", - "__ULLFRACT_MAX__=0XFFFFFFFFFFFFFFFFP-64ULLR", - "__UINT_FAST8_TYPE__=unsigned char", - "__USFRACT_EPSILON__=0x1P-8UHR", - "__ULACCUM_FBIT__=32", - "__QQ_IBIT__=0", - "__cpp_init_captures=201304", - "__ATOMIC_ACQ_REL=4", - "__ATOMIC_RELEASE=3", - "__BUILTIN_AVR_FMUL=1", - "USBCON" - ] - } - ] -} \ No newline at end of file diff --git a/arduino_sketch/.vscode/settings.json b/arduino_sketch/.vscode/settings.json deleted file mode 100644 index 46d9a84f..00000000 --- a/arduino_sketch/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ros.distro": "humble" -} \ No newline at end of file diff --git a/circuits/v2/Lower/Gerber.zip b/circuits/archie/Lower/Gerber.zip similarity index 100% rename from circuits/v2/Lower/Gerber.zip rename to circuits/archie/Lower/Gerber.zip diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_contour.gm1 b/circuits/archie/Lower/Gerber/Lower Feb 2021_contour.gm1 similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_contour.gm1 rename to circuits/archie/Lower/Gerber/Lower Feb 2021_contour.gm1 diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_copperBottom.gbl b/circuits/archie/Lower/Gerber/Lower Feb 2021_copperBottom.gbl similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_copperBottom.gbl rename to circuits/archie/Lower/Gerber/Lower Feb 2021_copperBottom.gbl diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_copperTop.gtl b/circuits/archie/Lower/Gerber/Lower Feb 2021_copperTop.gtl similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_copperTop.gtl rename to circuits/archie/Lower/Gerber/Lower Feb 2021_copperTop.gtl diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_drill.txt b/circuits/archie/Lower/Gerber/Lower Feb 2021_drill.txt similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_drill.txt rename to circuits/archie/Lower/Gerber/Lower Feb 2021_drill.txt diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_maskBottom.gbs b/circuits/archie/Lower/Gerber/Lower Feb 2021_maskBottom.gbs similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_maskBottom.gbs rename to circuits/archie/Lower/Gerber/Lower Feb 2021_maskBottom.gbs diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_maskTop.gts b/circuits/archie/Lower/Gerber/Lower Feb 2021_maskTop.gts similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_maskTop.gts rename to circuits/archie/Lower/Gerber/Lower Feb 2021_maskTop.gts diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_pnp.txt b/circuits/archie/Lower/Gerber/Lower Feb 2021_pnp.txt similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_pnp.txt rename to circuits/archie/Lower/Gerber/Lower Feb 2021_pnp.txt diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_silkBottom.gbo b/circuits/archie/Lower/Gerber/Lower Feb 2021_silkBottom.gbo similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_silkBottom.gbo rename to circuits/archie/Lower/Gerber/Lower Feb 2021_silkBottom.gbo diff --git a/circuits/v2/Lower/Gerber/Lower Feb 2021_silkTop.gto b/circuits/archie/Lower/Gerber/Lower Feb 2021_silkTop.gto similarity index 100% rename from circuits/v2/Lower/Gerber/Lower Feb 2021_silkTop.gto rename to circuits/archie/Lower/Gerber/Lower Feb 2021_silkTop.gto diff --git a/circuits/v2/Lower/Lower Feb 2021.fzz b/circuits/archie/Lower/Lower Feb 2021.fzz similarity index 100% rename from circuits/v2/Lower/Lower Feb 2021.fzz rename to circuits/archie/Lower/Lower Feb 2021.fzz diff --git a/circuits/v2/Lower/Lower Feb 2021_pcb.png b/circuits/archie/Lower/Lower Feb 2021_pcb.png similarity index 100% rename from circuits/v2/Lower/Lower Feb 2021_pcb.png rename to circuits/archie/Lower/Lower Feb 2021_pcb.png diff --git a/circuits/v2/Lower/Screenshot 2021-02-15 at 19.20.31.png b/circuits/archie/Lower/Screenshot 2021-02-15 at 19.20.31.png similarity index 100% rename from circuits/v2/Lower/Screenshot 2021-02-15 at 19.20.31.png rename to circuits/archie/Lower/Screenshot 2021-02-15 at 19.20.31.png diff --git a/circuits/v2/Lower/Screenshot 2021-02-15 at 19.20.37.png b/circuits/archie/Lower/Screenshot 2021-02-15 at 19.20.37.png similarity index 100% rename from circuits/v2/Lower/Screenshot 2021-02-15 at 19.20.37.png rename to circuits/archie/Lower/Screenshot 2021-02-15 at 19.20.37.png diff --git a/circuits/v2/Upper/Gerber.zip b/circuits/archie/Upper/Gerber.zip similarity index 100% rename from circuits/v2/Upper/Gerber.zip rename to circuits/archie/Upper/Gerber.zip diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_contour.gm1 b/circuits/archie/Upper/Gerber/Top Feb 2021_contour.gm1 similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_contour.gm1 rename to circuits/archie/Upper/Gerber/Top Feb 2021_contour.gm1 diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_copperBottom.gbl b/circuits/archie/Upper/Gerber/Top Feb 2021_copperBottom.gbl similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_copperBottom.gbl rename to circuits/archie/Upper/Gerber/Top Feb 2021_copperBottom.gbl diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_copperTop.gtl b/circuits/archie/Upper/Gerber/Top Feb 2021_copperTop.gtl similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_copperTop.gtl rename to circuits/archie/Upper/Gerber/Top Feb 2021_copperTop.gtl diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_drill.txt b/circuits/archie/Upper/Gerber/Top Feb 2021_drill.txt similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_drill.txt rename to circuits/archie/Upper/Gerber/Top Feb 2021_drill.txt diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_maskBottom.gbs b/circuits/archie/Upper/Gerber/Top Feb 2021_maskBottom.gbs similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_maskBottom.gbs rename to circuits/archie/Upper/Gerber/Top Feb 2021_maskBottom.gbs diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_maskTop.gts b/circuits/archie/Upper/Gerber/Top Feb 2021_maskTop.gts similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_maskTop.gts rename to circuits/archie/Upper/Gerber/Top Feb 2021_maskTop.gts diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_pnp.txt b/circuits/archie/Upper/Gerber/Top Feb 2021_pnp.txt similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_pnp.txt rename to circuits/archie/Upper/Gerber/Top Feb 2021_pnp.txt diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_silkBottom.gbo b/circuits/archie/Upper/Gerber/Top Feb 2021_silkBottom.gbo similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_silkBottom.gbo rename to circuits/archie/Upper/Gerber/Top Feb 2021_silkBottom.gbo diff --git a/circuits/v2/Upper/Gerber/Top Feb 2021_silkTop.gto b/circuits/archie/Upper/Gerber/Top Feb 2021_silkTop.gto similarity index 100% rename from circuits/v2/Upper/Gerber/Top Feb 2021_silkTop.gto rename to circuits/archie/Upper/Gerber/Top Feb 2021_silkTop.gto diff --git a/circuits/v2/Upper/Screenshot 2021-02-15 at 19.51.57.png b/circuits/archie/Upper/Screenshot 2021-02-15 at 19.51.57.png similarity index 100% rename from circuits/v2/Upper/Screenshot 2021-02-15 at 19.51.57.png rename to circuits/archie/Upper/Screenshot 2021-02-15 at 19.51.57.png diff --git a/circuits/v2/Upper/Screenshot 2021-02-15 at 19.52.01.png b/circuits/archie/Upper/Screenshot 2021-02-15 at 19.52.01.png similarity index 100% rename from circuits/v2/Upper/Screenshot 2021-02-15 at 19.52.01.png rename to circuits/archie/Upper/Screenshot 2021-02-15 at 19.52.01.png diff --git a/circuits/v2/Upper/Top Feb 2021.fzz b/circuits/archie/Upper/Top Feb 2021.fzz similarity index 100% rename from circuits/v2/Upper/Top Feb 2021.fzz rename to circuits/archie/Upper/Top Feb 2021.fzz diff --git a/circuits/v2/Upper/Top Feb 2021_pcb.png b/circuits/archie/Upper/Top Feb 2021_pcb.png similarity index 100% rename from circuits/v2/Upper/Top Feb 2021_pcb.png rename to circuits/archie/Upper/Top Feb 2021_pcb.png diff --git a/circuits/v3/archive/body_contour.gm1 b/circuits/buddy/archive/body_contour.gm1 similarity index 100% rename from circuits/v3/archive/body_contour.gm1 rename to circuits/buddy/archive/body_contour.gm1 diff --git a/circuits/v3/archive/body_copperBottom.gbl b/circuits/buddy/archive/body_copperBottom.gbl similarity index 100% rename from circuits/v3/archive/body_copperBottom.gbl rename to circuits/buddy/archive/body_copperBottom.gbl diff --git a/circuits/v3/archive/body_copperTop.gtl b/circuits/buddy/archive/body_copperTop.gtl similarity index 100% rename from circuits/v3/archive/body_copperTop.gtl rename to circuits/buddy/archive/body_copperTop.gtl diff --git a/circuits/v3/archive/body_drill.txt b/circuits/buddy/archive/body_drill.txt similarity index 100% rename from circuits/v3/archive/body_drill.txt rename to circuits/buddy/archive/body_drill.txt diff --git a/circuits/v3/archive/body_maskBottom.gbs b/circuits/buddy/archive/body_maskBottom.gbs similarity index 100% rename from circuits/v3/archive/body_maskBottom.gbs rename to circuits/buddy/archive/body_maskBottom.gbs diff --git a/circuits/v3/archive/body_maskTop.gts b/circuits/buddy/archive/body_maskTop.gts similarity index 100% rename from circuits/v3/archive/body_maskTop.gts rename to circuits/buddy/archive/body_maskTop.gts diff --git a/circuits/v3/archive/body_pnp.txt b/circuits/buddy/archive/body_pnp.txt similarity index 100% rename from circuits/v3/archive/body_pnp.txt rename to circuits/buddy/archive/body_pnp.txt diff --git a/circuits/v3/archive/body_silkBottom.gbo b/circuits/buddy/archive/body_silkBottom.gbo similarity index 100% rename from circuits/v3/archive/body_silkBottom.gbo rename to circuits/buddy/archive/body_silkBottom.gbo diff --git a/circuits/v3/archive/body_silkTop.gto b/circuits/buddy/archive/body_silkTop.gto similarity index 100% rename from circuits/v3/archive/body_silkTop.gto rename to circuits/buddy/archive/body_silkTop.gto diff --git a/circuits/v3/archive/body_v3.zip b/circuits/buddy/archive/body_v3.zip similarity index 100% rename from circuits/v3/archive/body_v3.zip rename to circuits/buddy/archive/body_v3.zip diff --git a/circuits/v3/head/Gerber.zip b/circuits/buddy/archive/head/Gerber.zip similarity index 100% rename from circuits/v3/head/Gerber.zip rename to circuits/buddy/archive/head/Gerber.zip diff --git a/circuits/v3/head/Gerber/Top Feb 2021_contour.gm1 b/circuits/buddy/archive/head/Gerber/Top Feb 2021_contour.gm1 similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_contour.gm1 rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_contour.gm1 diff --git a/circuits/v3/head/Gerber/Top Feb 2021_copperBottom.gbl b/circuits/buddy/archive/head/Gerber/Top Feb 2021_copperBottom.gbl similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_copperBottom.gbl rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_copperBottom.gbl diff --git a/circuits/v3/head/Gerber/Top Feb 2021_copperTop.gtl b/circuits/buddy/archive/head/Gerber/Top Feb 2021_copperTop.gtl similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_copperTop.gtl rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_copperTop.gtl diff --git a/circuits/v3/head/Gerber/Top Feb 2021_drill.txt b/circuits/buddy/archive/head/Gerber/Top Feb 2021_drill.txt similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_drill.txt rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_drill.txt diff --git a/circuits/v3/head/Gerber/Top Feb 2021_maskBottom.gbs b/circuits/buddy/archive/head/Gerber/Top Feb 2021_maskBottom.gbs similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_maskBottom.gbs rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_maskBottom.gbs diff --git a/circuits/v3/head/Gerber/Top Feb 2021_maskTop.gts b/circuits/buddy/archive/head/Gerber/Top Feb 2021_maskTop.gts similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_maskTop.gts rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_maskTop.gts diff --git a/circuits/v3/head/Gerber/Top Feb 2021_pnp.txt b/circuits/buddy/archive/head/Gerber/Top Feb 2021_pnp.txt similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_pnp.txt rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_pnp.txt diff --git a/circuits/v3/head/Gerber/Top Feb 2021_silkBottom.gbo b/circuits/buddy/archive/head/Gerber/Top Feb 2021_silkBottom.gbo similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_silkBottom.gbo rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_silkBottom.gbo diff --git a/circuits/v3/head/Gerber/Top Feb 2021_silkTop.gto b/circuits/buddy/archive/head/Gerber/Top Feb 2021_silkTop.gto similarity index 100% rename from circuits/v3/head/Gerber/Top Feb 2021_silkTop.gto rename to circuits/buddy/archive/head/Gerber/Top Feb 2021_silkTop.gto diff --git a/circuits/v3/head/head.fzz b/circuits/buddy/archive/head/head.fzz similarity index 100% rename from circuits/v3/head/head.fzz rename to circuits/buddy/archive/head/head.fzz diff --git a/circuits/v3/head/head_pcb.png b/circuits/buddy/archive/head/head_pcb.png similarity index 100% rename from circuits/v3/head/head_pcb.png rename to circuits/buddy/archive/head/head_pcb.png diff --git a/circuits/v3/archive/v3_1/body3_1.zip b/circuits/buddy/archive/v3_1/body3_1.zip similarity index 100% rename from circuits/v3/archive/v3_1/body3_1.zip rename to circuits/buddy/archive/v3_1/body3_1.zip diff --git a/circuits/v3/archive/v3_1/body_contour.gm1 b/circuits/buddy/archive/v3_1/body_contour.gm1 similarity index 100% rename from circuits/v3/archive/v3_1/body_contour.gm1 rename to circuits/buddy/archive/v3_1/body_contour.gm1 diff --git a/circuits/v3/archive/v3_1/body_copperBottom.gbl b/circuits/buddy/archive/v3_1/body_copperBottom.gbl similarity index 100% rename from circuits/v3/archive/v3_1/body_copperBottom.gbl rename to circuits/buddy/archive/v3_1/body_copperBottom.gbl diff --git a/circuits/v3/archive/v3_1/body_copperTop.gtl b/circuits/buddy/archive/v3_1/body_copperTop.gtl similarity index 100% rename from circuits/v3/archive/v3_1/body_copperTop.gtl rename to circuits/buddy/archive/v3_1/body_copperTop.gtl diff --git a/circuits/v3/archive/v3_1/body_drill.txt b/circuits/buddy/archive/v3_1/body_drill.txt similarity index 100% rename from circuits/v3/archive/v3_1/body_drill.txt rename to circuits/buddy/archive/v3_1/body_drill.txt diff --git a/circuits/v3/archive/v3_1/body_maskBottom.gbs b/circuits/buddy/archive/v3_1/body_maskBottom.gbs similarity index 100% rename from circuits/v3/archive/v3_1/body_maskBottom.gbs rename to circuits/buddy/archive/v3_1/body_maskBottom.gbs diff --git a/circuits/v3/archive/v3_1/body_maskTop.gts b/circuits/buddy/archive/v3_1/body_maskTop.gts similarity index 100% rename from circuits/v3/archive/v3_1/body_maskTop.gts rename to circuits/buddy/archive/v3_1/body_maskTop.gts diff --git a/circuits/v3/archive/v3_1/body_pnp.txt b/circuits/buddy/archive/v3_1/body_pnp.txt similarity index 100% rename from circuits/v3/archive/v3_1/body_pnp.txt rename to circuits/buddy/archive/v3_1/body_pnp.txt diff --git a/circuits/v3/archive/v3_1/body_silkBottom.gbo b/circuits/buddy/archive/v3_1/body_silkBottom.gbo similarity index 100% rename from circuits/v3/archive/v3_1/body_silkBottom.gbo rename to circuits/buddy/archive/v3_1/body_silkBottom.gbo diff --git a/circuits/v3/archive/v3_1/body_silkTop.gto b/circuits/buddy/archive/v3_1/body_silkTop.gto similarity index 100% rename from circuits/v3/archive/v3_1/body_silkTop.gto rename to circuits/buddy/archive/v3_1/body_silkTop.gto diff --git a/circuits/v3/body/body.fzz b/circuits/buddy/body/body.fzz similarity index 100% rename from circuits/v3/body/body.fzz rename to circuits/buddy/body/body.fzz diff --git a/circuits/v3/body/body_3_2.zip b/circuits/buddy/body/body_3_2.zip similarity index 100% rename from circuits/v3/body/body_3_2.zip rename to circuits/buddy/body/body_3_2.zip diff --git a/circuits/v3/body/body_contour.gm1 b/circuits/buddy/body/body_contour.gm1 similarity index 100% rename from circuits/v3/body/body_contour.gm1 rename to circuits/buddy/body/body_contour.gm1 diff --git a/circuits/v3/body/body_copperBottom.gbl b/circuits/buddy/body/body_copperBottom.gbl similarity index 100% rename from circuits/v3/body/body_copperBottom.gbl rename to circuits/buddy/body/body_copperBottom.gbl diff --git a/circuits/v3/body/body_copperTop.gtl b/circuits/buddy/body/body_copperTop.gtl similarity index 100% rename from circuits/v3/body/body_copperTop.gtl rename to circuits/buddy/body/body_copperTop.gtl diff --git a/circuits/v3/body/body_drill.txt b/circuits/buddy/body/body_drill.txt similarity index 100% rename from circuits/v3/body/body_drill.txt rename to circuits/buddy/body/body_drill.txt diff --git a/circuits/v3/body/body_maskBottom.gbs b/circuits/buddy/body/body_maskBottom.gbs similarity index 100% rename from circuits/v3/body/body_maskBottom.gbs rename to circuits/buddy/body/body_maskBottom.gbs diff --git a/circuits/v3/body/body_maskTop.gts b/circuits/buddy/body/body_maskTop.gts similarity index 100% rename from circuits/v3/body/body_maskTop.gts rename to circuits/buddy/body/body_maskTop.gts diff --git a/circuits/v3/body/body_pcb.png b/circuits/buddy/body/body_pcb.png similarity index 100% rename from circuits/v3/body/body_pcb.png rename to circuits/buddy/body/body_pcb.png diff --git a/circuits/v3/body/body_pnp.txt b/circuits/buddy/body/body_pnp.txt similarity index 100% rename from circuits/v3/body/body_pnp.txt rename to circuits/buddy/body/body_pnp.txt diff --git a/circuits/v3/body/body_silkBottom.gbo b/circuits/buddy/body/body_silkBottom.gbo similarity index 100% rename from circuits/v3/body/body_silkBottom.gbo rename to circuits/buddy/body/body_silkBottom.gbo diff --git a/circuits/v3/body/body_silkTop.gto b/circuits/buddy/body/body_silkTop.gto similarity index 100% rename from circuits/v3/body/body_silkTop.gto rename to circuits/buddy/body/body_silkTop.gto diff --git a/circuits/v4/Head/head_schematic.pdf b/circuits/buddy/head/head_schematic.pdf similarity index 100% rename from circuits/v4/Head/head_schematic.pdf rename to circuits/buddy/head/head_schematic.pdf diff --git a/circuits/v4/Head/head_schematic.png b/circuits/buddy/head/head_schematic.png similarity index 100% rename from circuits/v4/Head/head_schematic.png rename to circuits/buddy/head/head_schematic.png diff --git a/circuits/v4/Head/v4head/ComponentLibrary.bak b/circuits/buddy/head/v4head/ComponentLibrary.bak similarity index 100% rename from circuits/v4/Head/v4head/ComponentLibrary.bak rename to circuits/buddy/head/v4head/ComponentLibrary.bak diff --git a/circuits/v4/Head/v4head/ComponentLibrary.kicad_sym b/circuits/buddy/head/v4head/ComponentLibrary.kicad_sym similarity index 100% rename from circuits/v4/Head/v4head/ComponentLibrary.kicad_sym rename to circuits/buddy/head/v4head/ComponentLibrary.kicad_sym diff --git a/circuits/v4/Head/v4head/Gerber/v4head-1_0.zip b/circuits/buddy/head/v4head/Gerber/v4head-1_0.zip similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-1_0.zip rename to circuits/buddy/head/v4head/Gerber/v4head-1_0.zip diff --git a/circuits/v4/Head/v4head/Gerber/v4head-B_Cu.gbr b/circuits/buddy/head/v4head/Gerber/v4head-B_Cu.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-B_Cu.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-B_Cu.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-B_Mask.gbr b/circuits/buddy/head/v4head/Gerber/v4head-B_Mask.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-B_Mask.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-B_Mask.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-B_Paste.gbr b/circuits/buddy/head/v4head/Gerber/v4head-B_Paste.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-B_Paste.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-B_Paste.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-B_Silkscreen.gbr b/circuits/buddy/head/v4head/Gerber/v4head-B_Silkscreen.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-B_Silkscreen.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-B_Silkscreen.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-Edge_Cuts.gbr b/circuits/buddy/head/v4head/Gerber/v4head-Edge_Cuts.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-Edge_Cuts.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-Edge_Cuts.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-F_Cu.gbr b/circuits/buddy/head/v4head/Gerber/v4head-F_Cu.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-F_Cu.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-F_Cu.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-F_Mask.gbr b/circuits/buddy/head/v4head/Gerber/v4head-F_Mask.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-F_Mask.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-F_Mask.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-F_Paste.gbr b/circuits/buddy/head/v4head/Gerber/v4head-F_Paste.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-F_Paste.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-F_Paste.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-F_Silkscreen.gbr b/circuits/buddy/head/v4head/Gerber/v4head-F_Silkscreen.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-F_Silkscreen.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-F_Silkscreen.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-In1_Cu.gbr b/circuits/buddy/head/v4head/Gerber/v4head-In1_Cu.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-In1_Cu.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-In1_Cu.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-In2_Cu.gbr b/circuits/buddy/head/v4head/Gerber/v4head-In2_Cu.gbr similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-In2_Cu.gbr rename to circuits/buddy/head/v4head/Gerber/v4head-In2_Cu.gbr diff --git a/circuits/v4/Head/v4head/Gerber/v4head-NPTH.drl b/circuits/buddy/head/v4head/Gerber/v4head-NPTH.drl similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-NPTH.drl rename to circuits/buddy/head/v4head/Gerber/v4head-NPTH.drl diff --git a/circuits/v4/Head/v4head/Gerber/v4head-PTH.drl b/circuits/buddy/head/v4head/Gerber/v4head-PTH.drl similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-PTH.drl rename to circuits/buddy/head/v4head/Gerber/v4head-PTH.drl diff --git a/circuits/v4/Head/v4head/Gerber/v4head-job.gbrjob b/circuits/buddy/head/v4head/Gerber/v4head-job.gbrjob similarity index 100% rename from circuits/v4/Head/v4head/Gerber/v4head-job.gbrjob rename to circuits/buddy/head/v4head/Gerber/v4head-job.gbrjob diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.kicad_sym b/circuits/buddy/head/v4head/MakerForgeComponents.kicad_sym similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.kicad_sym rename to circuits/buddy/head/v4head/MakerForgeComponents.kicad_sym diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.pretty/I2C_NEOPIXEL.kicad_mod b/circuits/buddy/head/v4head/MakerForgeComponents.pretty/I2C_NEOPIXEL.kicad_mod similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.pretty/I2C_NEOPIXEL.kicad_mod rename to circuits/buddy/head/v4head/MakerForgeComponents.pretty/I2C_NEOPIXEL.kicad_mod diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.pretty/LCC_BIDIRECTIONAL.kicad_mod b/circuits/buddy/head/v4head/MakerForgeComponents.pretty/LCC_BIDIRECTIONAL.kicad_mod similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.pretty/LCC_BIDIRECTIONAL.kicad_mod rename to circuits/buddy/head/v4head/MakerForgeComponents.pretty/LCC_BIDIRECTIONAL.kicad_mod diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.pretty/MAX98357.kicad_mod b/circuits/buddy/head/v4head/MakerForgeComponents.pretty/MAX98357.kicad_mod similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.pretty/MAX98357.kicad_mod rename to circuits/buddy/head/v4head/MakerForgeComponents.pretty/MAX98357.kicad_mod diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.pretty/RCWL-0516.kicad_mod b/circuits/buddy/head/v4head/MakerForgeComponents.pretty/RCWL-0516.kicad_mod similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.pretty/RCWL-0516.kicad_mod rename to circuits/buddy/head/v4head/MakerForgeComponents.pretty/RCWL-0516.kicad_mod diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.pretty/RCWL-0516_MIN.kicad_mod b/circuits/buddy/head/v4head/MakerForgeComponents.pretty/RCWL-0516_MIN.kicad_mod similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.pretty/RCWL-0516_MIN.kicad_mod rename to circuits/buddy/head/v4head/MakerForgeComponents.pretty/RCWL-0516_MIN.kicad_mod diff --git a/circuits/v4/Head/v4head/MakerForgeComponents.pretty/SPH0645LM4H.kicad_mod b/circuits/buddy/head/v4head/MakerForgeComponents.pretty/SPH0645LM4H.kicad_mod similarity index 100% rename from circuits/v4/Head/v4head/MakerForgeComponents.pretty/SPH0645LM4H.kicad_mod rename to circuits/buddy/head/v4head/MakerForgeComponents.pretty/SPH0645LM4H.kicad_mod diff --git a/circuits/v4/Head/v4head/fp-info-cache b/circuits/buddy/head/v4head/fp-info-cache similarity index 100% rename from circuits/v4/Head/v4head/fp-info-cache rename to circuits/buddy/head/v4head/fp-info-cache diff --git a/circuits/v4/Head/v4head/fp-lib-table b/circuits/buddy/head/v4head/fp-lib-table similarity index 100% rename from circuits/v4/Head/v4head/fp-lib-table rename to circuits/buddy/head/v4head/fp-lib-table diff --git a/circuits/v4/Head/v4head/sym-lib-table b/circuits/buddy/head/v4head/sym-lib-table similarity index 100% rename from circuits/v4/Head/v4head/sym-lib-table rename to circuits/buddy/head/v4head/sym-lib-table diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-11_180045.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-11_180045.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-11_180045.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-11_180045.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-13_091042.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-13_091042.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-13_091042.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-13_091042.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-13_092432.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-13_092432.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-13_092432.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-13_092432.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-13_174035.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-13_174035.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-13_174035.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-13_174035.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_221017.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_221017.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_221017.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_221017.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_221951.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_221951.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_221951.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_221951.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_222823.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_222823.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_222823.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_222823.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_223412.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_223412.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_223412.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_223412.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_224144.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_224144.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-14_224144.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-14_224144.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_130951.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_130951.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_130951.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_130951.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_182215.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_182215.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_182215.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_182215.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_183159.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_183159.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_183159.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_183159.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_215232.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_215232.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_215232.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_215232.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_215749.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_215749.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-15_215749.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-15_215749.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_192523.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_192523.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_192523.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_192523.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_194250.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_194250.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_194250.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_194250.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_195116.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_195116.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_195116.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_195116.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_201013.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_201013.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_201013.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_201013.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_212027.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_212027.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-16_212027.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-16_212027.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_143741.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_143741.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_143741.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_143741.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_202344.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_202344.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_202344.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_202344.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_202913.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_202913.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_202913.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_202913.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_203837.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_203837.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_203837.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_203837.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_204729.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_204729.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2023-11-17_204729.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2023-11-17_204729.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2024-10-31_150534.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2024-10-31_150534.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2024-10-31_150534.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2024-10-31_150534.zip diff --git a/circuits/v4/Head/v4head/v4head-backups/v4head-2024-11-08_135158.zip b/circuits/buddy/head/v4head/v4head-backups/v4head-2024-11-08_135158.zip similarity index 100% rename from circuits/v4/Head/v4head/v4head-backups/v4head-2024-11-08_135158.zip rename to circuits/buddy/head/v4head/v4head-backups/v4head-2024-11-08_135158.zip diff --git a/circuits/v4/Head/v4head/v4head-brd.svg b/circuits/buddy/head/v4head/v4head-brd.svg similarity index 100% rename from circuits/v4/Head/v4head/v4head-brd.svg rename to circuits/buddy/head/v4head/v4head-brd.svg diff --git a/circuits/v4/Head/v4head/v4head.kicad_pcb b/circuits/buddy/head/v4head/v4head.kicad_pcb similarity index 100% rename from circuits/v4/Head/v4head/v4head.kicad_pcb rename to circuits/buddy/head/v4head/v4head.kicad_pcb diff --git a/circuits/v4/Head/v4head/v4head.kicad_prl b/circuits/buddy/head/v4head/v4head.kicad_prl similarity index 100% rename from circuits/v4/Head/v4head/v4head.kicad_prl rename to circuits/buddy/head/v4head/v4head.kicad_prl diff --git a/circuits/v4/Head/v4head/v4head.kicad_pro b/circuits/buddy/head/v4head/v4head.kicad_pro similarity index 100% rename from circuits/v4/Head/v4head/v4head.kicad_pro rename to circuits/buddy/head/v4head/v4head.kicad_pro diff --git a/circuits/v4/Head/v4head/v4head.kicad_sch b/circuits/buddy/head/v4head/v4head.kicad_sch similarity index 100% rename from circuits/v4/Head/v4head/v4head.kicad_sch rename to circuits/buddy/head/v4head/v4head.kicad_sch diff --git a/circuits/CM5 Head Wiring.drawio b/circuits/cody/CM5 Head Wiring.drawio similarity index 100% rename from circuits/CM5 Head Wiring.drawio rename to circuits/cody/CM5 Head Wiring.drawio diff --git a/circuits/CM5 Modules.drawio.svg b/circuits/cody/CM5 Modules.drawio.svg similarity index 100% rename from circuits/CM5 Modules.drawio.svg rename to circuits/cody/CM5 Modules.drawio.svg diff --git a/circuits/CM5HeadWiring.svg b/circuits/cody/CM5HeadWiring.svg similarity index 100% rename from circuits/CM5HeadWiring.svg rename to circuits/cody/CM5HeadWiring.svg diff --git a/config/animate.yml b/config/animate.yml deleted file mode 100755 index fc97553e..00000000 --- a/config/animate.yml +++ /dev/null @@ -1,4 +0,0 @@ -animate: - enabled: true - environment: 'robot' - path: modules.animate.Animate \ No newline at end of file diff --git a/config/bno055.yml b/config/bno055.yml deleted file mode 100644 index 399dee41..00000000 --- a/config/bno055.yml +++ /dev/null @@ -1,16 +0,0 @@ -bno055: - enabled: true - environment: robot - path: modules.sensor.imu.bno055.BNO055 - instances: - - name: "imu_head" - test_on_boot: false - address: 0x28 - - name: "imu_body" - test_on_boot: false - line_offset: 1 - address: 0x29 - dependencies: - python: - - adafruit-circuitpython-bno055 - - adafruit-blinka diff --git a/config/braillespeak.yml b/config/braillespeak.yml deleted file mode 100644 index 45fa6dfe..00000000 --- a/config/braillespeak.yml +++ /dev/null @@ -1,5 +0,0 @@ -braillespeak: - enabled: false - path: modules.audio.braillespeak.BrailleSpeak - config: - pin: 27 \ No newline at end of file diff --git a/config/buzzer.yml b/config/buzzer.yml deleted file mode 100644 index c69a77e7..00000000 --- a/config/buzzer.yml +++ /dev/null @@ -1,9 +0,0 @@ -buzzer: - enabled: false - path: "modules.audio.buzzer.Buzzer" - config: - pin: 27 - name: 'buzzer' - dependencies: - python: - - gpiozero diff --git a/config/chatgpt.yml b/config/chatgpt.yml deleted file mode 100644 index 38f7c98d..00000000 --- a/config/chatgpt.yml +++ /dev/null @@ -1,16 +0,0 @@ -chatgpt: - enabled: true - path: 'modules.chatgpt.ChatGPT' - config: - model: gpt-4o-mini - persona: "You are a helpful assistant robot. You respond with short phrases where possible. - Alternatively, you can respond with the following commands instead of text if you feel they are appropriate: - - animate/head_nod - - animate/head_shake - - gpio/laser/on - - gpio/laser/off" - dependencies: - python: - - openai - additional: - - https://platform.openai.com/api-keys \ No newline at end of file diff --git a/config/controller.yml b/config/controller.yml deleted file mode 100755 index 6832ce27..00000000 --- a/config/controller.yml +++ /dev/null @@ -1,33 +0,0 @@ -controller: - enabled: true - path: 'modules.xbox_controller.XboxController' - config: - normalised: true - debug: false - device: '/dev/input/js0' # Add this - deadzone: 4000 # thumbsticks deadzone (seems to be needed for xbox controllers) - button_names: # Add this - 0: 'BTN_A' - 1: 'BTN_B' - 2: 'BTN_X' - 3: 'BTN_Y' - 4: 'BTN_TL' - 5: 'BTN_TR' - 6: 'BTN_SELECT' - 7: 'BTN_START' - 8: 'BTN_MODE' - 9: 'BTN_THUMBL' - 10: 'BTN_THUMBR' - 11: 'KEY_RECORD' # Xbox Series controller - axis_names: # Add this - 0: 'ABS_X' - 1: 'ABS_Y' - 2: 'ABS_Z' - 3: 'ABS_RZ' - 4: 'ABS_RX' - 5: 'ABS_RY' - 6: 'ABS_HAT0X' - 7: 'ABS_HAT0Y' - axis_max: 32767.0 - axis_min: -32767.0 - diff --git a/config/controller_handler.yml b/config/controller_handler.yml deleted file mode 100644 index 8d7adda2..00000000 --- a/config/controller_handler.yml +++ /dev/null @@ -1,168 +0,0 @@ -controller: - enabled: true - path: 'modules.controller_handler.ControllerHandler' - config: - debug: false - modifier_buttons: - - BTN_TL - - BTN_TR - mapping: - default: - BTN_TL: - actions: - - topic: '' - args: {} - BTN_TR: - actions: - - topic: '' - args: {} - BTN_SELECT: - actions: - - topic: 'record/stop' - args: {} - BTN_START: - actions: - - topic: 'record/start' - args: {} - KEY_RECORD: # Only on Xbox S/X remotes - actions: - - topic: '' - args: {} - BTN_MODE: # Xbox Logo - actions: - - topic: '' - args: {} - ABS_HAT0X: # DPAD X - actions: - - topic: '' - args: {} - ABS_HAT0Y: # DPAD Y - actions: - - topic: '' - args: {} - BTN_A: - actions: - - topic: 'tts' - args: {msg: 'Test Default'} - BTN_X: - actions: - - topic: 'eye/blink' - args: {} - BTN_Y: - actions: - - topic: 'gpio/laser/toggle' - args: {} - BTN_B: - actions: - - topic: 'vision/toggle' - args: {} - ABS_RX: - actions: - - topic: 'servo:neck_pan:mv' - args: {} - modifier: - scale: 50 # max speed per event - ABS_RZ: - actions: - - topic: 'servo:neck_tilt:mv' - args: {} - modifier: - scale: -50 - - topic: 'servo:neck_tilt2:mv' - args: {} - modifier: - scale: 50 - ABS_RY: # Right trigger - start: -1.0 - actions: - - topic: 'servo:leg_r_ankle:mv' - args: {} - modifier: - scale: -50 - ABS_Z: # Left trigger - start: -1.0 - actions: - - topic: 'servo:leg_l_ankle:mv' - args: {} - modifier: - scale: -50 - ABS_X: - actions: - - topic: 'eye/move' - args: {axis: 'y'} - modifier: - mapping: [0, 240] - ABS_Y: - actions: - - topic: 'eye/move' - args: {axis: 'x'} - modifier: - mapping: [240, 0] - BTN_TL: - BTN_X: - actions: - - topic: 'servo:neck_pan:mv' - args: {delta: -50} - BTN_Y: - actions: - - topic: 'servo:neck_tilt:mv' - args: {delta: -50} - - topic: 'servo:neck_tilt2:mv' - args: {delta: 50} - BTN_B: - actions: - - topic: 'servo:neck_pan:mv' - args: {delta: 50} - BTN_A: - actions: - - topic: 'servo:neck_tilt:mv' - args: {delta: 50} - - topic: 'servo:neck_tilt2:mv' - args: {delta: -50} - BTN_TR: - BTN_X: - actions: - - topic: 'servo:neck_tilt:mv' - args: {delta: 50} - BTN_Y: - actions: - - topic: 'servo:neck_tilt:mv' - args: {delta: -50} - BTN_B: - actions: - - topic: 'servo:neck_tilt2:mv' - args: {delta: 50} - BTN_A: - actions: - - topic: 'servo:neck_tilt2:mv' - args: {delta: -50} - ABS_HAT0Y: - actions: - - topic: 'servo:neck_tilt:mvabs' - args: { - position: 511 - } - - topic: 'servo:neck_tilt2:mvabs' - args: { - position: 511 - } - ABS_X: - actions: - - topic: 'servo:leg_l_ankle:mv' - modifier: - scale: 50 - - topic: 'servo:leg_r_ankle:mv' - modifier: - scale: -50 - - topic: 'servo:leg_l_knee:mv' - modifier: - scale: 50 - - topic: 'servo:leg_r_knee:mv' - modifier: - scale: -50 - - topic: 'servo:leg_l_hip:mv' - modifier: - scale: 50 - - topic: 'servo:leg_r_hip:mv' - modifier: - scale: -50 diff --git a/config/gpio_laser.yml b/config/gpio_laser.yml deleted file mode 100644 index 5a43a07b..00000000 --- a/config/gpio_laser.yml +++ /dev/null @@ -1,10 +0,0 @@ -motion: - enabled: true - environment: robot - path: 'modules.gpio.laser.Laser' - config: - pin: 6 - test_on_boot: false - dependencies: - python: - - gpiozero \ No newline at end of file diff --git a/config/gpio_motion.yml b/config/gpio_motion.yml deleted file mode 100644 index a30d8a54..00000000 --- a/config/gpio_motion.yml +++ /dev/null @@ -1,10 +0,0 @@ -gpio_motion: - enabled: true - environment: robot - path: 'modules.gpio.motion.Motion' - config: - pin: 9 - test_on_boot: false - dependencies: - python: - - gpiozero \ No newline at end of file diff --git a/config/gpio_read.yml b/config/gpio_read.yml deleted file mode 100644 index 8c119ae5..00000000 --- a/config/gpio_read.yml +++ /dev/null @@ -1,9 +0,0 @@ -motion: - enabled: false - path: 'modules.gpio.read.Read' - config: - pin: 5 - test_on_boot: false - dependencies: - python: - - gpiozero \ No newline at end of file diff --git a/config/i2c_servo.yml b/config/i2c_servo.yml deleted file mode 100644 index fab21bad..00000000 --- a/config/i2c_servo.yml +++ /dev/null @@ -1,11 +0,0 @@ -i2c_servo: - enabled: false - path: modules.i2c_servo.I2CServo - config: - test_on_boot: false - servo_count: 16 - dependencies: - unix: - - python3-smbus - python: - - adafruit-circuitpython-servokit \ No newline at end of file diff --git a/config/logwrapper.yml b/config/logwrapper.yml deleted file mode 100644 index 2a766da2..00000000 --- a/config/logwrapper.yml +++ /dev/null @@ -1,7 +0,0 @@ -logwrapper: - enabled: true # Highly recommended to enable this module - path: modules.logwrapper.LogWrapper - config: - filename: app.log - log_level: 'debug' # debug, info, warning, error, critical - cli_level: 'info' # debug, info, warning, error, critical diff --git a/config/messaging_service.yml b/config/messaging_service.yml deleted file mode 100755 index 3c83aacf..00000000 --- a/config/messaging_service.yml +++ /dev/null @@ -1,14 +0,0 @@ -messaging_service: - enabled: true # Required for framework to function - path: modules.network.messaging_service.MessagingService - config: - protocol: 'pubsub' # 'mqtt' or 'pubsub' - something is wrong with mqtt that prevents the loops from executing correctly - mqtt_host: 'localhost' - mqtt_port: 1883 - dependencies: - python: - - paho-mqtt - - pypubsub - unix: - - mosquitto - # - mosquitto-clients \ No newline at end of file diff --git a/config/mpu6050.yml b/config/mpu6050.yml deleted file mode 100644 index 84163170..00000000 --- a/config/mpu6050.yml +++ /dev/null @@ -1,8 +0,0 @@ -mpu6050: - enabled: false - path: modules.sensor.imu.mpu6050.MPU6050 - config: - test_on_boot: true - dependencies: - unix: - - python3-smbus \ No newline at end of file diff --git a/config/neopixel.yml b/config/neopixel.yml deleted file mode 100644 index a12bea0f..00000000 --- a/config/neopixel.yml +++ /dev/null @@ -1,43 +0,0 @@ -neopixel: - enabled: false - path: 'modules.neopixel.neopx.NeoPx' - config: - pin: 12 # Only used for GPIO. GPIO2 and 3 are use for i2c, GPIO10 is used for SPI, GPIO12 is used for GPIO - protocol: 'I2C' # choose between GPIO, I2C and SPI - count: 12 - positions: { - 'status1': 0, - 'status2': 1, - 'status3': 2, - 'status4': 3, - 'status5': 4, - 'right' : 5, - 'top_right': 6, - 'top_left' : 7, - 'left' : 8, - 'bottom_left' : 9, - 'bottom_right' : 10, - 'middle': 11, - } - brightness: [ - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ] - dependencies: - python: - - colour - - adafruit-circuitpython-seesaw # i2c SUPPORT - - adafruit-blinka # SPI SUPPORT - - adafruit-circuitpython-neopixel-spi # SPI SUPPORT - emotion_analysis: - enable: false diff --git a/config/neopixel_emotions.yml b/config/neopixel_emotions.yml deleted file mode 100644 index 12a63aee..00000000 --- a/config/neopixel_emotions.yml +++ /dev/null @@ -1,2358 +0,0 @@ -neopixel_emotions: - enabled: false - path: modules.neopixel.emotion_analysis.EmotionAnalysis - dependencies: - python: - - tensorflow - - torch - - transformers - config: - colors: - grief: - COLOR_ABBEY: '(76, 79, 86)' - COLOR_AFFAIR: '(113, 70, 147)' - COLOR_ARSENIC: '(59, 68, 75)' - COLOR_BISMARK: '(73, 113, 131)' - COLOR_BLACK_CORAL: '(84, 98, 111)' - COLOR_BLUE_BAYOUX: '(73, 102, 121)' - COLOR_BLUE_MAGENTA_VIOLET: '(85, 53, 146)' - COLOR_BOSSANOVA: '(78, 42, 90)' - COLOR_BRIGHT_GRAY: '(60, 65, 81)' - COLOR_BUTTERFLY_BUSH: '(98, 78, 154)' - COLOR_CADET: '(83, 104, 114)' - COLOR_CASAL: '(47, 97, 104)' - COLOR_CHAMBRAY: '(53, 78, 140)' - COLOR_CHARCOAL: '(54, 69, 79)' - COLOR_COMET: '(92, 93, 117)' - COLOR_CYBER_GRAPE: '(88, 66, 124)' - COLOR_DARK_LAVENDER: '(115, 79, 150)' - COLOR_DARK_SLATE_BLUE: '(72, 61, 139)' - COLOR_DEEP_KOAMARU: '(51, 51, 102)' - COLOR_DEEP_SPACE_SPARKLE: '(74, 100, 108)' - COLOR_DOLPHIN: '(100, 96, 119)' - COLOR_EAST_BAY: '(65, 76, 125)' - COLOR_FIORD: '(64, 81, 105)' - COLOR_GIGAS: '(82, 60, 148)' - COLOR_GRAVEL: '(74, 68, 75)' - COLOR_GUN_POWDER: '(65, 66, 87)' - COLOR_GUNMETAL: '(42, 52, 57)' - COLOR_IMPERIAL: '(96, 47, 107)' - COLOR_INDEPENDENCE: '(76, 81, 109)' - COLOR_JAPANESE_INDIGO: '(38, 67, 72)' - COLOR_KASHMIR_BLUE: '(80, 112, 150)' - COLOR_LIBERTY: '(84, 90, 167)' - COLOR_LIMED_SPRUCE: '(57, 72, 81)' - COLOR_MAKO: '(68, 73, 84)' - COLOR_MARTINIQUE: '(54, 48, 80)' - COLOR_MID_GRAY: '(95, 95, 110)' - COLOR_MINSK: '(63, 48, 127)' - COLOR_MORTAR: '(80, 67, 81)' - COLOR_MULLED_WINE: '(78, 69, 98)' - COLOR_NEVADA: '(100, 110, 117)' - COLOR_OCEAN_BLUE: '(79, 66, 181)' - COLOR_OLD_HELIOTROPE: '(86, 60, 92)' - COLOR_ONYX: '(53, 56, 57)' - COLOR_ORACLE: '(55, 116, 117)' - COLOR_OUTER_SPACE: '(65, 74, 76)' - COLOR_PAYNES_GREY: '(83, 104, 120)' - COLOR_PICKLED_BLUEWOOD: '(49, 68, 89)' - COLOR_PLUMP_PURPLE: '(89, 70, 178)' - COLOR_PURPLE_NAVY: '(78, 81, 128)' - COLOR_QUEEN_BLUE: '(67, 107, 149)' - COLOR_RHINO: '(46, 63, 98)' - COLOR_RIVER_BED: '(67, 76, 89)' - COLOR_SALT_BOX: '(104, 94, 110)' - COLOR_SAN_JUAN: '(48, 75, 106)' - COLOR_SAN_MARINO: '(69, 108, 172)' - COLOR_SCARPA_FLOW: '(88, 85, 98)' - COLOR_SHIP_GRAY: '(62, 58, 68)' - COLOR_SHUTTLE_GRAY: '(95, 102, 114)' - COLOR_SMOKY: '(96, 91, 115)' - COLOR_STORMCLOUD: '(79, 102, 106)' - COLOR_TROUT: '(74, 78, 90)' - COLOR_TUNA: '(53, 53, 66)' - COLOR_UCLA_BLUE: '(83, 104, 149)' - COLOR_VICTORIA: '(83, 68, 145)' - COLOR_VOODOO: '(83, 52, 85)' - COLOR_WILLIAM: '(58, 104, 108)' - COLOR_ARMADILLO: '(67, 62, 55)' - COLOR_AU_CHICO: '(151, 96, 93)' - COLOR_CAMELOT: '(137, 52, 86)' - COLOR_CANNON_PINK: '(137, 67, 103)' - COLOR_CHICAGO: '(93, 92, 88)' - COLOR_COSMIC: '(118, 57, 93)' - COLOR_COYOTE_BROWN: '(129, 97, 62)' - COLOR_CROCODILE: '(115, 109, 88)' - COLOR_DARK_BROWN_TANGELO: '(136, 101, 78)' - COLOR_DARK_BYZANTIUM: '(93, 57, 84)' - COLOR_DARK_LIVER: '(83, 75, 79)' - COLOR_DARK_PUCE: '(79, 58, 60)' - COLOR_DEEP_RUBY: '(132, 63, 91)' - COLOR_DEEP_TAUPE: '(126, 94, 96)' - COLOR_DEEP_TUSCAN_RED: '(102, 66, 77)' - COLOR_DON_JUAN: '(93, 76, 81)' - COLOR_DORADO: '(107, 87, 85)' - COLOR_DOVE_GRAY: '(109, 108, 108)' - COLOR_DUNE: '(56, 53, 51)' - COLOR_EGGPLANT: '(97, 64, 81)' - COLOR_EMPEROR: '(81, 70, 73)' - COLOR_FALCON: '(127, 98, 109)' - COLOR_FEDORA: '(121, 106, 120)' - COLOR_FERRA: '(112, 79, 80)' - COLOR_FINN: '(105, 45, 84)' - COLOR_FLINT: '(111, 106, 97)' - COLOR_FRENCH_BISTRE: '(133, 109, 77)' - COLOR_FUSCOUS_GRAY: '(84, 83, 77)' - COLOR_GO_BEN: '(114, 109, 78)' - COLOR_GOLD_FUSION: '(133, 117, 78)' - COLOR_HEMLOCK: '(94, 93, 59)' - COLOR_HIPPIE_PINK: '(174, 69, 96)' - COLOR_IRONSIDE_GRAY: '(103, 102, 98)' - COLOR_JAPANESE_VIOLET: '(91, 50, 86)' - COLOR_KABUL: '(94, 72, 62)' - COLOR_KOKODA: '(110, 109, 87)' - COLOR_LIVER: '(103, 76, 71)' - COLOR_MAGENTA_HAZE: '(159, 69, 118)' - COLOR_MASALA: '(64, 59, 56)' - COLOR_MATTERHORN: '(78, 59, 65)' - COLOR_MAUVE_TAUPE: '(145, 95, 109)' - COLOR_MEDIUM_RUBY: '(170, 64, 105)' - COLOR_MERLIN: '(65, 60, 55)' - COLOR_OLD_LAVENDER: '(121, 104, 120)' - COLOR_OLD_MOSS_GREEN: '(134, 126, 54)' - COLOR_PABLO: '(119, 111, 97)' - COLOR_PASTEL_BROWN: '(131, 105, 83)' - COLOR_PEAT: '(113, 107, 86)' - COLOR_PINE_CONE: '(109, 94, 84)' - COLOR_PLUM: '(142, 69, 133)' - COLOR_PURPLE_TAUPE: '(80, 64, 77)' - COLOR_QUARTZ: '(81, 72, 79)' - COLOR_QUINACRIDONE_MAGENTA: '(142, 58, 89)' - COLOR_RAW_UMBER: '(130, 102, 68)' - COLOR_REDWOOD: '(164, 90, 82)' - COLOR_ROMAN_COFFEE: '(121, 93, 76)' - COLOR_ROSE_EBONY: '(103, 72, 70)' - COLOR_ROSE_TAUPE: '(144, 93, 93)' - COLOR_RUSSETT: '(117, 90, 87)' - COLOR_SANDSTONE: '(121, 109, 98)' - COLOR_SCORPION: '(105, 95, 98)' - COLOR_SOYA_BEAN: '(106, 96, 81)' - COLOR_SPICY_MIX: '(139, 95, 77)' - COLOR_SUGAR_PLUM: '(145, 78, 117)' - COLOR_SYCAMORE: '(144, 141, 57)' - COLOR_TOBACCO_BROWN: '(113, 93, 71)' - COLOR_TUATARA: '(54, 53, 52)' - COLOR_TUNDORA: '(74, 66, 68)' - COLOR_TUSCAN_RED: '(124, 72, 72)' - COLOR_TWILIGHT_LAVENDER: '(138, 73, 107)' - COLOR_UMBER: '(99, 81, 71)' - COLOR_VIN_ROUGE: '(152, 61, 97)' - COLOR_WENGE: '(100, 84, 82)' - COLOR_WINE_DREGS: '(103, 49, 71)' - COLOR_YELLOW_METAL: '(113, 99, 56)' - COLOR_ZAMBEZI: '(104, 85, 88)' - COLOR_AMAZON: '(59, 122, 87)' - COLOR_APPLE: '(79, 168, 61)' - COLOR_AXOLOTL: '(78, 102, 73)' - COLOR_BLACK_OLIVE: '(59, 60, 54)' - COLOR_CABBAGE_PONT: '(63, 76, 58)' - COLOR_CACTUS: '(88, 113, 86)' - COLOR_CAPE_COD: '(60, 68, 67)' - COLOR_CHALET_GREEN: '(81, 110, 61)' - COLOR_CHATEAU_GREEN: '(64, 168, 96)' - COLOR_COMO: '(81, 124, 102)' - COLOR_CORDUROY: '(96, 110, 104)' - COLOR_CUTTY_SARK: '(80, 118, 114)' - COLOR_DARK_OLIVE_GREEN: '(85, 107, 47)' - COLOR_DINGLEY: '(93, 119, 71)' - COLOR_EBONY: '(85, 93, 80)' - COLOR_FADED_JADE: '(66, 121, 119)' - COLOR_FELDGRAU: '(77, 93, 83)' - COLOR_FERN_GREEN: '(79, 121, 66)' - COLOR_FINCH: '(98, 102, 73)' - COLOR_FINLANDIA: '(85, 109, 86)' - COLOR_FRUIT_SALAD: '(79, 157, 93)' - COLOR_GLADE_GREEN: '(97, 132, 95)' - COLOR_GOBLIN: '(61, 125, 82)' - COLOR_GRAY_ASPARAGUS: '(70, 89, 69)' - COLOR_HIPPIE_GREEN: '(83, 130, 75)' - COLOR_HOOKERS_GREEN: '(73, 121, 107)' - COLOR_HUNTER_GREEN: '(53, 94, 59)' - COLOR_KELP: '(69, 73, 54)' - COLOR_KILLARNEY: '(58, 106, 71)' - COLOR_KOMBU_GREEN: '(53, 66, 48)' - COLOR_LIMA: '(118, 189, 23)' - COLOR_LIMED_ASH: '(116, 125, 99)' - COLOR_LUNAR_GREEN: '(60, 73, 58)' - COLOR_MAY_GREEN: '(76, 145, 65)' - COLOR_MINERAL_GREEN: '(63, 93, 83)' - COLOR_NANDOR: '(75, 93, 82)' - COLOR_NICKEL: '(114, 116, 114)' - COLOR_OLIVE_DRAB: '(107, 142, 35)' - COLOR_PLANTATION: '(39, 80, 75)' - COLOR_RYB_GREEN: '(102, 176, 50)' - COLOR_RIFLE_GREEN: '(68, 76, 56)' - COLOR_SAP_GREEN: '(80, 125, 42)' - COLOR_SIAM: '(100, 106, 84)' - COLOR_SPECTRA: '(47, 90, 87)' - COLOR_SPRING_LEAVES: '(87, 131, 99)' - COLOR_STROMBOLI: '(50, 93, 82)' - COLOR_TOM_THUMB: '(63, 88, 59)' - COLOR_VIRIDIAN: '(64, 130, 109)' - COLOR_WILLOW_GROVE: '(101, 116, 93)' - COLOR_WINTERGREEN_DREAM: '(86, 136, 125)' - COLOR_DAVYS_GREY: '(85, 85, 85)' - COLOR_DIM_GRAY: '(105, 105, 105)' - COLOR_GRANITE_GRAY: '(103, 103, 103)' - COLOR_JET: '(52, 52, 52)' - COLOR_MINE_SHAFT: '(50, 50, 50)' - COLOR_STORM_DUST: '(100, 100, 99)' - COLOR_DARK_SLATE_GRAY: '(47, 79, 79)' - disgust: - COLOR_ABSOLUTE_ZERO: '(0, 72, 186)' - COLOR_ALLPORTS: '(0, 118, 163)' - COLOR_ASTRAL: '(50, 125, 160)' - COLOR_ATOLL: '(10, 111, 117)' - COLOR_BAHAMA_BLUE: '(2, 99, 149)' - COLOR_BALL_BLUE: '(33, 171, 205)' - COLOR_BDAZZLED_BLUE: '(46, 88, 148)' - COLOR_BLUE_CHILL: '(12, 137, 144)' - COLOR_BLUE_DIANNE: '(32, 72, 82)' - COLOR_BLUE_GREEN: '(13, 152, 186)' - COLOR_BLUE_SAPPHIRE: '(18, 97, 128)' - COLOR_BLUE_STONE: '(1, 97, 98)' - COLOR_BLUMINE: '(24, 88, 122)' - COLOR_BONDI_BLUE: '(0, 149, 182)' - COLOR_BOSTON_BLUE: '(59, 145, 180)' - COLOR_BRIGHT_CERULEAN: '(29, 172, 214)' - COLOR_BRIGHT_NAVY_BLUE: '(25, 116, 210)' - COLOR_CG_BLUE: '(0, 122, 165)' - COLOR_CALYPSO: '(49, 114, 141)' - COLOR_CERULEAN: '(0, 123, 167)' - COLOR_CERULEAN_BLUE: '(42, 82, 190)' - COLOR_CHATHAMS_BLUE: '(23, 85, 121)' - COLOR_COBALT_BLUE: '(0, 71, 171)' - COLOR_CONGRESS_BLUE: '(2, 71, 142)' - COLOR_CURIOUS_BLUE: '(37, 150, 209)' - COLOR_CYAN_COBALT_BLUE: '(40, 88, 156)' - COLOR_CYAN_CORNFLOWER_BLUE: '(24, 139, 194)' - COLOR_DARK_CERULEAN: '(8, 69, 126)' - COLOR_DARK_TURQUOISE: '(0, 206, 209)' - COLOR_DEEP_SEA_GREEN: '(9, 88, 89)' - COLOR_DENIM: '(21, 96, 189)' - COLOR_DENIM_BLUE: '(34, 67, 182)' - COLOR_EAGLE_GREEN: '(0, 73, 83)' - COLOR_EASTERN_BLUE: '(30, 154, 176)' - COLOR_ELM: '(28, 124, 125)' - COLOR_ENDEAVOUR: '(0, 86, 167)' - COLOR_FRENCH_BLUE: '(0, 114, 187)' - COLOR_FUN_BLUE: '(25, 89, 168)' - COLOR_GREEN_BLUE: '(17, 100, 180)' - COLOR_HONOLULU_BLUE: '(0, 109, 176)' - COLOR_LAPIS_LAZULI: '(38, 97, 156)' - COLOR_LOCHMARA: '(0, 126, 199)' - COLOR_MARINER: '(40, 106, 205)' - COLOR_MATISSE: '(27, 101, 157)' - COLOR_MEDIUM_ELECTRIC_BLUE: '(3, 80, 150)' - COLOR_METALLIC_SEAWEED: '(10, 126, 140)' - COLOR_MING: '(54, 116, 125)' - COLOR_MOSQUE: '(3, 106, 110)' - COLOR_MUNSELL_BLUE: '(0, 147, 175)' - COLOR_NCS_BLUE: '(0, 135, 189)' - COLOR_NEW_CAR: '(33, 79, 198)' - COLOR_OCEAN_BOAT_BLUE: '(0, 119, 190)' - COLOR_ORIENT: '(1, 94, 133)' - COLOR_PACIFIC_BLUE: '(28, 169, 201)' - COLOR_PARADISO: '(49, 125, 130)' - COLOR_PELOROUS: '(62, 171, 191)' - COLOR_PROCESS_CYAN: '(0, 183, 235)' - COLOR_RICH_ELECTRIC_BLUE: '(8, 146, 208)' - COLOR_SAPPHIRE: '(15, 82, 186)' - COLOR_SAPPHIRE_BLUE: '(0, 103, 165)' - COLOR_SCIENCE_BLUE: '(0, 102, 204)' - COLOR_SEA_BLUE: '(0, 105, 148)' - COLOR_SHERPA_BLUE: '(0, 73, 80)' - COLOR_SPANISH_BLUE: '(0, 112, 184)' - COLOR_SPANISH_SKY_BLUE: '(0, 170, 228)' - COLOR_ST_TROPAZ: '(45, 86, 155)' - COLOR_STAR_COMMAND_BLUE: '(0, 123, 184)' - COLOR_TEAL_BLUE: '(54, 117, 136)' - COLOR_TORY_BLUE: '(20, 80, 170)' - COLOR_TRUE_BLUE: '(0, 115, 207)' - COLOR_USAFA_BLUE: '(0, 79, 152)' - COLOR_VENICE_BLUE: '(5, 89, 137)' - COLOR_VIOLET_BLUE: '(50, 74, 178)' - COLOR_VIRIDIAN_GREEN: '(0, 150, 152)' - COLOR_VIVID_CERULEAN: '(0, 170, 238)' - COLOR_YALE_BLUE: '(15, 77, 146)' - COLOR_AQUA_DEEP: '(1, 75, 67)' - COLOR_BOTTLE_GREEN: '(0, 106, 78)' - COLOR_CARIBBEAN_GREEN: '(0, 204, 153)' - COLOR_CELADON_GREEN: '(47, 132, 124)' - COLOR_CRAYOLA_GREEN: '(28, 172, 120)' - COLOR_DARK_SPRING_GREEN: '(23, 114, 69)' - COLOR_DEEP_GREEN_CYAN_TURQUOISE: '(14, 124, 97)' - COLOR_DEEP_JUNGLE_GREEN: '(0, 75, 73)' - COLOR_DEEP_SEA: '(1, 130, 107)' - COLOR_EDEN: '(16, 88, 82)' - COLOR_ELF_GREEN: '(8, 131, 112)' - COLOR_EVENING_SEA: '(2, 78, 70)' - COLOR_GO_GREEN: '(0, 171, 102)' - COLOR_GENERIC_VIRIDIAN: '(0, 127, 102)' - COLOR_GENOA: '(21, 115, 107)' - COLOR_GOSSAMER: '(6, 155, 129)' - COLOR_GREEN_CYAN: '(0, 153, 102)' - COLOR_GREEN_HAZE: '(1, 163, 104)' - COLOR_GREEN_PEA: '(29, 97, 66)' - COLOR_ILLUMINATING_EMERALD: '(49, 145, 119)' - COLOR_JADE: '(0, 168, 107)' - COLOR_JUNGLE_GREEN: '(41, 171, 135)' - COLOR_KEPPEL: '(58, 176, 158)' - COLOR_LIGHT_SEA_GREEN: '(32, 178, 170)' - COLOR_LOCHINVAR: '(44, 140, 132)' - COLOR_MEDIUM_SEA_GREEN: '(60, 179, 113)' - COLOR_MEDIUM_SPRING_GREEN: '(0, 250, 154)' - COLOR_MINT: '(62, 180, 137)' - COLOR_MOUNTAIN_MEADOW: '(48, 186, 143)' - COLOR_MUNSELL_GREEN: '(0, 168, 119)' - COLOR_MYRTLE_GREEN: '(49, 120, 115)' - COLOR_NCS_GREEN: '(0, 159, 107)' - COLOR_NIAGARA: '(6, 161, 137)' - COLOR_OBSERVATORY: '(2, 134, 111)' - COLOR_PANTONE_GREEN: '(0, 173, 67)' - COLOR_PAOLO_VERONESE_GREEN: '(0, 155, 125)' - COLOR_PEARL_MYSTIC_TURQUOISE: '(50, 198, 166)' - COLOR_PERSIAN_GREEN: '(0, 166, 147)' - COLOR_PIGMENT_GREEN: '(0, 165, 80)' - COLOR_PINE_GREEN: '(1, 121, 111)' - COLOR_PUERTO_RICO: '(63, 193, 170)' - COLOR_SALEM: '(9, 127, 75)' - COLOR_SEA_GREEN: '(46, 139, 87)' - COLOR_SHAMROCK: '(51, 204, 153)' - COLOR_SHAMROCK_GREEN: '(0, 158, 96)' - COLOR_SPANISH_GREEN: '(0, 145, 80)' - COLOR_SPANISH_VIRIDIAN: '(0, 127, 92)' - COLOR_SURFIE_GREEN: '(12, 122, 121)' - COLOR_TEAL_GREEN: '(0, 130, 127)' - COLOR_TIFFANY_BLUE: '(10, 186, 181)' - COLOR_TROPICAL_RAIN_FOREST: '(0, 117, 94)' - COLOR_UFO_GREEN: '(60, 208, 112)' - COLOR_WATERCOURSE: '(5, 111, 87)' - COLOR_ZOMP: '(57, 167, 142)' - COLOR_DARK_CYAN: '(0, 139, 139)' - COLOR_JAVA: '(31, 194, 194)' - COLOR_ROBIN_EGG_BLUE: '(0, 204, 204)' - COLOR_SKOBELOFF: '(0, 116, 116)' - COLOR_TEAL: '(0, 128, 128)' - COLOR_WARM_BLACK: '(0, 66, 66)' - curiosity: - COLOR_AERO: '(124, 185, 232)' - COLOR_ANAKIWA: '(157, 229, 255)' - COLOR_AQUAMARINE_BLUE: '(113, 217, 226)' - COLOR_BABY_BLUE: '(137, 207, 240)' - COLOR_BABY_BLUE_EYES: '(161, 202, 241)' - COLOR_BEAU_BLUE: '(188, 212, 230)' - COLOR_BELGION: '(173, 216, 255)' - COLOR_BILOBA_FLOWER: '(178, 161, 234)' - COLOR_BLEU_DE_FRANCE: '(49, 140, 231)' - COLOR_BLIZZARD_BLUE: '(163, 227, 237)' - COLOR_BLUE_JEANS: '(93, 173, 236)' - COLOR_BLUE_LAGOON: '(172, 229, 238)' - COLOR_BLUEBERRY: '(79, 134, 247)' - COLOR_BRILLIANT_AZURE: '(51, 153, 255)' - COLOR_CAPRI: '(0, 191, 255)' - COLOR_CAROLINA_BLUE: '(86, 160, 211)' - COLOR_CELESTIAL_BLUE: '(73, 151, 208)' - COLOR_CHARLOTTE: '(186, 238, 249)' - COLOR_COLUMBIA_BLUE: '(196, 216, 226)' - COLOR_CORNFLOWER_BLUE: '(100, 149, 237)' - COLOR_DANUBE: '(96, 147, 209)' - COLOR_DARK_IMPERIAL_BLUE: '(110, 110, 249)' - COLOR_DARK_SKY_BLUE: '(140, 190, 214)' - COLOR_DIAMOND: '(185, 242, 255)' - COLOR_DODGER_BLUE: '(30, 144, 255)' - COLOR_ELECTRIC_BLUE: '(125, 249, 255)' - COLOR_FOUNTAIN_BLUE: '(86, 180, 190)' - COLOR_FRENCH_PASS: '(189, 237, 253)' - COLOR_FRENCH_SKY_BLUE: '(119, 181, 254)' - COLOR_FRESH_AIR: '(166, 231, 255)' - COLOR_HALF_BAKED: '(133, 196, 204)' - COLOR_HAVELOCK_BLUE: '(85, 144, 217)' - COLOR_ICEBERG: '(113, 166, 210)' - COLOR_JORDY_BLUE: '(138, 185, 241)' - COLOR_LIGHT_BLUE: '(173, 216, 230)' - COLOR_LIGHT_COBALT_BLUE: '(136, 172, 224)' - COLOR_LIGHT_CORNFLOWER_BLUE: '(147, 204, 234)' - COLOR_LIGHT_SKY_BLUE: '(135, 206, 250)' - COLOR_LIGHT_STEEL_BLUE: '(176, 196, 222)' - COLOR_LITTLE_BOY_BLUE: '(108, 160, 220)' - COLOR_MALIBU: '(125, 200, 247)' - COLOR_MAXIMUM_BLUE: '(71, 171, 204)' - COLOR_MAYA_BLUE: '(115, 194, 251)' - COLOR_MEDIUM_SKY_BLUE: '(128, 218, 235)' - COLOR_MEDIUM_SLATE_BLUE: '(123, 104, 238)' - COLOR_MELROSE: '(199, 193, 255)' - COLOR_MORNING_GLORY: '(158, 222, 224)' - COLOR_NON_PHOTO_BLUE: '(164, 221, 237)' - COLOR_PALE_CERULEAN: '(155, 196, 226)' - COLOR_PALE_CORNFLOWER_BLUE: '(171, 205, 239)' - COLOR_PALE_CYAN: '(135, 211, 248)' - COLOR_PERANO: '(169, 190, 242)' - COLOR_PERIWINKLE_GRAY: '(195, 205, 230)' - COLOR_PICTON_BLUE: '(69, 177, 232)' - COLOR_PORTAGE: '(139, 159, 238)' - COLOR_POWDER_BLUE: '(176, 224, 230)' - COLOR_REGENT_ST_BLUE: '(170, 214, 230)' - COLOR_SAIL: '(184, 224, 249)' - COLOR_SCOOTER: '(46, 191, 212)' - COLOR_SEA_SERPENT: '(75, 199, 207)' - COLOR_SEAGULL: '(128, 204, 234)' - COLOR_SHAKESPEARE: '(78, 171, 209)' - COLOR_SKY_BLUE: '(135, 206, 235)' - COLOR_SPINDLE: '(182, 209, 234)' - COLOR_SPIRO_DISCO_BALL: '(15, 192, 252)' - COLOR_SPRAY: '(121, 222, 236)' - COLOR_TROPICAL_BLUE: '(195, 221, 249)' - COLOR_UNITED_NATIONS_BLUE: '(91, 146, 229)' - COLOR_VERY_LIGHT_AZURE: '(116, 187, 251)' - COLOR_VERY_LIGHT_BLUE: '(102, 102, 255)' - COLOR_VIKING: '(100, 204, 219)' - COLOR_VISTA_BLUE: '(124, 158, 217)' - COLOR_VIVID_SKY_BLUE: '(0, 204, 255)' - COLOR_WATERSPOUT: '(164, 244, 249)' - COLOR_WINTER_WIZARD: '(160, 230, 255)' - COLOR_ZIGGURAT: '(191, 219, 226)' - COLOR_AQUA_ISLAND: '(161, 218, 215)' - COLOR_AQUAMARINE: '(127, 255, 212)' - COLOR_BERMUDA: '(125, 216, 198)' - COLOR_BRIGHT_TURQUOISE: '(8, 232, 222)' - COLOR_CRUISE: '(181, 236, 223)' - COLOR_DOWNY: '(111, 208, 197)' - COLOR_EUCALYPTUS: '(68, 215, 168)' - COLOR_ICE_COLD: '(177, 244, 231)' - COLOR_JAGGED_ICE: '(194, 232, 229)' - COLOR_MAGIC_MINT: '(170, 240, 209)' - COLOR_MEDIUM_TURQUOISE: '(72, 209, 204)' - COLOR_MINT_TULIP: '(196, 244, 235)' - COLOR_MONTE_CARLO: '(131, 208, 198)' - COLOR_PALE_ROBIN_EGG_BLUE: '(150, 222, 209)' - COLOR_PEARL_AQUA: '(136, 216, 192)' - COLOR_RIPTIDE: '(139, 230, 216)' - COLOR_SINBAD: '(159, 215, 211)' - COLOR_TURQUOISE: '(64, 224, 208)' - COLOR_TURQUOISE_BLUE: '(0, 255, 239)' - COLOR_WATER_LEAF: '(161, 233, 222)' - COLOR_AERO_BLUE: '(201, 255, 229)' - COLOR_HUMMING_BIRD: '(207, 249, 243)' - COLOR_ONAHAU: '(205, 244, 255)' - COLOR_PERIWINKLE: '(204, 204, 255)' - COLOR_SCANDAL: '(207, 250, 244)' - COLOR_CELESTE: '(178, 255, 255)' - COLOR_CYAN: '(0, 255, 255)' - COLOR_LIGHT_TURQUOISE: '(175, 238, 238)' - disappointment: - COLOR_AFRICAN_VIOLET: '(178, 132, 190)' - COLOR_AIR_SUPERIORITY_BLUE: '(114, 160, 193)' - COLOR_ALUMINIUM: '(169, 172, 182)' - COLOR_AMETHYST: '(153, 102, 204)' - COLOR_AMETHYST_SMOKE: '(163, 151, 180)' - COLOR_BALI_HAI: '(133, 159, 175)' - COLOR_BLUE_BELL: '(162, 162, 208)' - COLOR_BLUE_GRAY: '(102, 153, 204)' - COLOR_BLUE_HAZE: '(191, 190, 216)' - COLOR_BOMBAY: '(175, 177, 184)' - COLOR_BRIGHT_LAVENDER: '(191, 148, 228)' - COLOR_CADET_GREY: '(145, 163, 176)' - COLOR_CASPER: '(173, 190, 209)' - COLOR_CEIL: '(146, 161, 207)' - COLOR_CERULEAN_FROST: '(109, 155, 195)' - COLOR_CHATELLE: '(189, 179, 199)' - COLOR_CHETWODE_BLUE: '(133, 129, 217)' - COLOR_COLD_PURPLE: '(171, 160, 217)' - COLOR_COOL_GREY: '(140, 146, 172)' - COLOR_DARK_PASTEL_BLUE: '(119, 158, 203)' - COLOR_DARK_PASTEL_PURPLE: '(150, 111, 214)' - COLOR_DULL_LAVENDER: '(168, 153, 230)' - COLOR_EAST_SIDE: '(172, 145, 206)' - COLOR_FRENCH_GRAY: '(189, 189, 198)' - COLOR_GLACIER: '(128, 179, 196)' - COLOR_GLOSSY_GRAPE: '(171, 146, 179)' - COLOR_GRAY_CHATEAU: '(162, 170, 179)' - COLOR_GRAY_SUIT: '(193, 190, 205)' - COLOR_GULL_GRAY: '(157, 172, 183)' - COLOR_GUMBO: '(124, 161, 166)' - COLOR_HEATHER: '(183, 195, 208)' - COLOR_HIT_GRAY: '(161, 173, 181)' - COLOR_JUNGLE_MIST: '(180, 207, 211)' - COLOR_LAVENDER: '(181, 126, 220)' - COLOR_LAVENDER_GRAY: '(196, 195, 208)' - COLOR_LAVENDER_INDIGO: '(148, 87, 235)' - COLOR_LAVENDER_PURPLE: '(150, 123, 182)' - COLOR_LENURPLE: '(186, 147, 216)' - COLOR_LIGHT_PASTEL_PURPLE: '(177, 156, 217)' - COLOR_LILAC_BUSH: '(152, 116, 211)' - COLOR_LOBLOLLY: '(189, 201, 206)' - COLOR_LOGAN: '(170, 169, 205)' - COLOR_LONDON_HUE: '(190, 166, 195)' - COLOR_MANATEE: '(151, 154, 170)' - COLOR_MEDIUM_PURPLE: '(147, 112, 219)' - COLOR_MOODY_BLUE: '(127, 118, 211)' - COLOR_MOONSTONE_BLUE: '(115, 169, 194)' - COLOR_MOUNTAIN_MIST: '(149, 147, 150)' - COLOR_NEPAL: '(142, 171, 193)' - COLOR_NEPTUNE: '(124, 183, 187)' - COLOR_PASTEL_BLUE: '(174, 198, 207)' - COLOR_PASTEL_PURPLE: '(179, 158, 181)' - COLOR_PEWTER_BLUE: '(139, 168, 183)' - COLOR_PIGEON_POST: '(175, 189, 217)' - COLOR_POLO_BLUE: '(141, 168, 204)' - COLOR_PURPLE_MOUNTAIN_MAJESTY: '(150, 120, 182)' - COLOR_RICH_LAVENDER: '(167, 107, 207)' - COLOR_ROCK_BLUE: '(158, 177, 205)' - COLOR_SANTAS_GRAY: '(159, 160, 177)' - COLOR_SILVER_SAND: '(191, 193, 194)' - COLOR_SPUN_PEARL: '(170, 171, 183)' - COLOR_SUBMARINE: '(186, 199, 201)' - COLOR_TOWER_GRAY: '(169, 189, 191)' - COLOR_TRUE_V: '(138, 115, 214)' - COLOR_UBE: '(136, 120, 195)' - COLOR_WELDON_BLUE: '(124, 152, 171)' - COLOR_WILD_BLUE_YONDER: '(162, 173, 208)' - COLOR_WISTERIA: '(201, 160, 220)' - COLOR_WISTFUL: '(164, 166, 211)' - COLOR_AKAROA: '(212, 196, 168)' - COLOR_ANTIQUE_BRASS: '(205, 149, 117)' - COLOR_APACHE: '(223, 190, 111)' - COLOR_ARYLIDE_YELLOW: '(233, 214, 107)' - COLOR_ASH: '(198, 195, 181)' - COLOR_BISON_HIDE: '(193, 183, 164)' - COLOR_BLACK_SHADOWS: '(191, 175, 178)' - COLOR_BOUQUET: '(174, 128, 158)' - COLOR_BRANDY: '(222, 193, 150)' - COLOR_BRANDY_ROSE: '(187, 137, 131)' - COLOR_BRONCO: '(171, 161, 150)' - COLOR_BROWN_YELLOW: '(204, 153, 102)' - COLOR_BURLYWOOD: '(222, 184, 135)' - COLOR_BURNING_SAND: '(217, 147, 118)' - COLOR_CAMEO: '(217, 185, 155)' - COLOR_CAN_CAN: '(213, 145, 164)' - COLOR_CAREYS_PINK: '(210, 158, 170)' - COLOR_CHENIN: '(223, 205, 111)' - COLOR_CHINO: '(206, 199, 167)' - COLOR_CLAM_SHELL: '(212, 182, 175)' - COLOR_CLOUD: '(199, 196, 191)' - COLOR_CLOUDY: '(172, 165, 159)' - COLOR_COLD_TURKEY: '(206, 186, 186)' - COLOR_CORAL_REEF: '(199, 188, 162)' - COLOR_COTTON_SEED: '(194, 189, 182)' - COLOR_DARK_KHAKI: '(189, 183, 107)' - COLOR_DARK_VANILLA: '(209, 190, 168)' - COLOR_DAWN: '(166, 162, 154)' - COLOR_DEL_RIO: '(176, 154, 149)' - COLOR_DUSTY_GRAY: '(168, 152, 155)' - COLOR_ECRU: '(194, 178, 128)' - COLOR_ENGLISH_LAVENDER: '(180, 131, 149)' - COLOR_EQUATOR: '(225, 188, 100)' - COLOR_EUNRY: '(207, 163, 157)' - COLOR_FALLOW: '(193, 154, 107)' - COLOR_FAWN: '(229, 170, 112)' - COLOR_FOGGY_GRAY: '(203, 202, 182)' - COLOR_GIMBLET: '(184, 181, 106)' - COLOR_GRAY_OLIVE: '(169, 164, 145)' - COLOR_GRULLO: '(169, 154, 134)' - COLOR_HEATHERED_GRAY: '(182, 176, 149)' - COLOR_HELIOTROPE_GRAY: '(170, 152, 169)' - COLOR_HILLARY: '(172, 165, 134)' - COLOR_KHAKI: '(195, 176, 145)' - COLOR_LASER: '(200, 181, 104)' - COLOR_LIGHT_FRENCH_BEIGE: '(200, 173, 127)' - COLOR_LIGHT_MEDIUM_ORCHID: '(211, 155, 203)' - COLOR_LILAC_LUSTER: '(174, 152, 170)' - COLOR_LILY: '(200, 170, 191)' - COLOR_MALTA: '(189, 178, 161)' - COLOR_MARTINI: '(175, 160, 158)' - COLOR_MISTY_MOSS: '(187, 180, 119)' - COLOR_MONGOOSE: '(181, 162, 127)' - COLOR_MY_PINK: '(214, 145, 136)' - COLOR_NAPA: '(172, 164, 148)' - COLOR_NOBEL: '(183, 177, 177)' - COLOR_NOMAD: '(186, 177, 162)' - COLOR_OLD_ROSE: '(192, 128, 129)' - COLOR_OLIVE_GREEN: '(181, 179, 92)' - COLOR_OPERA_MAUVE: '(183, 132, 167)' - COLOR_ORIENTAL_PINK: '(198, 145, 145)' - COLOR_PALE_SILVER: '(201, 192, 187)' - COLOR_PALE_SLATE: '(195, 191, 193)' - COLOR_PALE_TAUPE: '(188, 152, 126)' - COLOR_PASTEL_PINK: '(222, 165, 164)' - COLOR_PASTEL_VIOLET: '(203, 153, 201)' - COLOR_PAVLOVA: '(215, 196, 152)' - COLOR_PEARLY_PURPLE: '(183, 104, 162)' - COLOR_PETITE_ORCHID: '(219, 150, 144)' - COLOR_PINK_SWAN: '(190, 181, 183)' - COLOR_PUCE: '(204, 136, 153)' - COLOR_QUICKSAND: '(189, 151, 142)' - COLOR_ROB_ROY: '(234, 198, 116)' - COLOR_RODEO_DUST: '(201, 178, 155)' - COLOR_ROSY_BROWN: '(188, 143, 143)' - COLOR_SAGE: '(188, 184, 138)' - COLOR_SHADY_LADY: '(170, 165, 169)' - COLOR_SILK: '(189, 177, 168)' - COLOR_SILVER_PINK: '(196, 174, 173)' - COLOR_SOFT_AMBER: '(209, 198, 180)' - COLOR_SORRELL_BROWN: '(206, 185, 143)' - COLOR_STRAW: '(228, 217, 111)' - COLOR_SUNDANCE: '(201, 179, 91)' - COLOR_TACHA: '(214, 197, 98)' - COLOR_TALLOW: '(168, 165, 137)' - COLOR_TAN: '(210, 180, 140)' - COLOR_TEA: '(193, 186, 176)' - COLOR_THATCH: '(182, 157, 152)' - COLOR_THISTLE_GREEN: '(204, 202, 168)' - COLOR_TIDE: '(191, 184, 176)' - COLOR_TONYS_PINK: '(231, 159, 140)' - COLOR_TUMBLEWEED: '(222, 170, 136)' - COLOR_TUSCANY: '(192, 153, 153)' - COLOR_VEGAS_GOLD: '(197, 179, 88)' - COLOR_VIOLA: '(203, 143, 169)' - COLOR_WHISKEY: '(213, 154, 111)' - COLOR_WINTER_HAZEL: '(213, 209, 149)' - COLOR_YUMA: '(206, 194, 145)' - COLOR_ZORBA: '(165, 155, 145)' - COLOR_ACAPULCO: '(124, 176, 161)' - COLOR_ALGAE_GREEN: '(147, 223, 184)' - COLOR_ASH_GREY: '(178, 190, 181)' - COLOR_BOOGER_BUSTER: '(221, 226, 106)' - COLOR_BUD: '(168, 174, 156)' - COLOR_CAMBRIDGE_BLUE: '(163, 193, 173)' - COLOR_CASCADE: '(139, 169, 165)' - COLOR_CELADON: '(172, 225, 175)' - COLOR_CELERY: '(184, 194, 93)' - COLOR_CHINOOK: '(168, 227, 189)' - COLOR_CLAY_ASH: '(189, 200, 179)' - COLOR_CONIFER: '(172, 221, 77)' - COLOR_CORIANDER: '(196, 208, 176)' - COLOR_DARK_SEA_GREEN: '(143, 188, 143)' - COLOR_DE_YORK: '(122, 196, 136)' - COLOR_DECO: '(210, 218, 151)' - COLOR_EAGLE: '(182, 186, 164)' - COLOR_EDWARD: '(162, 174, 171)' - COLOR_ENVY: '(139, 166, 144)' - COLOR_ETON_BLUE: '(150, 200, 162)' - COLOR_FEIJOA: '(159, 221, 140)' - COLOR_FRINGY_FLOWER: '(177, 226, 193)' - COLOR_GRANNY_SMITH_APPLE: '(168, 228, 160)' - COLOR_GREEN_MIST: '(203, 211, 176)' - COLOR_GREEN_SMOKE: '(164, 175, 110)' - COLOR_GREEN_SPRING: '(184, 193, 177)' - COLOR_GULF_STREAM: '(128, 179, 174)' - COLOR_GUM_LEAF: '(182, 211, 191)' - COLOR_INCHWORM: '(178, 236, 93)' - COLOR_JET_STREAM: '(181, 210, 206)' - COLOR_JUNE_BUD: '(189, 218, 87)' - COLOR_KANGAROO: '(198, 200, 189)' - COLOR_LAUREL_GREEN: '(169, 186, 157)' - COLOR_LEMON_GRASS: '(155, 158, 143)' - COLOR_LIGHT_GREEN: '(144, 238, 144)' - COLOR_LIGHT_MOSS_GREEN: '(173, 223, 173)' - COLOR_LOCUST: '(168, 175, 142)' - COLOR_MADANG: '(183, 240, 190)' - COLOR_MEDIUM_AQUAMARINE: '(102, 221, 170)' - COLOR_MEDIUM_SPRING_BUD: '(201, 220, 135)' - COLOR_MINT_GREEN: '(152, 255, 152)' - COLOR_NORWAY: '(168, 189, 159)' - COLOR_OLIVINE: '(154, 185, 115)' - COLOR_OPAL: '(169, 198, 194)' - COLOR_PADUA: '(173, 230, 196)' - COLOR_PALE_GREEN: '(152, 251, 152)' - COLOR_PALE_LEAF: '(192, 211, 185)' - COLOR_PASTEL_GREEN: '(119, 221, 119)' - COLOR_PEWTER: '(150, 168, 161)' - COLOR_PINE_GLADE: '(199, 205, 144)' - COLOR_PISTACHIO: '(147, 197, 114)' - COLOR_PIXIE_GREEN: '(192, 216, 182)' - COLOR_POWDER_ASH: '(188, 201, 194)' - COLOR_PUMICE: '(194, 202, 196)' - COLOR_RAINEE: '(185, 200, 172)' - COLOR_REEF: '(201, 255, 162)' - COLOR_SCHIST: '(169, 180, 151)' - COLOR_SCREAMIN_GREEN: '(102, 255, 102)' - COLOR_SHADOW_GREEN: '(154, 194, 184)' - COLOR_SPRING_RAIN: '(172, 203, 177)' - COLOR_SPROUT: '(193, 215, 176)' - COLOR_SULU: '(193, 240, 124)' - COLOR_SUMMER_GREEN: '(150, 187, 171)' - COLOR_SURF: '(187, 215, 193)' - COLOR_SWAMP_GREEN: '(172, 183, 142)' - COLOR_TEAL_DEER: '(153, 230, 179)' - COLOR_TRADEWIND: '(95, 179, 172)' - COLOR_TURQUOISE_GREEN: '(160, 214, 180)' - COLOR_VERY_LIGHT_MALACHITE_GREEN: '(100, 233, 134)' - COLOR_WILD_WILLOW: '(185, 196, 106)' - COLOR_LIGHT_GRAYISH_MAGENTA: '(204, 153, 204)' - COLOR_LILAC: '(200, 162, 200)' - COLOR_DARK_MEDIUM_GRAY: '(169, 169, 169)' - COLOR_DELTA: '(164, 164, 157)' - COLOR_GRAY_NICKEL: '(195, 195, 189)' - COLOR_MIST_GRAY: '(196, 196, 188)' - COLOR_QUICK_SILVER: '(166, 166, 166)' - COLOR_SILVER: '(192, 192, 192)' - COLOR_SILVER_CHALICE: '(172, 172, 172)' - COLOR_SPANISH_GRAY: '(152, 152, 152)' - COLOR_STAR_DUST: '(159, 159, 156)' - COLOR_TIARA: '(195, 209, 209)' - COLOR_GRANNY_SMITH: '(132, 160, 160)' - pride: - COLOR_AIR_FORCE_BLUE: '(0, 48, 143)' - COLOR_ARAPAWA: '(17, 12, 108)' - COLOR_ASTRONAUT: '(40, 58, 119)' - COLOR_ASTRONAUT_BLUE: '(1, 62, 98)' - COLOR_BAY_OF_MANY: '(39, 58, 129)' - COLOR_BIG_STONE: '(22, 42, 64)' - COLOR_BISCAY: '(27, 49, 98)' - COLOR_BLACK_ROCK: '(13, 3, 50)' - COLOR_BLACKCURRANT: '(50, 41, 58)' - COLOR_BLEACHED_CEDAR: '(44, 33, 51)' - COLOR_BLUE_DIAMOND: '(56, 4, 116)' - COLOR_BLUE_GEM: '(44, 14, 140)' - COLOR_BLUE_WHALE: '(4, 46, 76)' - COLOR_BLUE_ZODIAC: '(19, 38, 77)' - COLOR_BUNTING: '(21, 31, 76)' - COLOR_CATALINA_BLUE: '(6, 42, 120)' - COLOR_CELLO: '(30, 56, 91)' - COLOR_CHARADE: '(41, 41, 55)' - COLOR_CHERRY_PIE: '(42, 3, 89)' - COLOR_CHRISTALLE: '(51, 3, 107)' - COLOR_CLOUD_BURST: '(32, 46, 84)' - COLOR_COOL_BLACK: '(0, 46, 99)' - COLOR_COSMIC_COBALT: '(46, 45, 136)' - COLOR_CYPRUS: '(0, 62, 64)' - COLOR_DARK_BLUE: '(0, 0, 139)' - COLOR_DARK_MIDNIGHT_BLUE: '(0, 51, 102)' - COLOR_DARK_PURPLE: '(48, 25, 52)' - COLOR_DEEP_BLUE: '(34, 8, 120)' - COLOR_DEEP_COVE: '(5, 16, 64)' - COLOR_DEEP_SAPPHIRE: '(8, 37, 103)' - COLOR_DEEP_VIOLET: '(51, 0, 102)' - COLOR_DOWNRIVER: '(9, 34, 86)' - COLOR_DUKE_BLUE: '(0, 0, 156)' - COLOR_EBONY_CLAY: '(38, 40, 59)' - COLOR_EGYPTIAN_BLUE: '(16, 52, 166)' - COLOR_ELEPHANT: '(18, 52, 71)' - COLOR_GOVERNOR_BAY: '(47, 60, 179)' - COLOR_GREEN_VOGUE: '(3, 43, 82)' - COLOR_GULF_BLUE: '(5, 22, 87)' - COLOR_HAITI: '(27, 16, 53)' - COLOR_IMPERIAL_BLUE: '(0, 35, 149)' - COLOR_INDIGO_DYE: '(9, 31, 146)' - COLOR_INTERNATIONAL_KLEIN_BLUE: '(0, 47, 167)' - COLOR_JACARTA: '(58, 42, 106)' - COLOR_JACKSONS_PURPLE: '(32, 32, 141)' - COLOR_JAGGER: '(53, 14, 87)' - COLOR_KINGFISHER_DAISY: '(62, 4, 128)' - COLOR_LUCKY_POINT: '(26, 26, 104)' - COLOR_MADISON: '(9, 37, 93)' - COLOR_METEORITE: '(60, 31, 118)' - COLOR_MIDNIGHT_BLUE: '(25, 25, 112)' - COLOR_NAVY: '(0, 0, 128)' - COLOR_NILE_BLUE: '(25, 55, 81)' - COLOR_OXFORD_BLUE: '(0, 33, 71)' - COLOR_PANTONE_BLUE: '(0, 24, 168)' - COLOR_PARIS_M: '(38, 5, 106)' - COLOR_PAUA: '(38, 3, 104)' - COLOR_PERSIAN_BLUE: '(28, 57, 187)' - COLOR_PERSIAN_INDIGO: '(50, 18, 122)' - COLOR_PHTHALO_BLUE: '(0, 15, 137)' - COLOR_PIGMENT_BLUE: '(51, 51, 153)' - COLOR_PIXIE_POWDER: '(57, 18, 133)' - COLOR_PORT_GORE: '(37, 31, 79)' - COLOR_PRUSSIAN_BLUE: '(0, 49, 83)' - COLOR_REGAL_BLUE: '(1, 63, 106)' - COLOR_RESOLUTION_BLUE: '(0, 35, 135)' - COLOR_REVOLVER: '(44, 22, 50)' - COLOR_ROYAL_AZURE: '(0, 56, 168)' - COLOR_RUSSIAN_VIOLET: '(50, 23, 77)' - COLOR_SMALT: '(0, 51, 153)' - COLOR_SPACE_CADET: '(29, 41, 81)' - COLOR_ST_PATRICKS_BLUE: '(35, 41, 122)' - COLOR_STEEL_GRAY: '(38, 35, 53)' - COLOR_STRATOS: '(0, 7, 65)' - COLOR_TANGAROA: '(3, 22, 60)' - COLOR_TARAWERA: '(7, 58, 80)' - COLOR_TIBER: '(6, 53, 55)' - COLOR_TOLOPEA: '(27, 2, 69)' - COLOR_TOREA_BAY: '(15, 45, 158)' - COLOR_UA_BLUE: '(0, 51, 170)' - COLOR_VALENTINO: '(53, 14, 66)' - COLOR_VALHALLA: '(43, 25, 79)' - COLOR_VIOLENT_VIOLET: '(41, 12, 94)' - COLOR_WINDSOR: '(60, 8, 120)' - COLOR_YANKEES_BLUE: '(28, 40, 65)' - COLOR_ZAFFRE: '(0, 20, 168)' - COLOR_BALTIC_SEA: '(42, 38, 48)' - COLOR_BASTILLE: '(41, 33, 48)' - COLOR_BLACK: '(0, 0, 0)' - COLOR_BLACK_PEARL: '(4, 19, 34)' - COLOR_BLACK_RUSSIAN: '(10, 0, 28)' - COLOR_BLUE_CHARCOAL: '(1, 13, 26)' - COLOR_BUNKER: '(13, 17, 23)' - COLOR_CHARLESTON_GREEN: '(35, 43, 43)' - COLOR_CINDER: '(14, 14, 24)' - COLOR_COD_GRAY: '(11, 11, 11)' - COLOR_DAINTREE: '(1, 39, 49)' - COLOR_DARK_GUNMETAL: '(31, 38, 42)' - COLOR_EERIE_BLACK: '(27, 27, 27)' - COLOR_FIREFLY: '(14, 42, 48)' - COLOR_JAGUAR: '(8, 1, 16)' - COLOR_MIRAGE: '(22, 25, 40)' - COLOR_RAISIN_BLACK: '(36, 33, 36)' - COLOR_SHARK: '(37, 39, 44)' - COLOR_SWAMP: '(0, 27, 28)' - COLOR_VULCAN: '(16, 18, 29)' - COLOR_WOODSMOKE: '(12, 13, 15)' - COLOR_RICH_BLACK: '(0, 64, 64)' - neutral: - COLOR_AURO_METAL_SAURUS: '(110, 127, 128)' - COLOR_BERMUDA_GRAY: '(107, 139, 162)' - COLOR_BLUE_MARGUERITE: '(118, 102, 198)' - COLOR_BLUE_YONDER: '(80, 114, 167)' - COLOR_CADET_BLUE: '(95, 158, 160)' - COLOR_CHINESE_VIOLET: '(133, 96, 136)' - COLOR_CYAN_AZURE: '(78, 130, 180)' - COLOR_CYAN_BLUE_AZURE: '(70, 130, 191)' - COLOR_DARK_BLUE_GRAY: '(102, 102, 153)' - COLOR_DEEP_LILAC: '(153, 85, 187)' - COLOR_DELUGE: '(117, 99, 168)' - COLOR_FRENCH_LILAC: '(134, 96, 142)' - COLOR_FUCHSIA_BLUE: '(122, 88, 193)' - COLOR_GLAUCOUS: '(96, 130, 182)' - COLOR_GOTHIC: '(109, 146, 161)' - COLOR_HAN_BLUE: '(68, 108, 207)' - COLOR_HIPPIE_BLUE: '(88, 154, 175)' - COLOR_HOKI: '(101, 134, 159)' - COLOR_HORIZON: '(90, 135, 160)' - COLOR_IRIS: '(90, 79, 207)' - COLOR_JUMBO: '(124, 123, 130)' - COLOR_KIMBERLY: '(115, 108, 159)' - COLOR_LIGHT_SLATE_GRAY: '(119, 136, 153)' - COLOR_LYNCH: '(105, 126, 154)' - COLOR_MAJORELLE_BLUE: '(96, 80, 220)' - COLOR_MAMBA: '(142, 129, 144)' - COLOR_MOBSTER: '(127, 117, 137)' - COLOR_OSLO_GRAY: '(135, 141, 145)' - COLOR_PALE_SKY: '(110, 119, 131)' - COLOR_PURPLE_PLUM: '(156, 81, 182)' - COLOR_PURPUREUS: '(154, 78, 174)' - COLOR_RAVEN: '(114, 123, 137)' - COLOR_REGENT_GRAY: '(134, 148, 159)' - COLOR_RHYTHM: '(119, 118, 150)' - COLOR_ROLLING_STONE: '(116, 125, 131)' - COLOR_ROMAN_SILVER: '(131, 137, 150)' - COLOR_ROYAL_AIR_FORCE_BLUE: '(93, 138, 168)' - COLOR_ROYAL_BLUE: '(65, 105, 225)' - COLOR_ROYAL_PURPLE: '(120, 81, 169)' - COLOR_RUM: '(121, 105, 137)' - COLOR_SCAMPI: '(103, 95, 166)' - COLOR_SHADOW_BLUE: '(119, 139, 165)' - COLOR_SHIP_COVE: '(120, 139, 186)' - COLOR_SILVER_LAKE_BLUE: '(93, 137, 186)' - COLOR_SLATE_BLUE: '(106, 90, 205)' - COLOR_SLATE_GRAY: '(112, 128, 144)' - COLOR_SMALT_BLUE: '(81, 128, 143)' - COLOR_STEEL_BLUE: '(70, 130, 180)' - COLOR_STEEL_TEAL: '(95, 138, 139)' - COLOR_STORM_GRAY: '(113, 116, 134)' - COLOR_STUDIO: '(113, 74, 178)' - COLOR_TOOLBOX: '(116, 108, 192)' - COLOR_TRENDY_PINK: '(140, 100, 149)' - COLOR_TUFTS_BLUE: '(65, 125, 193)' - COLOR_WAIKAWA_GRAY: '(90, 110, 156)' - COLOR_WATERLOO_: '(123, 124, 148)' - COLOR_WEDGEWOOD: '(78, 127, 158)' - COLOR_ALMOND_FROST: '(144, 123, 113)' - COLOR_AMERICANO: '(135, 117, 110)' - COLOR_ANTIQUE_FUCHSIA: '(145, 92, 131)' - COLOR_ARROWTOWN: '(148, 135, 113)' - COLOR_BANDICOOT: '(133, 132, 112)' - COLOR_BARLEY_CORN: '(166, 139, 91)' - COLOR_BAZAAR: '(152, 119, 123)' - COLOR_BEAVER: '(159, 129, 112)' - COLOR_BLAST_OFF_BRONZE: '(165, 113, 100)' - COLOR_BRASS: '(181, 166, 66)' - COLOR_BROWN_SUGAR: '(175, 110, 77)' - COLOR_BURNISHED_BROWN: '(161, 122, 116)' - COLOR_CADILLAC: '(176, 76, 106)' - COLOR_CEMENT: '(141, 118, 98)' - COLOR_CHAMOISEE: '(160, 120, 90)' - COLOR_CHINA_ROSE: '(168, 81, 110)' - COLOR_CINEREOUS: '(152, 129, 123)' - COLOR_CLAY_CREEK: '(138, 131, 96)' - COLOR_CONCORD: '(124, 123, 122)' - COLOR_COPPER_PENNY: '(173, 111, 105)' - COLOR_COPPER_ROSE: '(153, 102, 102)' - COLOR_CORAL_TREE: '(168, 107, 107)' - COLOR_DARK_CHESTNUT: '(152, 105, 96)' - COLOR_DARK_TAN: '(145, 129, 81)' - COLOR_DEEP_PUCE: '(169, 92, 104)' - COLOR_DEER: '(186, 135, 89)' - COLOR_DIRT: '(155, 118, 83)' - COLOR_DOMINO: '(142, 119, 94)' - COLOR_DRIFTWOOD: '(175, 135, 81)' - COLOR_EMPRESS: '(129, 115, 119)' - COLOR_FRIAR_GRAY: '(128, 126, 121)' - COLOR_GIANTS_CLUB: '(176, 92, 82)' - COLOR_GRANITE_GREEN: '(141, 137, 116)' - COLOR_GURKHA: '(154, 149, 119)' - COLOR_HEMP: '(144, 120, 116)' - COLOR_HURRICANE: '(135, 124, 123)' - COLOR_HUSK: '(183, 164, 88)' - COLOR_IRRESISTIBLE: '(179, 68, 108)' - COLOR_LEATHER: '(150, 112, 89)' - COLOR_LIGHT_TAUPE: '(179, 139, 109)' - COLOR_LIMED_OAK: '(172, 138, 86)' - COLOR_LIVER_CHESTNUT: '(152, 116, 86)' - COLOR_MAKARA: '(137, 125, 109)' - COLOR_MATRIX: '(176, 93, 84)' - COLOR_MONSOON: '(138, 131, 137)' - COLOR_MOUNTBATTEN_PINK: '(153, 122, 141)' - COLOR_MUDDY_WATERS: '(183, 142, 92)' - COLOR_MUESLI: '(170, 139, 91)' - COLOR_MYSTIC_MAROON: '(173, 67, 121)' - COLOR_NATURAL_GRAY: '(139, 134, 128)' - COLOR_OLIVE_HAZE: '(139, 132, 112)' - COLOR_OPIUM: '(142, 111, 112)' - COLOR_PALE_BROWN: '(152, 118, 84)' - COLOR_PALE_OYSTER: '(152, 141, 119)' - COLOR_PHARLAP: '(163, 128, 123)' - COLOR_RAZZMIC_BERRY: '(141, 78, 133)' - COLOR_ROCKET_METALLIC: '(138, 127, 128)' - COLOR_ROSE_DUST: '(158, 94, 111)' - COLOR_ROSE_GOLD: '(183, 110, 121)' - COLOR_SANDAL: '(170, 141, 111)' - COLOR_SANDRIFT: '(171, 145, 122)' - COLOR_SANTA_FE: '(177, 109, 82)' - COLOR_SCHOONER: '(139, 132, 126)' - COLOR_SHADOW: '(138, 121, 93)' - COLOR_SPICY_PINK: '(129, 110, 113)' - COLOR_SQUIRREL: '(143, 129, 118)' - COLOR_STONEWALL: '(146, 133, 115)' - COLOR_STRIKEMASTER: '(149, 99, 135)' - COLOR_SUVA_GRAY: '(136, 131, 135)' - COLOR_TAPA: '(123, 120, 116)' - COLOR_TAPESTRY: '(176, 94, 129)' - COLOR_TAUPE_GRAY: '(139, 133, 137)' - COLOR_TEAK: '(177, 148, 97)' - COLOR_TOAST: '(154, 110, 97)' - COLOR_TURKISH_ROSE: '(181, 114, 129)' - COLOR_TUSCAN_TAN: '(166, 123, 91)' - COLOR_TWINE: '(194, 149, 93)' - COLOR_VENUS: '(146, 133, 144)' - COLOR_AMULET: '(123, 159, 128)' - COLOR_ANDROID_GREEN: '(164, 198, 57)' - COLOR_AQUA_FOREST: '(95, 167, 119)' - COLOR_ARTICHOKE: '(143, 151, 121)' - COLOR_ASPARAGUS: '(135, 169, 107)' - COLOR_BATTLESHIP_GRAY: '(130, 143, 114)' - COLOR_BAY_LEAF: '(125, 169, 141)' - COLOR_BITTER: '(134, 137, 116)' - COLOR_BLUE_SMOKE: '(116, 136, 129)' - COLOR_BREAKER_BAY: '(93, 161, 159)' - COLOR_BUD_GREEN: '(123, 182, 97)' - COLOR_CAMOUFLAGE_GREEN: '(120, 134, 107)' - COLOR_CHELSEA_CUCUMBER: '(131, 170, 93)' - COLOR_DOLLAR_BILL: '(133, 187, 101)' - COLOR_EMERALD: '(80, 200, 120)' - COLOR_FERN: '(99, 183, 108)' - COLOR_FLAX_SMOKE: '(123, 130, 101)' - COLOR_GREEN_SHEEN: '(110, 174, 161)' - COLOR_GUNSMOKE: '(130, 134, 133)' - COLOR_HIGHLAND: '(111, 142, 99)' - COLOR_LAUREL: '(116, 147, 120)' - COLOR_MANTIS: '(116, 195, 101)' - COLOR_MANTLE: '(139, 156, 144)' - COLOR_MOSS_GREEN: '(138, 154, 91)' - COLOR_MUMMYS_TOMB: '(130, 142, 132)' - COLOR_OCEAN_GREEN: '(72, 191, 145)' - COLOR_OXLEY: '(119, 158, 134)' - COLOR_PATINA: '(99, 154, 143)' - COLOR_POLISHED_PINE: '(93, 164, 147)' - COLOR_RUSSIAN_GREEN: '(103, 146, 103)' - COLOR_SEA_NYMPH: '(120, 163, 156)' - COLOR_SHINY_SHAMROCK: '(95, 167, 120)' - COLOR_SILVER_TREE: '(102, 181, 143)' - COLOR_SMOKE: '(115, 130, 118)' - COLOR_STACK: '(138, 143, 138)' - COLOR_SUSHI: '(135, 171, 57)' - COLOR_VERDIGRIS: '(67, 179, 174)' - COLOR_XANADU: '(115, 134, 120)' - COLOR_YELLOW_GREEN: '(154, 205, 50)' - COLOR_BOULDER: '(122, 122, 122)' - COLOR_GRAY: '(128, 128, 128)' - COLOR_OLD_SILVER: '(132, 132, 130)' - COLOR_SONIC_SILVER: '(117, 117, 117)' - COLOR_DESATURATED_CYAN: '(102, 153, 153)' - COLOR_JUNIPER: '(109, 146, 146)' - COLOR_SIROCCO: '(113, 128, 128)' - sadness: - COLOR_AZURE: '(0, 127, 255)' - COLOR_BLUE: '(0, 0, 255)' - COLOR_BLUE_RIBBON: '(0, 102, 255)' - COLOR_BLUEBONNET: '(28, 28, 240)' - COLOR_BRANDEIS_BLUE: '(0, 112, 255)' - COLOR_CRAYOLA_BLUE: '(31, 117, 254)' - COLOR_ELECTRIC_INDIGO: '(111, 0, 255)' - COLOR_HAN_PURPLE: '(82, 24, 250)' - COLOR_MEDIUM_BLUE: '(0, 0, 205)' - COLOR_PALATINATE_BLUE: '(39, 59, 226)' - COLOR_RYB_BLUE: '(2, 71, 254)' - COLOR_ULTRAMARINE: '(63, 0, 255)' - COLOR_ULTRAMARINE_BLUE: '(65, 102, 245)' - COLOR_VIOLET: '(127, 0, 255)' - disapproval: - COLOR_BLUE_VIOLET: '(138, 43, 226)' - COLOR_CLAIRVOYANT: '(72, 6, 86)' - COLOR_DAISY_BUSH: '(79, 35, 152)' - COLOR_DARK_ORCHID: '(153, 50, 204)' - COLOR_DARK_VIOLET: '(148, 0, 211)' - COLOR_EMINENCE: '(108, 48, 130)' - COLOR_FRENCH_VIOLET: '(136, 6, 206)' - COLOR_GRAPE: '(111, 45, 168)' - COLOR_HELIOTROPE_MAGENTA: '(170, 0, 187)' - COLOR_HONEY_FLOWER: '(79, 28, 112)' - COLOR_INDIGO: '(75, 0, 130)' - COLOR_MUNSELL_PURPLE: '(159, 0, 197)' - COLOR_PURPLE_HEART: '(105, 53, 156)' - COLOR_RYB_VIOLET: '(134, 1, 175)' - COLOR_REBECCA_PURPLE: '(102, 51, 153)' - COLOR_REGALIA: '(82, 45, 128)' - COLOR_RIPE_PLUM: '(65, 0, 86)' - COLOR_SCARLET_GUM: '(67, 21, 96)' - COLOR_SEANCE: '(115, 30, 143)' - COLOR_SPANISH_VIOLET: '(76, 40, 130)' - COLOR_BOYSENBERRY: '(135, 50, 96)' - COLOR_BYZANTIUM: '(112, 41, 99)' - COLOR_CARDINAL_PINK: '(140, 5, 94)' - COLOR_DARK_RASPBERRY: '(135, 38, 87)' - COLOR_DISCO: '(135, 21, 80)' - COLOR_FANDANGO: '(181, 51, 137)' - COLOR_FLIRT: '(162, 0, 109)' - COLOR_FRENCH_PLUM: '(129, 20, 83)' - COLOR_FRESH_EGGPLANT: '(153, 0, 102)' - COLOR_HIBISCUS: '(182, 49, 108)' - COLOR_JAZZBERRY_JAM: '(165, 11, 94)' - COLOR_LIPSTICK: '(171, 5, 99)' - COLOR_LOULOU: '(70, 11, 65)' - COLOR_MARDI_GRAS: '(136, 0, 133)' - COLOR_PALATINATE_PURPLE: '(104, 40, 96)' - COLOR_PANSY_PURPLE: '(120, 24, 74)' - COLOR_PICTORIAL_CARMINE: '(195, 11, 78)' - COLOR_POMPADOUR: '(102, 0, 69)' - COLOR_RICH_MAROON: '(176, 48, 96)' - COLOR_ROSE_BUD_CHERRY: '(128, 11, 71)' - COLOR_ROSE_RED: '(194, 30, 86)' - COLOR_ROUGE: '(162, 59, 108)' - COLOR_ROYAL_HEATH: '(171, 52, 114)' - COLOR_DARK_MAGENTA: '(139, 0, 139)' - COLOR_MIDNIGHT: '(112, 38, 112)' - COLOR_PURPLE: '(128, 0, 128)' - COLOR_VIOLET_EGGPLANT: '(153, 17, 153)' - embarrassment: - COLOR_BOTTICELLI: '(199, 221, 229)' - COLOR_BRIGHT_LILAC: '(216, 145, 239)' - COLOR_BRIGHT_UBE: '(209, 159, 232)' - COLOR_BRILLIANT_LAVENDER: '(244, 187, 255)' - COLOR_GHOST: '(199, 201, 213)' - COLOR_MAUVE: '(224, 176, 255)' - COLOR_PALE_VIOLET: '(204, 153, 255)' - COLOR_PERFUME: '(208, 190, 248)' - COLOR_PRELUDE: '(208, 192, 229)' - COLOR_RICH_BRILLIANT_LAVENDER: '(241, 167, 254)' - COLOR_SOAP: '(206, 200, 239)' - COLOR_TROPICAL_VIOLET: '(205, 164, 222)' - COLOR_AMARANTH_PINK: '(241, 156, 187)' - COLOR_APRICOT: '(251, 206, 177)' - COLOR_ASTRA: '(250, 234, 185)' - COLOR_AZALEA: '(247, 200, 218)' - COLOR_BANANA_MANIA: '(250, 231, 181)' - COLOR_BEAUTY_BUSH: '(238, 193, 190)' - COLOR_BEESWAX: '(254, 242, 199)' - COLOR_BISQUE: '(255, 228, 196)' - COLOR_BLOND: '(250, 240, 190)' - COLOR_BLOSSOM: '(220, 180, 188)' - COLOR_BUBBLE_GUM: '(255, 193, 204)' - COLOR_BUFF: '(240, 220, 130)' - COLOR_BUTTERMILK: '(255, 241, 181)' - COLOR_CALICO: '(224, 192, 149)' - COLOR_CAMEO_PINK: '(239, 187, 204)' - COLOR_CAPE_HONEY: '(254, 229, 172)' - COLOR_CARAMEL: '(255, 221, 175)' - COLOR_CARNATION_PINK: '(255, 166, 201)' - COLOR_CASHMERE: '(230, 190, 165)' - COLOR_CAVERN_PINK: '(227, 190, 190)' - COLOR_CHALKY: '(238, 215, 148)' - COLOR_CHAMOIS: '(237, 220, 177)' - COLOR_CHANTILLY: '(248, 195, 223)' - COLOR_CHARDONNAY: '(255, 205, 140)' - COLOR_CHEROKEE: '(252, 218, 152)' - COLOR_CHERRY_BLOSSOM_PINK: '(255, 183, 197)' - COLOR_COLONIAL_WHITE: '(255, 237, 188)' - COLOR_CORNFLOWER_LILAC: '(255, 176, 172)' - COLOR_CORVETTE: '(250, 211, 162)' - COLOR_COTTON_CANDY: '(255, 188, 217)' - COLOR_CRAYOLA_YELLOW: '(252, 232, 131)' - COLOR_CREAM_BRULEE: '(255, 229, 160)' - COLOR_CUPID: '(251, 190, 218)' - COLOR_DAIRY_CREAM: '(249, 228, 188)' - COLOR_DESERT_SAND: '(237, 201, 175)' - COLOR_DOUBLE_COLONIAL_WHITE: '(238, 227, 173)' - COLOR_DOUBLE_SPANISH_WHITE: '(230, 215, 185)' - COLOR_DROVER: '(253, 247, 173)' - COLOR_DUTCH_WHITE: '(239, 223, 187)' - COLOR_EGG_WHITE: '(255, 239, 193)' - COLOR_FESTIVAL: '(251, 233, 108)' - COLOR_FLAVESCENT: '(247, 233, 142)' - COLOR_FLAX: '(238, 220, 130)' - COLOR_FRANGIPANI: '(255, 222, 179)' - COLOR_GIVRY: '(248, 228, 191)' - COLOR_GOLDEN_GLOW: '(253, 226, 149)' - COLOR_GOLDEN_SAND: '(240, 219, 125)' - COLOR_GRAIN_BROWN: '(228, 213, 183)' - COLOR_GRANDIS: '(255, 211, 140)' - COLOR_HAMPTON: '(229, 216, 175)' - COLOR_HIT_PINK: '(255, 171, 129)' - COLOR_ILLUSION: '(246, 164, 201)' - COLOR_JASMINE: '(248, 222, 126)' - COLOR_JUST_RIGHT: '(236, 205, 185)' - COLOR_KOBI: '(231, 159, 196)' - COLOR_KOURNIKOVA: '(255, 231, 114)' - COLOR_LAVENDER_PINK: '(251, 174, 210)' - COLOR_LAVENDER_ROSE: '(251, 160, 227)' - COLOR_LEMON_MERINGUE: '(246, 234, 190)' - COLOR_LIGHT_APRICOT: '(253, 213, 177)' - COLOR_LIGHT_HOT_PINK: '(255, 179, 222)' - COLOR_LIGHT_KHAKI: '(240, 230, 140)' - COLOR_LIGHT_ORCHID: '(230, 168, 215)' - COLOR_LIGHT_PINK: '(255, 182, 193)' - COLOR_MACARONI_AND_CHEESE: '(255, 185, 123)' - COLOR_MANDYS_PINK: '(242, 195, 178)' - COLOR_MANHATTAN: '(245, 201, 153)' - COLOR_MARIGOLD_YELLOW: '(251, 232, 112)' - COLOR_MARZIPAN: '(248, 219, 157)' - COLOR_MAVERICK: '(216, 194, 213)' - COLOR_MELANIE: '(228, 194, 213)' - COLOR_MELLOW_APRICOT: '(248, 184, 120)' - COLOR_MELON: '(253, 188, 180)' - COLOR_MINT_JULEP: '(241, 238, 193)' - COLOR_MOCCASIN: '(255, 228, 181)' - COLOR_MONA_LISA: '(255, 161, 148)' - COLOR_NADESHIKO_PINK: '(246, 173, 198)' - COLOR_NAVAJO_WHITE: '(255, 222, 173)' - COLOR_NEGRONI: '(255, 226, 197)' - COLOR_NEW_ORLEANS: '(243, 214, 157)' - COLOR_ORANGE_YELLOW: '(248, 213, 104)' - COLOR_ORCHID_PINK: '(242, 189, 205)' - COLOR_PALE_CHESTNUT: '(221, 173, 175)' - COLOR_PALE_GOLD: '(230, 190, 138)' - COLOR_PALE_GOLDENROD: '(238, 232, 170)' - COLOR_PALE_MAGENTA_PINK: '(255, 153, 204)' - COLOR_PALE_SPRING_BUD: '(236, 235, 189)' - COLOR_PANCHO: '(237, 205, 171)' - COLOR_PARIS_DAISY: '(255, 244, 110)' - COLOR_PASTEL_MAGENTA: '(244, 154, 194)' - COLOR_PEACH: '(255, 203, 164)' - COLOR_PEACH_ORANGE: '(255, 204, 153)' - COLOR_PEACH_PUFF: '(255, 218, 185)' - COLOR_PEACH_YELLOW: '(250, 223, 173)' - COLOR_PEARL: '(234, 224, 200)' - COLOR_PICASSO: '(255, 243, 157)' - COLOR_PINK: '(255, 192, 203)' - COLOR_PINK_FLARE: '(225, 192, 200)' - COLOR_PINK_LAVENDER: '(216, 178, 209)' - COLOR_PINK_PEARL: '(231, 172, 207)' - COLOR_PRIMROSE: '(237, 234, 153)' - COLOR_PUTTY: '(231, 205, 140)' - COLOR_RAFFIA: '(234, 218, 184)' - COLOR_ROMANTIC: '(255, 210, 183)' - COLOR_ROSE_BUD: '(251, 178, 163)' - COLOR_ROSE_FOG: '(231, 188, 180)' - COLOR_SAHARA_SAND: '(241, 231, 136)' - COLOR_SALOMIE: '(254, 219, 141)' - COLOR_SANDWISP: '(245, 231, 162)' - COLOR_SANDY_BEACH: '(255, 234, 200)' - COLOR_SAPLING: '(222, 212, 164)' - COLOR_SHILO: '(232, 185, 179)' - COLOR_SIDECAR: '(243, 231, 187)' - COLOR_SISAL: '(211, 203, 186)' - COLOR_SPANISH_PINK: '(247, 191, 190)' - COLOR_STARK_WHITE: '(229, 215, 189)' - COLOR_SUNDOWN: '(255, 177, 179)' - COLOR_SUNSET: '(250, 214, 165)' - COLOR_SWEET_CORN: '(251, 234, 140)' - COLOR_SWEET_PINK: '(253, 159, 162)' - COLOR_SWIRL: '(211, 205, 197)' - COLOR_TACAO: '(237, 179, 129)' - COLOR_TEA_ROSE: '(244, 194, 194)' - COLOR_TEQUILA: '(255, 230, 199)' - COLOR_TOPAZ: '(255, 200, 124)' - COLOR_VANILLA: '(243, 229, 171)' - COLOR_VERY_LIGHT_TANGELO: '(255, 176, 119)' - COLOR_VERY_PALE_ORANGE: '(255, 223, 191)' - COLOR_VIS_VIS: '(255, 239, 161)' - COLOR_WAFER: '(222, 203, 198)' - COLOR_WAX_FLOWER: '(255, 192, 168)' - COLOR_WEWAK: '(241, 155, 171)' - COLOR_WHEAT: '(245, 222, 179)' - COLOR_WILD_RICE: '(236, 224, 144)' - COLOR_WITCH_HAZE: '(255, 252, 153)' - COLOR_YOUR_PINK: '(255, 195, 192)' - COLOR_ZINNWALDITE: '(235, 194, 175)' - COLOR_ZOMBIE: '(228, 214, 155)' - COLOR_AUSTRALIAN_MINT: '(245, 255, 190)' - COLOR_BERYL_GREEN: '(222, 229, 192)' - COLOR_CAPER: '(220, 237, 180)' - COLOR_CHIFFON: '(241, 255, 200)' - COLOR_DOLLY: '(249, 255, 139)' - COLOR_EDGEWATER: '(200, 227, 215)' - COLOR_GOSSIP: '(210, 248, 176)' - COLOR_HONEYSUCKLE: '(237, 252, 132)' - COLOR_KEY_LIME: '(232, 244, 140)' - COLOR_MANZ: '(238, 239, 120)' - COLOR_MILAN: '(250, 255, 164)' - COLOR_MINDARO: '(227, 249, 136)' - COLOR_PALE_PRIM: '(253, 254, 184)' - COLOR_SEA_MIST: '(197, 219, 202)' - COLOR_SHALIMAR: '(251, 255, 186)' - COLOR_TAHUNA_SANDS: '(238, 240, 200)' - COLOR_TANA: '(217, 220, 193)' - COLOR_TEA_GREEN: '(208, 240, 192)' - COLOR_TEXAS: '(248, 249, 156)' - COLOR_TIDAL: '(241, 255, 173)' - COLOR_TUSK: '(238, 243, 195)' - COLOR_PALE_CANARY: '(255, 255, 153)' - COLOR_PASTEL_GRAY: '(207, 207, 196)' - COLOR_PASTEL_YELLOW: '(253, 253, 150)' - COLOR_PORTAFINO: '(255, 255, 180)' - COLOR_SUNNY: '(242, 242, 122)' - COLOR_VERY_PALE_YELLOW: '(255, 255, 191)' - COLOR_PALE_PLUM: '(221, 160, 221)' - COLOR_THISTLE: '(216, 191, 216)' - COLOR_ALABASTER: '(250, 250, 250)' - COLOR_ALBESCENT_WHITE: '(245, 233, 211)' - COLOR_ALICE_BLUE: '(240, 248, 255)' - COLOR_ALMOND: '(239, 222, 205)' - COLOR_ALTO: '(219, 219, 219)' - COLOR_AMOUR: '(249, 234, 243)' - COLOR_ANTI_FLASH_WHITE: '(242, 243, 244)' - COLOR_ANTIQUE_WHITE: '(250, 235, 215)' - COLOR_APRICOT_WHITE: '(255, 254, 236)' - COLOR_AQUA_HAZE: '(237, 245, 245)' - COLOR_AQUA_SPRING: '(234, 249, 245)' - COLOR_AQUA_SQUEEZE: '(232, 245, 242)' - COLOR_ATHENS_GRAY: '(238, 240, 243)' - COLOR_ATHS_SPECIAL: '(236, 235, 206)' - COLOR_AZURE_MIST: '(240, 255, 255)' - COLOR_AZUREISH_WHITE: '(219, 233, 244)' - COLOR_BABY_POWDER: '(254, 254, 250)' - COLOR_BAJA_WHITE: '(255, 248, 209)' - COLOR_BARLEY_WHITE: '(255, 244, 206)' - COLOR_BEIGE: '(245, 245, 220)' - COLOR_BIANCA: '(252, 251, 243)' - COLOR_BIZARRE: '(238, 222, 218)' - COLOR_BLACK_HAZE: '(246, 247, 247)' - COLOR_BLACK_SQUEEZE: '(242, 250, 250)' - COLOR_BLACK_WHITE: '(255, 254, 246)' - COLOR_BLANCHED_ALMOND: '(255, 235, 205)' - COLOR_BLEACH_WHITE: '(254, 243, 216)' - COLOR_BLUE_CHALK: '(241, 233, 255)' - COLOR_BLUE_ROMANCE: '(210, 246, 222)' - COLOR_BON_JOUR: '(229, 224, 225)' - COLOR_BONE: '(227, 218, 201)' - COLOR_BRIDAL_HEATH: '(255, 250, 244)' - COLOR_BRIDESMAID: '(254, 240, 236)' - COLOR_BUBBLES: '(231, 254, 255)' - COLOR_BUTTERY_WHITE: '(255, 252, 234)' - COLOR_CARARRA: '(238, 238, 232)' - COLOR_CARLA: '(243, 255, 216)' - COLOR_CAROUSEL_PINK: '(249, 224, 237)' - COLOR_CATSKILL_WHITE: '(238, 246, 247)' - COLOR_CERAMIC: '(252, 255, 249)' - COLOR_CHABLIS: '(255, 244, 243)' - COLOR_CHAMPAGNE: '(247, 231, 206)' - COLOR_CHARDON: '(255, 243, 241)' - COLOR_CHERUB: '(248, 217, 233)' - COLOR_CHILEAN_HEATH: '(255, 253, 230)' - COLOR_CHINA_IVORY: '(252, 255, 231)' - COLOR_CHROME_WHITE: '(232, 241, 212)' - COLOR_CINDERELLA: '(253, 225, 220)' - COLOR_CITRINE_WHITE: '(250, 247, 214)' - COLOR_CLASSIC_ROSE: '(251, 204, 231)' - COLOR_CLEAR_DAY: '(233, 255, 253)' - COLOR_COCONUT_CREAM: '(248, 247, 220)' - COLOR_CONCH: '(201, 217, 210)' - COLOR_CONCRETE: '(242, 242, 242)' - COLOR_CORN_FIELD: '(248, 250, 205)' - COLOR_CORNSILK: '(255, 248, 220)' - COLOR_COSMIC_LATTE: '(255, 248, 231)' - COLOR_COSMOS: '(255, 216, 217)' - COLOR_CREAM: '(255, 253, 208)' - COLOR_CUMULUS: '(253, 255, 213)' - COLOR_DAWN_PINK: '(243, 233, 229)' - COLOR_DERBY: '(255, 238, 216)' - COLOR_DESERT_STORM: '(248, 248, 247)' - COLOR_DEW: '(234, 255, 254)' - COLOR_DOUBLE_PEARL_LUSTA: '(252, 244, 208)' - COLOR_DUST_STORM: '(229, 204, 201)' - COLOR_EARLY_DAWN: '(255, 249, 230)' - COLOR_EBB: '(233, 227, 227)' - COLOR_ECRU_WHITE: '(245, 243, 229)' - COLOR_EGG_SOUR: '(255, 244, 221)' - COLOR_EGGSHELL: '(240, 234, 214)' - COLOR_FAIR_PINK: '(255, 239, 236)' - COLOR_FANTASY: '(250, 243, 240)' - COLOR_FETA: '(240, 252, 234)' - COLOR_FLORAL_WHITE: '(255, 250, 240)' - COLOR_FOAM: '(216, 252, 250)' - COLOR_FOG: '(215, 208, 255)' - COLOR_FORGET_ME_NOT: '(255, 241, 238)' - COLOR_FROST: '(237, 245, 221)' - COLOR_FROSTED_MINT: '(219, 255, 248)' - COLOR_FROSTEE: '(228, 246, 231)' - COLOR_GAINSBORO: '(220, 220, 220)' - COLOR_GALLERY: '(239, 239, 239)' - COLOR_GEYSER: '(212, 223, 226)' - COLOR_GHOST_WHITE: '(248, 248, 255)' - COLOR_GIN: '(232, 242, 235)' - COLOR_GIN_FIZZ: '(255, 249, 226)' - COLOR_GLITTER: '(230, 232, 250)' - COLOR_GRANNY_APPLE: '(213, 246, 227)' - COLOR_GRAY_NURSE: '(231, 236, 230)' - COLOR_GREEN_WHITE: '(232, 235, 224)' - COLOR_HALF_COLONIAL_WHITE: '(253, 246, 211)' - COLOR_HALF_DUTCH_WHITE: '(254, 247, 222)' - COLOR_HALF_SPANISH_WHITE: '(254, 244, 219)' - COLOR_HALF_AND_HALF: '(255, 254, 225)' - COLOR_HARP: '(230, 242, 234)' - COLOR_HAWKES_BLUE: '(212, 226, 252)' - COLOR_HINT_OF_GREEN: '(230, 255, 233)' - COLOR_HINT_OF_RED: '(251, 249, 249)' - COLOR_HINT_OF_YELLOW: '(250, 253, 228)' - COLOR_HONEYDEW: '(240, 255, 240)' - COLOR_IRON: '(212, 215, 217)' - COLOR_ISABELLINE: '(244, 240, 236)' - COLOR_ISLAND_SPICE: '(255, 252, 238)' - COLOR_IVORY: '(255, 255, 240)' - COLOR_JANNA: '(244, 235, 211)' - COLOR_KARRY: '(255, 234, 212)' - COLOR_KIDNAPPER: '(225, 234, 212)' - COLOR_LANGUID_LAVENDER: '(214, 202, 221)' - COLOR_LAVENDER_BLUSH: '(255, 240, 245)' - COLOR_LAVENDER_MIST: '(230, 230, 250)' - COLOR_LEMON_CHIFFON: '(255, 250, 205)' - COLOR_LIGHT_CYAN: '(224, 255, 255)' - COLOR_LIGHT_GOLDENROD_YELLOW: '(250, 250, 210)' - COLOR_LIGHT_GRAY: '(211, 211, 211)' - COLOR_LIGHT_YELLOW: '(255, 255, 224)' - COLOR_LILY_WHITE: '(231, 248, 255)' - COLOR_LINEN: '(250, 240, 230)' - COLOR_LINK_WATER: '(217, 228, 245)' - COLOR_LOAFER: '(238, 244, 222)' - COLOR_LOLA: '(223, 207, 219)' - COLOR_LUMBER: '(255, 228, 205)' - COLOR_MABEL: '(217, 247, 255)' - COLOR_MAGNOLIA: '(248, 244, 255)' - COLOR_MERCURY: '(229, 229, 229)' - COLOR_MERINO: '(246, 240, 230)' - COLOR_MILK_PUNCH: '(255, 246, 212)' - COLOR_MIMOSA: '(248, 253, 211)' - COLOR_MINT_CREAM: '(245, 255, 250)' - COLOR_MISCHKA: '(209, 210, 221)' - COLOR_MISTY_ROSE: '(255, 228, 225)' - COLOR_MOON_GLOW: '(252, 254, 218)' - COLOR_MOON_MIST: '(220, 221, 204)' - COLOR_MOON_RAKER: '(214, 206, 246)' - COLOR_NARVIK: '(237, 249, 241)' - COLOR_NEBULA: '(203, 219, 214)' - COLOR_NYANZA: '(233, 255, 219)' - COLOR_OASIS: '(254, 239, 206)' - COLOR_OFF_GREEN: '(230, 248, 243)' - COLOR_OFF_YELLOW: '(254, 249, 227)' - COLOR_OLD_LACE: '(253, 245, 230)' - COLOR_ORANGE_WHITE: '(254, 252, 237)' - COLOR_ORCHID_WHITE: '(255, 253, 243)' - COLOR_ORINOCO: '(243, 251, 212)' - COLOR_OTTOMAN: '(233, 248, 237)' - COLOR_OYSTER_BAY: '(218, 250, 255)' - COLOR_OYSTER_PINK: '(233, 206, 205)' - COLOR_PALE_LAVENDER: '(220, 208, 255)' - COLOR_PALE_PINK: '(250, 218, 221)' - COLOR_PALE_ROSE: '(255, 225, 242)' - COLOR_PAMPAS: '(244, 242, 238)' - COLOR_PANACHE: '(234, 246, 238)' - COLOR_PAPAYA_WHIP: '(255, 239, 213)' - COLOR_PARCHMENT: '(241, 233, 210)' - COLOR_PARIS_WHITE: '(202, 220, 212)' - COLOR_PATTENS_BLUE: '(222, 245, 255)' - COLOR_PEACH_CREAM: '(255, 240, 219)' - COLOR_PEACH_SCHNAPPS: '(255, 220, 214)' - COLOR_PEARL_BUSH: '(232, 224, 213)' - COLOR_PEARL_LUSTA: '(252, 244, 220)' - COLOR_PEPPERMINT: '(227, 245, 225)' - COLOR_PERIGLACIAL_BLUE: '(225, 230, 214)' - COLOR_PIG_PINK: '(253, 215, 228)' - COLOR_PIGGY_PINK: '(253, 221, 230)' - COLOR_PINK_LACE: '(255, 221, 244)' - COLOR_PINK_LADY: '(255, 241, 216)' - COLOR_PIPI: '(254, 244, 204)' - COLOR_PIPPIN: '(255, 225, 223)' - COLOR_PLATINUM: '(229, 228, 226)' - COLOR_POLAR: '(229, 249, 246)' - COLOR_PORCELAIN: '(239, 242, 243)' - COLOR_POT_POURRI: '(245, 231, 226)' - COLOR_PRIM: '(240, 226, 236)' - COLOR_PROVINCIAL_PINK: '(254, 245, 241)' - COLOR_QUARTER_PEARL_LUSTA: '(255, 253, 244)' - COLOR_QUARTER_SPANISH_WHITE: '(247, 242, 225)' - COLOR_QUEEN_PINK: '(232, 204, 215)' - COLOR_QUILL_GRAY: '(214, 214, 209)' - COLOR_REMY: '(254, 235, 243)' - COLOR_RICE_CAKE: '(255, 254, 240)' - COLOR_RICE_FLOWER: '(238, 255, 226)' - COLOR_ROMANCE: '(255, 254, 253)' - COLOR_ROSE_WHITE: '(255, 246, 245)' - COLOR_RUM_SWIZZLE: '(249, 248, 228)' - COLOR_SALTPAN: '(241, 247, 242)' - COLOR_SATIN_LINEN: '(230, 228, 212)' - COLOR_SAUVIGNON: '(255, 245, 243)' - COLOR_SAZERAC: '(255, 244, 224)' - COLOR_SCOTCH_MIST: '(255, 251, 220)' - COLOR_SEASHELL: '(255, 245, 238)' - COLOR_SELAGO: '(240, 238, 253)' - COLOR_SERENADE: '(255, 244, 232)' - COLOR_SHAMPOO: '(255, 207, 241)' - COLOR_SKEPTIC: '(202, 230, 218)' - COLOR_SNOW: '(255, 250, 250)' - COLOR_SNOW_DRIFT: '(247, 250, 247)' - COLOR_SNOW_FLURRY: '(228, 255, 209)' - COLOR_SNOWY_MINT: '(214, 255, 219)' - COLOR_SNUFF: '(226, 216, 237)' - COLOR_SOAPSTONE: '(255, 251, 249)' - COLOR_SOFT_PEACH: '(245, 237, 239)' - COLOR_SOLITAIRE: '(254, 248, 226)' - COLOR_SOLITUDE: '(234, 246, 255)' - COLOR_SPRING_SUN: '(246, 255, 220)' - COLOR_SPRING_WOOD: '(248, 246, 241)' - COLOR_SUGAR_CANE: '(249, 255, 246)' - COLOR_SURF_CREST: '(207, 229, 210)' - COLOR_SWANS_DOWN: '(220, 240, 234)' - COLOR_SWISS_COFFEE: '(221, 214, 213)' - COLOR_TARA: '(225, 246, 232)' - COLOR_TASMAN: '(207, 220, 207)' - COLOR_TIMBERWOLF: '(219, 215, 210)' - COLOR_TITAN_WHITE: '(240, 238, 255)' - COLOR_TRANQUIL: '(230, 255, 255)' - COLOR_TRAVERTINE: '(255, 253, 232)' - COLOR_TUFT_BUSH: '(255, 221, 205)' - COLOR_TUTU: '(255, 241, 249)' - COLOR_TWILIGHT: '(228, 207, 222)' - COLOR_TWILIGHT_BLUE: '(238, 253, 255)' - COLOR_UNBLEACHED_SILK: '(255, 221, 202)' - COLOR_UNDERAGE_PINK: '(249, 230, 244)' - COLOR_VARDEN: '(255, 246, 223)' - COLOR_VISTA_WHITE: '(252, 248, 247)' - COLOR_WATUSI: '(255, 221, 207)' - COLOR_WE_PEEP: '(247, 219, 230)' - COLOR_WESTAR: '(220, 217, 210)' - COLOR_WHEATFIELD: '(243, 237, 207)' - COLOR_WHISPER: '(247, 245, 250)' - COLOR_WHITE: '(255, 255, 255)' - COLOR_WHITE_ICE: '(221, 249, 241)' - COLOR_WHITE_LILAC: '(248, 247, 252)' - COLOR_WHITE_LINEN: '(248, 240, 232)' - COLOR_WHITE_POINTER: '(254, 248, 255)' - COLOR_WHITE_ROCK: '(234, 232, 212)' - COLOR_WHITE_SMOKE: '(245, 245, 245)' - COLOR_WILD_SAND: '(244, 244, 244)' - COLOR_WILLOW_BROOK: '(223, 236, 218)' - COLOR_WISP_PINK: '(254, 244, 248)' - COLOR_ZANAH: '(218, 236, 214)' - COLOR_ZIRCON: '(244, 248, 255)' - COLOR_ZUMTHOR: '(237, 246, 255)' - confusion: - COLOR_ELECTRIC_PURPLE: '(191, 0, 255)' - COLOR_ELECTRIC_VIOLET: '(139, 0, 255)' - COLOR_PSYCHEDELIC_PURPLE: '(223, 0, 255)' - COLOR_VERONICA: '(160, 32, 240)' - COLOR_VIVID_MULBERRY: '(184, 12, 227)' - COLOR_VIVID_ORCHID: '(204, 0, 255)' - COLOR_VIVID_VIOLET: '(159, 0, 255)' - COLOR_HOT_MAGENTA: '(255, 29, 206)' - COLOR_METAL_PINK: '(255, 0, 253)' - COLOR_DEEP_MAGENTA: '(204, 0, 204)' - COLOR_FUCHSIA: '(255, 0, 255)' - surprise: - COLOR_HELIOTROPE: '(223, 115, 255)' - COLOR_MEDIUM_ORCHID: '(186, 85, 211)' - COLOR_PINK_FLAMINGO: '(252, 116, 253)' - COLOR_RICH_LILAC: '(182, 102, 210)' - COLOR_BAKER_MILLER_PINK: '(255, 145, 175)' - COLOR_BLUSH: '(222, 93, 131)' - COLOR_BRILLIANT_ROSE: '(255, 85, 163)' - COLOR_BRINK_PINK: '(251, 96, 127)' - COLOR_CARISSMA: '(234, 136, 168)' - COLOR_CHARM: '(212, 116, 148)' - COLOR_CHARM_PINK: '(230, 143, 172)' - COLOR_CINNAMON_SATIN: '(205, 96, 126)' - COLOR_CYCLAMEN: '(245, 111, 161)' - COLOR_DARK_PINK: '(231, 84, 128)' - COLOR_DEEP_BLUSH: '(228, 118, 152)' - COLOR_FANDANGO_PINK: '(222, 82, 133)' - COLOR_FLAMINGO_PINK: '(252, 142, 172)' - COLOR_FRENCH_PINK: '(253, 108, 158)' - COLOR_FRENCH_ROSE: '(246, 74, 138)' - COLOR_FROLY: '(245, 117, 132)' - COLOR_GERALDINE: '(251, 137, 137)' - COLOR_HOPBUSH: '(208, 109, 161)' - COLOR_HOT_PINK: '(255, 105, 180)' - COLOR_LIGHT_CORAL: '(240, 128, 128)' - COLOR_LIGHT_CRIMSON: '(245, 105, 145)' - COLOR_LIGHT_DEEP_PINK: '(255, 92, 205)' - COLOR_LIGHT_FUCHSIA_PINK: '(249, 132, 239)' - COLOR_LIGHT_SALMON_PINK: '(255, 153, 153)' - COLOR_MAUVELOUS: '(239, 152, 170)' - COLOR_MULBERRY: '(197, 75, 140)' - COLOR_MYSTIC: '(214, 82, 130)' - COLOR_NEW_YORK_PINK: '(215, 131, 127)' - COLOR_ORCHID: '(218, 112, 214)' - COLOR_PALE_MAGENTA: '(249, 132, 229)' - COLOR_PALE_RED_VIOLET: '(219, 112, 147)' - COLOR_PANTONE_PINK: '(215, 72, 148)' - COLOR_PERSIAN_PINK: '(247, 127, 190)' - COLOR_PINK_SHERBET: '(247, 143, 167)' - COLOR_PRINCESS_PERFUME: '(255, 133, 207)' - COLOR_PURPLE_PIZZAZZ: '(254, 78, 218)' - COLOR_RASPBERRY_PINK: '(226, 80, 152)' - COLOR_RAZZLE_DAZZLE_ROSE: '(255, 51, 204)' - COLOR_ROSE_BONBON: '(249, 66, 158)' - COLOR_ROSE_PINK: '(255, 102, 204)' - COLOR_RUDDY_PINK: '(225, 142, 150)' - COLOR_SALMON_PINK: '(255, 145, 164)' - COLOR_SEA_PINK: '(237, 152, 158)' - COLOR_SHIMMERING_BLUSH: '(217, 134, 149)' - COLOR_SHOCKING: '(226, 146, 192)' - COLOR_SKY_MAGENTA: '(207, 113, 175)' - COLOR_STRAWBERRY: '(252, 90, 141)' - COLOR_SUPER_PINK: '(207, 107, 169)' - COLOR_THULIAN_PINK: '(222, 111, 161)' - COLOR_TICKLE_ME_PINK: '(252, 137, 172)' - COLOR_TULIP: '(255, 135, 141)' - COLOR_VANILLA_ICE: '(243, 143, 169)' - COLOR_VIOLET_RED: '(247, 83, 148)' - COLOR_VIVID_TANGERINE: '(255, 160, 137)' - COLOR_WILD_ORCHID: '(212, 112, 162)' - COLOR_WILD_STRAWBERRY: '(255, 67, 164)' - COLOR_WILD_WATERMELON: '(252, 108, 133)' - COLOR_DEEP_FUCHSIA: '(193, 84, 193)' - COLOR_FRENCH_MAUVE: '(212, 115, 212)' - COLOR_FUCHSIA_PINK: '(255, 119, 255)' - COLOR_LAVENDER_MAGENTA: '(238, 130, 238)' - COLOR_STEEL_PINK: '(204, 51, 204)' - COLOR_ULTRA_PINK: '(255, 111, 255)' - nervousness: - COLOR_ALABAMA_CRIMSON: '(175, 0, 42)' - COLOR_AUBERGINE: '(59, 9, 16)' - COLOR_BARN_RED: '(124, 10, 2)' - COLOR_BAROSSA: '(68, 1, 45)' - COLOR_BLACK_BEAN: '(61, 12, 2)' - COLOR_BLACK_ROSE: '(103, 3, 45)' - COLOR_BLACKBERRY: '(77, 1, 53)' - COLOR_BORDEAUX: '(92, 1, 32)' - COLOR_BRACKEN: '(74, 42, 4)' - COLOR_BRIGHT_RED: '(177, 0, 0)' - COLOR_BROWN_BRAMBLE: '(89, 40, 4)' - COLOR_BROWN_DERBY: '(73, 38, 21)' - COLOR_BROWN_POD: '(64, 24, 1)' - COLOR_BROWN_TUMBLEWEED: '(55, 41, 14)' - COLOR_BULGARIAN_ROSE: '(72, 6, 7)' - COLOR_BURGUNDY: '(128, 0, 32)' - COLOR_BURNT_MAROON: '(66, 3, 3)' - COLOR_CAB_SAV: '(77, 10, 24)' - COLOR_CARMINE: '(150, 0, 24)' - COLOR_CASTRO: '(82, 0, 31)' - COLOR_CEDAR: '(62, 28, 20)' - COLOR_CEDAR_WOOD_FINISH: '(113, 26, 0)' - COLOR_CHERRYWOOD: '(101, 26, 20)' - COLOR_CIOCCOLATO: '(85, 40, 12)' - COLOR_CLARET: '(127, 23, 52)' - COLOR_CLINKER: '(55, 29, 9)' - COLOR_COCOA_BEAN: '(72, 28, 28)' - COLOR_COLA: '(63, 37, 0)' - COLOR_CRATER_BROWN: '(70, 36, 37)' - COLOR_CRIMSON_GLORY: '(190, 0, 50)' - COLOR_CRIMSON_RED: '(153, 0, 0)' - COLOR_DARK_BURGUNDY: '(119, 15, 5)' - COLOR_DARK_CANDY_APPLE_RED: '(164, 0, 0)' - COLOR_DARK_EBONY: '(60, 32, 5)' - COLOR_DARK_RED: '(139, 0, 0)' - COLOR_DARK_SCARLET: '(86, 3, 25)' - COLOR_DARK_SIENNA: '(60, 20, 20)' - COLOR_DEEP_MAROON: '(130, 0, 0)' - COLOR_DEEP_OAK: '(65, 32, 16)' - COLOR_DEEP_RED: '(133, 1, 1)' - COLOR_ENGINEERING_INTERNATIONAL_ORANGE: '(186, 22, 12)' - COLOR_FALU_RED: '(128, 24, 24)' - COLOR_FRENCH_PUCE: '(78, 22, 9)' - COLOR_GUARDSMAN_RED: '(186, 1, 1)' - COLOR_HEATH: '(84, 16, 18)' - COLOR_INDIAN_TAN: '(77, 30, 1)' - COLOR_JAPANESE_MAPLE: '(120, 1, 9)' - COLOR_JON: '(59, 31, 31)' - COLOR_KENYAN_COPPER: '(124, 28, 5)' - COLOR_LONESTAR: '(109, 1, 1)' - COLOR_MAROON: '(128, 0, 0)' - COLOR_MAROON_OAK: '(82, 12, 23)' - COLOR_MERLOT: '(131, 25, 35)' - COLOR_MILANO_RED: '(184, 17, 4)' - COLOR_MOCCACCINO: '(110, 29, 20)' - COLOR_MONARCH: '(139, 7, 35)' - COLOR_MORDANT_RED: '(174, 12, 0)' - COLOR_MOROCCO_BROWN: '(68, 29, 0)' - COLOR_MULBERRY_WOOD: '(92, 5, 54)' - COLOR_PACO: '(65, 31, 16)' - COLOR_PAPRIKA: '(141, 2, 38)' - COLOR_PERSIAN_PLUM: '(112, 28, 28)' - COLOR_PINK_RASPBERRY: '(152, 0, 54)' - COLOR_POHUTUKAWA: '(143, 2, 28)' - COLOR_REBEL: '(60, 18, 6)' - COLOR_RED_BERRY: '(142, 0, 0)' - COLOR_RED_DEVIL: '(134, 1, 17)' - COLOR_RED_OXIDE: '(110, 9, 2)' - COLOR_ROSEWOOD: '(101, 0, 11)' - COLOR_RUBY_RED: '(155, 17, 30)' - COLOR_RUFOUS: '(168, 28, 7)' - COLOR_RUSTIC_RED: '(72, 4, 4)' - COLOR_SAMBUCA: '(58, 32, 16)' - COLOR_SANGRIA: '(146, 0, 10)' - COLOR_SCARLETT: '(149, 0, 21)' - COLOR_SEAL_BROWN: '(89, 38, 11)' - COLOR_SHIRAZ: '(178, 9, 49)' - COLOR_SIREN: '(122, 1, 58)' - COLOR_SPARTAN_CRIMSON: '(158, 19, 22)' - COLOR_TAMARILLO: '(153, 22, 19)' - COLOR_TAMARIND: '(52, 21, 21)' - COLOR_TEMPTRESS: '(59, 0, 11)' - COLOR_TOLEDO: '(58, 0, 32)' - COLOR_TOTEM_POLE: '(153, 27, 7)' - COLOR_TREEHOUSE: '(59, 40, 32)' - COLOR_TYRIAN_PURPLE: '(102, 2, 60)' - COLOR_UP_MAROON: '(123, 17, 19)' - COLOR_VAN_CLEEF: '(73, 23, 12)' - COLOR_WINE_BERRY: '(89, 29, 53)' - COLOR_ACADIA: '(27, 20, 4)' - COLOR_ASPHALT: '(19, 10, 6)' - COLOR_CANNON_BLACK: '(37, 23, 6)' - COLOR_COFFEE_BEAN: '(42, 20, 14)' - COLOR_CREOLE: '(30, 15, 4)' - COLOR_CROWSHEAD: '(28, 18, 8)' - COLOR_DIESEL: '(19, 0, 0)' - COLOR_ECLIPSE: '(49, 28, 23)' - COLOR_EL_PASO: '(30, 23, 8)' - COLOR_ETERNITY: '(33, 26, 14)' - COLOR_GONDOLA: '(38, 20, 20)' - COLOR_GRAPHITE: '(37, 22, 7)' - COLOR_JACARANDA: '(46, 3, 41)' - COLOR_JACKO_BEAN: '(46, 25, 5)' - COLOR_KARAKA: '(30, 22, 9)' - COLOR_KILAMANJARO: '(36, 12, 2)' - COLOR_LICORICE: '(26, 17, 16)' - COLOR_MELANZANE: '(48, 5, 41)' - COLOR_MIKADO: '(45, 37, 16)' - COLOR_NERO: '(20, 6, 0)' - COLOR_NIGHT_RIDER: '(31, 18, 15)' - COLOR_OIL: '(40, 30, 21)' - COLOR_ONION: '(47, 39, 14)' - COLOR_SEPIA_BLACK: '(43, 2, 2)' - COLOR_SMOKY_BLACK: '(16, 12, 8)' - COLOR_WOOD_BARK: '(38, 17, 5)' - COLOR_WOODRUSH: '(48, 42, 15)' - COLOR_ZEUS: '(41, 35, 25)' - COLOR_ZINNWALDITE_BROWN: '(44, 22, 8)' - desire: - COLOR_ALIZARIN_CRIMSON: '(227, 38, 54)' - COLOR_BAMBOO: '(218, 99, 4)' - COLOR_BLAZE_ORANGE: '(255, 103, 0)' - COLOR_BURNT_ORANGE: '(204, 85, 0)' - COLOR_COQUELICOT: '(255, 56, 0)' - COLOR_FERRARI_RED: '(255, 40, 0)' - COLOR_GIANTS_ORANGE: '(254, 90, 29)' - COLOR_GRENADIER: '(213, 70, 0)' - COLOR_INTERNATIONAL_ORANGE: '(255, 79, 0)' - COLOR_LIGHT_BRILLIANT_RED: '(254, 46, 46)' - COLOR_ORANGE_RED: '(255, 69, 0)' - COLOR_ORIOLES_ORANGE: '(251, 79, 20)' - COLOR_PANTONE_ORANGE: '(255, 88, 0)' - COLOR_PERMANENT_GERANIUM_LAKE: '(225, 44, 44)' - COLOR_PERSIMMON: '(236, 88, 0)' - COLOR_POMEGRANATE: '(243, 71, 35)' - COLOR_RYB_RED: '(254, 39, 18)' - COLOR_RED_STAGE: '(208, 95, 4)' - COLOR_SCARLET: '(255, 36, 0)' - COLOR_SINOPIA: '(203, 65, 11)' - COLOR_SPANISH_ORANGE: '(232, 97, 0)' - COLOR_TANGELO: '(249, 77, 0)' - COLOR_TENNE: '(205, 87, 0)' - COLOR_TRINIDAD: '(230, 78, 3)' - COLOR_VERMILION: '(217, 56, 30)' - COLOR_VIVID_ORANGE: '(255, 95, 0)' - COLOR_WILLPOWER_ORANGE: '(253, 88, 0)' - remorse: - COLOR_ALLOY_ORANGE: '(196, 98, 16)' - COLOR_ALPINE: '(175, 143, 44)' - COLOR_AMARANTH_PURPLE: '(171, 39, 79)' - COLOR_ANTIQUE_BRONZE: '(102, 93, 30)' - COLOR_ANTIQUE_RUBY: '(132, 27, 45)' - COLOR_APPLE_BLOSSOM: '(175, 77, 67)' - COLOR_AUBURN: '(165, 42, 42)' - COLOR_BIG_DIP_ORUBY: '(156, 37, 66)' - COLOR_BIRCH: '(55, 48, 33)' - COLOR_BISTRE: '(61, 43, 31)' - COLOR_BLACK_MARLIN: '(62, 44, 28)' - COLOR_BOLE: '(121, 68, 59)' - COLOR_BOURBON: '(186, 111, 30)' - COLOR_BRIGHT_MAROON: '(195, 33, 72)' - COLOR_BRONZE_OLIVE: '(78, 66, 12)' - COLOR_BRONZE_YELLOW: '(115, 112, 0)' - COLOR_BRONZETONE: '(77, 64, 15)' - COLOR_BROWN: '(150, 75, 0)' - COLOR_BROWN_RUST: '(175, 89, 62)' - COLOR_BUCCANEER: '(98, 47, 48)' - COLOR_BULL_SHOT: '(134, 77, 30)' - COLOR_BURNT_UMBER: '(138, 51, 36)' - COLOR_BUTTERED_RUM: '(161, 117, 13)' - COLOR_CAFE_NOIR: '(75, 54, 33)' - COLOR_CAFE_ROYALE: '(111, 68, 12)' - COLOR_CAMOUFLAGE: '(60, 57, 16)' - COLOR_CAPE_PALLISER: '(162, 102, 69)' - COLOR_CAPUT_MORTUUM: '(89, 39, 32)' - COLOR_CARDINAL: '(196, 30, 58)' - COLOR_CARNABY_TAN: '(92, 46, 1)' - COLOR_CARNELIAN: '(179, 27, 27)' - COLOR_CATAWBA: '(112, 54, 66)' - COLOR_CHELSEA_GEM: '(158, 83, 2)' - COLOR_CHESTNUT: '(149, 69, 53)' - COLOR_CHINESE_RED: '(170, 56, 30)' - COLOR_CHOCOLATE: '(123, 63, 0)' - COLOR_COCONUT: '(150, 90, 62)' - COLOR_COFFEE: '(111, 78, 55)' - COLOR_COGNAC: '(159, 56, 29)' - COLOR_CONGO_BROWN: '(89, 55, 55)' - COLOR_COPPER: '(184, 115, 51)' - COLOR_COPPER_CANYON: '(126, 58, 21)' - COLOR_COPPER_RUST: '(148, 71, 71)' - COLOR_CORDOVAN: '(137, 63, 69)' - COLOR_CORK: '(64, 41, 29)' - COLOR_CORN_HARVEST: '(139, 107, 11)' - COLOR_COSTA_DEL_SOL: '(97, 93, 48)' - COLOR_COWBOY: '(77, 40, 45)' - COLOR_CRAIL: '(185, 81, 64)' - COLOR_CROWN_OF_THORNS: '(119, 31, 31)' - COLOR_CUMIN: '(146, 67, 33)' - COLOR_DALLAS: '(110, 75, 38)' - COLOR_DARK_BROWN: '(101, 67, 33)' - COLOR_DARK_PASTEL_RED: '(194, 59, 34)' - COLOR_DARK_YELLOW: '(155, 135, 12)' - COLOR_DEEP_BRONZE: '(74, 48, 4)' - COLOR_DEEP_CARMINE: '(169, 32, 62)' - COLOR_DEEP_CHESTNUT: '(185, 78, 72)' - COLOR_DESERT: '(174, 96, 32)' - COLOR_DOGS: '(184, 109, 41)' - COLOR_DONKEY_BROWN: '(102, 76, 40)' - COLOR_EL_SALVA: '(143, 62, 51)' - COLOR_ENGLISH_RED: '(171, 75, 82)' - COLOR_ENGLISH_WALNUT: '(62, 43, 35)' - COLOR_ESPRESSO: '(97, 39, 24)' - COLOR_FIELD_DRAB: '(108, 84, 30)' - COLOR_FIERY_ORANGE: '(179, 82, 19)' - COLOR_FIRE: '(170, 66, 3)' - COLOR_FIREBRICK: '(178, 34, 34)' - COLOR_FRENCH_WINE: '(172, 30, 68)' - COLOR_GAMBOGE_ORANGE: '(153, 102, 0)' - COLOR_GINGER: '(176, 101, 0)' - COLOR_GOLDEN_BROWN: '(153, 101, 21)' - COLOR_GOLDEN_GATE_BRIDGE: '(192, 54, 44)' - COLOR_GRIZZLY: '(136, 88, 24)' - COLOR_HACIENDA: '(152, 129, 27)' - COLOR_HAIRY_HEATH: '(107, 42, 20)' - COLOR_HAWAIIAN_TAN: '(157, 86, 22)' - COLOR_HIMALAYA: '(106, 93, 27)' - COLOR_HORSES: '(84, 61, 55)' - COLOR_HORSES_NECK: '(96, 73, 19)' - COLOR_HOT_TODDY: '(179, 128, 7)' - COLOR_INDOCHINE: '(194, 107, 3)' - COLOR_IRISH_COFFEE: '(95, 61, 38)' - COLOR_IROKO: '(67, 49, 32)' - COLOR_IRONSTONE: '(134, 72, 60)' - COLOR_JAMBALAYA: '(91, 48, 19)' - COLOR_JAPANESE_CARMINE: '(157, 41, 51)' - COLOR_JUDGE_GRAY: '(84, 67, 51)' - COLOR_KOBICHA: '(107, 68, 35)' - COLOR_KORMA: '(143, 75, 14)' - COLOR_KUMERA: '(136, 98, 33)' - COLOR_LIGHT_BROWN: '(181, 101, 29)' - COLOR_LISBON_BROWN: '(66, 57, 33)' - COLOR_LIVID_BROWN: '(77, 40, 46)' - COLOR_LOTUS: '(134, 60, 60)' - COLOR_LUXOR_GOLD: '(167, 136, 44)' - COLOR_MADRAS: '(63, 48, 2)' - COLOR_MAHOGANY: '(192, 64, 0)' - COLOR_MAI_TAI: '(176, 102, 8)' - COLOR_MANDALAY: '(173, 120, 27)' - COLOR_METALLIC_BRONZE: '(73, 55, 27)' - COLOR_METALLIC_COPPER: '(113, 41, 29)' - COLOR_METALLIC_SUNBURST: '(156, 124, 56)' - COLOR_MEXICAN_RED: '(167, 37, 37)' - COLOR_MILLBROOK: '(89, 68, 51)' - COLOR_MOCHA: '(120, 45, 25)' - COLOR_MOJO: '(192, 71, 55)' - COLOR_MONDO: '(74, 60, 48)' - COLOR_MULE_FAWN: '(140, 71, 47)' - COLOR_NIGHT_SHADZ: '(170, 55, 90)' - COLOR_NUTMEG: '(129, 66, 44)' - COLOR_NUTMEG_WOOD_FINISH: '(104, 54, 0)' - COLOR_OLD_BRICK: '(144, 30, 30)' - COLOR_OLD_BURGUNDY: '(67, 48, 46)' - COLOR_OLD_COPPER: '(114, 74, 47)' - COLOR_OLIVE_DRAB_SEVEN: '(60, 52, 31)' - COLOR_OLIVETONE: '(113, 110, 16)' - COLOR_ORANGE_ROUGHY: '(196, 87, 25)' - COLOR_OREGON: '(155, 71, 3)' - COLOR_ORGAN: '(108, 46, 31)' - COLOR_PAARL: '(166, 85, 41)' - COLOR_PALE_CARMINE: '(175, 64, 53)' - COLOR_PEANUT: '(120, 47, 22)' - COLOR_PERU_TAN: '(127, 58, 2)' - COLOR_PESTO: '(124, 118, 49)' - COLOR_PICKLED_BEAN: '(110, 72, 38)' - COLOR_POTTERS_CLAY: '(140, 87, 56)' - COLOR_PRAIRIE_SAND: '(154, 56, 32)' - COLOR_PUEBLO: '(125, 44, 20)' - COLOR_PULLMAN_BROWN: '(100, 65, 23)' - COLOR_PULLMAN_GREEN: '(59, 51, 28)' - COLOR_PUMPKIN_SKIN: '(177, 97, 11)' - COLOR_PUNGA: '(77, 61, 20)' - COLOR_QUINCY: '(98, 63, 45)' - COLOR_RED_BEECH: '(123, 56, 1)' - COLOR_RED_ROBIN: '(128, 52, 31)' - COLOR_REEF_GOLD: '(159, 130, 28)' - COLOR_RENO_SAND: '(168, 101, 21)' - COLOR_RICH_GOLD: '(168, 83, 7)' - COLOR_ROAST_COFFEE: '(112, 66, 65)' - COLOR_ROCK: '(77, 56, 51)' - COLOR_ROCK_SPRAY: '(186, 69, 12)' - COLOR_ROOF_TERRACOTTA: '(166, 47, 32)' - COLOR_ROPE: '(142, 77, 30)' - COLOR_ROSE_VALE: '(171, 78, 82)' - COLOR_ROSE_OF_SHARON: '(191, 85, 0)' - COLOR_RUDDY_BROWN: '(187, 101, 40)' - COLOR_RUSSET: '(128, 70, 27)' - COLOR_RUST: '(183, 65, 14)' - COLOR_RUSTY_NAIL: '(134, 86, 10)' - COLOR_SADDLE: '(76, 48, 36)' - COLOR_SADDLE_BROWN: '(139, 69, 19)' - COLOR_SAND_DUNE: '(150, 113, 23)' - COLOR_SANGUINE_BROWN: '(141, 61, 56)' - COLOR_SEPIA: '(112, 66, 20)' - COLOR_SEPIA_SKIN: '(158, 91, 64)' - COLOR_SHINGLE_FAWN: '(107, 78, 49)' - COLOR_SIENNA: '(136, 45, 23)' - COLOR_SMOKEY_TOPAZ: '(131, 42, 13)' - COLOR_SMOKY_TOPAZ: '(147, 61, 65)' - COLOR_SOLID_PINK: '(137, 56, 67)' - COLOR_SPANISH_BISTRE: '(128, 117, 50)' - COLOR_SPICE: '(106, 68, 46)' - COLOR_SPICY_MUSTARD: '(116, 100, 13)' - COLOR_STILETTO: '(156, 51, 54)' - COLOR_SWEET_BROWN: '(168, 55, 49)' - COLOR_TABASCO: '(160, 39, 18)' - COLOR_TALL_POPPY: '(179, 45, 41)' - COLOR_TAUPE: '(72, 60, 50)' - COLOR_TAWNY_PORT: '(105, 37, 69)' - COLOR_THATCH_GREEN: '(64, 61, 25)' - COLOR_THUNDER: '(51, 41, 47)' - COLOR_THUNDERBIRD: '(192, 43, 24)' - COLOR_TIA_MARIA: '(193, 68, 14)' - COLOR_TOSCA: '(141, 63, 63)' - COLOR_UNIVERSITY_OF_CALIFORNIA_GOLD: '(183, 135, 39)' - COLOR_UPSDELL_RED: '(174, 32, 41)' - COLOR_VAN_DYKE_BROWN: '(102, 66, 40)' - COLOR_VESUVIUS: '(177, 74, 11)' - COLOR_VIVID_AUBURN: '(146, 39, 36)' - COLOR_VIVID_BURGUNDY: '(159, 29, 53)' - COLOR_WALNUT: '(119, 63, 26)' - COLOR_WELL_READ: '(180, 51, 50)' - COLOR_WEST_COAST: '(98, 81, 25)' - COLOR_WINDSOR_TAN: '(167, 85, 2)' - COLOR_WINE: '(114, 47, 55)' - COLOR_WOODY_BROWN: '(72, 49, 49)' - COLOR_YUKON_GOLD: '(123, 102, 8)' - COLOR_APPLE_GREEN: '(141, 182, 0)' - COLOR_ARMY_GREEN: '(75, 83, 32)' - COLOR_AVOCADO: '(86, 130, 3)' - COLOR_CHRISTI: '(103, 167, 18)' - COLOR_CRETE: '(115, 120, 41)' - COLOR_DARK_MOSS_GREEN: '(74, 93, 35)' - COLOR_FERN_FROND: '(101, 114, 32)' - COLOR_LIMEADE: '(111, 157, 2)' - COLOR_PACIFIKA: '(119, 129, 32)' - COLOR_RANGITOTO: '(46, 50, 34)' - COLOR_SARATOGA: '(85, 91, 16)' - COLOR_TRENDY_GREEN: '(124, 136, 26)' - COLOR_VERDUN_GREEN: '(73, 84, 0)' - COLOR_WAIOURU: '(54, 60, 13)' - COLOR_WASABI: '(120, 138, 37)' - COLOR_WOODLAND: '(77, 83, 40)' - COLOR_OLIVE: '(128, 128, 0)' - annoyance: - COLOR_AMARANTH: '(229, 43, 80)' - COLOR_AZTEC_GOLD: '(195, 153, 83)' - COLOR_BIG_FOOT_FEET: '(232, 142, 90)' - COLOR_BITTERSWEET: '(254, 111, 94)' - COLOR_BITTERSWEET_SHIMMER: '(191, 79, 81)' - COLOR_BRICK_RED: '(203, 65, 84)' - COLOR_BRONZE: '(205, 127, 50)' - COLOR_BURNING_ORANGE: '(255, 112, 52)' - COLOR_BURNT_SIENNA: '(233, 116, 81)' - COLOR_CG_RED: '(224, 60, 49)' - COLOR_CABARET: '(217, 73, 114)' - COLOR_CADMIUM_ORANGE: '(237, 135, 45)' - COLOR_CARMINE_PINK: '(235, 76, 66)' - COLOR_CARNATION: '(249, 90, 97)' - COLOR_CEDAR_CHEST: '(201, 90, 73)' - COLOR_CINNABAR: '(227, 66, 52)' - COLOR_COCOA_BROWN: '(210, 105, 30)' - COLOR_CONGO_PINK: '(248, 131, 121)' - COLOR_CONTESSA: '(198, 114, 107)' - COLOR_COPPER_RED: '(203, 109, 81)' - COLOR_CORAL: '(255, 127, 80)' - COLOR_CORAL_RED: '(255, 64, 64)' - COLOR_CRANBERRY: '(219, 80, 121)' - COLOR_CRAYOLA_ORANGE: '(255, 117, 56)' - COLOR_CRAYOLA_RED: '(238, 32, 77)' - COLOR_CRUSTA: '(253, 123, 51)' - COLOR_DARK_CORAL: '(205, 91, 69)' - COLOR_DARK_SALMON: '(233, 150, 122)' - COLOR_DARK_TERRA_COTTA: '(204, 78, 92)' - COLOR_DEEP_CARMINE_PINK: '(239, 48, 56)' - COLOR_DEEP_CARROT_ORANGE: '(233, 105, 44)' - COLOR_DESIRE: '(234, 60, 83)' - COLOR_DI_SERRIA: '(219, 153, 94)' - COLOR_DINGY_DUNGEON: '(197, 49, 81)' - COLOR_EARTH_YELLOW: '(225, 169, 95)' - COLOR_ENGLISH_VERMILLION: '(204, 71, 75)' - COLOR_FIERY_ROSE: '(255, 84, 112)' - COLOR_FLAME: '(226, 88, 34)' - COLOR_FLAME_PEA: '(218, 91, 56)' - COLOR_FLAMINGO: '(242, 85, 42)' - COLOR_FLUSH_MAHOGANY: '(202, 52, 53)' - COLOR_FRENCH_RASPBERRY: '(199, 44, 72)' - COLOR_FUZZY_WUZZY: '(204, 102, 102)' - COLOR_FUZZY_WUZZY_BROWN: '(196, 86, 85)' - COLOR_IMPERIAL_RED: '(237, 41, 57)' - COLOR_INDIAN_RED: '(205, 92, 92)' - COLOR_INDIAN_YELLOW: '(227, 168, 87)' - COLOR_JAFFA: '(239, 134, 63)' - COLOR_JAPONICA: '(216, 124, 99)' - COLOR_JASPER: '(215, 59, 62)' - COLOR_JELLY_BEAN: '(218, 97, 78)' - COLOR_KOROMIKO: '(255, 189, 95)' - COLOR_LIGHT_CARMINE_PINK: '(230, 103, 113)' - COLOR_LIGHT_SALMON: '(255, 160, 122)' - COLOR_MAGIC_POTION: '(255, 68, 102)' - COLOR_MANDARIN: '(243, 122, 72)' - COLOR_MANDY: '(226, 84, 101)' - COLOR_MANGO_TANGO: '(255, 130, 67)' - COLOR_MEDIUM_VERMILION: '(217, 96, 59)' - COLOR_NEON_CARROT: '(255, 163, 67)' - COLOR_NEON_FUCHSIA: '(254, 65, 100)' - COLOR_OGRE_ODOR: '(253, 82, 64)' - COLOR_ORANGE_SODA: '(250, 91, 61)' - COLOR_OUTRAGEOUS_ORANGE: '(255, 110, 74)' - COLOR_PALE_COPPER: '(218, 138, 103)' - COLOR_PARADISE_PINK: '(230, 62, 98)' - COLOR_PASTEL_RED: '(255, 105, 97)' - COLOR_PERSIAN_ORANGE: '(217, 144, 88)' - COLOR_PERSIAN_RED: '(204, 51, 51)' - COLOR_PERU: '(205, 133, 63)' - COLOR_PINK_ORANGE: '(255, 153, 102)' - COLOR_PIPER: '(201, 99, 35)' - COLOR_POPSTAR: '(190, 79, 98)' - COLOR_PORSCHE: '(234, 174, 105)' - COLOR_PORTLAND_ORANGE: '(255, 90, 54)' - COLOR_PUNCH: '(220, 67, 51)' - COLOR_RADICAL_RED: '(255, 53, 94)' - COLOR_RAJAH: '(251, 171, 96)' - COLOR_RAW_SIENNA: '(214, 138, 89)' - COLOR_RED_DAMASK: '(218, 106, 65)' - COLOR_RED_ORANGE: '(255, 83, 73)' - COLOR_RED_SALSA: '(253, 58, 74)' - COLOR_ROMAN: '(222, 99, 96)' - COLOR_ROTI: '(198, 168, 75)' - COLOR_RUBER: '(206, 70, 118)' - COLOR_RUSTY_RED: '(218, 44, 67)' - COLOR_SALMON: '(250, 128, 114)' - COLOR_SANDY_BROWN: '(244, 164, 96)' - COLOR_SIZZLING_RED: '(255, 56, 85)' - COLOR_SMASHED_PUMPKIN: '(255, 109, 58)' - COLOR_SUNBURNT_CYCLOPS: '(255, 64, 76)' - COLOR_SUNGLO: '(225, 104, 101)' - COLOR_SUNRAY: '(227, 171, 87)' - COLOR_SUNSET_ORANGE: '(253, 94, 83)' - COLOR_TAN_HIDE: '(250, 157, 90)' - COLOR_TANGO_PINK: '(228, 113, 122)' - COLOR_TART_ORANGE: '(251, 77, 70)' - COLOR_TERRA_COTTA: '(226, 114, 91)' - COLOR_TEXAS_ROSE: '(255, 181, 85)' - COLOR_TIGERS_EYE: '(224, 141, 60)' - COLOR_TOMATO: '(255, 99, 71)' - COLOR_TUSSOCK: '(197, 153, 75)' - COLOR_VALENCIA: '(216, 68, 55)' - COLOR_VIVID_RED_TANGELO: '(223, 97, 36)' - COLOR_VIVID_TANGELO: '(240, 116, 39)' - COLOR_VIVID_VERMILION: '(229, 96, 36)' - love: - COLOR_AMARANTH_RED: '(211, 33, 45)' - COLOR_AMERICAN_ROSE: '(255, 3, 62)' - COLOR_BOSTON_UNIVERSITY_RED: '(204, 0, 0)' - COLOR_CADMIUM_RED: '(227, 0, 34)' - COLOR_CANDY_APPLE_RED: '(255, 8, 0)' - COLOR_CARMINE_RED: '(255, 0, 56)' - COLOR_CRIMSON: '(220, 20, 60)' - COLOR_ELECTRIC_CRIMSON: '(255, 0, 63)' - COLOR_FIRE_ENGINE_RED: '(206, 32, 41)' - COLOR_HARVARD_CRIMSON: '(201, 0, 22)' - COLOR_KU_CRIMSON: '(232, 0, 13)' - COLOR_LAVA: '(207, 16, 32)' - COLOR_LUST: '(230, 32, 32)' - COLOR_MEDIUM_CANDY_APPLE_RED: '(226, 6, 44)' - COLOR_MONZA: '(199, 3, 30)' - COLOR_MUNSELL_RED: '(242, 0, 60)' - COLOR_NCS_RED: '(196, 2, 51)' - COLOR_PIGMENT_RED: '(237, 28, 36)' - COLOR_RED: '(255, 0, 0)' - COLOR_RED_RIBBON: '(237, 10, 63)' - COLOR_RICH_CARMINE: '(215, 0, 64)' - COLOR_ROSSO_CORSA: '(212, 0, 0)' - COLOR_RUDDY: '(255, 0, 40)' - COLOR_SPANISH_CARMINE: '(209, 0, 71)' - COLOR_SPANISH_RED: '(230, 0, 38)' - COLOR_TRACTOR_RED: '(253, 14, 53)' - COLOR_UTAH_CRIMSON: '(211, 0, 63)' - COLOR_VENETIAN_RED: '(200, 8, 21)' - COLOR_VIVID_CRIMSON: '(204, 0, 51)' - COLOR_VIVID_RED: '(247, 13, 26)' - admiration: - COLOR_AMBER: '(255, 191, 0)' - COLOR_BANANA_YELLOW: '(255, 225, 53)' - COLOR_BARBERRY: '(222, 215, 23)' - COLOR_BIRD_FLOWER: '(212, 205, 22)' - COLOR_BRIGHT_SUN: '(254, 211, 60)' - COLOR_CANDLELIGHT: '(252, 217, 23)' - COLOR_CITRINE: '(228, 208, 10)' - COLOR_CONFETTI: '(233, 215, 90)' - COLOR_CORN: '(231, 191, 5)' - COLOR_CREAM_CAN: '(245, 200, 92)' - COLOR_CYBER_YELLOW: '(255, 211, 0)' - COLOR_DANDELION: '(240, 225, 48)' - COLOR_DEEP_LEMON: '(245, 199, 26)' - COLOR_ENERGY_YELLOW: '(248, 221, 92)' - COLOR_GARGOYLE_GAS: '(255, 223, 70)' - COLOR_GOLDEN: '(255, 215, 0)' - COLOR_GOLDEN_DREAM: '(240, 213, 45)' - COLOR_GOLDEN_POPPY: '(252, 194, 0)' - COLOR_GOLDEN_TAINOI: '(255, 204, 92)' - COLOR_GOLDEN_YELLOW: '(255, 223, 0)' - COLOR_JONQUIL: '(244, 202, 22)' - COLOR_LIGHTNING_YELLOW: '(252, 192, 30)' - COLOR_MIKADO_YELLOW: '(255, 196, 12)' - COLOR_MINION_YELLOW: '(245, 224, 80)' - COLOR_MUNSELL_YELLOW: '(239, 204, 0)' - COLOR_MUSTARD: '(255, 219, 88)' - COLOR_NAPLES_YELLOW: '(250, 218, 94)' - COLOR_PANTONE_YELLOW: '(254, 223, 0)' - COLOR_PERIDOT: '(230, 226, 0)' - COLOR_PORTICA: '(249, 230, 99)' - COLOR_RIPE_LEMON: '(244, 216, 28)' - COLOR_RONCHI: '(236, 197, 78)' - COLOR_SAFETY_YELLOW: '(238, 210, 2)' - COLOR_SAFFRON: '(244, 196, 48)' - COLOR_SAFFRON_MANGO: '(249, 191, 88)' - COLOR_SANDSTORM: '(236, 213, 64)' - COLOR_SCHOOL_BUS_YELLOW: '(255, 216, 0)' - COLOR_SIZZLING_SUNRISE: '(255, 219, 0)' - COLOR_SUNFLOWER: '(228, 212, 34)' - COLOR_SUNGLOW: '(255, 204, 51)' - COLOR_SUPERNOVA: '(255, 201, 1)' - COLOR_TANGERINE_YELLOW: '(255, 204, 0)' - COLOR_TITANIUM_YELLOW: '(238, 230, 0)' - COLOR_TURBO: '(250, 230, 0)' - COLOR_VIVID_YELLOW: '(255, 227, 2)' - COLOR_WATTLE: '(220, 215, 71)' - COLOR_ACID_GREEN: '(176, 191, 26)' - COLOR_ALIEN_ARMPIT: '(132, 222, 2)' - COLOR_ATLANTIS: '(151, 205, 45)' - COLOR_BAHIA: '(165, 203, 12)' - COLOR_BITTER_LEMON: '(202, 224, 13)' - COLOR_CITRUS: '(161, 197, 10)' - COLOR_FUEGO: '(190, 222, 13)' - COLOR_INCH_WORM: '(176, 227, 19)' - COLOR_KEY_LIME_PIE: '(191, 201, 33)' - COLOR_LA_RIOJA: '(179, 193, 16)' - COLOR_LAS_PALMAS: '(198, 230, 16)' - COLOR_LIMERICK: '(157, 194, 9)' - COLOR_PEAR: '(209, 226, 49)' - COLOR_RIO_GRANDE: '(187, 208, 9)' - COLOR_SHEEN_GREEN: '(143, 212, 0)' - COLOR_VIVID_LIME_GREEN: '(166, 214, 8)' - realization: - COLOR_ANZAC: '(224, 182, 70)' - COLOR_BRIGHT_YELLOW: '(255, 170, 29)' - COLOR_BUDDHA_GOLD: '(193, 160, 4)' - COLOR_BUTTERCUP: '(243, 173, 22)' - COLOR_CALIFORNIA: '(254, 157, 4)' - COLOR_CASABLANCA: '(248, 184, 83)' - COLOR_CHROME_YELLOW: '(255, 167, 0)' - COLOR_DARK_TANGERINE: '(255, 168, 18)' - COLOR_DEEP_SAFFRON: '(255, 153, 51)' - COLOR_EARLS_GREEN: '(201, 185, 59)' - COLOR_FIRE_BUSH: '(232, 153, 40)' - COLOR_FUEL_YELLOW: '(236, 169, 39)' - COLOR_GALLIANO: '(220, 178, 12)' - COLOR_GAMBOGE: '(228, 155, 15)' - COLOR_GOLD_TIPS: '(222, 186, 19)' - COLOR_GOLDENROD: '(218, 165, 32)' - COLOR_HOKEY_POKEY: '(200, 165, 40)' - COLOR_LEMON_CURRY: '(204, 160, 29)' - COLOR_LEMON_GINGER: '(172, 158, 34)' - COLOR_LUCKY: '(175, 159, 28)' - COLOR_MARIGOLD: '(234, 162, 33)' - COLOR_MEAT_BROWN: '(229, 183, 59)' - COLOR_METALLIC_GOLD: '(212, 175, 55)' - COLOR_MY_SIN: '(255, 179, 31)' - COLOR_NUGGET: '(197, 153, 34)' - COLOR_OLD_GOLD: '(207, 181, 59)' - COLOR_ORANGE_PEEL: '(255, 159, 0)' - COLOR_PASTEL_ORANGE: '(255, 179, 71)' - COLOR_RYB_ORANGE: '(251, 153, 2)' - COLOR_SAHARA: '(183, 162, 20)' - COLOR_SATIN_SHEEN_GOLD: '(203, 161, 53)' - COLOR_SEA_BUCKTHORN: '(251, 161, 41)' - COLOR_SELECTIVE_YELLOW: '(255, 186, 0)' - COLOR_SUN: '(251, 172, 19)' - COLOR_SUNSHADE: '(255, 158, 44)' - COLOR_TREE_POPPY: '(252, 156, 29)' - COLOR_TULIP_TREE: '(234, 179, 59)' - COLOR_TURMERIC: '(202, 187, 72)' - COLOR_UCLA_GOLD: '(255, 179, 0)' - COLOR_UROBILIN: '(225, 173, 33)' - COLOR_VIVID_AMBER: '(204, 153, 0)' - COLOR_VIVID_GAMBOGE: '(255, 153, 0)' - COLOR_VIVID_ORANGE_PEEL: '(255, 160, 0)' - COLOR_WEB_ORANGE: '(255, 165, 0)' - COLOR_YELLOW_ORANGE: '(255, 174, 66)' - COLOR_YELLOW_SEA: '(254, 169, 4)' - COLOR_CITRON: '(159, 169, 31)' - optimism: - COLOR_AUREOLIN: '(253, 238, 0)' - COLOR_BROOM: '(255, 236, 19)' - COLOR_CADMIUM_YELLOW: '(255, 246, 0)' - COLOR_CANARY_YELLOW: '(255, 239, 0)' - COLOR_GORSE: '(255, 241, 79)' - COLOR_ICTERINE: '(252, 247, 94)' - COLOR_LEMON: '(255, 247, 0)' - COLOR_LEMON_YELLOW: '(255, 244, 79)' - COLOR_MAIZE: '(251, 236, 93)' - COLOR_YELLOW_ROSE: '(255, 240, 0)' - COLOR_ARCTIC_LIME: '(208, 255, 20)' - COLOR_CANARY: '(243, 251, 98)' - COLOR_CHARTREUSE: '(223, 255, 0)' - COLOR_ELECTRIC_LIME: '(204, 255, 0)' - COLOR_FRENCH_LIME: '(158, 253, 56)' - COLOR_GOLDEN_FIZZ: '(245, 251, 61)' - COLOR_GREEN_LIZARD: '(167, 244, 50)' - COLOR_GREEN_YELLOW: '(173, 255, 47)' - COLOR_LEMON_GLACIER: '(253, 255, 0)' - COLOR_LEMON_LIME: '(227, 255, 0)' - COLOR_LIME: '(191, 255, 0)' - COLOR_SPRING_BUD: '(167, 252, 0)' - COLOR_SPRING_FROST: '(135, 255, 42)' - COLOR_STARSHIP: '(236, 242, 69)' - COLOR_VOLT: '(206, 255, 0)' - COLOR_DAFFODIL: '(255, 255, 49)' - COLOR_ELECTRIC_YELLOW: '(255, 255, 51)' - COLOR_MAXIMUM_YELLOW: '(250, 250, 55)' - COLOR_RYB_YELLOW: '(254, 254, 51)' - COLOR_UNMELLOW_YELLOW: '(255, 255, 102)' - COLOR_YELLOW: '(255, 255, 0)' - excitement: - COLOR_BARBIE_PINK: '(224, 33, 138)' - COLOR_BYZANTINE: '(189, 51, 164)' - COLOR_CERISE: '(222, 49, 99)' - COLOR_CERISE_PINK: '(236, 59, 131)' - COLOR_DEBIAN_RED: '(215, 10, 83)' - COLOR_DEEP_CERISE: '(218, 50, 135)' - COLOR_DEEP_PINK: '(255, 20, 147)' - COLOR_DOGWOOD_ROSE: '(215, 24, 104)' - COLOR_FASHION_FUCHSIA: '(244, 0, 161)' - COLOR_FOLLY: '(255, 0, 79)' - COLOR_FRENCH_FUCHSIA: '(253, 63, 146)' - COLOR_FROSTBITE: '(233, 54, 167)' - COLOR_FUCHSIA_PURPLE: '(204, 57, 123)' - COLOR_FUCHSIA_ROSE: '(199, 67, 117)' - COLOR_MAGENTA: '(202, 31, 123)' - COLOR_MAGENTA_PINK: '(204, 51, 139)' - COLOR_MEDIUM_RED_VIOLET: '(187, 51, 133)' - COLOR_MEXICAN_PINK: '(228, 0, 124)' - COLOR_PANTONE_MAGENTA: '(208, 65, 126)' - COLOR_PERSIAN_ROSE: '(254, 40, 162)' - COLOR_PROCESS_MAGENTA: '(255, 0, 144)' - COLOR_RASPBERRY: '(227, 11, 93)' - COLOR_RAZZMATAZZ: '(227, 37, 107)' - COLOR_RED_PURPLE: '(228, 0, 120)' - COLOR_RED_VIOLET: '(199, 21, 133)' - COLOR_ROSE: '(255, 0, 127)' - COLOR_ROYAL_FUCHSIA: '(202, 44, 146)' - COLOR_RUBINE_RED: '(209, 0, 86)' - COLOR_RUBY: '(224, 17, 95)' - COLOR_SASQUATCH_SOCKS: '(255, 70, 129)' - COLOR_SHOCKING_PINK: '(252, 15, 192)' - COLOR_SMITTEN: '(200, 65, 134)' - COLOR_SPANISH_CRIMSON: '(229, 26, 76)' - COLOR_TELEMAGENTA: '(207, 52, 118)' - COLOR_UA_RED: '(217, 0, 76)' - COLOR_VIVID_CERISE: '(218, 29, 129)' - COLOR_VIVID_RASPBERRY: '(255, 0, 108)' - COLOR_WINTER_SKY: '(255, 0, 124)' - amusement: - COLOR_BRANDY_PUNCH: '(205, 132, 41)' - COLOR_CARROT_ORANGE: '(237, 145, 33)' - COLOR_CHILEAN_FIRE: '(247, 119, 3)' - COLOR_CHRISTINE: '(231, 115, 10)' - COLOR_CLEMENTINE: '(233, 110, 0)' - COLOR_DARK_GOLDENROD: '(184, 134, 11)' - COLOR_DARK_ORANGE: '(255, 140, 0)' - COLOR_DIXIE: '(226, 148, 24)' - COLOR_ECSTASY: '(250, 120, 20)' - COLOR_FLAMENCO: '(255, 125, 7)' - COLOR_FULVOUS: '(228, 132, 0)' - COLOR_GEEBUNG: '(209, 143, 27)' - COLOR_GOLD_DROP: '(241, 130, 0)' - COLOR_GOLDEN_BELL: '(226, 137, 19)' - COLOR_HARVEST_GOLD: '(218, 145, 0)' - COLOR_HEAT_WAVE: '(255, 122, 0)' - COLOR_METEOR: '(208, 125, 18)' - COLOR_OCHRE: '(204, 119, 34)' - COLOR_ORANGE: '(255, 127, 0)' - COLOR_PIRATE_GOLD: '(186, 127, 3)' - COLOR_PIZAZZ: '(255, 144, 0)' - COLOR_PIZZA: '(201, 148, 21)' - COLOR_PRINCETON_ORANGE: '(245, 128, 37)' - COLOR_PUMPKIN: '(255, 117, 24)' - COLOR_SAE_ECE_AMBER: '(255, 126, 0)' - COLOR_SAFETY_ORANGE: '(255, 120, 0)' - COLOR_SORBUS: '(253, 124, 7)' - COLOR_TAHITI_GOLD: '(233, 124, 7)' - COLOR_TANGERINE: '(242, 133, 0)' - COLOR_TANGO: '(237, 122, 28)' - COLOR_UNIVERSITY_OF_TENNESSEE_ORANGE: '(247, 127, 0)' - COLOR_WEST_SIDE: '(255, 145, 15)' - COLOR_ZEST: '(229, 132, 27)' - gratitude: - COLOR_AO: '(0, 128, 0)' - COLOR_BILBAO: '(50, 124, 20)' - COLOR_BLACK_LEATHER_JACKET: '(37, 53, 41)' - COLOR_BRITISH_RACING_GREEN: '(0, 66, 37)' - COLOR_BRUNSWICK_GREEN: '(27, 77, 62)' - COLOR_CADMIUM_GREEN: '(0, 107, 60)' - COLOR_CAL_POLY_GREEN: '(30, 77, 43)' - COLOR_CAMARONE: '(0, 88, 26)' - COLOR_CARDIN_GREEN: '(1, 54, 28)' - COLOR_CASTLETON_GREEN: '(0, 86, 59)' - COLOR_CELTIC: '(22, 50, 34)' - COLOR_CLOVER: '(56, 73, 16)' - COLOR_COUNTY_GREEN: '(1, 55, 26)' - COLOR_CRUSOE: '(0, 72, 22)' - COLOR_DARK_FERN: '(10, 72, 13)' - COLOR_DARK_GREEN: '(1, 50, 32)' - COLOR_DARTMOUTH_GREEN: '(0, 112, 60)' - COLOR_DEEP_GREEN: '(5, 102, 8)' - COLOR_DEEP_TEAL: '(0, 53, 50)' - COLOR_DELL: '(57, 100, 19)' - COLOR_EVERGLADE: '(28, 64, 46)' - COLOR_FOREST_GREEN: '(34, 139, 34)' - COLOR_FUN_GREEN: '(1, 109, 57)' - COLOR_GABLE_GREEN: '(22, 53, 49)' - COLOR_GREEN_HOUSE: '(36, 80, 15)' - COLOR_GREEN_LEAF: '(67, 106, 13)' - COLOR_HEAVY_METAL: '(43, 50, 40)' - COLOR_INDIA_GREEN: '(19, 136, 8)' - COLOR_ISLAMIC_GREEN: '(0, 144, 0)' - COLOR_JAPANESE_LAUREL: '(10, 105, 6)' - COLOR_JEWEL: '(18, 107, 64)' - COLOR_KAITOKE_GREEN: '(0, 70, 32)' - COLOR_KELLY_GREEN: '(76, 187, 23)' - COLOR_LA_PALMA: '(54, 135, 22)' - COLOR_LA_SALLE_GREEN: '(8, 120, 48)' - COLOR_LINCOLN_GREEN: '(25, 89, 5)' - COLOR_MSU_GREEN: '(24, 69, 59)' - COLOR_MALLARD: '(35, 52, 24)' - COLOR_MEDIUM_JUNGLE_GREEN: '(28, 53, 45)' - COLOR_MUGHAL_GREEN: '(48, 96, 48)' - COLOR_NAPIER_GREEN: '(42, 128, 0)' - COLOR_NORTH_TEXAS_GREEN: '(5, 144, 51)' - COLOR_PAKISTAN_GREEN: '(0, 102, 0)' - COLOR_PALM_LEAF: '(25, 51, 14)' - COLOR_PARSLEY: '(19, 79, 25)' - COLOR_PHTHALO_GREEN: '(18, 53, 36)' - COLOR_SACRAMENTO_STATE_GREEN: '(4, 57, 39)' - COLOR_SAN_FELIX: '(11, 98, 7)' - COLOR_SHERWOOD_GREEN: '(2, 64, 44)' - COLOR_SLIMY_GREEN: '(41, 150, 23)' - COLOR_TE_PAPA_GREEN: '(30, 67, 60)' - COLOR_TIMBER_GREEN: '(22, 50, 44)' - COLOR_TURTLE_GREEN: '(42, 56, 11)' - COLOR_UP_FOREST_GREEN: '(1, 68, 33)' - COLOR_VIDA_LOCA: '(84, 144, 25)' - COLOR_ZUCCINI: '(4, 64, 34)' - COLOR_AZTEC: '(13, 28, 25)' - COLOR_BLACK_FOREST: '(11, 19, 4)' - COLOR_BURNHAM: '(0, 46, 32)' - COLOR_BUSH: '(13, 46, 28)' - COLOR_DARK_JUNGLE_GREEN: '(26, 36, 33)' - COLOR_DEEP_FIR: '(0, 41, 0)' - COLOR_DEEP_FOREST_GREEN: '(24, 45, 9)' - COLOR_ENGLISH_HOLLY: '(2, 45, 21)' - COLOR_GORDONS_GREEN: '(11, 17, 7)' - COLOR_GREEN_KELP: '(37, 49, 28)' - COLOR_GREEN_WATERLOO: '(16, 20, 5)' - COLOR_HOLLY: '(1, 29, 19)' - COLOR_LOG_CABIN: '(36, 42, 29)' - COLOR_MARSHLAND: '(11, 15, 8)' - COLOR_MIDNIGHT_MOSS: '(4, 16, 4)' - COLOR_PALM_GREEN: '(9, 35, 15)' - COLOR_PINE_TREE: '(23, 31, 4)' - COLOR_RACING_GREEN: '(12, 25, 17)' - COLOR_RANGOON_GREEN: '(28, 30, 19)' - COLOR_SEAWEED: '(27, 47, 17)' - relief: - COLOR_BRIGHT_GREEN: '(102, 255, 0)' - COLOR_CHLOROPHYLL_GREEN: '(74, 255, 0)' - COLOR_DARK_PASTEL_GREEN: '(3, 192, 60)' - COLOR_GREEN: '(0, 255, 0)' - COLOR_HARLEQUIN: '(63, 255, 0)' - COLOR_HARLEQUIN_GREEN: '(70, 203, 24)' - COLOR_LAWN_GREEN: '(124, 252, 0)' - COLOR_LIME_GREEN: '(50, 205, 50)' - COLOR_MALACHITE: '(11, 218, 81)' - COLOR_NEON_GREEN: '(57, 255, 20)' - COLOR_SPRING_GREEN: '(0, 255, 127)' - COLOR_VIVID_MALACHITE: '(0, 204, 51)' - COLOR_WEB_CHARTREUSE: '(127, 255, 0)' \ No newline at end of file diff --git a/config/oled_display.yml b/config/oled_display.yml deleted file mode 100644 index 69896cb2..00000000 --- a/config/oled_display.yml +++ /dev/null @@ -1,16 +0,0 @@ -oled_display: - enabled: true - environment: robot - path: modules.display.waveshare_oled.WaveshareOLED - config: - test_on_boot: false - time_on_boot: false - reset_pin: 26 # Changed directly in waveshare config.py to avoid conflict with TFT display - dc_pin: 24 # Changed directly in waveshare config.py to avoid conflict with TFT display - dependencies: - unix: - - python3-pip - - python3-pil - - python3-numpy - python: - - RPi.GPIO \ No newline at end of file diff --git a/config/personality.yml b/config/personality.yml deleted file mode 100644 index 3f19fa38..00000000 --- a/config/personality.yml +++ /dev/null @@ -1,7 +0,0 @@ -personality: - enabled: true - environment: robot - path: modules.personality.Personality - config: - mode: 6 # Config.MODE_LIVE - sleep_timeout: 60 # Time in seconds before entering sleep mode \ No newline at end of file diff --git a/config/piservo.yml b/config/piservo.yml deleted file mode 100644 index 84b596d9..00000000 --- a/config/piservo.yml +++ /dev/null @@ -1,13 +0,0 @@ -piservo: - enabled: false - path: modules.actuators.piservo.PiServo - instances: - - name: "ear" - id: 0 - pin: 22 - range: [-40, 40] - start: 0 - test_on_boot: false - dependencies: - python: - - gpiozero diff --git a/config/pitemperature.yml b/config/pitemperature.yml deleted file mode 100644 index 4d389c99..00000000 --- a/config/pitemperature.yml +++ /dev/null @@ -1,4 +0,0 @@ -pitemperature: - enabled: true - environment: robot - path: modules.pitemperature.PiTemperature \ No newline at end of file diff --git a/config/rtlsdr.yml b/config/rtlsdr.yml deleted file mode 100644 index 5612604e..00000000 --- a/config/rtlsdr.yml +++ /dev/null @@ -1,12 +0,0 @@ -rtl_sdr: - enabled: false - path: modules.network.rtlsdr.RTLSDR - config: - udp_host: "127.0.0.1" - udp_port: 8433 - timeout: 70 - dependencies: - unix: - - rtl-433 - python: - - requests \ No newline at end of file diff --git a/config/serial.yml b/config/serial.yml deleted file mode 100644 index 376c9e0b..00000000 --- a/config/serial.yml +++ /dev/null @@ -1,6 +0,0 @@ -serial: - enabled: false - path: "modules.network.arduinoserial.ArduinoSerial" - config: - port: '/dev/ttyAMA0' - baudrate: '115200' \ No newline at end of file diff --git a/config/servo_bus.yml b/config/servo_bus.yml deleted file mode 100755 index d15c4653..00000000 --- a/config/servo_bus.yml +++ /dev/null @@ -1,105 +0,0 @@ -servos: - enabled: true - environment: robot - path: "modules.actuators.bus_servo.servo.Servo" # Include class name here - config: - poses: - - legs_forward: {'leg_r_tilt': 2806, 'leg_r_hip': 1989, 'leg_r_knee': 1349, 'leg_r_ankle': 2754, 'neck_pan': 1698, 'neck_tilt': 118, 'leg_l_tilt': 1619, 'leg_l_hip': 1028, 'leg_l_knee': 2634, 'leg_l_ankle': 1567} - - sit: {'leg_r_tilt': 2806, 'leg_r_hip': 2063, 'leg_r_knee': 1840, 'leg_r_ankle': 3641, 'neck_pan': 1697, 'neck_tilt': 118, 'leg_l_tilt': 1621, 'leg_l_hip': 853, 'leg_l_knee': 2161, 'leg_l_ankle': 614} - - wave_1: {'leg_r_tilt': 2809, 'leg_r_hip': 2063, 'leg_r_knee': 1840, 'leg_r_ankle': 3641, 'neck_pan': 1697, 'neck_tilt': 118, 'leg_l_tilt': 1618, 'leg_l_hip': 855, 'leg_l_knee': 2614, 'leg_l_ankle': 1337} - - wave_2: {'leg_r_tilt': 2807, 'leg_r_hip': 2063, 'leg_r_knee': 1840, 'leg_r_ankle': 3641, 'neck_pan': 1697, 'neck_tilt': 118, 'leg_l_tilt': 1618, 'leg_l_hip': 855, 'leg_l_knee': 2614, 'leg_l_ankle': 874} - - stand_low: {'leg_r_tilt': 2790, 'leg_r_hip': 422, 'leg_r_knee': 717, 'leg_r_ankle': 3174, 'neck_pan': 1697, 'neck_tilt': 119, 'leg_l_tilt': 1578, 'leg_l_hip': 2668, 'leg_l_knee': 3342, 'leg_l_ankle': 1107} - - stand_high: {'leg_r_tilt': 2790, 'leg_r_hip': 692, 'leg_r_knee': 1322, 'leg_r_ankle': 2861, 'neck_pan': 1700, 'neck_tilt': 123, 'leg_l_tilt': 1578, 'leg_l_hip': 2510, 'leg_l_knee': 2712, 'leg_l_ankle': 1560} - - sit_edge: {'leg_r_tilt': 2790, 'leg_r_hip': 1918, 'leg_r_knee': 2768, 'leg_r_ankle': 2854, 'neck_pan': 1704, 'neck_tilt': 124, 'leg_l_tilt': 1577, 'leg_l_hip': 1183, 'leg_l_knee': 1332, 'leg_l_ankle': 1562} - - sit_edge_swing_l: {'leg_r_tilt': 2790, 'leg_r_hip': 1918, 'leg_r_knee': 2768, 'leg_r_ankle': 2854, 'neck_pan': 1704, 'neck_tilt': 124, 'leg_l_tilt': 1577, 'leg_l_hip': 1185, 'leg_l_knee': 1789, 'leg_l_ankle': 1988} - - sit_edge_swing_r: {'leg_r_tilt': 2790, 'leg_r_hip': 1918, 'leg_r_knee': 2222, 'leg_r_ankle': 2496, 'neck_pan': 1704, 'neck_tilt': 124, 'leg_l_tilt': 1577, 'leg_l_hip': 1182, 'leg_l_knee': 1345, 'leg_l_ankle': 1511} - instances: - - name: "leg_r_tilt" - model: 'ST3215' - id: 1 - range: [2511, 3944] # 1433 total range (4095 = 360 degrees) - range_degrees: 125.9 - start: 2810 - # calibrate_on_boot: true - - name: "leg_r_hip" - model: 'ST3215' - id: 3 - range: [0, 2063] - range_degrees: 181.3 - # calibrate_on_boot: true - # start: 1688 - - name: "leg_r_knee" - model: 'ST3215' - id: 2 - range: [717, 3297] - range_degrees: 226.2 - # start: 1345 - - name: "leg_r_ankle" - model: 'ST3215' - id: 6 - range: [2459, 3872] - range_degrees: 123.7 - # start: 2762 - - name: "neck_pan" - model: 'ST3215' - id: 12 - range: [0, 3412] # 300 degrees = 3412, 360 degrees = 4095 - range_degrees: 299.7 - start: 1706 - # calibrate_on_boot: true - # demonstrate_on_boot: true - # - name: "neck_pan" - # model: 'SC09' - # baudrate: 115200 - # id: 4 - # range: [0, 1023] - # range_degrees: 300 - # start: 511 - # calibrate_on_boot: true - # demonstrate_on_boot: true - # speed: 60 # tested up to 600 - - name: "neck_tilt" - model: 'SC09' - baudrate: 115200 - id: 11 - range: [30, 220] - range_degrees: 55.6 - start: 120 - speed: 60 # max=0, 1 - 3000 tested - # acceleration: 0 # max (no significant difference between this and 1 - 3000) - # demonstrate_on_boot: true - # - name: "neck_tilt2" - # model: 'SC09' - # baudrate: 115200 - # id: 5 - # range: [0, 610] # 180 degree (may need reducing) - # start: 305 - # speed: 300 - # acceleration: 1 - # demonstrate_on_boot: true - - name: "leg_l_tilt" - model: 'ST3215' - id: 7 - range: [8, 1796] # 1788 total range (4095 = 360 degrees) - range_degrees: 156.9 - start: 1617 - # calibrate_on_boot: true - - name: "leg_l_hip" - model: 'ST3215' - id: 8 - range: [853, 3098] - range_degrees: 197.3 - # start: 2451 - - name: "leg_l_knee" - model: 'ST3215' - id: 9 - range: [1332, 3404] - range_degrees: 181.1 - # start: 2643 - - name: "leg_l_ankle" - model: 'ST3215' - id: 10 - range: [614, 2270] - range_degrees: 145.2 - # start: 1566 - # demonstrate_on_boot: true \ No newline at end of file diff --git a/config/servos.yml b/config/servos.yml deleted file mode 100644 index 1990f0b2..00000000 --- a/config/servos.yml +++ /dev/null @@ -1,57 +0,0 @@ -servos: - enabled: false - path: "modules.actuators.servo.Servo" # Include class name here - instances: - - name: "leg_l_hip" - id: 0 - pin: 9 - range: [0, 180] - start: 40 - - name: "leg_l_knee" - id: 1 - pin: 10 - range: [0, 180] - start: 10 - - name: "leg_l_ankle" - id: 2 - pin: 11 - range: [5, 165] - start: 100 - - name: "leg_r_hip" - id: 3 - pin: 6 - range: [0, 180] - start: 60 - - name: "leg_r_knee" - id: 4 - pin: 7 - range: [0, 180] - start: 90 - - name: "leg_r_ankle" - id: 5 - pin: 8 - range: [10, 180] - start: 0 - - name: "tilt" - id: 6 - pin: 2 - range: [60, 120] - start: 50 - - name: "pan" - id: 7 - pin: 3 - range: [30, 150] - start: 50 - - name: "hip_l" - id: 8 - pin: 4 - range: [0, 180] - start: 100 - - name: "hip_r" - id: 9 - pin: 5 - range: [0, 180] - start: 0 - dependencies: - python: - - pigpio \ No newline at end of file diff --git a/config/speechinput.yml b/config/speechinput.yml deleted file mode 100644 index 6cf5fbb7..00000000 --- a/config/speechinput.yml +++ /dev/null @@ -1,23 +0,0 @@ -speechinput: - enabled: false - path: modules.audio.speechinput.SpeechInput - config: - device_name: "pulse" - start_on_boot: false - dependencies: - unix: - - flac - - python3-pyaudio - # - matplotlib - # - scipy - - ladspa-sdk - python: - - SpeechRecognition - # - adafruit-python-shell - # - libportaudio0 - # - libportaudio2 - # - libportaudiocpp0 - # - portaudio19-dev - additional: - - installers/install-mics.sh - - installers/install-speech-recognition.sh \ No newline at end of file diff --git a/config/telegram.yml b/config/telegram.yml deleted file mode 100644 index 43085d53..00000000 --- a/config/telegram.yml +++ /dev/null @@ -1,10 +0,0 @@ -telegram: - enabled: true - path: modules.network.telegrambot.TelegramBot - config: - user_whitelist: [] - dependencies: - python: - - python-telegram-bot - additional: - - https://core.telegram.org/bots/tutorial#obtain-your-bot-token \ No newline at end of file diff --git a/config/tft_display.yml b/config/tft_display.yml deleted file mode 100644 index 26b159e0..00000000 --- a/config/tft_display.yml +++ /dev/null @@ -1,28 +0,0 @@ -tft_display: - enabled: true - environment: robot - path: modules.display.tft_display_eye.TFTDisplayEye - config: - bus: 0 # Use `ls /dev/spi*` to identify. (/dev/spidev0.0) is bus 0, device 0 - device: 0 # Use `ls /dev/spi*` to identify. (/dev/spidev0.0) is bus 0, device 0 - rst_pin: 27 - dc_pin: 25 - bl_pin: 18 # Not used if no backlight pin on device - rotation: -90.0 - test_on_boot: true - colors: - red: (255, 32, 32) - blue: (0, 245, 255) - green: (0, 255, 180) - purple: (255, 0, 255) - yellow: (255, 255, 0) - white: (255, 255, 255) - gray: (128, 128, 128) - dark_gray: (64, 64, 64) - dependencies: - unix: - - python3-pip - - python3-pil - - python3-numpy - python: - - spidev \ No newline at end of file diff --git a/config/tracking.yml b/config/tracking.yml deleted file mode 100644 index c4746d76..00000000 --- a/config/tracking.yml +++ /dev/null @@ -1,7 +0,0 @@ -tracking: - enabled: false - path: 'modules.vision.imx500.tracking.Tracking' - config: - active: true - name: 'tracking' - filter: 'person' \ No newline at end of file diff --git a/config/translator.yml b/config/translator.yml deleted file mode 100644 index 852b1071..00000000 --- a/config/translator.yml +++ /dev/null @@ -1,10 +0,0 @@ -translator: - enabled: false - path: modules.translator.Translator - config: - default: - src: "en" - dest: "en" - dependencies: - python: - - googletrans==3.1.0a0 \ No newline at end of file diff --git a/config/ttsmodule.yml b/config/ttsmodule.yml deleted file mode 100644 index 6b23ac55..00000000 --- a/config/ttsmodule.yml +++ /dev/null @@ -1,17 +0,0 @@ -ttsmodule: - enabled: true - environment: robot - path: "modules.audio.ttsmodule.TTSModule" - config: - service: "elevenlabs" # or pyttsx3 - voice_id: "pMsXgVXv3BLzUgSXRplE" - dependencies: - python: - - pyttsx3 - - elevenlabs - unix: - - espeak - - ffmpeg - - libespeak1 - additional: - - installers/install-amp.sh \ No newline at end of file diff --git a/config/vision_imx500.yml b/config/vision_imx500.yml deleted file mode 100644 index 3e8207c2..00000000 --- a/config/vision_imx500.yml +++ /dev/null @@ -1,13 +0,0 @@ -vision_imx500: - enabled: true - environment: robot - path: 'modules.vision.imx500.vision.Vision' - config: - preview: false - dependencies: - unix: - - imx500-all - python: - - opencv-python - # additional: - # - "add dtoverlay=imx500-pi5,cam0 to /boot/firmware/config.txt" diff --git a/config/vision_opencv.yml b/config/vision_opencv.yml deleted file mode 100644 index 92bc0a54..00000000 --- a/config/vision_opencv.yml +++ /dev/null @@ -1,5 +0,0 @@ -vision_opencv: - enabled: false - path: 'modules.vision.opencv.vision.Vision' - config: - preview: false diff --git a/docs/ModuleLoader.md b/docs/ModuleLoader.md deleted file mode 100644 index f98cc786..00000000 --- a/docs/ModuleLoader.md +++ /dev/null @@ -1,77 +0,0 @@ -# ModuleLoader Documentation - -## Overview - -The `ModuleLoader` class is responsible for dynamically loading and instantiating modules in the project based on YAML configuration files. It supports environment-based filtering, allowing selective loading of modules depending on the specified environment (e.g., 'robot', 'dev'). This approach enables modular, configurable, and environment-aware system composition. - -## Key Features - -1. **Dynamic Module Loading**: Loads Python modules and classes at runtime using paths specified in YAML config files. -2. **Environment Filtering**: Only loads modules enabled for the current environment, supporting both string and list-based environment fields. -3. **Configurable Instantiation**: Passes configuration parameters from YAML directly to module constructors via `**kwargs`. -4. **Messaging Service Integration**: Optionally injects a messaging service into loaded modules for inter-module communication. -5. **Multiple Instances**: Supports creating multiple instances of a module with different configurations. - -## Configuration File Example - -A typical module configuration file (e.g., `config/buzzer.yml`): - -```yaml -buzzer: - enabled: true - path: "modules.audio.buzzer.Buzzer" - config: - pin: 27 - name: 'buzzer' - environment: ['robot', 'dev'] # Optional: restricts loading to these environments -``` - -## Class Reference - -### `ModuleLoader.__init__(config_folder='config', environment=None)` -- **config_folder**: Directory containing YAML config files for modules. -- **environment**: The current environment (string). Only modules matching this environment are loaded. Defaults to 'robot' if not specified. - -### `load_yaml_files()` -- Scans the config folder for `.yml` files. -- Loads and parses each YAML file. -- For each module config: - - Skips if `enabled` is not `true`. - - If `environment` is specified, only includes the module if the current environment matches (supports string or list). -- Returns a list of enabled module configs for the current environment. - -### `load_modules()` -- Iterates over the filtered module configs. -- Dynamically loads the specified Python class using the `path` field. -- Instantiates each module, passing config as `**kwargs`. -- Supports multiple instances per module if the `instances` field is present. -- Returns a dictionary of module instances, keyed by a combination of class and instance name. - -### `set_messaging_service(module_instances, messaging_service)` -- Injects a messaging service into all loaded modules (except those named 'MessagingService'). -- Sets the `messaging_service` attribute on each module instance. - -## Environment Filtering Logic - -- If a module config includes an `environment` field: - - If it is a string, the module is loaded only if it matches the current environment. - - If it is a list, the module is loaded if the current environment is in the list. - - If not present, the module is loaded for all environments. - -## Example: Environment Filtering - -Given the following config: - -```yaml -example_module: - enabled: true - path: modules.example.ExampleModule - environment: 'dev' -``` - -### Usage Example - -```bash -./startup.sh dev -./startup.sh # defaults to 'robot' environment -``` \ No newline at end of file diff --git a/environments/buddy.yml b/environments/buddy.yml new file mode 100644 index 00000000..10e95df5 --- /dev/null +++ b/environments/buddy.yml @@ -0,0 +1,135 @@ +# Buddy — biped robot with PWM servos (pigpio), NeoPixel LEDs, motion sensor, and vision. +# Device-specific hardware configuration (servo pins, GPIO pins) lives here. +# Generic module configuration stays in each module's config.yml. +animate: + enabled: true + +gpio_motion: + enabled: true + config: + pin: 26 + test_on_boot: false + +logwrapper: + enabled: true + +messaging_service: + enabled: true + +motion: + enabled: true + +neopixel: + enabled: true + config: + pin: 12 # Only used for GPIO. GPIO2 and 3 are use for i2c, GPIO10 is used for SPI, GPIO12 is used for GPIO + protocol: 'I2C' # choose between GPIO, I2C and SPI + count: 12 + positions: { + 'status1': 0, + 'status2': 1, + 'status3': 2, + 'status4': 3, + 'status5': 4, + 'right' : 5, + 'top_right': 6, + 'top_left' : 7, + 'left' : 8, + 'bottom_left' : 9, + 'bottom_right' : 10, + 'middle': 11, + } + brightness: [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + +personality: + enabled: false + +piservo: + enabled: true + instances: + - name: "ear" + id: 0 + pin: 12 + range: [-40, 40] + start: 0 + +pitemperature: + enabled: true + +serial: + enabled: true + config: + port: '/dev/ttyAMA0' + baudrate: '115200' + +servo: + enabled: true + instances: + - name: "leg_l_hip" + id: 0 + pin: 9 + range: [0, 180] + start: 40 + - name: "leg_l_knee" + id: 1 + pin: 10 + range: [0, 180] + start: 10 + - name: "leg_l_ankle" + id: 2 + pin: 11 + range: [5, 165] + start: 100 + - name: "leg_r_hip" + id: 3 + pin: 6 + range: [0, 180] + start: 60 + - name: "leg_r_knee" + id: 4 + pin: 7 + range: [0, 180] + start: 90 + - name: "leg_r_ankle" + id: 5 + pin: 8 + range: [10, 180] + start: 0 + - name: "tilt" + id: 6 + pin: 2 + range: [60, 120] + start: 50 + - name: "pan" + id: 7 + pin: 3 + range: [30, 150] + start: 50 + +tracking: + enabled: true + config: + active: true + name: tracking + filter: person + +ttsmodule: + enabled: false + +vision_imx500: + enabled: true + config: + preview: false \ No newline at end of file diff --git a/environments/cody.yml b/environments/cody.yml new file mode 100644 index 00000000..cd2e9b17 --- /dev/null +++ b/environments/cody.yml @@ -0,0 +1,422 @@ +# Cody — biped robot with bus servos, IMU, vision, and TFT display. +# Based on the develop branch robot-environment configuration. +# Device-specific hardware configuration (servo pins, GPIO pins, I2C addresses) lives here. +# Generic module configuration stays in each module's config.yml. +animate: + enabled: true + +bno055: + enabled: true + instances: + - name: imu_head + test_on_boot: false + address: 0x28 + - name: imu_body + test_on_boot: false + line_offset: 1 + address: 0x29 + +bus_servo: + enabled: true + config: + poses: + - legs_forward: {'leg_r_tilt': 2806, 'leg_r_hip': 1989, 'leg_r_knee': 1349, 'leg_r_ankle': 2754, 'neck_pan': 1698, 'neck_tilt': 118, 'leg_l_tilt': 1619, 'leg_l_hip': 1028, 'leg_l_knee': 2634, 'leg_l_ankle': 1567} + - sit: {'leg_r_tilt': 2806, 'leg_r_hip': 2063, 'leg_r_knee': 1840, 'leg_r_ankle': 3641, 'neck_pan': 1697, 'neck_tilt': 118, 'leg_l_tilt': 1621, 'leg_l_hip': 853, 'leg_l_knee': 2161, 'leg_l_ankle': 614} + - wave_1: {'leg_r_tilt': 2809, 'leg_r_hip': 2063, 'leg_r_knee': 1840, 'leg_r_ankle': 3641, 'neck_pan': 1697, 'neck_tilt': 118, 'leg_l_tilt': 1618, 'leg_l_hip': 855, 'leg_l_knee': 2614, 'leg_l_ankle': 1337} + - wave_2: {'leg_r_tilt': 2807, 'leg_r_hip': 2063, 'leg_r_knee': 1840, 'leg_r_ankle': 3641, 'neck_pan': 1697, 'neck_tilt': 118, 'leg_l_tilt': 1618, 'leg_l_hip': 855, 'leg_l_knee': 2614, 'leg_l_ankle': 874} + - stand_low: {'leg_r_tilt': 2790, 'leg_r_hip': 422, 'leg_r_knee': 717, 'leg_r_ankle': 3174, 'neck_pan': 1697, 'neck_tilt': 119, 'leg_l_tilt': 1578, 'leg_l_hip': 2668, 'leg_l_knee': 3342, 'leg_l_ankle': 1107} + - stand_high: {'leg_r_tilt': 2790, 'leg_r_hip': 692, 'leg_r_knee': 1322, 'leg_r_ankle': 2861, 'neck_pan': 1700, 'neck_tilt': 123, 'leg_l_tilt': 1578, 'leg_l_hip': 2510, 'leg_l_knee': 2712, 'leg_l_ankle': 1560} + - sit_edge: {'leg_r_tilt': 2790, 'leg_r_hip': 1918, 'leg_r_knee': 2768, 'leg_r_ankle': 2854, 'neck_pan': 1704, 'neck_tilt': 124, 'leg_l_tilt': 1577, 'leg_l_hip': 1183, 'leg_l_knee': 1332, 'leg_l_ankle': 1562} + - sit_edge_swing_l: {'leg_r_tilt': 2790, 'leg_r_hip': 1918, 'leg_r_knee': 2768, 'leg_r_ankle': 2854, 'neck_pan': 1704, 'neck_tilt': 124, 'leg_l_tilt': 1577, 'leg_l_hip': 1185, 'leg_l_knee': 1789, 'leg_l_ankle': 1988} + - sit_edge_swing_r: {'leg_r_tilt': 2790, 'leg_r_hip': 1918, 'leg_r_knee': 2222, 'leg_r_ankle': 2496, 'neck_pan': 1704, 'neck_tilt': 124, 'leg_l_tilt': 1577, 'leg_l_hip': 1182, 'leg_l_knee': 1345, 'leg_l_ankle': 1511} + instances: + - name: "leg_r_tilt" + model: 'ST3215' + id: 1 + range: [2511, 3944] # 1433 total range (4095 = 360 degrees) + range_degrees: 125.9 + start: 2810 + # calibrate_on_boot: true + - name: "leg_r_hip" + model: 'ST3215' + id: 3 + range: [0, 2063] + range_degrees: 181.3 + # calibrate_on_boot: true + # start: 1688 + - name: "leg_r_knee" + model: 'ST3215' + id: 2 + range: [717, 3297] + range_degrees: 226.2 + # start: 1345 + - name: "leg_r_ankle" + model: 'ST3215' + id: 6 + range: [2459, 3872] + range_degrees: 123.7 + # start: 2762 + - name: "neck_pan" + model: 'ST3215' + id: 12 + range: [0, 3412] # 300 degrees = 3412, 360 degrees = 4095 + range_degrees: 299.7 + start: 1706 + # calibrate_on_boot: true + # demonstrate_on_boot: true + # - name: "neck_pan" + # model: 'SC09' + # baudrate: 115200 + # id: 4 + # range: [0, 1023] + # range_degrees: 300 + # start: 511 + # calibrate_on_boot: true + # demonstrate_on_boot: true + # speed: 60 # tested up to 600 + - name: "neck_tilt" + model: 'SC09' + baudrate: 115200 + id: 11 + range: [30, 220] + range_degrees: 55.6 + start: 120 + speed: 60 # max=0, 1 - 3000 tested + # acceleration: 0 # max (no significant difference between this and 1 - 3000) + # demonstrate_on_boot: true + # - name: "neck_tilt2" + # model: 'SC09' + # baudrate: 115200 + # id: 5 + # range: [0, 610] # 180 degree (may need reducing) + # start: 305 + # speed: 300 + # acceleration: 1 + # demonstrate_on_boot: true + - name: "leg_l_tilt" + model: 'ST3215' + id: 7 + range: [8, 1796] # 1788 total range (4095 = 360 degrees) + range_degrees: 156.9 + start: 1617 + # calibrate_on_boot: true + - name: "leg_l_hip" + model: 'ST3215' + id: 8 + range: [853, 3098] + range_degrees: 197.3 + # start: 2451 + - name: "leg_l_knee" + model: 'ST3215' + id: 9 + range: [1332, 3404] + range_degrees: 181.1 + # start: 2643 + - name: "leg_l_ankle" + model: 'ST3215' + id: 10 + range: [614, 2270] + range_degrees: 145.2 + # start: 1566 + # demonstrate_on_boot: true + +chatgpt: + enabled: true + config: + model: gpt-4o-mini + persona: "You are a helpful assistant robot. You respond with short phrases where possible." + +controller_handler: + enabled: true + inject: + controller: XboxController + on_inject: + - start + config: + debug: false + modifier_buttons: + - BTN_TL + - BTN_TR + mapping: + default: + BTN_TL: + actions: + - topic: '' + args: {} + BTN_TR: + actions: + - topic: '' + args: {} + BTN_SELECT: + actions: + - topic: 'record/stop' + args: {} + BTN_START: + actions: + - topic: 'record/start' + args: {} + KEY_RECORD: # Only on Xbox S/X remotes + actions: + - topic: '' + args: {} + BTN_MODE: # Xbox Logo + actions: + - topic: '' + args: {} + ABS_HAT0X: # DPAD X + actions: + - topic: '' + args: {} + ABS_HAT0Y: # DPAD Y + actions: + - topic: '' + args: {} + BTN_A: + actions: + - topic: 'tts' + args: {msg: 'Test Default'} + BTN_X: + actions: + - topic: 'eye/blink' + args: {} + BTN_Y: + actions: + - topic: 'gpio/laser/toggle' + args: {} + BTN_B: + actions: + - topic: 'vision/toggle' + args: {} + ABS_RX: + actions: + - topic: 'servo:neck_pan:mv' + args: {} + modifier: + scale: 50 # max speed per event + ABS_RZ: + actions: + - topic: 'servo:neck_tilt:mv' + args: {} + modifier: + scale: -50 + - topic: 'servo:neck_tilt2:mv' + args: {} + modifier: + scale: 50 + ABS_RY: # Right trigger + start: -1.0 + actions: + - topic: 'servo:leg_r_ankle:mv' + args: {} + modifier: + scale: -50 + ABS_Z: # Left trigger + start: -1.0 + actions: + - topic: 'servo:leg_l_ankle:mv' + args: {} + modifier: + scale: -50 + ABS_X: + actions: + - topic: 'eye/move' + args: {axis: 'y'} + modifier: + mapping: [0, 240] + ABS_Y: + actions: + - topic: 'eye/move' + args: {axis: 'x'} + modifier: + mapping: [240, 0] + BTN_TL: + BTN_X: + actions: + - topic: 'servo:neck_pan:mv' + args: {delta: -50} + BTN_Y: + actions: + - topic: 'servo:neck_tilt:mv' + args: {delta: -50} + - topic: 'servo:neck_tilt2:mv' + args: {delta: 50} + BTN_B: + actions: + - topic: 'servo:neck_pan:mv' + args: {delta: 50} + BTN_A: + actions: + - topic: 'servo:neck_tilt:mv' + args: {delta: 50} + - topic: 'servo:neck_tilt2:mv' + args: {delta: -50} + BTN_TR: + BTN_X: + actions: + - topic: 'servo:neck_tilt:mv' + args: {delta: 50} + BTN_Y: + actions: + - topic: 'servo:neck_tilt:mv' + args: {delta: -50} + BTN_B: + actions: + - topic: 'servo:neck_tilt2:mv' + args: {delta: 50} + BTN_A: + actions: + - topic: 'servo:neck_tilt2:mv' + args: {delta: -50} + ABS_HAT0Y: + actions: + - topic: 'servo:neck_tilt:mvabs' + args: { + position: 511 + } + - topic: 'servo:neck_tilt2:mvabs' + args: { + position: 511 + } + ABS_X: + actions: + - topic: 'servo:leg_l_ankle:mv' + modifier: + scale: 50 + - topic: 'servo:leg_r_ankle:mv' + modifier: + scale: -50 + - topic: 'servo:leg_l_knee:mv' + modifier: + scale: 50 + - topic: 'servo:leg_r_knee:mv' + modifier: + scale: -50 + - topic: 'servo:leg_l_hip:mv' + modifier: + scale: 50 + - topic: 'servo:leg_r_hip:mv' + modifier: + scale: -50 + +gpio_laser: + enabled: true + config: + pin: 6 + test_on_boot: false + +gpio_motion: + enabled: true + config: + pin: 9 + test_on_boot: false + +gpio_read: + enabled: false + config: + pin: 5 + test_on_boot: false + +input_recorder: + enabled: true + config: + animations_dir: animations/recorded + +logwrapper: + enabled: true # Highly recommended to enable this module + config: + filename: app.log + log_level: 'debug' # debug, info, warning, error, critical + cli_level: 'info' # debug, info, warning, error, critical + +messaging_service: + enabled: true # Required for framework to function + config: + protocol: 'pubsub' # 'mqtt' or 'pubsub' - something is wrong with mqtt that prevents the loops from executing correctly + mqtt_host: 'localhost' + mqtt_port: 1883 + +oled_display: + enabled: true + config: + reset_pin: 26 # Changed directly in waveshare config.py to avoid conflict with TFT display + dc_pin: 24 # Changed directly in waveshare config.py to avoid conflict with TFT display + +personality: + enabled: true + inject: + servos: "Servo_*" + imu.head: BNO055_imu_head + imu.body: BNO055_imu_body + vision: Vision + config: + mode: 6 # Config.MODE_LIVE + sleep_timeout: 60 # Time in seconds before entering sleep mode + +pitemperature: + enabled: true + +tft_display: + enabled: true + config: + bus: 0 # Use `ls /dev/spi*` to identify. (/dev/spidev0.0) is bus 0, device 0 + device: 0 # Use `ls /dev/spi*` to identify. (/dev/spidev0.0) is bus 0, device 0 + rst_pin: 27 + dc_pin: 25 + bl_pin: 18 # Not used if no backlight pin on device + rotation: -90.0 + test_on_boot: true + colors: + red: (255, 32, 32) + blue: (0, 245, 255) + green: (0, 255, 180) + purple: (255, 0, 255) + yellow: (255, 255, 0) + white: (255, 255, 255) + gray: (128, 128, 128) + dark_gray: (64, 64, 64) + +ttsmodule: + enabled: true + config: + service: "elevenlabs" + voice_id: "pMsXgVXv3BLzUgSXRplE" + +tracking: + enabled: false + +vision_imx500: + enabled: true + config: + preview: false + +xbox_controller: + enabled: true + config: + normalised: true + debug: false + device: '/dev/input/js0' # Add this + deadzone: 4000 # thumbsticks deadzone (seems to be needed for xbox controllers) + button_names: # Add this + 0: 'BTN_A' + 1: 'BTN_B' + 2: 'BTN_X' + 3: 'BTN_Y' + 4: 'BTN_TL' + 5: 'BTN_TR' + 6: 'BTN_SELECT' + 7: 'BTN_START' + 8: 'BTN_MODE' + 9: 'BTN_THUMBL' + 10: 'BTN_THUMBR' + 11: 'KEY_RECORD' # Xbox Series controller + axis_names: # Add this + 0: 'ABS_X' + 1: 'ABS_Y' + 2: 'ABS_Z' + 3: 'ABS_RZ' + 4: 'ABS_RX' + 5: 'ABS_RY' + 6: 'ABS_HAT0X' + 7: 'ABS_HAT0Y' + axis_max: 32767.0 + axis_min: -32767.0 \ No newline at end of file diff --git a/environments/laptop.yml b/environments/laptop.yml new file mode 100644 index 00000000..e756f6f8 --- /dev/null +++ b/environments/laptop.yml @@ -0,0 +1,16 @@ +# Laptop — local development and testing environment with no hardware modules. +# Useful for developing, testing, and running the Discord bot locally. +# Matches modules configured with environment: 'laptop' or without 'robot' restriction. + +# ── Core ────────────────────────────────────────────────────────────────────── + +messaging_service: + enabled: true + +logwrapper: + enabled: true + +# ── Connectivity ────────────────────────────────────────────────────────────── + +chatgpt: + enabled: false diff --git a/environments/server.yml b/environments/server.yml new file mode 100644 index 00000000..64ec1ed1 --- /dev/null +++ b/environments/server.yml @@ -0,0 +1,20 @@ +# Server — headless server environment with no hardware modules. +# Runs the Discord bot and core logging/messaging service only. + +# ── Core ────────────────────────────────────────────────────────────────────── + +messaging_service: + enabled: true + +logwrapper: + enabled: true + +# ── Connectivity ────────────────────────────────────────────────────────────── + +chatgpt: + enabled: true + +discordbot: + enabled: true + inject: + ai_instance: ChatGPT diff --git a/install.sh b/install.sh index 282eb609..965a4cf5 100755 --- a/install.sh +++ b/install.sh @@ -23,63 +23,94 @@ import yaml, sys, os config_file = "$1" env = "$2" or 'robot' -module_name = os.path.basename(config_file).replace('.yml', '') # Get the module name from the filename +module_name = os.path.basename(os.path.dirname(config_file)) try: with open(config_file) as f: config = yaml.safe_load(f) - if isinstance(config, dict): - for section in config.values(): - # Check if enabled - if not (isinstance(section, dict) and section.get('enabled', False)): - continue - # Environment filtering logic (match module_loader.py) - env_field = section.get('environment') - if env_field is not None: - if isinstance(env_field, str): - if env_field != env: - continue - elif isinstance(env_field, list): - if env not in env_field: - continue - # If passed, print dependencies - if 'dependencies' in section: - print(f"MODULE:{module_name}") - for dep_type, deps in section['dependencies'].items(): - if dep_type == 'python': - for dep in deps: - print(f"PYTHON:{dep}") - elif dep_type == 'unix': - for dep in deps: - print(f"UNIX:{dep}") - elif dep_type == 'additional': - for url in deps: - print(f"ADDITIONAL:{module_name}:{url}") + # Expect structure: {module_name: {config: ..., dependencies: ...}} + section = config.get(module_name) + if section: + # Check if enabled (if present in config) + enabled = section.get('enabled', True) + if not enabled: + sys.exit(0) + # Environment filtering logic + env_field = section.get('environment') + if env_field is not None: + if isinstance(env_field, str): + if env_field != env: + sys.exit(0) + elif isinstance(env_field, list): + if env not in env_field: + sys.exit(0) + # Print dependencies + if 'dependencies' in section: + print(f"MODULE:{module_name}") + for dep_type, deps in section['dependencies'].items(): + if dep_type == 'python': + for dep in deps: + print(f"PYTHON:{dep}") + elif dep_type == 'unix': + for dep in deps: + print(f"UNIX:{dep}") + elif dep_type == 'additional': + for url in deps: + print(f"ADDITIONAL:{module_name}:{url}") except yaml.YAMLError as e: print(f"Error reading {config_file}: {e}", file=sys.stderr) EOF } -# Determine environment argument (default to 'robot') + +# Determine environment YAML (default to environments/server.yml) if [ -z "$1" ]; then - ENVIRONMENT="robot" + ENV_YAML="environments/server.yml" else - ENVIRONMENT="$1" + ENV_YAML="environments/$1.yml" fi -# Iterate over each YAML config file in the config directory, passing environment -for config_file in config/*.yml; do - while IFS= read -r dependency; do - # Separate Python and Unix dependencies and capture active module names - if [[ $dependency == MODULE:* ]]; then - ACTIVE_MODULES+=("${dependency#MODULE:}") - elif [[ $dependency == PYTHON:* ]]; then - PYTHON_DEPENDENCIES+=("${dependency#PYTHON:}") - elif [[ $dependency == UNIX:* ]]; then - UNIX_DEPENDENCIES+=("${dependency#UNIX:}") - elif [[ $dependency == ADDITIONAL:* ]]; then - ADDITIONAL_URLS+=("${dependency#ADDITIONAL:}") +# Helper: get enabled modules from environment YAML +get_enabled_modules() { + myenv/bin/python3 - <] | disable] +# +# is the device configuration to use (e.g. archie, buddy, cody, server, laptop). +# Defaults to 'laptop' when not specified. +# Available environments: environments/*.yml # Get the current user USER=$(whoami) @@ -10,23 +15,32 @@ SCRIPT_DIR=$(dirname "$(realpath "$0")") BASE_DIR=$(dirname "$SCRIPT_DIR") LAST_DIR=$(basename "$BASE_DIR") - SERVICE_NAME="$LAST_DIR-launcher.service" SERVICE_FILE_PATH="/etc/systemd/system/$SERVICE_NAME" # Function to install the service install_service() { - echo "Installing the $SERVICE_NAME service..." + local ENV_NAME="${1:-laptop}" + echo "Installing the $SERVICE_NAME service for environment: $ENV_NAME..." + + # Validate that the environment file exists + ENV_FILE="$BASE_DIR/environments/$ENV_NAME.yml" + if [ ! -f "$ENV_FILE" ]; then + echo "Error: Environment file not found: $ENV_FILE" + echo "Available environments:" + ls "$BASE_DIR/environments/" 2>/dev/null | sed 's/\.yml//' | sed 's/^/ /' + exit 1 + fi # Create the service file cat < /dev/null [Unit] -Description=Modular Biped Launcher +Description=Modular Robot Launcher ($ENV_NAME) After=network.target [Service] Type=simple -ExecStart=$BASE_DIR/startup.sh +ExecStart=$BASE_DIR/startup.sh $ENV_NAME WorkingDirectory=$BASE_DIR User=$USER Restart=on-failure @@ -48,12 +62,11 @@ EOF echo "$SERVICE_NAME has been installed and started." echo "To debug issues, run:" - echo "sudo journalctl -u modular-biped-launcher.service -f" + echo "sudo journalctl -u $SERVICE_NAME -f" echo "IMPORTANT: Ensure that environment variables are set in /home/$USER/.myenv for the service to function properly: E.g:" echo "OPENAI_API_KEY=" echo "etc for: TELEGRAM_BOT_TOKEN, TELEGRAM_USER_ID, ELEVENLABS_KEY as required" - } # Function to remove the service @@ -76,21 +89,25 @@ remove_service() { # Parse user input case "$1" in enable) - install_service + install_service "$2" ;; disable) remove_service ;; "") - echo "No argument provided. Enabling the service by default." - install_service + echo "No argument provided. Enabling the service with default environment (laptop)." + install_service "laptop" + echo + echo "To use a different environment, run:" + echo " $0 enable (e.g. $0 enable buddy)" echo echo "To disable this service, run:" echo " $0 disable" ;; *) - echo "Usage: $0 {enable|disable}" + echo "Usage: $0 [enable [] | disable]" + echo "Available environments:" + ls "$BASE_DIR/environments/" 2>/dev/null | sed 's/\.yml//' | sed 's/^/ /' exit 1 ;; esac - diff --git a/main.py b/main.py deleted file mode 100644 index 2c160822..00000000 --- a/main.py +++ /dev/null @@ -1,105 +0,0 @@ -import os, sys -from time import sleep, time -import signal -from modules.config import Config -from module_loader import ModuleLoader - -def main(): - print('Starting...') - # Throw exception to safely exit script when terminated - signal.signal(signal.SIGTERM, Config.exit) - - # Get environment argument (default to 'robot') using argparse - import argparse - parser = argparse.ArgumentParser(description="Modular Biped Main Script") - parser.add_argument('--env', default='robot', help="Set the environment (default: robot)") - args = parser.parse_args() - env = args.env - - # Dynamically load and initialize modules, passing env - loader = ModuleLoader(config_folder="config", environment=env) - module_instances = loader.load_modules() - - # Set messaging service for all modules - messaging_service = module_instances['MessagingService'].messaging_service - loader.set_messaging_service(module_instances, messaging_service) - - # Add your business logic here using module_instances as needed - # Example: module_instances[0].some_method() - # vision = module_instances['vision'] - - # print(module_instances.keys()) - # Pass ChatGPT instance to DiscordBot if both are present - if 'ChatGPT' in module_instances and 'DiscordBot' in module_instances: - chatgpt = module_instances['ChatGPT'] - discordbot = module_instances['DiscordBot'] - discordbot.ai_instance = chatgpt - - # Inject bno055 modules into personality if both are enabled - # ['ControllerHandler', 'WaveshareOLED', 'MessagingService', 'TFTDisplayEye', 'InputRecorder', 'Laser', 'Personality', 'Motion', 'PiTemperature', 'XboxController', 'TelegramBot', 'Animate', 'LogWrapper', 'BNO055_imu_head', 'BNO055_imu_body', 'Vision', 'TTSModule', 'Servo_leg_r_tilt', 'Servo_leg_r_hip', 'Servo_leg_r_knee', 'Servo_leg_r_ankle', 'Servo_neck_pan', 'Servo_neck_tilt', 'Servo_neck_tilt2', 'Servo_leg_l_tilt', 'Servo_leg_l_hip', 'Servo_leg_l_knee', 'Servo_leg_l_ankle', 'ChatGPT']) - if 'Personality' in module_instances: - personality = module_instances['Personality'] - # Store all servo modules in personality for easy access within Personality.servos - for key, module in module_instances.items(): - if key.startswith('Servo_'): - personality.servos[module.identifier] = module - - if 'BNO055_imu_head' in module_instances: - personality.imu['head'] = module_instances['BNO055_imu_head'] - if 'BNO055_imu_body' in module_instances: - personality.imu['body'] = module_instances['BNO055_imu_body'] - - if 'Vision' in module_instances: - personality.vision = module_instances['Vision'] - - - - ## output all module instance keys for reference - # print(module_instances.keys()) - # dict_keys(['ArduinoSerial', 'NeoPx', 'BrailleSpeak', 'Animate', 'Vision', 'PiTemperature', 'Servo_leg_l_hip', 'Servo_leg_l_knee', 'Servo_leg_l_ankle', 'Servo_leg_r_hip', 'Servo_leg_r_knee', 'Servo_leg_r_ankle', 'Servo_tilt', 'Servo_pan', 'Translator', 'Tracking_tracking', 'Sensor', 'Buzzer_buzzer']) - - # Use animate to nod head - # messaging_service.publish('animate', action='head_nod') - # sleep(1) - # messaging_service.publish('animate', action='head_shake') - - # Enable translator in log wrapper - # log.translator = module_instances['Translator'] # Set the translator for the log wrapper - - # Use braillespeak to say hi - # messaging_service.publish('speak', msg="Hi") - - # Play happy birthday with buzzer - # messaging_service.publish('play', song="happy birthday") # Also available: 'merry christmas' - - # Check temperature of Raspberry Pi - # messaging_service.subscribe('temperature', callback) # callback should accept 'value' as a parameter - - # Move pi servo - # messaging_service.publish('piservo:move', angle=30) - # messaging_service.publish('piservo:move', angle=-30) - - # Move servo - # messaging_service.publish('servo::mv', percentage=50) # e.g. servo:pan:mv - # messaging_service.publish('servo::mvabs', percentage=50) # Absolute position. e.g. servo:pan:mvabs - - # Test emotion analysis - # messaging_service.publish('speech', text='I am so happy today!') - - # Test speech input - # messaging_service.publish('speech:listen') - - # Inject controller into controller handler if both are enabled - if 'ControllerHandler' in module_instances and 'XboxController' in module_instances: - controller_handler = module_instances['ControllerHandler'] - xbox_controller = module_instances['XboxController'] - controller_handler.controller = xbox_controller - controller_handler.start() - - # Use the new SystemLoop class to run the main loop - from system_loop import SystemLoop - system_loop = SystemLoop(messaging_service, module_instances.get('Personality')) - system_loop.start() - -if __name__ == '__main__': - main() diff --git a/module_loader.py b/module_loader.py deleted file mode 100755 index 1430d36c..00000000 --- a/module_loader.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import yaml -import importlib.util -from pubsub import pub - -class ModuleLoader: - def __init__(self, config_folder='config', environment=None): - """ - ModuleLoader class - :param config_folder: folder containing the module configuration files - - Example config file: - config/modules.yml - --- - buzzer: - enabled: true # Required - path: "modules.audio.buzzer.Buzzer" # Required - config: # Passed as **kwargs to the module's __init__ method - pin: 27 - name: 'buzzer' - - Example: - loader = ModuleLoader() - modules = loader.load_modules() - - Reference module once loaded: - translator_inst = modules['Translator'] - """ - self.config_folder = config_folder - self.environment = environment or 'robot' - print(f"[ModuleLoader] Loading modules for environment: {self.environment}") - self.modules = self.load_yaml_files() - - def load_yaml_files(self): - """Load and parse YAML files from the config folder.""" - config_files = [os.path.join(self.config_folder, f) for f in os.listdir(self.config_folder) if f.endswith('.yml')] - loaded_modules = [] - for file_path in config_files: - with open(file_path, 'r') as stream: - try: - config = yaml.safe_load(stream) - for module_name, module_config in config.items(): - if not module_config.get('enabled', False): - continue - # If 'environment' is specified, filter by self.environment - env_field = module_config.get('environment') - print(f"[ModuleLoader] Found module: {module_name} with environment filter: {env_field}") - if env_field is not None: - if isinstance(env_field, str): - if env_field != self.environment: - continue - elif isinstance(env_field, list): - if self.environment not in env_field: - continue - loaded_modules.append(module_config) - except yaml.YAMLError as e: - print(f"Error loading {file_path}: {e}") - return loaded_modules - - def set_messaging_service(self, module_instances, messaging_service): - """Set the messaging service for the modules.""" - # Iterate through the module instances, extract name and module - for name, module in module_instances.items(): - # get module name from object - # if module is not messaging_service: - if 'MessagingService' in name: - continue - module.messaging_service = messaging_service - - def load_modules(self): - """Dynamically load and instantiate the modules based on the config.""" - instances = {} # Use a dictionary to store instances for easy access - for module in self.modules: - print(f"Enabling {module['path']}") - module_path = module['path'].rsplit('.', 1)[0].replace('.', '/') - module_name = module['path'].split('.')[-1] - instances_config = module.get('instances', [module.get('config')]) - shared_config = module.get('config', {}) - if instances_config[0] is None: - instances_config = [{}] - - # Dynamically load the module - spec = importlib.util.spec_from_file_location(module_name, f"{module_path}.py") - mod = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(mod) - except Exception as e: - print(f"Error loading module {module_name}: {e}") - - # If multiple instances, pass shared config to each instance - multiple_instances = 'instances' in module and isinstance(module['instances'], list) and len(module['instances']) > 0 - for instance_config in instances_config: - # Merge shared config if multiple instances - if multiple_instances: - # Avoid overwriting instance-specific keys with shared config - merged_config = {**shared_config, **instance_config} - else: - merged_config = instance_config - instance_name = module_name + '_' + instance_config.get('name') if instance_config.get('name') is not None else module_name - instance = getattr(mod, module_name)(**merged_config) - instances[instance_name] = instance - - print("All modules loaded") - return instances diff --git a/modules/archived/scripts/speech.wav b/modules/archived/scripts/speech.wav deleted file mode 100644 index 79620084..00000000 Binary files a/modules/archived/scripts/speech.wav and /dev/null differ diff --git a/modules/network/messaging_service.py b/modules/network/messaging_service.py deleted file mode 100755 index 7062d7f5..00000000 --- a/modules/network/messaging_service.py +++ /dev/null @@ -1,128 +0,0 @@ -import json -import paho.mqtt.client as mqtt -from pubsub import pub - -class MessagingService: - def __init__(self, **kwargs): - self.protocol = kwargs.get('protocol', 'pubsub') - self.messaging_service = None - if self.protocol == 'mqtt': - self.host = kwargs.get('mqtt_host', 'localhost') - self.port = kwargs.get('port', 1883) - self.messaging_service = MQTTMessagingService(broker=self.host, port=self.port) - elif self.protocol == 'pubsub': - self.messaging_service = PubSubMessagingService() - else: - raise ValueError(f"Invalid protocol: {self.protocol}") - - """Base class for messaging services""" - def subscribe(self, topic, callback, **kwargs): - raise NotImplementedError - - def publish(self, topic, message=None): - raise NotImplementedError - - -class PubSubMessagingService(MessagingService): - def __init__(self): - self.protocol = 'pubsub' - print("[PubSubMessagingService] Initialized") - - """pypubsub-based messaging implementation""" - def subscribe(self, topic, callback, **kwargs): - """ - Subscribe to a topic. - - :param topic: Topic string (e.g., 'system/log'). - :param callback: Callback function. - :param kwargs: Optional keyword arguments. - - Example: - ``` - def callback(message): - print(message) - - messaging_service.subscribe('system/log', callback) - - ``` - """ - if hasattr(callback, '__self__') and hasattr(callback, '__name__'): - cb_name = f"{callback.__self__.__class__.__name__}.{callback.__name__}" - else: - cb_name = getattr(callback, '__name__', repr(callback)) - print(f"[PubSubMessagingService] Subscribing to {topic} to call {cb_name}") - pub.subscribe(callback, topic, **kwargs) - - def publish(self, topic, *args, **kwargs): - """ - Publish a message to a topic. - - - If only one argument is provided, send it as-is. - - If multiple arguments or keyword arguments are provided, send as JSON. - - :param topic: Topic string (e.g., 'system/log'). - :param args: Optional positional arguments. - :param kwargs: Optional keyword arguments. - """ - - if len(args) == 1 and not kwargs: - pub.sendMessage(topic, message=args[0]) - else: - # pass kwargs to pubsub - pub.sendMessage(topic, **kwargs) - -class MQTTMessagingService(MessagingService): - """MQTT-based messaging implementation""" - def __init__(self, broker="localhost", port=1883): - self.protocol = 'mqtt' - print(f"[MQTTMessagingService] Connecting to {broker}:{port}") - self.client = mqtt.Client() - # self.client.on_message = self._on_message - self.client.connect(broker, port, 60) - self.subscriptions = {} - self.client.loop_start() - - def __del__(self): - self.client.loop_stop() - - def subscribe(self, topic, callback, **kwargs): - """ - Subscribe to an MQTT topic and call the callback on message. - """ - def on_message(client, userdata, msg): - try: - payload = msg.payload.decode('utf-8') - # Try to parse as JSON, fallback to string - try: - data = json.loads(payload) - except Exception: - data = payload - # Call the callback with the message or unpacked kwargs - if isinstance(data, dict): - callback(**data) - else: - callback(data) - except Exception as e: - print(f"[MQTTMessagingService] Error in on_message: {e}") - - qos = kwargs.get('qos', 1) - self.client.subscribe(topic, qos=qos) - self.client.message_callback_add(topic, on_message) - print(f"[MQTTMessagingService] Subscribed to topic: {topic}") - - def publish(self, topic, *args, **kwargs): - """ - Publish a message to an MQTT topic. - - If only one argument is provided, send it as-is. - - If multiple arguments or keyword arguments are provided, send as JSON. - """ - qos = kwargs.pop('qos', 1) - if len(args) == 1 and not kwargs: - payload = args[0] - if not isinstance(payload, str): - payload = json.dumps(payload) - self.client.publish(topic, payload, qos=qos) - else: - # Send kwargs as JSON - self.client.publish(topic, json.dumps(kwargs), qos=qos) - # print(f"[MQTTMessagingService] Published to topic: {topic}") diff --git a/mqtt_subscriber.py b/mqtt_subscriber.py deleted file mode 100755 index 6609e1e6..00000000 --- a/mqtt_subscriber.py +++ /dev/null @@ -1,32 +0,0 @@ -import paho.mqtt.client as mqtt -import json - -BROKER = 'localhost' -PORT = 1883 -TOPIC = '#' - -# Callback when a message is received -def on_message(client, userdata, msg): - try: - payload = msg.payload.decode('utf-8') - try: - data = json.loads(payload) - except Exception: - data = payload - if msg.topic != 'system/loop': - print(f"[MQTT] Topic: {msg.topic} | Message: {data}") - except Exception as e: - print(f"Error decoding message: {e}") - -# Callback when connected to broker -def on_connect(client, userdata, flags, rc): - print(f"Connected to MQTT broker at {BROKER}:{PORT} with result code {rc}") - client.subscribe(TOPIC) - -if __name__ == '__main__': - client = mqtt.Client() - client.on_connect = on_connect - client.on_message = on_message - client.connect(BROKER, PORT, 60) - print(f"Subscribing to all topics on {BROKER}:{PORT}") - client.loop_forever() diff --git a/requirements.txt b/requirements.txt index 73e7cb69..d866afb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -### NOTE -# This file is not in use, please add your dependencies to the module's config file: -# dependencies: -# python: -# - -# unix: -# - +# Test dependencies — packages required for CI unit tests. +# Module runtime dependencies belong in each module's config.yml under +# dependencies.python rather than here. +pyyaml +requests +pypubsub +paho-mqtt diff --git a/shutdown_pi.py b/shutdown_pi.py deleted file mode 100644 index f3a305a8..00000000 --- a/shutdown_pi.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import RPi.GPIO as GPIO -import subprocess - -GPIO.setmode(GPIO.BCM) -GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP) -GPIO.wait_for_edge(26, GPIO.FALLING) - -subprocess.call(['pkill', '-f', 'main.py'], shell=False) # kill main script safely -subprocess.call(['shutdown', '-h', 'now'], shell=False) \ No newline at end of file diff --git a/modules/archived/scripts/__init__.py b/src/__init__.py similarity index 100% rename from modules/archived/scripts/__init__.py rename to src/__init__.py diff --git a/docs/BaseModule.md b/src/docs/BaseModule.md similarity index 100% rename from docs/BaseModule.md rename to src/docs/BaseModule.md diff --git a/src/docs/ModuleLoader.md b/src/docs/ModuleLoader.md new file mode 100644 index 00000000..fbebb133 --- /dev/null +++ b/src/docs/ModuleLoader.md @@ -0,0 +1,86 @@ +# ModuleLoader Documentation + +## Overview + +The `ModuleLoader` class (`src/module_loader.py`) is responsible for dynamically loading and instantiating modules in the project based on YAML configuration files. It uses per-environment YAML files to determine which modules are enabled and how they are configured and wired together. + +## Key Features + +1. **Dynamic Module Loading**: Loads Python modules and classes at runtime, inferring the Python file path from the `config.yml` directory location. +2. **Environment-based Configuration**: Loads `environments/.yml` to determine which modules are enabled and provides device-specific configuration overrides. +3. **Config Merging**: Device-specific `config:` values shallow-merge over module defaults; device-specific `instances:` replaces the module's instance list. +4. **Config-driven Dependency Injection**: `inject_dependencies()` reads `inject:` / `on_inject:` blocks from the environment YAML and automatically wires modules together — no manual wiring in `main.py`. +5. **Messaging Service Integration**: Automatically injects the messaging service into all loaded modules. + +## Directory Structure + +``` +environments/ # Per-device environment YAML files + archie.yml + buddy.yml + cody.yml + server.yml + laptop.yml + +src/ + module_loader.py # ModuleLoader implementation + main.py + system_loop.py + modules/ # All module source code + / + .py # Python implementation (filename matches directory) + config.yml # Module class + generic defaults + README.md # Documentation + tests/ # Unit tests +``` + +## Environment File Format + +```yaml +# environments/archie.yml +messaging_service: + enabled: true + +personality: + enabled: true + inject: + servos: "Servo_*" # wildcard → {servo.identifier: servo} dict + imu.head: BNO055_imu_head # dotted path → target.imu['head'] = instance + imu.body: BNO055_imu_body + vision: Vision + +controller_handler: + enabled: true + inject: + controller: XboxController + on_inject: + - start # called after all injections + +bus_servo: + enabled: true + config: + # device-specific overrides merged over module defaults + instances: + - name: neck_pan + id: 12 + range: [0, 3412] +``` + +## Usage + +```python +from module_loader import ModuleLoader + +loader = ModuleLoader(config_folder="modules", environment="laptop") +module_instances = loader.load_modules() +loader.inject_dependencies(module_instances) +``` + +## inject_dependencies Injection Syntax + +| Syntax | Effect | +|--------|--------| +| `attr: InstanceKey` | `target.attr = instances['InstanceKey']` | +| `attr.sub: InstanceKey` | `target.attr['sub'] = instances['InstanceKey']` | +| `attr: "Prefix_*"` | `target.attr = {inst.identifier: inst for all matching}` | +| `on_inject: [method]` | calls `target.method()` after all injections | diff --git a/docs/SystemLoop.md b/src/docs/SystemLoop.md similarity index 100% rename from docs/SystemLoop.md rename to src/docs/SystemLoop.md diff --git a/src/main.py b/src/main.py new file mode 100644 index 00000000..5c82ebaa --- /dev/null +++ b/src/main.py @@ -0,0 +1,44 @@ +import os, sys +import signal + +# Ensure src/ is on the path so module imports work when running from project root +_SRC_DIR = os.path.dirname(os.path.abspath(__file__)) +if _SRC_DIR not in sys.path: + sys.path.insert(0, _SRC_DIR) + +from modules.config import Config +from module_loader import ModuleLoader +from system_loop import SystemLoop + +def parse_args(): + import argparse + parser = argparse.ArgumentParser(description="Makerforge Modular Robot") + parser.add_argument('--env', default='laptop', help="Set the environment (e.g. archie, buddy, cody, server, laptop)") + args, unknown = parser.parse_known_args() + return args + +def main(): + print('Starting...') + # Throw exception to safely exit script when terminated + signal.signal(signal.SIGTERM, Config.exit) + + # Get environment argument (default to 'laptop') using argparse + env = parse_args().env + print(f"Using environment: {env}") + + # Dynamically load and initialize modules, passing env + # config_folder is relative to src/ (the directory containing this file) + modules_folder = os.path.join(_SRC_DIR, "modules") + loader = ModuleLoader(config_folder=modules_folder, environment=env) + module_instances = loader.load_modules() + + # Inject messaging service and all module-to-module dependencies declared in the + # environment YAML file (inject: / on_inject: blocks). + loader.inject_dependencies(module_instances) + + # Use the new SystemLoop class to run the main loop + system_loop = SystemLoop(module_instances['MessagingService'].messaging_service, module_instances) + system_loop.start() + +if __name__ == '__main__': + main() diff --git a/src/module_loader.py b/src/module_loader.py new file mode 100755 index 00000000..1da2afc0 --- /dev/null +++ b/src/module_loader.py @@ -0,0 +1,274 @@ +import os +import yaml +import importlib.util +from pubsub import pub + +class ModuleLoader: + def __init__(self, config_folder='modules', environment=None, environments_folder=None): + """ + ModuleLoader class + :param config_folder: root folder to search for module config.yml files (searched recursively) + :param environment: name of the environment to load (e.g. 'archie', 'laptop', 'server'). + Must match a YAML file in environments/.yml. + :param environments_folder: folder containing per-environment YAML files. + Defaults to 'environments' next to the project root. + + Each module lives in its own directory containing: + - .py (Python implementation, filename matches directory name) + - config.yml (generic module configuration with 'class' key for the Python class name) + - README.md (documentation) + - tests/ (unit tests) + + Which modules are enabled, and any device-specific configuration overrides, are + controlled by a per-environment file: + environments/.yml + + Example environment file (environments/archie.yml): + --- + messaging_service: + enabled: true + logwrapper: + enabled: true + gpio_motion: + enabled: true + config: # merged over the module's generic config + pin: 9 + bus_servo: + enabled: true + instances: # replaces the module's instances list + - name: leg_r_tilt + id: 1 + range: [2511, 3944] + + Example module config.yml (modules/gpio/motion/config.yml): + --- + gpio_motion: + class: Motion # Python class name (required) + config: # generic defaults (device-specific values go in environment file) + test_on_boot: false + + Example: + loader = ModuleLoader(environment='laptop') + modules = loader.load_modules() + + Reference module once loaded: + translator_inst = modules['Translator'] + """ + self.config_folder = config_folder + self.environment = environment or 'laptop' + # Resolve environments folder: use provided value, or 'environments' at the project root + # module_loader.py lives in src/, so project root is one level up + _src_dir = os.path.dirname(os.path.abspath(__file__)) + _project_root = os.path.dirname(_src_dir) + self.environments_folder = environments_folder or os.path.join(_project_root, 'environments') + print(f"[ModuleLoader] Loading modules for environment: {self.environment}") + self.modules = self.load_yaml_files() + + def _load_environment_config(self): + """Load the environment YAML file to determine which modules are active and their overrides.""" + env_file = os.path.join(self.environments_folder, f'{self.environment}.yml') + if not os.path.exists(env_file): + print(f"[ModuleLoader] Warning: environment file not found: {env_file}") + return {} + with open(env_file, 'r') as f: + try: + return yaml.safe_load(f) or {} + except yaml.YAMLError as e: + print(f"Error loading {env_file}: {e}") + return {} + + def load_yaml_files(self): + """Recursively search config_folder for config.yml files and load enabled module configurations.""" + env_config = self._load_environment_config() + + config_files = [] + for root, dirs, files in os.walk(self.config_folder): + for f in files: + if f == 'config.yml': + config_files.append(os.path.join(root, f)) + + loaded_modules = [] + for file_path in config_files: + with open(file_path, 'r') as stream: + try: + config = yaml.safe_load(stream) + for module_name, module_config in config.items(): + # Check if this module is enabled in the environment file + env_entry = env_config.get(module_name, {}) + if not env_entry.get('enabled', False): + continue + print(f"[ModuleLoader] Loading module: {module_name}") + + # Merge environment config overrides into module config (shallow merge) + env_config_override = env_entry.get('config', {}) + if env_config_override: + module_config['config'] = {**(module_config.get('config') or {}), **env_config_override} + + # Environment can also supply/override the instances list + if 'instances' in env_entry: + module_config['instances'] = env_entry['instances'] + + # Infer the Python file path from the config.yml directory + dir_path = os.path.dirname(file_path) + dir_name = os.path.basename(dir_path) + module_config['_py_file'] = os.path.join(dir_path, f"{dir_name}.py") + module_config['_class'] = module_config.get('class') + module_config['_module_key'] = module_name + loaded_modules.append(module_config) + except yaml.YAMLError as e: + print(f"Error loading {file_path}: {e}") + return loaded_modules + + def set_messaging_service(self, module_instances, messaging_service): + """Set the messaging service for the modules.""" + for name, module in module_instances.items(): + if 'MessagingService' in name: + continue + module.messaging_service = messaging_service + + def inject_dependencies(self, instances): + """ + Inject module dependencies as declared in the environment YAML file and wire + the messaging service. Call this once after load_modules(). + + Each module in the environment file can declare: + + inject: + attr_name: TargetInstanceKey # simple attribute injection + attr_name: "Prefix_*" # wildcard → {inst.identifier: inst} dict + dict_attr.subkey: TargetInstanceKey # dict-path (e.g. imu.head, imu.body) + + on_inject: + - method_name # method(s) to call after all injections + + The messaging service is always injected first for all loaded modules. + + Example (modules/environments/archie.yml): + --- + personality: + inject: + servos: "Servo_*" + imu.head: BNO055_imu_head + imu.body: BNO055_imu_body + vision: Vision + controller_handler: + inject: + controller: XboxController + on_inject: + - start + """ + # Step 1: inject messaging service for all modules + if 'MessagingService' in instances: + messaging_service = instances['MessagingService'].messaging_service + self.set_messaging_service(instances, messaging_service) + + # Step 2: build a map from environment module_key → instance keys + module_key_to_instances = {} + for module in self.modules: + module_key = module.get('_module_key') + class_name = module.get('_class') + if not module_key or not class_name: + continue + related = [k for k in instances if k == class_name or k.startswith(class_name + '_')] + module_key_to_instances[module_key] = related + + # Step 3: process inject / on_inject declarations from the environment config + env_config = self._load_environment_config() + for module_key, env_entry in env_config.items(): + inject_map = env_entry.get('inject', {}) + on_inject = env_entry.get('on_inject', []) + if not inject_map and not on_inject: + continue + + target_keys = module_key_to_instances.get(module_key, []) + for target_key in target_keys: + target = instances.get(target_key) + if target is None: + continue + + for attr_path, source_spec in inject_map.items(): + value = self._resolve_inject_source(source_spec, instances) + if value is None: + print(f"[ModuleLoader] inject: source '{source_spec}' not found for {target_key}.{attr_path}") + continue + self._set_attr(target, attr_path, value) + print(f"[ModuleLoader] inject: {target_key}.{attr_path} = {source_spec}") + + if isinstance(on_inject, str): + on_inject = [on_inject] + for method_name in on_inject: + method = getattr(target, method_name, None) + if callable(method): + print(f"[ModuleLoader] on_inject: {target_key}.{method_name}()") + method() + else: + print(f"[ModuleLoader] on_inject: {target_key}.{method_name} not found or not callable") + + def _resolve_inject_source(self, source_spec, instances): + """ + Resolve an inject source specification to a value. + + - "InstanceKey" → instances['InstanceKey'] + - "Prefix_*" → {inst.identifier (or key_suffix): inst} for all matching keys + """ + if '*' in str(source_spec): + prefix = source_spec.replace('*', '') + result = {} + for key, inst in instances.items(): + if key.startswith(prefix): + id_key = getattr(inst, 'identifier', None) or key[len(prefix):] + result[id_key] = inst + return result if result else None + return instances.get(source_spec) + + def _set_attr(self, target, attr_path, value): + """ + Set an attribute on target using dotted path notation. + + - "vision" → target.vision = value + - "imu.head" → target.imu['head'] = value (creates dict if absent) + """ + if '.' in attr_path: + attr_name, sub_key = attr_path.split('.', 1) + container = getattr(target, attr_name, None) + if isinstance(container, dict): + container[sub_key] = value + else: + setattr(target, attr_name, {sub_key: value}) + else: + setattr(target, attr_path, value) + + def load_modules(self): + """Dynamically load and instantiate the modules based on the config.""" + instances = {} # Use a dictionary to store instances for easy access + for module in self.modules: + py_file = module['_py_file'] + class_name = module['_class'] + print(f"Enabling {class_name} from {py_file}") + instances_config = module.get('instances', [module.get('config')]) + shared_config = module.get('config', {}) + if instances_config[0] is None: + instances_config = [{}] + + # Dynamically load the module from its inferred file path + spec = importlib.util.spec_from_file_location(class_name, py_file) + mod = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(mod) + except Exception as e: + print(f"Error loading module {class_name}: {e}") + + # If multiple instances, merge shared config into each instance config + multiple_instances = 'instances' in module and isinstance(module['instances'], list) and len(module['instances']) > 0 + for instance_config in instances_config: + if multiple_instances: + # Avoid overwriting instance-specific keys with shared config + merged_config = {**shared_config, **instance_config} + else: + merged_config = instance_config + instance_name = class_name + '_' + instance_config.get('name') if instance_config.get('name') is not None else class_name + instance = getattr(mod, class_name)(**merged_config) + instances[instance_name] = instance + + print("All modules loaded") + return instances diff --git a/modules/__init__.py b/src/modules/__init__.py similarity index 100% rename from modules/__init__.py rename to src/modules/__init__.py diff --git a/modules/display/lib/__init__.py b/src/modules/actuators/__init__.py similarity index 100% rename from modules/display/lib/__init__.py rename to src/modules/actuators/__init__.py diff --git a/docs/BusServo.md b/src/modules/actuators/bus_servo/README.md old mode 100755 new mode 100644 similarity index 99% rename from docs/BusServo.md rename to src/modules/actuators/bus_servo/README.md index d48394d8..79031884 --- a/docs/BusServo.md +++ b/src/modules/actuators/bus_servo/README.md @@ -1,4 +1,4 @@ -# BaseModule Documentation +# BusServo Module Documentation ## Overview diff --git a/modules/actuators/bus_servo/SCServo_examples/ping.py b/src/modules/actuators/bus_servo/SCServo_examples/ping.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/ping.py rename to src/modules/actuators/bus_servo/SCServo_examples/ping.py diff --git a/modules/actuators/bus_servo/SCServo_examples/read_write.py b/src/modules/actuators/bus_servo/SCServo_examples/read_write.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/read_write.py rename to src/modules/actuators/bus_servo/SCServo_examples/read_write.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/__init__.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/__init__.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/__init__.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/__init__.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_read.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_read.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_read.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_read.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_write.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_write.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_write.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/group_sync_write.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/packet_handler.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/packet_handler.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/packet_handler.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/packet_handler.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/port_handler.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/port_handler.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/port_handler.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/port_handler.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/protocol_packet_handler.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/protocol_packet_handler.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/protocol_packet_handler.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/protocol_packet_handler.py diff --git a/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/scservo_def.py b/src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/scservo_def.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/scservo_sdk/scservo_def.py rename to src/modules/actuators/bus_servo/SCServo_examples/scservo_sdk/scservo_def.py diff --git a/modules/actuators/bus_servo/SCServo_examples/sync_read_write.py b/src/modules/actuators/bus_servo/SCServo_examples/sync_read_write.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/sync_read_write.py rename to src/modules/actuators/bus_servo/SCServo_examples/sync_read_write.py diff --git a/modules/actuators/bus_servo/SCServo_examples/sync_write.py b/src/modules/actuators/bus_servo/SCServo_examples/sync_write.py similarity index 100% rename from modules/actuators/bus_servo/SCServo_examples/sync_write.py rename to src/modules/actuators/bus_servo/SCServo_examples/sync_write.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/__init__.py b/src/modules/actuators/bus_servo/SCservo_sdk/__init__.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/__init__.py rename to src/modules/actuators/bus_servo/SCservo_sdk/__init__.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/group_sync_read.py b/src/modules/actuators/bus_servo/SCservo_sdk/group_sync_read.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/group_sync_read.py rename to src/modules/actuators/bus_servo/SCservo_sdk/group_sync_read.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/group_sync_write.py b/src/modules/actuators/bus_servo/SCservo_sdk/group_sync_write.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/group_sync_write.py rename to src/modules/actuators/bus_servo/SCservo_sdk/group_sync_write.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/packet_handler.py b/src/modules/actuators/bus_servo/SCservo_sdk/packet_handler.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/packet_handler.py rename to src/modules/actuators/bus_servo/SCservo_sdk/packet_handler.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/port_handler.py b/src/modules/actuators/bus_servo/SCservo_sdk/port_handler.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/port_handler.py rename to src/modules/actuators/bus_servo/SCservo_sdk/port_handler.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/protocol_packet_handler.py b/src/modules/actuators/bus_servo/SCservo_sdk/protocol_packet_handler.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/protocol_packet_handler.py rename to src/modules/actuators/bus_servo/SCservo_sdk/protocol_packet_handler.py diff --git a/modules/actuators/bus_servo/SCservo_sdk/scservo_def.py b/src/modules/actuators/bus_servo/SCservo_sdk/scservo_def.py similarity index 100% rename from modules/actuators/bus_servo/SCservo_sdk/scservo_def.py rename to src/modules/actuators/bus_servo/SCservo_sdk/scservo_def.py diff --git a/modules/actuators/bus_servo/STServo_examples/change_id.py b/src/modules/actuators/bus_servo/STServo_examples/change_id.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/change_id.py rename to src/modules/actuators/bus_servo/STServo_examples/change_id.py diff --git a/modules/actuators/bus_servo/STServo_examples/ping.py b/src/modules/actuators/bus_servo/STServo_examples/ping.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/ping.py rename to src/modules/actuators/bus_servo/STServo_examples/ping.py diff --git a/modules/actuators/bus_servo/STServo_examples/read.py b/src/modules/actuators/bus_servo/STServo_examples/read.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/read.py rename to src/modules/actuators/bus_servo/STServo_examples/read.py diff --git a/modules/actuators/bus_servo/STServo_examples/read_all.py b/src/modules/actuators/bus_servo/STServo_examples/read_all.py similarity index 97% rename from modules/actuators/bus_servo/STServo_examples/read_all.py rename to src/modules/actuators/bus_servo/STServo_examples/read_all.py index 7b31b1ce..10645d41 100644 --- a/modules/actuators/bus_servo/STServo_examples/read_all.py +++ b/src/modules/actuators/bus_servo/STServo_examples/read_all.py @@ -35,7 +35,7 @@ def getch(): # Default setting STS_ID = 1 # STServo ID : 1 BAUDRATE = 1000000 # STServo default baudrate : 1000000 -DEVICENAME = '/dev/ttyAMA0' # Check which port is being used on your controller +DEVICENAME = '/dev/ttyACM0' # Check which port is being used on your controller # ex) Windows: "COM1" Linux: "/dev/ttyUSB0" Mac: "/dev/tty.usbserial-*" SERVO_CNT = 20 # Number of servos to read diff --git a/modules/actuators/bus_servo/STServo_examples/read_write.py b/src/modules/actuators/bus_servo/STServo_examples/read_write.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/read_write.py rename to src/modules/actuators/bus_servo/STServo_examples/read_write.py diff --git a/modules/actuators/bus_servo/STServo_examples/reg_write.py b/src/modules/actuators/bus_servo/STServo_examples/reg_write.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/reg_write.py rename to src/modules/actuators/bus_servo/STServo_examples/reg_write.py diff --git a/modules/actuators/bus_servo/STServo_examples/sync_read.py b/src/modules/actuators/bus_servo/STServo_examples/sync_read.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/sync_read.py rename to src/modules/actuators/bus_servo/STServo_examples/sync_read.py diff --git a/modules/actuators/bus_servo/STServo_examples/sync_read_write.py b/src/modules/actuators/bus_servo/STServo_examples/sync_read_write.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/sync_read_write.py rename to src/modules/actuators/bus_servo/STServo_examples/sync_read_write.py diff --git a/modules/actuators/bus_servo/STServo_examples/sync_write.py b/src/modules/actuators/bus_servo/STServo_examples/sync_write.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/sync_write.py rename to src/modules/actuators/bus_servo/STServo_examples/sync_write.py diff --git a/modules/actuators/bus_servo/STServo_examples/wheel.py b/src/modules/actuators/bus_servo/STServo_examples/wheel.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/wheel.py rename to src/modules/actuators/bus_servo/STServo_examples/wheel.py diff --git a/modules/actuators/bus_servo/STServo_examples/write.py b/src/modules/actuators/bus_servo/STServo_examples/write.py similarity index 100% rename from modules/actuators/bus_servo/STServo_examples/write.py rename to src/modules/actuators/bus_servo/STServo_examples/write.py diff --git a/modules/actuators/bus_servo/STservo_sdk/__init__.py b/src/modules/actuators/bus_servo/STservo_sdk/__init__.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/__init__.py rename to src/modules/actuators/bus_servo/STservo_sdk/__init__.py diff --git a/modules/actuators/bus_servo/STservo_sdk/group_sync_read.py b/src/modules/actuators/bus_servo/STservo_sdk/group_sync_read.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/group_sync_read.py rename to src/modules/actuators/bus_servo/STservo_sdk/group_sync_read.py diff --git a/modules/actuators/bus_servo/STservo_sdk/group_sync_write.py b/src/modules/actuators/bus_servo/STservo_sdk/group_sync_write.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/group_sync_write.py rename to src/modules/actuators/bus_servo/STservo_sdk/group_sync_write.py diff --git a/modules/actuators/bus_servo/STservo_sdk/port_handler.py b/src/modules/actuators/bus_servo/STservo_sdk/port_handler.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/port_handler.py rename to src/modules/actuators/bus_servo/STservo_sdk/port_handler.py diff --git a/modules/actuators/bus_servo/STservo_sdk/protocol_packet_handler.py b/src/modules/actuators/bus_servo/STservo_sdk/protocol_packet_handler.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/protocol_packet_handler.py rename to src/modules/actuators/bus_servo/STservo_sdk/protocol_packet_handler.py diff --git a/modules/actuators/bus_servo/STservo_sdk/scscl.py b/src/modules/actuators/bus_servo/STservo_sdk/scscl.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/scscl.py rename to src/modules/actuators/bus_servo/STservo_sdk/scscl.py diff --git a/modules/actuators/bus_servo/STservo_sdk/sts.py b/src/modules/actuators/bus_servo/STservo_sdk/sts.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/sts.py rename to src/modules/actuators/bus_servo/STservo_sdk/sts.py diff --git a/modules/actuators/bus_servo/STservo_sdk/stservo_def.py b/src/modules/actuators/bus_servo/STservo_sdk/stservo_def.py similarity index 100% rename from modules/actuators/bus_servo/STservo_sdk/stservo_def.py rename to src/modules/actuators/bus_servo/STservo_sdk/stservo_def.py diff --git a/modules/display/lib/waveshare/__init__.py b/src/modules/actuators/bus_servo/__init__.py similarity index 100% rename from modules/display/lib/waveshare/__init__.py rename to src/modules/actuators/bus_servo/__init__.py diff --git a/modules/actuators/bus_servo/servo.py b/src/modules/actuators/bus_servo/bus_servo.py similarity index 99% rename from modules/actuators/bus_servo/servo.py rename to src/modules/actuators/bus_servo/bus_servo.py index ddff27b4..fd53ab8f 100644 --- a/modules/actuators/bus_servo/servo.py +++ b/src/modules/actuators/bus_servo/bus_servo.py @@ -109,7 +109,6 @@ def setup_messaging(self): self.subscribe('servo:' + self.identifier + ':queue', self.move) self.subscribe('system/exit', self.exit) self.subscribe('servo/pose', self.move_to_pose) - self.subscribe('system/loop', self._process_queue) if self.calibrate_on_boot: self.calibrate_dynamic() # Log will show current position repeatedly to help with manual configuration @@ -206,10 +205,14 @@ def move(self, position, speed=None, acceleration=None, delay=0, **kwargs): 'delay': delay, }) + def loop(self): + """Called every system loop cycle to drain the move queue.""" + self._process_queue() + def _process_queue(self, **kwargs): """ Process the next item in the move queue if the servo is not moving. - Called on system/loop. + Called every loop cycle. """ if not self._move_queue: return diff --git a/modules/actuators/bus_servo/change_id.py b/src/modules/actuators/bus_servo/change_id.py similarity index 82% rename from modules/actuators/bus_servo/change_id.py rename to src/modules/actuators/bus_servo/change_id.py index 4a966caa..22c437d5 100644 --- a/modules/actuators/bus_servo/change_id.py +++ b/src/modules/actuators/bus_servo/change_id.py @@ -30,19 +30,19 @@ def getch(): from STservo_sdk import * # User config -SERVO_TYPE = input("Enter servo type (STS for ST3215, SCSCL for SC09): ").strip().upper() +SERVO_TYPE = input("Enter servo type (ST for ST3215, SC for SC09): ").strip().upper() SERVO_ID = int(input("Enter current servo ID (1-253): ")) BAUDRATE = 1000000 -DEVICENAME = '/dev/ttyAMA0' # Change as needed, find with `ls /dev/ttyACM*` +DEVICENAME = '/dev/ttyACM0' # Change as needed, find with `ls /dev/ttyACM*` # Initialize PortHandler portHandler = PortHandler(DEVICENAME) # Select correct packet handler -if SERVO_TYPE == "STS": +if SERVO_TYPE == "ST": packetHandler = sts(portHandler) ID_ADDR = 5 -elif SERVO_TYPE == "SCSCL": +elif SERVO_TYPE == "SC": packetHandler = scscl(portHandler) ID_ADDR = 5 else: @@ -75,7 +75,7 @@ def getch(): # Read present position/speed (optional, for feedback) try: - if SERVO_TYPE == "STS": + if SERVO_TYPE == "ST": pos, spd, comm_result, err = packetHandler.ReadPosSpeed(SERVO_ID) else: pos, spd, comm_result, err = packetHandler.ReadPosSpeed(SERVO_ID) @@ -128,20 +128,18 @@ def getch(): print(f"{SERVO_TYPE} servo ID changed successfully to {new_id}") SERVO_ID = new_id - print(f"Press any key to center servo ID {SERVO_ID} (required for further calibration)...") - getch() - # Center servo - if SERVO_TYPE == "STS": + if SERVO_TYPE == "ST": + print(f"Press any key to center servo ID {SERVO_ID} (required for further calibration)...") + getch() + # Center servo center_result, center_error = packetHandler.WritePosEx(SERVO_ID, 2048, 1000, 50) - else: - center_result, center_error = packetHandler.WritePosEx(SERVO_ID, 512, 1000, 50) - if center_result != COMM_SUCCESS or center_error != 0: - print("Failed to center servo: %s %s" % ( - packetHandler.getTxRxResult(center_result), - packetHandler.getRxPacketError(center_error) - )) - else: - print(f"Centered servo ID {SERVO_ID}") + if center_result != COMM_SUCCESS or center_error != 0: + print("Failed to center servo: %s %s" % ( + packetHandler.getTxRxResult(center_result), + packetHandler.getRxPacketError(center_error) + )) + else: + print(f"Centered servo ID {SERVO_ID}") # Close port portHandler.closePort() diff --git a/src/modules/actuators/bus_servo/config.yml b/src/modules/actuators/bus_servo/config.yml new file mode 100644 index 00000000..0f0a3ba9 --- /dev/null +++ b/src/modules/actuators/bus_servo/config.yml @@ -0,0 +1,5 @@ +bus_servo: + class: Servo + dependencies: + unix: + - python3-serial diff --git a/tests/__init__.py b/src/modules/actuators/bus_servo/tests/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from tests/__init__.py rename to src/modules/actuators/bus_servo/tests/__init__.py diff --git a/src/modules/actuators/bus_servo/tests/test_bus_servo.py b/src/modules/actuators/bus_servo/tests/test_bus_servo.py new file mode 100644 index 00000000..9e90d3dc --- /dev/null +++ b/src/modules/actuators/bus_servo/tests/test_bus_servo.py @@ -0,0 +1,28 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +# Mock the SDK modules +mock_st_sdk = MagicMock() +mock_sc_sdk = MagicMock() +sys.modules['modules.actuators.bus_servo.STservo_sdk'] = mock_st_sdk +sys.modules['modules.actuators.bus_servo.SCservo_sdk'] = mock_sc_sdk + +from modules.actuators.bus_servo.bus_servo import Servo + +class TestBusServo(unittest.TestCase): + + def test_init(self): + servo = Servo(name='test', id=1, range=[0, 4095], model='ST3215') + self.assertEqual(servo.identifier, 'test') + self.assertEqual(servo.index, 1) + self.assertEqual(servo.range, [0, 4095]) + self.assertEqual(servo.model, 'ST3215') + + def test_init_defaults(self): + servo = Servo(name='test', id=1, range=[0, 1023]) + self.assertEqual(servo.baudrate, 1000000) + self.assertEqual(servo.port, '/dev/ttyAMA0') + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/actuators/piservo/README.md b/src/modules/actuators/piservo/README.md new file mode 100644 index 00000000..a83b4ffb --- /dev/null +++ b/src/modules/actuators/piservo/README.md @@ -0,0 +1,28 @@ +# PiServo + +## Overview + +Raspberry Pi GPIO servo controller. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +piservo: + enabled: false # Set to true to enable this module + path: modules.actuators.piservo.piservo.PiServo + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/docs/NeoPx.md b/src/modules/actuators/piservo/__init__.py similarity index 100% rename from docs/NeoPx.md rename to src/modules/actuators/piservo/__init__.py diff --git a/src/modules/actuators/piservo/config.yml b/src/modules/actuators/piservo/config.yml new file mode 100644 index 00000000..77f54b00 --- /dev/null +++ b/src/modules/actuators/piservo/config.yml @@ -0,0 +1,5 @@ +piservo: + class: PiServo + dependencies: + python: + - gpiozero diff --git a/modules/actuators/piservo.py b/src/modules/actuators/piservo/piservo.py similarity index 100% rename from modules/actuators/piservo.py rename to src/modules/actuators/piservo/piservo.py diff --git a/src/modules/actuators/piservo/tests/__init__.py b/src/modules/actuators/piservo/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/actuators/piservo/tests/test_piservo.py b/src/modules/actuators/piservo/tests/test_piservo.py new file mode 100644 index 00000000..f4baba4b --- /dev/null +++ b/src/modules/actuators/piservo/tests/test_piservo.py @@ -0,0 +1,25 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_gpiozero = MagicMock() +sys.modules['gpiozero'] = mock_gpiozero + +from modules.actuators.piservo.piservo import PiServo + +class TestPiServo(unittest.TestCase): + + def test_init(self): + servo = PiServo(pin=22, range=[-40, 40], start=0) + self.assertEqual(servo.pin, 22) + self.assertEqual(servo.range, [-40, 40]) + self.assertEqual(servo.start, 0) + + def test_setup_messaging(self): + servo = PiServo(pin=22, range=[-40, 40], start=0) + with patch.object(servo, 'subscribe') as mock_sub: + servo.setup_messaging() + mock_sub.assert_called_with('piservo/move', servo.move) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/actuators/servo/README.md b/src/modules/actuators/servo/README.md new file mode 100644 index 00000000..26e62a28 --- /dev/null +++ b/src/modules/actuators/servo/README.md @@ -0,0 +1,28 @@ +# Servo + +## Overview + +Arduino serial servo controller with range and buffer support. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +servos: + enabled: false # Set to true to enable this module + path: modules.actuators.servo.servo.Servo + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/actuators/servo/__init__.py b/src/modules/actuators/servo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/actuators/servo/config.yml b/src/modules/actuators/servo/config.yml new file mode 100644 index 00000000..40193520 --- /dev/null +++ b/src/modules/actuators/servo/config.yml @@ -0,0 +1,5 @@ +servo: + class: Servo + dependencies: + python: + - pigpio diff --git a/modules/actuators/servo.py b/src/modules/actuators/servo/servo.py similarity index 100% rename from modules/actuators/servo.py rename to src/modules/actuators/servo/servo.py diff --git a/src/modules/actuators/servo/tests/__init__.py b/src/modules/actuators/servo/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/actuators/servo/tests/test_servo.py b/src/modules/actuators/servo/tests/test_servo.py new file mode 100644 index 00000000..5d7f1520 --- /dev/null +++ b/src/modules/actuators/servo/tests/test_servo.py @@ -0,0 +1,29 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_pigpio = MagicMock() +sys.modules['pigpio'] = mock_pigpio + +# Mock ArduinoSerial +mock_arduino = MagicMock() +sys.modules['modules.network.arduinoserial'] = MagicMock() +sys.modules['modules.network.arduinoserial.arduinoserial'] = MagicMock() + +from modules.actuators.servo.servo import Servo + +class TestServo(unittest.TestCase): + + def test_init(self): + servo = Servo(pin=1, name='test', range=[0, 180], start=50, serial=True) + self.assertEqual(servo.pin, 1) + self.assertEqual(servo.identifier, 'test') + self.assertEqual(servo.range, [0, 180]) + + def test_translate(self): + servo = Servo(name='test', range=[0, 200], start=50, serial=True) + # 50% of range [0,200] should be 100 + self.assertEqual(servo.translate(50), 100) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/actuators/tests/__init__.py b/src/modules/actuators/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/actuators/tests/bus_servo_test.py b/src/modules/actuators/tests/bus_servo_test.py similarity index 95% rename from modules/actuators/tests/bus_servo_test.py rename to src/modules/actuators/tests/bus_servo_test.py index 533a3d32..e6e723ef 100644 --- a/modules/actuators/tests/bus_servo_test.py +++ b/src/modules/actuators/tests/bus_servo_test.py @@ -194,14 +194,20 @@ def test_move_uses_instance_defaults_when_not_supplied(self): # ------------------------------------------------------------------ # setup_messaging subscriptions # ------------------------------------------------------------------ - def test_setup_messaging_subscribes_to_system_loop(self): + def test_loop_calls_process_queue(self): + """loop() should call _process_queue to drain the move queue.""" + with patch.object(self.sv, '_process_queue') as mock_pq: + self.sv.loop() + mock_pq.assert_called_once() + + def test_setup_messaging_does_not_subscribe_to_system_loop(self): + """system/loop subscription replaced by direct loop() call — should not be in subscriptions.""" messaging_service = MagicMock() - # Set via property so setup_messaging() is called with patch.object(self.sv, 'get_position', return_value=100): self.sv.messaging_service = messaging_service topics = [c.args[0] for c in messaging_service.subscribe.call_args_list] - self.assertIn('system/loop', topics) + self.assertNotIn('system/loop', topics) def test_setup_messaging_subscribes_queue_topic(self): messaging_service = MagicMock() diff --git a/modules/actuators/tests/servo_test.py b/src/modules/actuators/tests/servo_test.py similarity index 100% rename from modules/actuators/tests/servo_test.py rename to src/modules/actuators/tests/servo_test.py diff --git a/src/modules/animate/README.md b/src/modules/animate/README.md new file mode 100644 index 00000000..ec7e1618 --- /dev/null +++ b/src/modules/animate/README.md @@ -0,0 +1,28 @@ +# Animate + +## Overview + +Animation playback module for controlling robot animations. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +animate: + enabled: false # Set to true to enable this module + path: modules.animate.animate.Animate + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/animate/__init__.py b/src/modules/animate/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/animate.py b/src/modules/animate/animate.py similarity index 100% rename from modules/animate.py rename to src/modules/animate/animate.py diff --git a/src/modules/animate/config.yml b/src/modules/animate/config.yml new file mode 100644 index 00000000..eea7b08f --- /dev/null +++ b/src/modules/animate/config.yml @@ -0,0 +1,2 @@ +animate: + class: Animate diff --git a/src/modules/animate/tests/__init__.py b/src/modules/animate/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/animate/tests/test_animate.py b/src/modules/animate/tests/test_animate.py new file mode 100644 index 00000000..0574a096 --- /dev/null +++ b/src/modules/animate/tests/test_animate.py @@ -0,0 +1,30 @@ +import unittest +from unittest.mock import MagicMock, patch +import sys + +mock_gpiozero = MagicMock() +sys.modules['gpiozero'] = mock_gpiozero + +from modules.animate.animate import Animate + +class TestAnimate(unittest.TestCase): + + def test_init_default_path(self): + anim = Animate() + self.assertTrue(anim.path.endswith('/')) + + def test_animate_missing_file(self): + anim = Animate(path='/tmp') + anim.messaging_service = MagicMock() + anim.log = MagicMock() + with self.assertRaises(ValueError): + anim.animate('nonexistent') + + def test_setup_messaging(self): + anim = Animate(path='/tmp') + with patch.object(anim, 'subscribe') as mock_sub: + anim.setup_messaging() + mock_sub.assert_called_with('animate', anim.animate) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/archived/actuators/linear_actuator.py b/src/modules/archived/actuators/linear_actuator.py similarity index 100% rename from modules/archived/actuators/linear_actuator.py rename to src/modules/archived/actuators/linear_actuator.py diff --git a/modules/archived/actuators/stepper.py b/src/modules/archived/actuators/stepper.py similarity index 100% rename from modules/archived/actuators/stepper.py rename to src/modules/archived/actuators/stepper.py diff --git a/modules/archived/actuators/stepper_test.py b/src/modules/archived/actuators/stepper_test.py similarity index 100% rename from modules/archived/actuators/stepper_test.py rename to src/modules/archived/actuators/stepper_test.py diff --git a/modules/archived/battery.py b/src/modules/archived/battery.py similarity index 100% rename from modules/archived/battery.py rename to src/modules/archived/battery.py diff --git a/modules/archived/chatbot/chatbot.py b/src/modules/archived/chatbot/chatbot.py similarity index 100% rename from modules/archived/chatbot/chatbot.py rename to src/modules/archived/chatbot/chatbot.py diff --git a/modules/archived/controller.py b/src/modules/archived/controller.py similarity index 100% rename from modules/archived/controller.py rename to src/modules/archived/controller.py diff --git a/modules/archived/controllers/gamepad.py b/src/modules/archived/controllers/gamepad.py similarity index 100% rename from modules/archived/controllers/gamepad.py rename to src/modules/archived/controllers/gamepad.py diff --git a/modules/archived/controllers/keyboard.py b/src/modules/archived/controllers/keyboard.py similarity index 100% rename from modules/archived/controllers/keyboard.py rename to src/modules/archived/controllers/keyboard.py diff --git a/modules/archived/coral/aiymakerkit/__init__.py b/src/modules/archived/coral/aiymakerkit/__init__.py similarity index 100% rename from modules/archived/coral/aiymakerkit/__init__.py rename to src/modules/archived/coral/aiymakerkit/__init__.py diff --git a/modules/archived/coral/aiymakerkit/audio.py b/src/modules/archived/coral/aiymakerkit/audio.py similarity index 100% rename from modules/archived/coral/aiymakerkit/audio.py rename to src/modules/archived/coral/aiymakerkit/audio.py diff --git a/modules/archived/coral/aiymakerkit/ring_buffer.py b/src/modules/archived/coral/aiymakerkit/ring_buffer.py similarity index 100% rename from modules/archived/coral/aiymakerkit/ring_buffer.py rename to src/modules/archived/coral/aiymakerkit/ring_buffer.py diff --git a/modules/archived/coral/aiymakerkit/utils.py b/src/modules/archived/coral/aiymakerkit/utils.py similarity index 100% rename from modules/archived/coral/aiymakerkit/utils.py rename to src/modules/archived/coral/aiymakerkit/utils.py diff --git a/modules/archived/coral/aiymakerkit/vision.py b/src/modules/archived/coral/aiymakerkit/vision.py similarity index 100% rename from modules/archived/coral/aiymakerkit/vision.py rename to src/modules/archived/coral/aiymakerkit/vision.py diff --git a/modules/archived/coral/models.py b/src/modules/archived/coral/models.py similarity index 100% rename from modules/archived/coral/models.py rename to src/modules/archived/coral/models.py diff --git a/modules/archived/coral/models/coco_labels.txt b/src/modules/archived/coral/models/coco_labels.txt similarity index 100% rename from modules/archived/coral/models/coco_labels.txt rename to src/modules/archived/coral/models/coco_labels.txt diff --git a/modules/archived/coral/models/imagenet_labels.txt b/src/modules/archived/coral/models/imagenet_labels.txt similarity index 100% rename from modules/archived/coral/models/imagenet_labels.txt rename to src/modules/archived/coral/models/imagenet_labels.txt diff --git a/modules/archived/coral/models/mobilenet_v1_1.0_224_l2norm_quant_edgetpu.tflite b/src/modules/archived/coral/models/mobilenet_v1_1.0_224_l2norm_quant_edgetpu.tflite similarity index 100% rename from modules/archived/coral/models/mobilenet_v1_1.0_224_l2norm_quant_edgetpu.tflite rename to src/modules/archived/coral/models/mobilenet_v1_1.0_224_l2norm_quant_edgetpu.tflite diff --git a/modules/archived/coral/models/movenet_single_pose_lightning_ptq_edgetpu.tflite b/src/modules/archived/coral/models/movenet_single_pose_lightning_ptq_edgetpu.tflite similarity index 100% rename from modules/archived/coral/models/movenet_single_pose_lightning_ptq_edgetpu.tflite rename to src/modules/archived/coral/models/movenet_single_pose_lightning_ptq_edgetpu.tflite diff --git a/modules/archived/coral/models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite b/src/modules/archived/coral/models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite similarity index 100% rename from modules/archived/coral/models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite rename to src/modules/archived/coral/models/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite diff --git a/modules/archived/coral/models/ssd_mobilenet_v2_face_quant_postprocess_edgetpu.tflite b/src/modules/archived/coral/models/ssd_mobilenet_v2_face_quant_postprocess_edgetpu.tflite similarity index 100% rename from modules/archived/coral/models/ssd_mobilenet_v2_face_quant_postprocess_edgetpu.tflite rename to src/modules/archived/coral/models/ssd_mobilenet_v2_face_quant_postprocess_edgetpu.tflite diff --git a/modules/archived/coral/models/tf2_mobilenet_v2_1.0_224_ptq_edgetpu.tflite b/src/modules/archived/coral/models/tf2_mobilenet_v2_1.0_224_ptq_edgetpu.tflite similarity index 100% rename from modules/archived/coral/models/tf2_mobilenet_v2_1.0_224_ptq_edgetpu.tflite rename to src/modules/archived/coral/models/tf2_mobilenet_v2_1.0_224_ptq_edgetpu.tflite diff --git a/modules/archived/coral/test.py b/src/modules/archived/coral/test.py similarity index 100% rename from modules/archived/coral/test.py rename to src/modules/archived/coral/test.py diff --git a/modules/archived/coral/tracking.py b/src/modules/archived/coral/tracking.py similarity index 100% rename from modules/archived/coral/tracking.py rename to src/modules/archived/coral/tracking.py diff --git a/modules/archived/coral/vision.py b/src/modules/archived/coral/vision.py similarity index 100% rename from modules/archived/coral/vision.py rename to src/modules/archived/coral/vision.py diff --git a/modules/archived/hotword.py b/src/modules/archived/hotword.py similarity index 100% rename from modules/archived/hotword.py rename to src/modules/archived/hotword.py diff --git a/modules/archived/main_viam.py b/src/modules/archived/main_viam.py similarity index 100% rename from modules/archived/main_viam.py rename to src/modules/archived/main_viam.py diff --git a/modules/archived/opencv/faces.py b/src/modules/archived/opencv/faces.py similarity index 100% rename from modules/archived/opencv/faces.py rename to src/modules/archived/opencv/faces.py diff --git a/modules/archived/opencv/haarcascade_frontalface_default.xml b/src/modules/archived/opencv/haarcascade_frontalface_default.xml similarity index 100% rename from modules/archived/opencv/haarcascade_frontalface_default.xml rename to src/modules/archived/opencv/haarcascade_frontalface_default.xml diff --git a/modules/archived/opencv/timelapse.py b/src/modules/archived/opencv/timelapse.py similarity index 100% rename from modules/archived/opencv/timelapse.py rename to src/modules/archived/opencv/timelapse.py diff --git a/modules/archived/opencv/tracking.py b/src/modules/archived/opencv/tracking.py similarity index 100% rename from modules/archived/opencv/tracking.py rename to src/modules/archived/opencv/tracking.py diff --git a/modules/archived/opencv/train_model.py b/src/modules/archived/opencv/train_model.py similarity index 100% rename from modules/archived/opencv/train_model.py rename to src/modules/archived/opencv/train_model.py diff --git a/modules/archived/opencv/video_stream.py b/src/modules/archived/opencv/video_stream.py similarity index 100% rename from modules/archived/opencv/video_stream.py rename to src/modules/archived/opencv/video_stream.py diff --git a/modules/archived/opencv/vision.py b/src/modules/archived/opencv/vision.py similarity index 100% rename from modules/archived/opencv/vision.py rename to src/modules/archived/opencv/vision.py diff --git a/modules/archived/power.py b/src/modules/archived/power.py similarity index 100% rename from modules/archived/power.py rename to src/modules/archived/power.py diff --git a/modules/archived/power_test.py b/src/modules/archived/power_test.py similarity index 100% rename from modules/archived/power_test.py rename to src/modules/archived/power_test.py diff --git a/modules/archived/rgb.py b/src/modules/archived/rgb.py similarity index 100% rename from modules/archived/rgb.py rename to src/modules/archived/rgb.py diff --git a/modules/archived/scripts/Sample_TFLite_model/detect.tflite b/src/modules/archived/scripts/Sample_TFLite_model/detect.tflite similarity index 100% rename from modules/archived/scripts/Sample_TFLite_model/detect.tflite rename to src/modules/archived/scripts/Sample_TFLite_model/detect.tflite diff --git a/modules/archived/scripts/Sample_TFLite_model/labelmap.txt b/src/modules/archived/scripts/Sample_TFLite_model/labelmap.txt similarity index 100% rename from modules/archived/scripts/Sample_TFLite_model/labelmap.txt rename to src/modules/archived/scripts/Sample_TFLite_model/labelmap.txt diff --git a/src/modules/archived/scripts/__init__.py b/src/modules/archived/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/archived/scripts/buzzer_player.py b/src/modules/archived/scripts/buzzer_player.py similarity index 100% rename from modules/archived/scripts/buzzer_player.py rename to src/modules/archived/scripts/buzzer_player.py diff --git a/modules/archived/scripts/camera_tst.py b/src/modules/archived/scripts/camera_tst.py similarity index 100% rename from modules/archived/scripts/camera_tst.py rename to src/modules/archived/scripts/camera_tst.py diff --git a/modules/archived/scripts/chatbot.py b/src/modules/archived/scripts/chatbot.py similarity index 100% rename from modules/archived/scripts/chatbot.py rename to src/modules/archived/scripts/chatbot.py diff --git a/modules/archived/scripts/chirp/chirp.py b/src/modules/archived/scripts/chirp/chirp.py similarity index 100% rename from modules/archived/scripts/chirp/chirp.py rename to src/modules/archived/scripts/chirp/chirp.py diff --git a/modules/archived/scripts/detect_faces.py b/src/modules/archived/scripts/detect_faces.py similarity index 100% rename from modules/archived/scripts/detect_faces.py rename to src/modules/archived/scripts/detect_faces.py diff --git a/modules/archived/scripts/detect_motion.py b/src/modules/archived/scripts/detect_motion.py similarity index 100% rename from modules/archived/scripts/detect_motion.py rename to src/modules/archived/scripts/detect_motion.py diff --git a/modules/archived/scripts/gamepad.py b/src/modules/archived/scripts/gamepad.py similarity index 100% rename from modules/archived/scripts/gamepad.py rename to src/modules/archived/scripts/gamepad.py diff --git a/modules/archived/scripts/haarcascade_frontalface_default.xml b/src/modules/archived/scripts/haarcascade_frontalface_default.xml similarity index 100% rename from modules/archived/scripts/haarcascade_frontalface_default.xml rename to src/modules/archived/scripts/haarcascade_frontalface_default.xml diff --git a/modules/archived/scripts/i2cneopixel.py b/src/modules/archived/scripts/i2cneopixel.py similarity index 100% rename from modules/archived/scripts/i2cneopixel.py rename to src/modules/archived/scripts/i2cneopixel.py diff --git a/modules/archived/scripts/listen_hotword.py b/src/modules/archived/scripts/listen_hotword.py similarity index 100% rename from modules/archived/scripts/listen_hotword.py rename to src/modules/archived/scripts/listen_hotword.py diff --git a/src/modules/archived/scripts/modules/tests/__init__.py b/src/modules/archived/scripts/modules/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/archived/scripts/modules/tests/rgb_test.py b/src/modules/archived/scripts/modules/tests/rgb_test.py similarity index 100% rename from modules/archived/scripts/modules/tests/rgb_test.py rename to src/modules/archived/scripts/modules/tests/rgb_test.py diff --git a/modules/archived/scripts/mouse_recorder.py b/src/modules/archived/scripts/mouse_recorder.py similarity index 100% rename from modules/archived/scripts/mouse_recorder.py rename to src/modules/archived/scripts/mouse_recorder.py diff --git a/modules/archived/scripts/neo.py b/src/modules/archived/scripts/neo.py similarity index 100% rename from modules/archived/scripts/neo.py rename to src/modules/archived/scripts/neo.py diff --git a/modules/archived/scripts/personality.py b/src/modules/archived/scripts/personality.py similarity index 100% rename from modules/archived/scripts/personality.py rename to src/modules/archived/scripts/personality.py diff --git a/modules/archived/scripts/pitemp.py b/src/modules/archived/scripts/pitemp.py similarity index 100% rename from modules/archived/scripts/pitemp.py rename to src/modules/archived/scripts/pitemp.py diff --git a/modules/archived/scripts/power-saving-sensor.py b/src/modules/archived/scripts/power-saving-sensor.py similarity index 100% rename from modules/archived/scripts/power-saving-sensor.py rename to src/modules/archived/scripts/power-saving-sensor.py diff --git a/modules/archived/scripts/power-saving.py b/src/modules/archived/scripts/power-saving.py similarity index 100% rename from modules/archived/scripts/power-saving.py rename to src/modules/archived/scripts/power-saving.py diff --git a/modules/archived/scripts/rgb-led.py b/src/modules/archived/scripts/rgb-led.py similarity index 100% rename from modules/archived/scripts/rgb-led.py rename to src/modules/archived/scripts/rgb-led.py diff --git a/modules/archived/scripts/serial_tst.py b/src/modules/archived/scripts/serial_tst.py similarity index 100% rename from modules/archived/scripts/serial_tst.py rename to src/modules/archived/scripts/serial_tst.py diff --git a/modules/archived/scripts/servo_cycle.py b/src/modules/archived/scripts/servo_cycle.py similarity index 100% rename from modules/archived/scripts/servo_cycle.py rename to src/modules/archived/scripts/servo_cycle.py diff --git a/modules/archived/scripts/servo_key.py b/src/modules/archived/scripts/servo_key.py similarity index 100% rename from modules/archived/scripts/servo_key.py rename to src/modules/archived/scripts/servo_key.py diff --git a/modules/archived/scripts/shutdown_timer.py b/src/modules/archived/scripts/shutdown_timer.py similarity index 100% rename from modules/archived/scripts/shutdown_timer.py rename to src/modules/archived/scripts/shutdown_timer.py diff --git a/modules/archived/scripts/speech.py b/src/modules/archived/scripts/speech.py similarity index 100% rename from modules/archived/scripts/speech.py rename to src/modules/archived/scripts/speech.py diff --git a/modules/archived/scripts/speech_bg.py b/src/modules/archived/scripts/speech_bg.py similarity index 100% rename from modules/archived/scripts/speech_bg.py rename to src/modules/archived/scripts/speech_bg.py diff --git a/modules/archived/scripts/tensorflow_install.sh b/src/modules/archived/scripts/tensorflow_install.sh similarity index 100% rename from modules/archived/scripts/tensorflow_install.sh rename to src/modules/archived/scripts/tensorflow_install.sh diff --git a/modules/archived/scripts/tensorflow_tst.py b/src/modules/archived/scripts/tensorflow_tst.py similarity index 100% rename from modules/archived/scripts/tensorflow_tst.py rename to src/modules/archived/scripts/tensorflow_tst.py diff --git a/modules/archived/scripts/time_experiment.py b/src/modules/archived/scripts/time_experiment.py similarity index 100% rename from modules/archived/scripts/time_experiment.py rename to src/modules/archived/scripts/time_experiment.py diff --git a/modules/archived/scripts/track_faces.py b/src/modules/archived/scripts/track_faces.py similarity index 100% rename from modules/archived/scripts/track_faces.py rename to src/modules/archived/scripts/track_faces.py diff --git a/modules/archived/scripts/track_motion.py b/src/modules/archived/scripts/track_motion.py similarity index 100% rename from modules/archived/scripts/track_motion.py rename to src/modules/archived/scripts/track_motion.py diff --git a/modules/archived/scripts/tst_relay.py b/src/modules/archived/scripts/tst_relay.py similarity index 100% rename from modules/archived/scripts/tst_relay.py rename to src/modules/archived/scripts/tst_relay.py diff --git a/modules/archived/snowboy/Makefile b/src/modules/archived/snowboy/Makefile similarity index 100% rename from modules/archived/snowboy/Makefile rename to src/modules/archived/snowboy/Makefile diff --git a/modules/archived/snowboy/_snowboydetect.so b/src/modules/archived/snowboy/_snowboydetect.so similarity index 100% rename from modules/archived/snowboy/_snowboydetect.so rename to src/modules/archived/snowboy/_snowboydetect.so diff --git a/modules/archived/snowboy/resources/alexa/SnowboyAlexaDemo.apk b/src/modules/archived/snowboy/resources/alexa/SnowboyAlexaDemo.apk similarity index 100% rename from modules/archived/snowboy/resources/alexa/SnowboyAlexaDemo.apk rename to src/modules/archived/snowboy/resources/alexa/SnowboyAlexaDemo.apk diff --git a/modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/alexa.umdl b/src/modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/alexa.umdl similarity index 100% rename from modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/alexa.umdl rename to src/modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/alexa.umdl diff --git a/modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/avs-kittai.patch b/src/modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/avs-kittai.patch similarity index 100% rename from modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/avs-kittai.patch rename to src/modules/archived/snowboy/resources/alexa/alexa-avs-sample-app/avs-kittai.patch diff --git a/modules/archived/snowboy/resources/alexa/alexa_02092017.umdl b/src/modules/archived/snowboy/resources/alexa/alexa_02092017.umdl similarity index 100% rename from modules/archived/snowboy/resources/alexa/alexa_02092017.umdl rename to src/modules/archived/snowboy/resources/alexa/alexa_02092017.umdl diff --git a/modules/archived/snowboy/resources/common.res b/src/modules/archived/snowboy/resources/common.res similarity index 100% rename from modules/archived/snowboy/resources/common.res rename to src/modules/archived/snowboy/resources/common.res diff --git a/modules/archived/snowboy/resources/ding.wav b/src/modules/archived/snowboy/resources/ding.wav similarity index 100% rename from modules/archived/snowboy/resources/ding.wav rename to src/modules/archived/snowboy/resources/ding.wav diff --git a/modules/archived/snowboy/resources/dong.wav b/src/modules/archived/snowboy/resources/dong.wav similarity index 100% rename from modules/archived/snowboy/resources/dong.wav rename to src/modules/archived/snowboy/resources/dong.wav diff --git a/modules/archived/snowboy/resources/models/archimedes.pmdl b/src/modules/archived/snowboy/resources/models/archimedes.pmdl similarity index 100% rename from modules/archived/snowboy/resources/models/archimedes.pmdl rename to src/modules/archived/snowboy/resources/models/archimedes.pmdl diff --git a/modules/archived/snowboy/resources/models/computer.umdl b/src/modules/archived/snowboy/resources/models/computer.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/computer.umdl rename to src/modules/archived/snowboy/resources/models/computer.umdl diff --git a/modules/archived/snowboy/resources/models/hey_extreme.umdl b/src/modules/archived/snowboy/resources/models/hey_extreme.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/hey_extreme.umdl rename to src/modules/archived/snowboy/resources/models/hey_extreme.umdl diff --git a/modules/archived/snowboy/resources/models/jarvis.umdl b/src/modules/archived/snowboy/resources/models/jarvis.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/jarvis.umdl rename to src/modules/archived/snowboy/resources/models/jarvis.umdl diff --git a/modules/archived/snowboy/resources/models/neoya.umdl b/src/modules/archived/snowboy/resources/models/neoya.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/neoya.umdl rename to src/modules/archived/snowboy/resources/models/neoya.umdl diff --git a/modules/archived/snowboy/resources/models/robot.pmdl b/src/modules/archived/snowboy/resources/models/robot.pmdl similarity index 100% rename from modules/archived/snowboy/resources/models/robot.pmdl rename to src/modules/archived/snowboy/resources/models/robot.pmdl diff --git a/modules/archived/snowboy/resources/models/smart_mirror.umdl b/src/modules/archived/snowboy/resources/models/smart_mirror.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/smart_mirror.umdl rename to src/modules/archived/snowboy/resources/models/smart_mirror.umdl diff --git a/modules/archived/snowboy/resources/models/snowboy.umdl b/src/modules/archived/snowboy/resources/models/snowboy.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/snowboy.umdl rename to src/modules/archived/snowboy/resources/models/snowboy.umdl diff --git a/modules/archived/snowboy/resources/models/subex.umdl b/src/modules/archived/snowboy/resources/models/subex.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/subex.umdl rename to src/modules/archived/snowboy/resources/models/subex.umdl diff --git a/modules/archived/snowboy/resources/models/view_glass.umdl b/src/modules/archived/snowboy/resources/models/view_glass.umdl similarity index 100% rename from modules/archived/snowboy/resources/models/view_glass.umdl rename to src/modules/archived/snowboy/resources/models/view_glass.umdl diff --git a/modules/archived/snowboy/resources/snowboy.raw b/src/modules/archived/snowboy/resources/snowboy.raw similarity index 100% rename from modules/archived/snowboy/resources/snowboy.raw rename to src/modules/archived/snowboy/resources/snowboy.raw diff --git a/modules/archived/snowboy/resources/snowboy.wav b/src/modules/archived/snowboy/resources/snowboy.wav similarity index 100% rename from modules/archived/snowboy/resources/snowboy.wav rename to src/modules/archived/snowboy/resources/snowboy.wav diff --git a/modules/archived/snowboy/snowboy-detect-swig.cc b/src/modules/archived/snowboy/snowboy-detect-swig.cc similarity index 100% rename from modules/archived/snowboy/snowboy-detect-swig.cc rename to src/modules/archived/snowboy/snowboy-detect-swig.cc diff --git a/modules/archived/snowboy/snowboy-detect-swig.i b/src/modules/archived/snowboy/snowboy-detect-swig.i similarity index 100% rename from modules/archived/snowboy/snowboy-detect-swig.i rename to src/modules/archived/snowboy/snowboy-detect-swig.i diff --git a/modules/archived/snowboy/snowboy-detect-swig.o b/src/modules/archived/snowboy/snowboy-detect-swig.o similarity index 100% rename from modules/archived/snowboy/snowboy-detect-swig.o rename to src/modules/archived/snowboy/snowboy-detect-swig.o diff --git a/modules/archived/snowboy/snowboydecoder.py b/src/modules/archived/snowboy/snowboydecoder.py similarity index 100% rename from modules/archived/snowboy/snowboydecoder.py rename to src/modules/archived/snowboy/snowboydecoder.py diff --git a/modules/archived/snowboy/snowboydetect.py b/src/modules/archived/snowboy/snowboydetect.py similarity index 100% rename from modules/archived/snowboy/snowboydetect.py rename to src/modules/archived/snowboy/snowboydetect.py diff --git a/src/modules/audio/__init__.py b/src/modules/audio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/braillespeak/README.md b/src/modules/audio/braillespeak/README.md new file mode 100644 index 00000000..2bd2f772 --- /dev/null +++ b/src/modules/audio/braillespeak/README.md @@ -0,0 +1,38 @@ +# BrailleSpeak + +## Overview + +Braille-based speech output module. + +Communicate with tones, letters converted to tone pairs +Uses Buzzer module to play tones via pubsub + +:param kwargs: pin, duration + +Subscribes to 'speak' event + +Example: +self.publish('speak', msg="Hi") + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +braillespeak: + enabled: false # Set to true to enable this module + path: modules.audio.braillespeak.braillespeak.BrailleSpeak + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/audio/braillespeak/__init__.py b/src/modules/audio/braillespeak/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/audio/braillespeak.py b/src/modules/audio/braillespeak/braillespeak.py similarity index 100% rename from modules/audio/braillespeak.py rename to src/modules/audio/braillespeak/braillespeak.py diff --git a/src/modules/audio/braillespeak/config.yml b/src/modules/audio/braillespeak/config.yml new file mode 100644 index 00000000..3fb20c16 --- /dev/null +++ b/src/modules/audio/braillespeak/config.yml @@ -0,0 +1,4 @@ +braillespeak: + config: + pin: 27 + class: BrailleSpeak diff --git a/src/modules/audio/braillespeak/tests/__init__.py b/src/modules/audio/braillespeak/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/braillespeak/tests/test_braillespeak.py b/src/modules/audio/braillespeak/tests/test_braillespeak.py new file mode 100644 index 00000000..9cba4e42 --- /dev/null +++ b/src/modules/audio/braillespeak/tests/test_braillespeak.py @@ -0,0 +1,20 @@ +import unittest +from unittest.mock import MagicMock, patch +from modules.audio.braillespeak.braillespeak import BrailleSpeak + +class TestBrailleSpeak(unittest.TestCase): + + def test_init(self): + bs = BrailleSpeak(pin=27, duration=0.05) + self.assertEqual(bs.pin, 27) + self.assertEqual(bs.duration, 0.05) + self.assertEqual(len(bs.notes), 8) + + def test_setup_messaging(self): + bs = BrailleSpeak(pin=27) + with patch.object(bs, 'subscribe') as mock_sub: + bs.setup_messaging() + mock_sub.assert_called_with('speak', bs.send) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/audio/buzzer/README.md b/src/modules/audio/buzzer/README.md new file mode 100644 index 00000000..fe61614c --- /dev/null +++ b/src/modules/audio/buzzer/README.md @@ -0,0 +1,28 @@ +# Buzzer + +## Overview + +GPIO buzzer module for playing tones and melodies. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +buzzer: + enabled: false # Set to true to enable this module + path: modules.audio.buzzer.buzzer.Buzzer + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/audio/buzzer/__init__.py b/src/modules/audio/buzzer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/audio/buzzer.py b/src/modules/audio/buzzer/buzzer.py similarity index 100% rename from modules/audio/buzzer.py rename to src/modules/audio/buzzer/buzzer.py diff --git a/src/modules/audio/buzzer/config.yml b/src/modules/audio/buzzer/config.yml new file mode 100644 index 00000000..b58c4ece --- /dev/null +++ b/src/modules/audio/buzzer/config.yml @@ -0,0 +1,8 @@ +buzzer: + config: + pin: 27 + name: buzzer + dependencies: + python: + - gpiozero + class: Buzzer diff --git a/src/modules/audio/buzzer/tests/__init__.py b/src/modules/audio/buzzer/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/buzzer/tests/test_buzzer.py b/src/modules/audio/buzzer/tests/test_buzzer.py new file mode 100644 index 00000000..081ec30a --- /dev/null +++ b/src/modules/audio/buzzer/tests/test_buzzer.py @@ -0,0 +1,26 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_gpiozero = MagicMock() +sys.modules['gpiozero'] = mock_gpiozero +sys.modules['gpiozero.tones'] = mock_gpiozero.tones + +from modules.audio.buzzer.buzzer import Buzzer + +class TestBuzzer(unittest.TestCase): + + def test_init(self): + buzzer = Buzzer(pin=27) + self.assertEqual(buzzer.pin, 27) + mock_gpiozero.TonalBuzzer.assert_called_with(27) + + def test_setup_messaging(self): + buzzer = Buzzer(pin=27) + with patch.object(buzzer, 'subscribe') as mock_sub: + buzzer.setup_messaging() + mock_sub.assert_any_call('play', buzzer.play_song) + mock_sub.assert_any_call('buzz', buzzer.buzz) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/audio/melodies/deck_the_halls.py b/src/modules/audio/melodies/deck_the_halls.py similarity index 100% rename from modules/audio/melodies/deck_the_halls.py rename to src/modules/audio/melodies/deck_the_halls.py diff --git a/modules/audio/melodies/happy_birthday.py b/src/modules/audio/melodies/happy_birthday.py similarity index 100% rename from modules/audio/melodies/happy_birthday.py rename to src/modules/audio/melodies/happy_birthday.py diff --git a/modules/audio/melodies/notes.py b/src/modules/audio/melodies/notes.py similarity index 100% rename from modules/audio/melodies/notes.py rename to src/modules/audio/melodies/notes.py diff --git a/src/modules/audio/speechinput/README.md b/src/modules/audio/speechinput/README.md new file mode 100644 index 00000000..4029c4a8 --- /dev/null +++ b/src/modules/audio/speechinput/README.md @@ -0,0 +1,30 @@ +# SpeechInput + +## Overview + +Speech recognition input module. + +Use speech_recognition to detect and interpret audio + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +speechinput: + enabled: false # Set to true to enable this module + path: modules.audio.speechinput.speechinput.SpeechInput + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/audio/speechinput/__init__.py b/src/modules/audio/speechinput/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/speechinput/config.yml b/src/modules/audio/speechinput/config.yml new file mode 100644 index 00000000..b5900864 --- /dev/null +++ b/src/modules/audio/speechinput/config.yml @@ -0,0 +1,15 @@ +speechinput: + config: + device_name: pulse + start_on_boot: false + dependencies: + unix: + - flac + - python3-pyaudio + - ladspa-sdk + python: + - SpeechRecognition + additional: + - installers/install-mics.sh + - installers/install-speech-recognition.sh + class: SpeechInput diff --git a/modules/audio/speechinput.py b/src/modules/audio/speechinput/speechinput.py similarity index 100% rename from modules/audio/speechinput.py rename to src/modules/audio/speechinput/speechinput.py diff --git a/src/modules/audio/speechinput/tests/__init__.py b/src/modules/audio/speechinput/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/speechinput/tests/test_speechinput.py b/src/modules/audio/speechinput/tests/test_speechinput.py new file mode 100644 index 00000000..5e6f418c --- /dev/null +++ b/src/modules/audio/speechinput/tests/test_speechinput.py @@ -0,0 +1,30 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_speech_recognition = MagicMock() +mock_recognizer = MagicMock() +mock_microphone = MagicMock() +mock_speech_recognition.Recognizer.return_value = mock_recognizer +mock_speech_recognition.Microphone.return_value = mock_microphone +sys.modules['speech_recognition'] = mock_speech_recognition + +from modules.audio.speechinput.speechinput import SpeechInput + +class TestSpeechInput(unittest.TestCase): + + def test_init(self): + si = SpeechInput(device_name='test', sample_rate=16000) + self.assertEqual(si.device_name, 'test') + self.assertEqual(si.sample_rate, 16000) + self.assertFalse(si.listening) + + def test_setup_messaging(self): + si = SpeechInput() + si.log = MagicMock() + with patch.object(si, 'subscribe') as mock_sub: + si.setup_messaging() + mock_sub.assert_any_call('speech:listen', si.start) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/audio/ttsmodule/README.md b/src/modules/audio/ttsmodule/README.md new file mode 100644 index 00000000..33768c7a --- /dev/null +++ b/src/modules/audio/ttsmodule/README.md @@ -0,0 +1,28 @@ +# TTSModule + +## Overview + +Text-to-speech output module. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +ttsmodule: + enabled: false # Set to true to enable this module + path: modules.audio.ttsmodule.ttsmodule.TTSModule + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/audio/ttsmodule/__init__.py b/src/modules/audio/ttsmodule/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/ttsmodule/config.yml b/src/modules/audio/ttsmodule/config.yml new file mode 100644 index 00000000..36be8926 --- /dev/null +++ b/src/modules/audio/ttsmodule/config.yml @@ -0,0 +1,15 @@ +ttsmodule: + config: + service: elevenlabs + voice_id: pMsXgVXv3BLzUgSXRplE + dependencies: + python: + - pyttsx3 + - elevenlabs + unix: + - espeak + - ffmpeg + - libespeak1 + additional: + - installers/install-amp.sh + class: TTSModule diff --git a/src/modules/audio/ttsmodule/tests/__init__.py b/src/modules/audio/ttsmodule/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/audio/ttsmodule/tests/test_ttsmodule.py b/src/modules/audio/ttsmodule/tests/test_ttsmodule.py new file mode 100644 index 00000000..b1e9f47e --- /dev/null +++ b/src/modules/audio/ttsmodule/tests/test_ttsmodule.py @@ -0,0 +1,25 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_pyttsx3 = MagicMock() +mock_elevenlabs = MagicMock() +sys.modules['pyttsx3'] = mock_pyttsx3 +sys.modules['elevenlabs'] = mock_elevenlabs + +from modules.audio.ttsmodule.ttsmodule import TTSModule + +class TestTTSModule(unittest.TestCase): + + def test_init_pyttsx3(self): + tts = TTSModule(service='pyttsx3') + self.assertEqual(tts.service, 'pyttsx3') + + def test_setup_messaging(self): + tts = TTSModule(service='pyttsx3') + with patch.object(tts, 'subscribe') as mock_sub: + tts.setup_messaging() + mock_sub.assert_called_with('tts', tts.speak) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/audio/ttsmodule.py b/src/modules/audio/ttsmodule/ttsmodule.py similarity index 100% rename from modules/audio/ttsmodule.py rename to src/modules/audio/ttsmodule/ttsmodule.py diff --git a/modules/base_module.py b/src/modules/base_module.py similarity index 73% rename from modules/base_module.py rename to src/modules/base_module.py index 5d6b2a9e..2a70004f 100644 --- a/modules/base_module.py +++ b/src/modules/base_module.py @@ -20,19 +20,31 @@ def publish(self, topic, *args, **kwargs): if self.messaging_service is None: raise ValueError("Messaging service not set.") self.messaging_service.publish(topic, *args, **kwargs) - + + def publish_mqtt(self, topic, *args, **kwargs): + """Publish only to the MQTT backend (no-op if MQTT is not configured).""" + if self.messaging_service is None: + raise ValueError("Messaging service not set.") + self.messaging_service.publish_mqtt(topic, *args, **kwargs) + def subscribe(self, topic, callback, **kwargs): if self.messaging_service is None: raise ValueError("Messaging service not set.") self.messaging_service.subscribe(topic, callback, **kwargs) + + def loop(self): + """Called every system loop cycle. Override in subclasses that need per-cycle polling.""" + pass - def log(self, message, level='info'): + def log(self, message, level='info', type=None): """ Advanced logging, includes class name, method name, and line number to message string """ # get class name, method name class_name = self.__class__.__name__ method_name = inspect.stack()[1].function + if type: + level = type #get line number of calling class frame = inspect.stack()[1] diff --git a/docs/ChatGPT.md b/src/modules/chatgpt/README.md similarity index 100% rename from docs/ChatGPT.md rename to src/modules/chatgpt/README.md diff --git a/src/modules/chatgpt/__init__.py b/src/modules/chatgpt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/chatgpt.py b/src/modules/chatgpt/chatgpt.py similarity index 100% rename from modules/chatgpt.py rename to src/modules/chatgpt/chatgpt.py diff --git a/src/modules/chatgpt/config.yml b/src/modules/chatgpt/config.yml new file mode 100644 index 00000000..756ef472 --- /dev/null +++ b/src/modules/chatgpt/config.yml @@ -0,0 +1,13 @@ +chatgpt: + config: + model: gpt-4o-mini + persona: 'You are a helpful assistant robot. You respond with short phrases where + possible. Alternatively, you can respond with the following commands instead + of text if you feel they are appropriate: - animate/head_nod - animate/head_shake + - gpio/laser/on - gpio/laser/off' + dependencies: + python: + - openai + additional: + - https://platform.openai.com/api-keys + class: ChatGPT diff --git a/src/modules/chatgpt/tests/__init__.py b/src/modules/chatgpt/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/chatgpt/tests/test_chatgpt.py b/src/modules/chatgpt/tests/test_chatgpt.py new file mode 100644 index 00000000..b94e1b11 --- /dev/null +++ b/src/modules/chatgpt/tests/test_chatgpt.py @@ -0,0 +1,32 @@ +import sys +import os +import unittest +from unittest.mock import MagicMock, patch + +mock_openai = MagicMock() +sys.modules['openai'] = mock_openai + +from modules.chatgpt.chatgpt import ChatGPT + +class TestChatGPT(unittest.TestCase): + + def test_init_no_api_key(self): + env = {k: v for k, v in os.environ.items() if k != 'OPENAI_API_KEY'} + with patch.dict('os.environ', env, clear=True): + with self.assertRaises(RuntimeError): + ChatGPT() + + def test_init_with_api_key(self): + with patch.dict('os.environ', {'OPENAI_API_KEY': 'test_key'}): + bot = ChatGPT() + self.assertEqual(bot.model, 'gpt-4o-mini') + + def test_setup_messaging(self): + with patch.dict('os.environ', {'OPENAI_API_KEY': 'test_key'}): + bot = ChatGPT() + with patch.object(bot, 'subscribe') as mock_sub: + bot.setup_messaging() + mock_sub.assert_any_call('speech', bot.completion) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/config.py b/src/modules/config.py similarity index 100% rename from modules/config.py rename to src/modules/config.py diff --git a/src/modules/controller_handler/README.md b/src/modules/controller_handler/README.md new file mode 100644 index 00000000..62a51c04 --- /dev/null +++ b/src/modules/controller_handler/README.md @@ -0,0 +1,32 @@ +# ControllerHandler + +## Overview + +Maps controller inputs to robot actions based on configurable mappings. + +Consume controller input and map to messaging events. + +Requires injection of a controller module that provides input data. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +controller: + enabled: false # Set to true to enable this module + path: modules.controller_handler.controller_handler.ControllerHandler + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/controller_handler/__init__.py b/src/modules/controller_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/controller_handler/config.yml b/src/modules/controller_handler/config.yml new file mode 100644 index 00000000..5cb8e55a --- /dev/null +++ b/src/modules/controller_handler/config.yml @@ -0,0 +1,182 @@ +controller_handler: + config: + debug: false + modifier_buttons: + - BTN_TL + - BTN_TR + mapping: + default: + BTN_TL: + actions: + - topic: '' + args: {} + BTN_TR: + actions: + - topic: '' + args: {} + BTN_SELECT: + actions: + - topic: record/stop + args: {} + BTN_START: + actions: + - topic: record/start + args: {} + KEY_RECORD: + actions: + - topic: '' + args: {} + BTN_MODE: + actions: + - topic: '' + args: {} + ABS_HAT0X: + actions: + - topic: '' + args: {} + ABS_HAT0Y: + actions: + - topic: '' + args: {} + BTN_A: + actions: + - topic: tts + args: + msg: Test Default + BTN_X: + actions: + - topic: eye/blink + args: {} + BTN_Y: + actions: + - topic: gpio/laser/toggle + args: {} + BTN_B: + actions: + - topic: vision/toggle + args: {} + ABS_RX: + actions: + - topic: servo:neck_pan:mv + args: {} + modifier: + scale: 50 + ABS_RZ: + actions: + - topic: servo:neck_tilt:mv + args: {} + modifier: + scale: -50 + - topic: servo:neck_tilt2:mv + args: {} + modifier: + scale: 50 + ABS_RY: + start: -1.0 + actions: + - topic: servo:leg_r_ankle:mv + args: {} + modifier: + scale: -50 + ABS_Z: + start: -1.0 + actions: + - topic: servo:leg_l_ankle:mv + args: {} + modifier: + scale: -50 + ABS_X: + actions: + - topic: eye/move + args: + axis: y + modifier: + mapping: + - 0 + - 240 + ABS_Y: + actions: + - topic: eye/move + args: + axis: x + modifier: + mapping: + - 240 + - 0 + BTN_TL: + BTN_X: + actions: + - topic: servo:neck_pan:mv + args: + delta: -50 + BTN_Y: + actions: + - topic: servo:neck_tilt:mv + args: + delta: -50 + - topic: servo:neck_tilt2:mv + args: + delta: 50 + BTN_B: + actions: + - topic: servo:neck_pan:mv + args: + delta: 50 + BTN_A: + actions: + - topic: servo:neck_tilt:mv + args: + delta: 50 + - topic: servo:neck_tilt2:mv + args: + delta: -50 + BTN_TR: + BTN_X: + actions: + - topic: servo:neck_tilt:mv + args: + delta: 50 + BTN_Y: + actions: + - topic: servo:neck_tilt:mv + args: + delta: -50 + BTN_B: + actions: + - topic: servo:neck_tilt2:mv + args: + delta: 50 + BTN_A: + actions: + - topic: servo:neck_tilt2:mv + args: + delta: -50 + ABS_HAT0Y: + actions: + - topic: servo:neck_tilt:mvabs + args: + position: 511 + - topic: servo:neck_tilt2:mvabs + args: + position: 511 + ABS_X: + actions: + - topic: servo:leg_l_ankle:mv + modifier: + scale: 50 + - topic: servo:leg_r_ankle:mv + modifier: + scale: -50 + - topic: servo:leg_l_knee:mv + modifier: + scale: 50 + - topic: servo:leg_r_knee:mv + modifier: + scale: -50 + - topic: servo:leg_l_hip:mv + modifier: + scale: 50 + - topic: servo:leg_r_hip:mv + modifier: + scale: -50 + class: ControllerHandler diff --git a/modules/controller_handler.py b/src/modules/controller_handler/controller_handler.py similarity index 95% rename from modules/controller_handler.py rename to src/modules/controller_handler/controller_handler.py index e3ffeae4..ccff2303 100644 --- a/modules/controller_handler.py +++ b/src/modules/controller_handler/controller_handler.py @@ -19,8 +19,11 @@ def __init__(self, **kwargs): def start(self): self.running = True self.log("Controller Handler started", level='info') - # Subscribe to loop event - self.messaging_service.subscribe('system/loop', self._process_input) + + def loop(self): + """Called every system loop cycle to process controller input.""" + if self.running: + self._process_input() def _process_input(self): """Process input from the controller and publish corresponding events.""" diff --git a/src/modules/controller_handler/tests/__init__.py b/src/modules/controller_handler/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/controller_handler/tests/test_controller_handler.py b/src/modules/controller_handler/tests/test_controller_handler.py new file mode 100644 index 00000000..e283fe79 --- /dev/null +++ b/src/modules/controller_handler/tests/test_controller_handler.py @@ -0,0 +1,44 @@ +import unittest +from unittest.mock import MagicMock, patch +from modules.controller_handler.controller_handler import ControllerHandler + +class TestControllerHandler(unittest.TestCase): + + def setUp(self): + self.handler = ControllerHandler( + mapping={'default': {}}, + modifier_buttons=[], + debug=False + ) + self.handler.messaging_service = MagicMock() + + def test_init(self): + self.assertIsNone(self.handler.controller) + self.assertFalse(self.handler.running) + + def test_start(self): + self.handler.log = MagicMock() + self.handler.start() + self.assertTrue(self.handler.running) + + def test_loop_calls_process_input_when_running(self): + self.handler.log = MagicMock() + self.handler.running = True + with patch.object(self.handler, '_process_input') as mock_pi: + self.handler.loop() + mock_pi.assert_called_once() + + def test_loop_does_not_call_process_input_when_not_running(self): + self.handler.log = MagicMock() + self.handler.running = False + with patch.object(self.handler, '_process_input') as mock_pi: + self.handler.loop() + mock_pi.assert_not_called() + + def test_process_input_no_controller(self): + self.handler.log = MagicMock() + self.handler._process_input() + self.handler.log.assert_called() + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/display/__init__.py b/src/modules/display/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/display/images/chatgpt_eye1.png b/src/modules/display/images/chatgpt_eye1.png similarity index 100% rename from modules/display/images/chatgpt_eye1.png rename to src/modules/display/images/chatgpt_eye1.png diff --git a/modules/display/images/chatgpt_eye2.png b/src/modules/display/images/chatgpt_eye2.png similarity index 100% rename from modules/display/images/chatgpt_eye2.png rename to src/modules/display/images/chatgpt_eye2.png diff --git a/modules/display/images/chatgpt_eye3.png b/src/modules/display/images/chatgpt_eye3.png similarity index 100% rename from modules/display/images/chatgpt_eye3.png rename to src/modules/display/images/chatgpt_eye3.png diff --git a/modules/display/images/chatgpt_eye4.png b/src/modules/display/images/chatgpt_eye4.png similarity index 100% rename from modules/display/images/chatgpt_eye4.png rename to src/modules/display/images/chatgpt_eye4.png diff --git a/modules/display/images/makerforge.png b/src/modules/display/images/makerforge.png similarity index 100% rename from modules/display/images/makerforge.png rename to src/modules/display/images/makerforge.png diff --git a/modules/display/images/makerforge_bl.png b/src/modules/display/images/makerforge_bl.png similarity index 100% rename from modules/display/images/makerforge_bl.png rename to src/modules/display/images/makerforge_bl.png diff --git a/modules/display/lib/LCD_0inch96.py b/src/modules/display/lib/LCD_0inch96.py similarity index 100% rename from modules/display/lib/LCD_0inch96.py rename to src/modules/display/lib/LCD_0inch96.py diff --git a/modules/display/lib/LCD_1inch14.py b/src/modules/display/lib/LCD_1inch14.py similarity index 100% rename from modules/display/lib/LCD_1inch14.py rename to src/modules/display/lib/LCD_1inch14.py diff --git a/modules/display/lib/LCD_1inch28.py b/src/modules/display/lib/LCD_1inch28.py similarity index 100% rename from modules/display/lib/LCD_1inch28.py rename to src/modules/display/lib/LCD_1inch28.py diff --git a/modules/display/lib/LCD_1inch3.py b/src/modules/display/lib/LCD_1inch3.py similarity index 100% rename from modules/display/lib/LCD_1inch3.py rename to src/modules/display/lib/LCD_1inch3.py diff --git a/modules/display/lib/LCD_1inch47.py b/src/modules/display/lib/LCD_1inch47.py similarity index 100% rename from modules/display/lib/LCD_1inch47.py rename to src/modules/display/lib/LCD_1inch47.py diff --git a/modules/display/lib/LCD_1inch54.py b/src/modules/display/lib/LCD_1inch54.py similarity index 100% rename from modules/display/lib/LCD_1inch54.py rename to src/modules/display/lib/LCD_1inch54.py diff --git a/modules/display/lib/LCD_1inch69.py b/src/modules/display/lib/LCD_1inch69.py similarity index 100% rename from modules/display/lib/LCD_1inch69.py rename to src/modules/display/lib/LCD_1inch69.py diff --git a/modules/display/lib/LCD_1inch8.py b/src/modules/display/lib/LCD_1inch8.py similarity index 100% rename from modules/display/lib/LCD_1inch8.py rename to src/modules/display/lib/LCD_1inch8.py diff --git a/modules/display/lib/LCD_1inch9.py b/src/modules/display/lib/LCD_1inch9.py similarity index 100% rename from modules/display/lib/LCD_1inch9.py rename to src/modules/display/lib/LCD_1inch9.py diff --git a/modules/display/lib/LCD_2inch.py b/src/modules/display/lib/LCD_2inch.py similarity index 100% rename from modules/display/lib/LCD_2inch.py rename to src/modules/display/lib/LCD_2inch.py diff --git a/modules/display/lib/LCD_2inch4.py b/src/modules/display/lib/LCD_2inch4.py similarity index 100% rename from modules/display/lib/LCD_2inch4.py rename to src/modules/display/lib/LCD_2inch4.py diff --git a/src/modules/display/lib/__init__.py b/src/modules/display/lib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/display/lib/lcdconfig.py b/src/modules/display/lib/lcdconfig.py similarity index 100% rename from modules/display/lib/lcdconfig.py rename to src/modules/display/lib/lcdconfig.py diff --git a/modules/display/lib/waveshare/OLED_0in49.py b/src/modules/display/lib/waveshare/OLED_0in49.py similarity index 100% rename from modules/display/lib/waveshare/OLED_0in49.py rename to src/modules/display/lib/waveshare/OLED_0in49.py diff --git a/modules/display/lib/waveshare/OLED_0in91.py b/src/modules/display/lib/waveshare/OLED_0in91.py similarity index 100% rename from modules/display/lib/waveshare/OLED_0in91.py rename to src/modules/display/lib/waveshare/OLED_0in91.py diff --git a/modules/display/lib/waveshare/OLED_0in95_rgb.py b/src/modules/display/lib/waveshare/OLED_0in95_rgb.py similarity index 100% rename from modules/display/lib/waveshare/OLED_0in95_rgb.py rename to src/modules/display/lib/waveshare/OLED_0in95_rgb.py diff --git a/modules/display/lib/waveshare/OLED_0in96.py b/src/modules/display/lib/waveshare/OLED_0in96.py similarity index 100% rename from modules/display/lib/waveshare/OLED_0in96.py rename to src/modules/display/lib/waveshare/OLED_0in96.py diff --git a/modules/display/lib/waveshare/OLED_0in96_rgb.py b/src/modules/display/lib/waveshare/OLED_0in96_rgb.py similarity index 100% rename from modules/display/lib/waveshare/OLED_0in96_rgb.py rename to src/modules/display/lib/waveshare/OLED_0in96_rgb.py diff --git a/modules/display/lib/waveshare/OLED_1in27_rgb.py b/src/modules/display/lib/waveshare/OLED_1in27_rgb.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in27_rgb.py rename to src/modules/display/lib/waveshare/OLED_1in27_rgb.py diff --git a/modules/display/lib/waveshare/OLED_1in3.py b/src/modules/display/lib/waveshare/OLED_1in3.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in3.py rename to src/modules/display/lib/waveshare/OLED_1in3.py diff --git a/modules/display/lib/waveshare/OLED_1in32.py b/src/modules/display/lib/waveshare/OLED_1in32.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in32.py rename to src/modules/display/lib/waveshare/OLED_1in32.py diff --git a/modules/display/lib/waveshare/OLED_1in3_c.py b/src/modules/display/lib/waveshare/OLED_1in3_c.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in3_c.py rename to src/modules/display/lib/waveshare/OLED_1in3_c.py diff --git a/modules/display/lib/waveshare/OLED_1in5.py b/src/modules/display/lib/waveshare/OLED_1in5.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in5.py rename to src/modules/display/lib/waveshare/OLED_1in5.py diff --git a/modules/display/lib/waveshare/OLED_1in51.py b/src/modules/display/lib/waveshare/OLED_1in51.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in51.py rename to src/modules/display/lib/waveshare/OLED_1in51.py diff --git a/modules/display/lib/waveshare/OLED_1in54.py b/src/modules/display/lib/waveshare/OLED_1in54.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in54.py rename to src/modules/display/lib/waveshare/OLED_1in54.py diff --git a/modules/display/lib/waveshare/OLED_1in5_b.py b/src/modules/display/lib/waveshare/OLED_1in5_b.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in5_b.py rename to src/modules/display/lib/waveshare/OLED_1in5_b.py diff --git a/modules/display/lib/waveshare/OLED_1in5_rgb.py b/src/modules/display/lib/waveshare/OLED_1in5_rgb.py similarity index 100% rename from modules/display/lib/waveshare/OLED_1in5_rgb.py rename to src/modules/display/lib/waveshare/OLED_1in5_rgb.py diff --git a/modules/display/lib/waveshare/OLED_2in42.py b/src/modules/display/lib/waveshare/OLED_2in42.py similarity index 100% rename from modules/display/lib/waveshare/OLED_2in42.py rename to src/modules/display/lib/waveshare/OLED_2in42.py diff --git a/src/modules/display/lib/waveshare/__init__.py b/src/modules/display/lib/waveshare/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/display/lib/waveshare/config.py b/src/modules/display/lib/waveshare/config.py similarity index 100% rename from modules/display/lib/waveshare/config.py rename to src/modules/display/lib/waveshare/config.py diff --git a/modules/display/tft_display.py b/src/modules/display/tft_display.py similarity index 96% rename from modules/display/tft_display.py rename to src/modules/display/tft_display.py index 14bcd67b..1ccbde76 100644 --- a/modules/display/tft_display.py +++ b/src/modules/display/tft_display.py @@ -54,7 +54,7 @@ def clear_display(self): def draw_image(self, img): disp = self.disp - image = Image.open(os.getcwd() + '/modules/display/images/' + img) + image = Image.open(os.getcwd() + '/src/modules/display/images/' + img) new_image = image.resize((disp.width, disp.height)).rotate(self.rotation) self.show_image(new_image) diff --git a/docs/TFTDisplay.md b/src/modules/display/tft_display_eye/README.md similarity index 100% rename from docs/TFTDisplay.md rename to src/modules/display/tft_display_eye/README.md diff --git a/src/modules/display/tft_display_eye/__init__.py b/src/modules/display/tft_display_eye/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/display/tft_display_eye/config.yml b/src/modules/display/tft_display_eye/config.yml new file mode 100644 index 00000000..34aec457 --- /dev/null +++ b/src/modules/display/tft_display_eye/config.yml @@ -0,0 +1,19 @@ +tft_display: + class: TFTDisplayEye + config: + colors: + red: (255, 32, 32) + blue: (0, 245, 255) + green: (0, 255, 180) + purple: (255, 0, 255) + yellow: (255, 255, 0) + white: (255, 255, 255) + gray: (128, 128, 128) + dark_gray: (64, 64, 64) + dependencies: + unix: + - python3-pip + - python3-pil + - python3-numpy + python: + - spidev diff --git a/src/modules/display/tft_display_eye/tests/__init__.py b/src/modules/display/tft_display_eye/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/display/tft_display_eye/tests/test_tft_display_eye.py b/src/modules/display/tft_display_eye/tests/test_tft_display_eye.py new file mode 100644 index 00000000..e2559f4d --- /dev/null +++ b/src/modules/display/tft_display_eye/tests/test_tft_display_eye.py @@ -0,0 +1,33 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_spi = MagicMock() +mock_pil = MagicMock() +mock_lcd = MagicMock() +mock_lcd_module = MagicMock() +sys.modules['spidev'] = mock_spi +sys.modules['PIL'] = mock_pil +sys.modules['PIL.Image'] = mock_pil.Image +sys.modules['PIL.ImageDraw'] = mock_pil.ImageDraw +sys.modules['modules.display.lib'] = mock_lcd +sys.modules['modules.display.lib.LCD_1inch28'] = mock_lcd_module + +# Mock the TFTDisplay parent +mock_tft_display_mod = MagicMock() +sys.modules['modules.display.tft_display'] = mock_tft_display_mod + +# Make TFTDisplay a usable base class +class MockTFTDisplay(MagicMock): + pass +mock_tft_display_mod.TFTDisplay = MockTFTDisplay + +from modules.display.tft_display_eye.tft_display_eye import TFTDisplayEye + +class TestTFTDisplayEye(unittest.TestCase): + + def test_class_exists(self): + self.assertTrue(hasattr(TFTDisplayEye, '__init__')) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/display/tft_display_eye.py b/src/modules/display/tft_display_eye/tft_display_eye.py similarity index 100% rename from modules/display/tft_display_eye.py rename to src/modules/display/tft_display_eye/tft_display_eye.py diff --git a/src/modules/display/waveshare_oled/README.md b/src/modules/display/waveshare_oled/README.md new file mode 100644 index 00000000..7e292912 --- /dev/null +++ b/src/modules/display/waveshare_oled/README.md @@ -0,0 +1,28 @@ +# WaveshareOLED + +## Overview + +Waveshare OLED display driver module. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +oled_display: + enabled: false # Set to true to enable this module + path: modules.display.waveshare_oled.waveshare_oled.WaveshareOLED + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/display/waveshare_oled/__init__.py b/src/modules/display/waveshare_oled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/display/waveshare_oled/config.yml b/src/modules/display/waveshare_oled/config.yml new file mode 100644 index 00000000..eaf9f08e --- /dev/null +++ b/src/modules/display/waveshare_oled/config.yml @@ -0,0 +1,9 @@ +oled_display: + class: WaveshareOLED + dependencies: + unix: + - python3-pip + - python3-pil + - python3-numpy + python: + - RPi.GPIO diff --git a/modules/display/pic/Font.ttc b/src/modules/display/waveshare_oled/pic/Font.ttc similarity index 100% rename from modules/display/pic/Font.ttc rename to src/modules/display/waveshare_oled/pic/Font.ttc diff --git a/src/modules/display/waveshare_oled/tests/__init__.py b/src/modules/display/waveshare_oled/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/display/waveshare_oled/tests/test_waveshare_oled.py b/src/modules/display/waveshare_oled/tests/test_waveshare_oled.py new file mode 100644 index 00000000..d3779188 --- /dev/null +++ b/src/modules/display/waveshare_oled/tests/test_waveshare_oled.py @@ -0,0 +1,32 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_pil = MagicMock() +sys.modules['PIL'] = mock_pil +sys.modules['PIL.Image'] = mock_pil.Image +sys.modules['PIL.ImageDraw'] = mock_pil.ImageDraw +sys.modules['PIL.ImageFont'] = mock_pil.ImageFont + +mock_waveshare = MagicMock() +sys.modules['modules.display.lib.waveshare'] = mock_waveshare +sys.modules['modules.display.lib.waveshare.OLED_0in91'] = mock_waveshare.OLED_0in91 +mock_waveshare.OLED_0in91.OLED_0in91.return_value.width = 128 +mock_waveshare.OLED_0in91.OLED_0in91.return_value.height = 32 + +from modules.display.waveshare_oled.waveshare_oled import WaveshareOLED + +class TestWaveshareOLED(unittest.TestCase): + + def test_init(self): + oled = WaveshareOLED(test_on_boot=False) + # init should not raise + + def test_setup_messaging(self): + oled = WaveshareOLED() + with patch.object(oled, 'subscribe') as mock_sub: + oled.setup_messaging() + mock_sub.assert_any_call('system/exit', oled.exit) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/display/waveshare_oled.py b/src/modules/display/waveshare_oled/waveshare_oled.py similarity index 96% rename from modules/display/waveshare_oled.py rename to src/modules/display/waveshare_oled/waveshare_oled.py index 06b1fc52..2aa7b0a6 100644 --- a/modules/display/waveshare_oled.py +++ b/src/modules/display/waveshare_oled/waveshare_oled.py @@ -49,7 +49,7 @@ def show_text(self, text, font_size=12): try: font = ImageFont.truetype(font_path, font_size) except Exception as e: - self.log(f"Font load error: {e}", type='error') + self.log(f"Font load error: {e}", level='error') font = None if font: w, h = draw.textsize(text, font=font) @@ -73,7 +73,7 @@ def draw_demo(self): font1 = ImageFont.truetype(font_path, 12) font2 = ImageFont.truetype(font_path, 18) except Exception as e: - self.log(f"Font load error: {e}", type='error') + self.log(f"Font load error: {e}", level='error') font1 = font2 = None draw.line([(0,0),(127,0)], fill=0) draw.line([(0,0),(0,31)], fill=0) diff --git a/src/modules/gpio/__init__.py b/src/modules/gpio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/gpio/laser/README.md b/src/modules/gpio/laser/README.md new file mode 100644 index 00000000..5fe8d6c6 --- /dev/null +++ b/src/modules/gpio/laser/README.md @@ -0,0 +1,28 @@ +# Laser + +## Overview + +GPIO laser control module. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +gpio_laser: + enabled: false # Set to true to enable this module + path: modules.gpio.laser.laser.Laser + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/gpio/laser/__init__.py b/src/modules/gpio/laser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/gpio/laser/config.yml b/src/modules/gpio/laser/config.yml new file mode 100644 index 00000000..71b88336 --- /dev/null +++ b/src/modules/gpio/laser/config.yml @@ -0,0 +1,5 @@ +laser: + class: Laser + dependencies: + python: + - gpiozero diff --git a/modules/gpio/laser.py b/src/modules/gpio/laser/laser.py similarity index 100% rename from modules/gpio/laser.py rename to src/modules/gpio/laser/laser.py diff --git a/src/modules/gpio/laser/tests/__init__.py b/src/modules/gpio/laser/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/gpio/laser/tests/test_laser.py b/src/modules/gpio/laser/tests/test_laser.py new file mode 100644 index 00000000..af78f189 --- /dev/null +++ b/src/modules/gpio/laser/tests/test_laser.py @@ -0,0 +1,30 @@ +import unittest +from unittest import mock + +mock_gpiozero = mock.MagicMock() +with mock.patch.dict('sys.modules', {'gpiozero': mock_gpiozero}): + from modules.gpio.laser.laser import Laser + +class TestLaser(unittest.TestCase): + + def test_init(self): + laser = Laser(pin=17) + self.assertEqual(laser.pin, 17) + mock_gpiozero.LED.assert_called_with(17) + + def test_activate(self): + laser = Laser(pin=17) + laser.activate(True) + self.assertTrue(laser.state) + laser.activate(False) + self.assertFalse(laser.state) + + def test_toggle(self): + laser = Laser(pin=17, state=False) + laser.toggle() + self.assertTrue(laser.state) + laser.toggle() + self.assertFalse(laser.state) + +if __name__ == '__main__': + unittest.main() diff --git a/docs/Sensor.md b/src/modules/gpio/motion/README.md similarity index 100% rename from docs/Sensor.md rename to src/modules/gpio/motion/README.md diff --git a/src/modules/gpio/motion/__init__.py b/src/modules/gpio/motion/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/gpio/motion/config.yml b/src/modules/gpio/motion/config.yml new file mode 100644 index 00000000..c50f8c86 --- /dev/null +++ b/src/modules/gpio/motion/config.yml @@ -0,0 +1,5 @@ +gpio_motion: + class: Motion + dependencies: + python: + - gpiozero diff --git a/modules/gpio/motion.py b/src/modules/gpio/motion/motion.py similarity index 100% rename from modules/gpio/motion.py rename to src/modules/gpio/motion/motion.py diff --git a/src/modules/gpio/motion/tests/__init__.py b/src/modules/gpio/motion/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_sensor.py b/src/modules/gpio/motion/tests/test_motion.py similarity index 95% rename from tests/test_sensor.py rename to src/modules/gpio/motion/tests/test_motion.py index d90011ab..7be111a7 100644 --- a/tests/test_sensor.py +++ b/src/modules/gpio/motion/tests/test_motion.py @@ -5,7 +5,7 @@ mock_gpiozero = mock.MagicMock() mock_gpiozero.MotionSensor = mock.MagicMock() with mock.patch.dict('sys.modules', {'gpiozero': mock_gpiozero}): - from modules.gpio.motion import Motion as Sensor + from modules.gpio.motion.motion import Motion as Sensor class TestSensor(unittest.TestCase): diff --git a/src/modules/gpio/read/README.md b/src/modules/gpio/read/README.md new file mode 100644 index 00000000..c5d98c95 --- /dev/null +++ b/src/modules/gpio/read/README.md @@ -0,0 +1,28 @@ +# Read + +## Overview + +General purpose GPIO input reader module. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +gpio_read: + enabled: false # Set to true to enable this module + path: modules.gpio.read.read.Read + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/gpio/read/__init__.py b/src/modules/gpio/read/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/gpio/read/config.yml b/src/modules/gpio/read/config.yml new file mode 100644 index 00000000..94c9a07a --- /dev/null +++ b/src/modules/gpio/read/config.yml @@ -0,0 +1,5 @@ +gpio_read: + class: Read + dependencies: + python: + - gpiozero diff --git a/modules/gpio/read.py b/src/modules/gpio/read/read.py similarity index 100% rename from modules/gpio/read.py rename to src/modules/gpio/read/read.py diff --git a/src/modules/gpio/read/tests/__init__.py b/src/modules/gpio/read/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/gpio/read/tests/test_read.py b/src/modules/gpio/read/tests/test_read.py new file mode 100644 index 00000000..47c1c42b --- /dev/null +++ b/src/modules/gpio/read/tests/test_read.py @@ -0,0 +1,22 @@ +import unittest +from unittest import mock + +mock_gpiozero = mock.MagicMock() +with mock.patch.dict('sys.modules', {'gpiozero': mock_gpiozero}): + from modules.gpio.read.read import Read + +class TestRead(unittest.TestCase): + + def test_init(self): + reader = Read(pin=17) + self.assertEqual(reader.pin, 17) + mock_gpiozero.InputDevice.assert_called_with(17) + + def test_read(self): + reader = Read(pin=17) + mock_gpiozero.InputDevice.return_value.value = 1 + result = reader.read() + self.assertEqual(reader.value, 1) + +if __name__ == '__main__': + unittest.main() diff --git a/docs/I2CServo.md b/src/modules/i2c_servo/README.md similarity index 100% rename from docs/I2CServo.md rename to src/modules/i2c_servo/README.md diff --git a/src/modules/i2c_servo/__init__.py b/src/modules/i2c_servo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/i2c_servo/config.yml b/src/modules/i2c_servo/config.yml new file mode 100644 index 00000000..39a8d937 --- /dev/null +++ b/src/modules/i2c_servo/config.yml @@ -0,0 +1,10 @@ +i2c_servo: + config: + test_on_boot: false + servo_count: 16 + dependencies: + unix: + - python3-smbus + python: + - adafruit-circuitpython-servokit + class: I2CServo diff --git a/modules/i2c_servo.py b/src/modules/i2c_servo/i2c_servo.py similarity index 100% rename from modules/i2c_servo.py rename to src/modules/i2c_servo/i2c_servo.py diff --git a/src/modules/i2c_servo/tests/__init__.py b/src/modules/i2c_servo/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/i2c_servo/tests/test_i2c_servo.py b/src/modules/i2c_servo/tests/test_i2c_servo.py new file mode 100644 index 00000000..d00b24ca --- /dev/null +++ b/src/modules/i2c_servo/tests/test_i2c_servo.py @@ -0,0 +1,18 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_adafruit = MagicMock() +sys.modules['adafruit_servokit'] = mock_adafruit + +from modules.i2c_servo.i2c_servo import I2CServo + +class TestI2CServo(unittest.TestCase): + + def test_init(self): + servo = I2CServo(servo_count=3) + self.assertEqual(servo.count, 3) + mock_adafruit.ServoKit.assert_called_with(channels=16) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/input_recorder/README.md b/src/modules/input_recorder/README.md new file mode 100644 index 00000000..1b6fbae4 --- /dev/null +++ b/src/modules/input_recorder/README.md @@ -0,0 +1,28 @@ +# InputRecorder + +## Overview + +Records and replays controller input events for animation creation. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +input_recorder: + enabled: false # Set to true to enable this module + path: modules.input_recorder.input_recorder.InputRecorder + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/input_recorder/__init__.py b/src/modules/input_recorder/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/config/input_recorder.yml b/src/modules/input_recorder/config.yml old mode 100755 new mode 100644 similarity index 51% rename from config/input_recorder.yml rename to src/modules/input_recorder/config.yml index 7950e732..a3329912 --- a/config/input_recorder.yml +++ b/src/modules/input_recorder/config.yml @@ -1,5 +1,4 @@ input_recorder: - enabled: true - path: modules.input_recorder.InputRecorder config: animations_dir: animations/recorded + class: InputRecorder diff --git a/config/discordbot.yml b/src/modules/input_recorder/discordbot.yml similarity index 100% rename from config/discordbot.yml rename to src/modules/input_recorder/discordbot.yml diff --git a/modules/input_recorder.py b/src/modules/input_recorder/input_recorder.py similarity index 100% rename from modules/input_recorder.py rename to src/modules/input_recorder/input_recorder.py diff --git a/src/modules/input_recorder/tests/__init__.py b/src/modules/input_recorder/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_input_recorder.py b/src/modules/input_recorder/tests/test_input_recorder.py old mode 100755 new mode 100644 similarity index 95% rename from tests/test_input_recorder.py rename to src/modules/input_recorder/tests/test_input_recorder.py index bae721f3..ccd3c9e6 --- a/tests/test_input_recorder.py +++ b/src/modules/input_recorder/tests/test_input_recorder.py @@ -4,7 +4,7 @@ class TestInputRecorder(unittest.TestCase): def setUp(self): - from modules.input_recorder import InputRecorder + from modules.input_recorder.input_recorder import InputRecorder self.recorder = InputRecorder(animations_dir='/tmp') self.recorder.log = lambda *a, **k: None self.recorder.save_events = MagicMock() diff --git a/src/modules/logwrapper/README.md b/src/modules/logwrapper/README.md new file mode 100644 index 00000000..0ccb1114 --- /dev/null +++ b/src/modules/logwrapper/README.md @@ -0,0 +1,28 @@ +# LogWrapper + +## Overview + +Logging module that wraps Python logging with pub/sub integration. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +logwrapper: + enabled: false # Set to true to enable this module + path: modules.logwrapper.logwrapper.LogWrapper + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/logwrapper/__init__.py b/src/modules/logwrapper/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/logwrapper/config.yml b/src/modules/logwrapper/config.yml new file mode 100644 index 00000000..0002d730 --- /dev/null +++ b/src/modules/logwrapper/config.yml @@ -0,0 +1,6 @@ +logwrapper: + config: + filename: app.log + log_level: debug + cli_level: info + class: LogWrapper diff --git a/modules/logwrapper.py b/src/modules/logwrapper/logwrapper.py similarity index 100% rename from modules/logwrapper.py rename to src/modules/logwrapper/logwrapper.py diff --git a/src/modules/logwrapper/tests/__init__.py b/src/modules/logwrapper/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_logwrapper.py b/src/modules/logwrapper/tests/test_logwrapper.py similarity index 88% rename from tests/test_logwrapper.py rename to src/modules/logwrapper/tests/test_logwrapper.py index 42ab2bff..59903022 100644 --- a/tests/test_logwrapper.py +++ b/src/modules/logwrapper/tests/test_logwrapper.py @@ -2,11 +2,11 @@ from unittest import mock import os import logging -from modules.logwrapper import LogWrapper +from modules.logwrapper.logwrapper import LogWrapper class TestLogWrapper(unittest.TestCase): - @mock.patch('modules.logwrapper.logging') + @mock.patch('modules.logwrapper.logwrapper.logging') def test_init(self, mock_logging): log_wrapper = LogWrapper(path='/tmp', filename='test.log', log_level='info', cli_level='debug') self.assertEqual(log_wrapper.path, '/tmp') @@ -21,19 +21,19 @@ def test_init(self, mock_logging): datefmt='%Y/%m/%d %I:%M:%S %p' ) - @mock.patch('modules.logwrapper.logging') + @mock.patch('modules.logwrapper.logwrapper.logging') def test_log(self, mock_logging): log_wrapper = LogWrapper(path='/tmp', filename='test.log', log_level='info', cli_level='debug') log_wrapper.log('Test message', type='info') mock_logging.log.assert_called_with(logging.INFO, 'Test message') - @mock.patch('modules.logwrapper.logging') + @mock.patch('modules.logwrapper.logwrapper.logging') def test_log_json_message(self, mock_logging): log_wrapper = LogWrapper(path='/tmp', filename='test.log', log_level='info', cli_level='debug') log_wrapper.log('{"message": "Test JSON message"}', type='info') mock_logging.log.assert_called_with(logging.INFO, 'Test JSON message') - @mock.patch('modules.logwrapper.logging') + @mock.patch('modules.logwrapper.logwrapper.logging') def test_log_with_translator(self, mock_logging): mock_translator = mock.Mock() mock_translator.request.return_value = 'Translated message' @@ -42,7 +42,7 @@ def test_log_with_translator(self, mock_logging): mock_translator.request.assert_called_with('Test message') mock_logging.log.assert_called_with(logging.INFO, 'Translated message') - @mock.patch('modules.logwrapper.logging') + @mock.patch('modules.logwrapper.logwrapper.logging') def test_setup_messaging(self, mock_logging): log_wrapper = LogWrapper(path='/tmp', filename='test.log', log_level='info', cli_level='debug') with mock.patch.object(log_wrapper, 'subscribe') as mock_subscribe: diff --git a/src/modules/neopixel/__init__.py b/src/modules/neopixel/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/neopixel/emotion_analysis/README.md b/src/modules/neopixel/emotion_analysis/README.md new file mode 100644 index 00000000..63bbb185 --- /dev/null +++ b/src/modules/neopixel/emotion_analysis/README.md @@ -0,0 +1,28 @@ +# EmotionAnalysis + +## Overview + +Emotion analysis module that maps text sentiment to NeoPixel LED colors. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +neopixel_emotions: + enabled: false # Set to true to enable this module + path: modules.neopixel.emotion_analysis.emotion_analysis.EmotionAnalysis + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/neopixel/emotion_analysis/__init__.py b/src/modules/neopixel/emotion_analysis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/neopixel/emotion_analysis/config.yml b/src/modules/neopixel/emotion_analysis/config.yml new file mode 100644 index 00000000..39fd305b --- /dev/null +++ b/src/modules/neopixel/emotion_analysis/config.yml @@ -0,0 +1,2357 @@ +neopixel_emotions: + dependencies: + python: + - tensorflow + - torch + - transformers + config: + colors: + grief: + COLOR_ABBEY: (76, 79, 86) + COLOR_AFFAIR: (113, 70, 147) + COLOR_ARSENIC: (59, 68, 75) + COLOR_BISMARK: (73, 113, 131) + COLOR_BLACK_CORAL: (84, 98, 111) + COLOR_BLUE_BAYOUX: (73, 102, 121) + COLOR_BLUE_MAGENTA_VIOLET: (85, 53, 146) + COLOR_BOSSANOVA: (78, 42, 90) + COLOR_BRIGHT_GRAY: (60, 65, 81) + COLOR_BUTTERFLY_BUSH: (98, 78, 154) + COLOR_CADET: (83, 104, 114) + COLOR_CASAL: (47, 97, 104) + COLOR_CHAMBRAY: (53, 78, 140) + COLOR_CHARCOAL: (54, 69, 79) + COLOR_COMET: (92, 93, 117) + COLOR_CYBER_GRAPE: (88, 66, 124) + COLOR_DARK_LAVENDER: (115, 79, 150) + COLOR_DARK_SLATE_BLUE: (72, 61, 139) + COLOR_DEEP_KOAMARU: (51, 51, 102) + COLOR_DEEP_SPACE_SPARKLE: (74, 100, 108) + COLOR_DOLPHIN: (100, 96, 119) + COLOR_EAST_BAY: (65, 76, 125) + COLOR_FIORD: (64, 81, 105) + COLOR_GIGAS: (82, 60, 148) + COLOR_GRAVEL: (74, 68, 75) + COLOR_GUN_POWDER: (65, 66, 87) + COLOR_GUNMETAL: (42, 52, 57) + COLOR_IMPERIAL: (96, 47, 107) + COLOR_INDEPENDENCE: (76, 81, 109) + COLOR_JAPANESE_INDIGO: (38, 67, 72) + COLOR_KASHMIR_BLUE: (80, 112, 150) + COLOR_LIBERTY: (84, 90, 167) + COLOR_LIMED_SPRUCE: (57, 72, 81) + COLOR_MAKO: (68, 73, 84) + COLOR_MARTINIQUE: (54, 48, 80) + COLOR_MID_GRAY: (95, 95, 110) + COLOR_MINSK: (63, 48, 127) + COLOR_MORTAR: (80, 67, 81) + COLOR_MULLED_WINE: (78, 69, 98) + COLOR_NEVADA: (100, 110, 117) + COLOR_OCEAN_BLUE: (79, 66, 181) + COLOR_OLD_HELIOTROPE: (86, 60, 92) + COLOR_ONYX: (53, 56, 57) + COLOR_ORACLE: (55, 116, 117) + COLOR_OUTER_SPACE: (65, 74, 76) + COLOR_PAYNES_GREY: (83, 104, 120) + COLOR_PICKLED_BLUEWOOD: (49, 68, 89) + COLOR_PLUMP_PURPLE: (89, 70, 178) + COLOR_PURPLE_NAVY: (78, 81, 128) + COLOR_QUEEN_BLUE: (67, 107, 149) + COLOR_RHINO: (46, 63, 98) + COLOR_RIVER_BED: (67, 76, 89) + COLOR_SALT_BOX: (104, 94, 110) + COLOR_SAN_JUAN: (48, 75, 106) + COLOR_SAN_MARINO: (69, 108, 172) + COLOR_SCARPA_FLOW: (88, 85, 98) + COLOR_SHIP_GRAY: (62, 58, 68) + COLOR_SHUTTLE_GRAY: (95, 102, 114) + COLOR_SMOKY: (96, 91, 115) + COLOR_STORMCLOUD: (79, 102, 106) + COLOR_TROUT: (74, 78, 90) + COLOR_TUNA: (53, 53, 66) + COLOR_UCLA_BLUE: (83, 104, 149) + COLOR_VICTORIA: (83, 68, 145) + COLOR_VOODOO: (83, 52, 85) + COLOR_WILLIAM: (58, 104, 108) + COLOR_ARMADILLO: (67, 62, 55) + COLOR_AU_CHICO: (151, 96, 93) + COLOR_CAMELOT: (137, 52, 86) + COLOR_CANNON_PINK: (137, 67, 103) + COLOR_CHICAGO: (93, 92, 88) + COLOR_COSMIC: (118, 57, 93) + COLOR_COYOTE_BROWN: (129, 97, 62) + COLOR_CROCODILE: (115, 109, 88) + COLOR_DARK_BROWN_TANGELO: (136, 101, 78) + COLOR_DARK_BYZANTIUM: (93, 57, 84) + COLOR_DARK_LIVER: (83, 75, 79) + COLOR_DARK_PUCE: (79, 58, 60) + COLOR_DEEP_RUBY: (132, 63, 91) + COLOR_DEEP_TAUPE: (126, 94, 96) + COLOR_DEEP_TUSCAN_RED: (102, 66, 77) + COLOR_DON_JUAN: (93, 76, 81) + COLOR_DORADO: (107, 87, 85) + COLOR_DOVE_GRAY: (109, 108, 108) + COLOR_DUNE: (56, 53, 51) + COLOR_EGGPLANT: (97, 64, 81) + COLOR_EMPEROR: (81, 70, 73) + COLOR_FALCON: (127, 98, 109) + COLOR_FEDORA: (121, 106, 120) + COLOR_FERRA: (112, 79, 80) + COLOR_FINN: (105, 45, 84) + COLOR_FLINT: (111, 106, 97) + COLOR_FRENCH_BISTRE: (133, 109, 77) + COLOR_FUSCOUS_GRAY: (84, 83, 77) + COLOR_GO_BEN: (114, 109, 78) + COLOR_GOLD_FUSION: (133, 117, 78) + COLOR_HEMLOCK: (94, 93, 59) + COLOR_HIPPIE_PINK: (174, 69, 96) + COLOR_IRONSIDE_GRAY: (103, 102, 98) + COLOR_JAPANESE_VIOLET: (91, 50, 86) + COLOR_KABUL: (94, 72, 62) + COLOR_KOKODA: (110, 109, 87) + COLOR_LIVER: (103, 76, 71) + COLOR_MAGENTA_HAZE: (159, 69, 118) + COLOR_MASALA: (64, 59, 56) + COLOR_MATTERHORN: (78, 59, 65) + COLOR_MAUVE_TAUPE: (145, 95, 109) + COLOR_MEDIUM_RUBY: (170, 64, 105) + COLOR_MERLIN: (65, 60, 55) + COLOR_OLD_LAVENDER: (121, 104, 120) + COLOR_OLD_MOSS_GREEN: (134, 126, 54) + COLOR_PABLO: (119, 111, 97) + COLOR_PASTEL_BROWN: (131, 105, 83) + COLOR_PEAT: (113, 107, 86) + COLOR_PINE_CONE: (109, 94, 84) + COLOR_PLUM: (142, 69, 133) + COLOR_PURPLE_TAUPE: (80, 64, 77) + COLOR_QUARTZ: (81, 72, 79) + COLOR_QUINACRIDONE_MAGENTA: (142, 58, 89) + COLOR_RAW_UMBER: (130, 102, 68) + COLOR_REDWOOD: (164, 90, 82) + COLOR_ROMAN_COFFEE: (121, 93, 76) + COLOR_ROSE_EBONY: (103, 72, 70) + COLOR_ROSE_TAUPE: (144, 93, 93) + COLOR_RUSSETT: (117, 90, 87) + COLOR_SANDSTONE: (121, 109, 98) + COLOR_SCORPION: (105, 95, 98) + COLOR_SOYA_BEAN: (106, 96, 81) + COLOR_SPICY_MIX: (139, 95, 77) + COLOR_SUGAR_PLUM: (145, 78, 117) + COLOR_SYCAMORE: (144, 141, 57) + COLOR_TOBACCO_BROWN: (113, 93, 71) + COLOR_TUATARA: (54, 53, 52) + COLOR_TUNDORA: (74, 66, 68) + COLOR_TUSCAN_RED: (124, 72, 72) + COLOR_TWILIGHT_LAVENDER: (138, 73, 107) + COLOR_UMBER: (99, 81, 71) + COLOR_VIN_ROUGE: (152, 61, 97) + COLOR_WENGE: (100, 84, 82) + COLOR_WINE_DREGS: (103, 49, 71) + COLOR_YELLOW_METAL: (113, 99, 56) + COLOR_ZAMBEZI: (104, 85, 88) + COLOR_AMAZON: (59, 122, 87) + COLOR_APPLE: (79, 168, 61) + COLOR_AXOLOTL: (78, 102, 73) + COLOR_BLACK_OLIVE: (59, 60, 54) + COLOR_CABBAGE_PONT: (63, 76, 58) + COLOR_CACTUS: (88, 113, 86) + COLOR_CAPE_COD: (60, 68, 67) + COLOR_CHALET_GREEN: (81, 110, 61) + COLOR_CHATEAU_GREEN: (64, 168, 96) + COLOR_COMO: (81, 124, 102) + COLOR_CORDUROY: (96, 110, 104) + COLOR_CUTTY_SARK: (80, 118, 114) + COLOR_DARK_OLIVE_GREEN: (85, 107, 47) + COLOR_DINGLEY: (93, 119, 71) + COLOR_EBONY: (85, 93, 80) + COLOR_FADED_JADE: (66, 121, 119) + COLOR_FELDGRAU: (77, 93, 83) + COLOR_FERN_GREEN: (79, 121, 66) + COLOR_FINCH: (98, 102, 73) + COLOR_FINLANDIA: (85, 109, 86) + COLOR_FRUIT_SALAD: (79, 157, 93) + COLOR_GLADE_GREEN: (97, 132, 95) + COLOR_GOBLIN: (61, 125, 82) + COLOR_GRAY_ASPARAGUS: (70, 89, 69) + COLOR_HIPPIE_GREEN: (83, 130, 75) + COLOR_HOOKERS_GREEN: (73, 121, 107) + COLOR_HUNTER_GREEN: (53, 94, 59) + COLOR_KELP: (69, 73, 54) + COLOR_KILLARNEY: (58, 106, 71) + COLOR_KOMBU_GREEN: (53, 66, 48) + COLOR_LIMA: (118, 189, 23) + COLOR_LIMED_ASH: (116, 125, 99) + COLOR_LUNAR_GREEN: (60, 73, 58) + COLOR_MAY_GREEN: (76, 145, 65) + COLOR_MINERAL_GREEN: (63, 93, 83) + COLOR_NANDOR: (75, 93, 82) + COLOR_NICKEL: (114, 116, 114) + COLOR_OLIVE_DRAB: (107, 142, 35) + COLOR_PLANTATION: (39, 80, 75) + COLOR_RYB_GREEN: (102, 176, 50) + COLOR_RIFLE_GREEN: (68, 76, 56) + COLOR_SAP_GREEN: (80, 125, 42) + COLOR_SIAM: (100, 106, 84) + COLOR_SPECTRA: (47, 90, 87) + COLOR_SPRING_LEAVES: (87, 131, 99) + COLOR_STROMBOLI: (50, 93, 82) + COLOR_TOM_THUMB: (63, 88, 59) + COLOR_VIRIDIAN: (64, 130, 109) + COLOR_WILLOW_GROVE: (101, 116, 93) + COLOR_WINTERGREEN_DREAM: (86, 136, 125) + COLOR_DAVYS_GREY: (85, 85, 85) + COLOR_DIM_GRAY: (105, 105, 105) + COLOR_GRANITE_GRAY: (103, 103, 103) + COLOR_JET: (52, 52, 52) + COLOR_MINE_SHAFT: (50, 50, 50) + COLOR_STORM_DUST: (100, 100, 99) + COLOR_DARK_SLATE_GRAY: (47, 79, 79) + disgust: + COLOR_ABSOLUTE_ZERO: (0, 72, 186) + COLOR_ALLPORTS: (0, 118, 163) + COLOR_ASTRAL: (50, 125, 160) + COLOR_ATOLL: (10, 111, 117) + COLOR_BAHAMA_BLUE: (2, 99, 149) + COLOR_BALL_BLUE: (33, 171, 205) + COLOR_BDAZZLED_BLUE: (46, 88, 148) + COLOR_BLUE_CHILL: (12, 137, 144) + COLOR_BLUE_DIANNE: (32, 72, 82) + COLOR_BLUE_GREEN: (13, 152, 186) + COLOR_BLUE_SAPPHIRE: (18, 97, 128) + COLOR_BLUE_STONE: (1, 97, 98) + COLOR_BLUMINE: (24, 88, 122) + COLOR_BONDI_BLUE: (0, 149, 182) + COLOR_BOSTON_BLUE: (59, 145, 180) + COLOR_BRIGHT_CERULEAN: (29, 172, 214) + COLOR_BRIGHT_NAVY_BLUE: (25, 116, 210) + COLOR_CG_BLUE: (0, 122, 165) + COLOR_CALYPSO: (49, 114, 141) + COLOR_CERULEAN: (0, 123, 167) + COLOR_CERULEAN_BLUE: (42, 82, 190) + COLOR_CHATHAMS_BLUE: (23, 85, 121) + COLOR_COBALT_BLUE: (0, 71, 171) + COLOR_CONGRESS_BLUE: (2, 71, 142) + COLOR_CURIOUS_BLUE: (37, 150, 209) + COLOR_CYAN_COBALT_BLUE: (40, 88, 156) + COLOR_CYAN_CORNFLOWER_BLUE: (24, 139, 194) + COLOR_DARK_CERULEAN: (8, 69, 126) + COLOR_DARK_TURQUOISE: (0, 206, 209) + COLOR_DEEP_SEA_GREEN: (9, 88, 89) + COLOR_DENIM: (21, 96, 189) + COLOR_DENIM_BLUE: (34, 67, 182) + COLOR_EAGLE_GREEN: (0, 73, 83) + COLOR_EASTERN_BLUE: (30, 154, 176) + COLOR_ELM: (28, 124, 125) + COLOR_ENDEAVOUR: (0, 86, 167) + COLOR_FRENCH_BLUE: (0, 114, 187) + COLOR_FUN_BLUE: (25, 89, 168) + COLOR_GREEN_BLUE: (17, 100, 180) + COLOR_HONOLULU_BLUE: (0, 109, 176) + COLOR_LAPIS_LAZULI: (38, 97, 156) + COLOR_LOCHMARA: (0, 126, 199) + COLOR_MARINER: (40, 106, 205) + COLOR_MATISSE: (27, 101, 157) + COLOR_MEDIUM_ELECTRIC_BLUE: (3, 80, 150) + COLOR_METALLIC_SEAWEED: (10, 126, 140) + COLOR_MING: (54, 116, 125) + COLOR_MOSQUE: (3, 106, 110) + COLOR_MUNSELL_BLUE: (0, 147, 175) + COLOR_NCS_BLUE: (0, 135, 189) + COLOR_NEW_CAR: (33, 79, 198) + COLOR_OCEAN_BOAT_BLUE: (0, 119, 190) + COLOR_ORIENT: (1, 94, 133) + COLOR_PACIFIC_BLUE: (28, 169, 201) + COLOR_PARADISO: (49, 125, 130) + COLOR_PELOROUS: (62, 171, 191) + COLOR_PROCESS_CYAN: (0, 183, 235) + COLOR_RICH_ELECTRIC_BLUE: (8, 146, 208) + COLOR_SAPPHIRE: (15, 82, 186) + COLOR_SAPPHIRE_BLUE: (0, 103, 165) + COLOR_SCIENCE_BLUE: (0, 102, 204) + COLOR_SEA_BLUE: (0, 105, 148) + COLOR_SHERPA_BLUE: (0, 73, 80) + COLOR_SPANISH_BLUE: (0, 112, 184) + COLOR_SPANISH_SKY_BLUE: (0, 170, 228) + COLOR_ST_TROPAZ: (45, 86, 155) + COLOR_STAR_COMMAND_BLUE: (0, 123, 184) + COLOR_TEAL_BLUE: (54, 117, 136) + COLOR_TORY_BLUE: (20, 80, 170) + COLOR_TRUE_BLUE: (0, 115, 207) + COLOR_USAFA_BLUE: (0, 79, 152) + COLOR_VENICE_BLUE: (5, 89, 137) + COLOR_VIOLET_BLUE: (50, 74, 178) + COLOR_VIRIDIAN_GREEN: (0, 150, 152) + COLOR_VIVID_CERULEAN: (0, 170, 238) + COLOR_YALE_BLUE: (15, 77, 146) + COLOR_AQUA_DEEP: (1, 75, 67) + COLOR_BOTTLE_GREEN: (0, 106, 78) + COLOR_CARIBBEAN_GREEN: (0, 204, 153) + COLOR_CELADON_GREEN: (47, 132, 124) + COLOR_CRAYOLA_GREEN: (28, 172, 120) + COLOR_DARK_SPRING_GREEN: (23, 114, 69) + COLOR_DEEP_GREEN_CYAN_TURQUOISE: (14, 124, 97) + COLOR_DEEP_JUNGLE_GREEN: (0, 75, 73) + COLOR_DEEP_SEA: (1, 130, 107) + COLOR_EDEN: (16, 88, 82) + COLOR_ELF_GREEN: (8, 131, 112) + COLOR_EVENING_SEA: (2, 78, 70) + COLOR_GO_GREEN: (0, 171, 102) + COLOR_GENERIC_VIRIDIAN: (0, 127, 102) + COLOR_GENOA: (21, 115, 107) + COLOR_GOSSAMER: (6, 155, 129) + COLOR_GREEN_CYAN: (0, 153, 102) + COLOR_GREEN_HAZE: (1, 163, 104) + COLOR_GREEN_PEA: (29, 97, 66) + COLOR_ILLUMINATING_EMERALD: (49, 145, 119) + COLOR_JADE: (0, 168, 107) + COLOR_JUNGLE_GREEN: (41, 171, 135) + COLOR_KEPPEL: (58, 176, 158) + COLOR_LIGHT_SEA_GREEN: (32, 178, 170) + COLOR_LOCHINVAR: (44, 140, 132) + COLOR_MEDIUM_SEA_GREEN: (60, 179, 113) + COLOR_MEDIUM_SPRING_GREEN: (0, 250, 154) + COLOR_MINT: (62, 180, 137) + COLOR_MOUNTAIN_MEADOW: (48, 186, 143) + COLOR_MUNSELL_GREEN: (0, 168, 119) + COLOR_MYRTLE_GREEN: (49, 120, 115) + COLOR_NCS_GREEN: (0, 159, 107) + COLOR_NIAGARA: (6, 161, 137) + COLOR_OBSERVATORY: (2, 134, 111) + COLOR_PANTONE_GREEN: (0, 173, 67) + COLOR_PAOLO_VERONESE_GREEN: (0, 155, 125) + COLOR_PEARL_MYSTIC_TURQUOISE: (50, 198, 166) + COLOR_PERSIAN_GREEN: (0, 166, 147) + COLOR_PIGMENT_GREEN: (0, 165, 80) + COLOR_PINE_GREEN: (1, 121, 111) + COLOR_PUERTO_RICO: (63, 193, 170) + COLOR_SALEM: (9, 127, 75) + COLOR_SEA_GREEN: (46, 139, 87) + COLOR_SHAMROCK: (51, 204, 153) + COLOR_SHAMROCK_GREEN: (0, 158, 96) + COLOR_SPANISH_GREEN: (0, 145, 80) + COLOR_SPANISH_VIRIDIAN: (0, 127, 92) + COLOR_SURFIE_GREEN: (12, 122, 121) + COLOR_TEAL_GREEN: (0, 130, 127) + COLOR_TIFFANY_BLUE: (10, 186, 181) + COLOR_TROPICAL_RAIN_FOREST: (0, 117, 94) + COLOR_UFO_GREEN: (60, 208, 112) + COLOR_WATERCOURSE: (5, 111, 87) + COLOR_ZOMP: (57, 167, 142) + COLOR_DARK_CYAN: (0, 139, 139) + COLOR_JAVA: (31, 194, 194) + COLOR_ROBIN_EGG_BLUE: (0, 204, 204) + COLOR_SKOBELOFF: (0, 116, 116) + COLOR_TEAL: (0, 128, 128) + COLOR_WARM_BLACK: (0, 66, 66) + curiosity: + COLOR_AERO: (124, 185, 232) + COLOR_ANAKIWA: (157, 229, 255) + COLOR_AQUAMARINE_BLUE: (113, 217, 226) + COLOR_BABY_BLUE: (137, 207, 240) + COLOR_BABY_BLUE_EYES: (161, 202, 241) + COLOR_BEAU_BLUE: (188, 212, 230) + COLOR_BELGION: (173, 216, 255) + COLOR_BILOBA_FLOWER: (178, 161, 234) + COLOR_BLEU_DE_FRANCE: (49, 140, 231) + COLOR_BLIZZARD_BLUE: (163, 227, 237) + COLOR_BLUE_JEANS: (93, 173, 236) + COLOR_BLUE_LAGOON: (172, 229, 238) + COLOR_BLUEBERRY: (79, 134, 247) + COLOR_BRILLIANT_AZURE: (51, 153, 255) + COLOR_CAPRI: (0, 191, 255) + COLOR_CAROLINA_BLUE: (86, 160, 211) + COLOR_CELESTIAL_BLUE: (73, 151, 208) + COLOR_CHARLOTTE: (186, 238, 249) + COLOR_COLUMBIA_BLUE: (196, 216, 226) + COLOR_CORNFLOWER_BLUE: (100, 149, 237) + COLOR_DANUBE: (96, 147, 209) + COLOR_DARK_IMPERIAL_BLUE: (110, 110, 249) + COLOR_DARK_SKY_BLUE: (140, 190, 214) + COLOR_DIAMOND: (185, 242, 255) + COLOR_DODGER_BLUE: (30, 144, 255) + COLOR_ELECTRIC_BLUE: (125, 249, 255) + COLOR_FOUNTAIN_BLUE: (86, 180, 190) + COLOR_FRENCH_PASS: (189, 237, 253) + COLOR_FRENCH_SKY_BLUE: (119, 181, 254) + COLOR_FRESH_AIR: (166, 231, 255) + COLOR_HALF_BAKED: (133, 196, 204) + COLOR_HAVELOCK_BLUE: (85, 144, 217) + COLOR_ICEBERG: (113, 166, 210) + COLOR_JORDY_BLUE: (138, 185, 241) + COLOR_LIGHT_BLUE: (173, 216, 230) + COLOR_LIGHT_COBALT_BLUE: (136, 172, 224) + COLOR_LIGHT_CORNFLOWER_BLUE: (147, 204, 234) + COLOR_LIGHT_SKY_BLUE: (135, 206, 250) + COLOR_LIGHT_STEEL_BLUE: (176, 196, 222) + COLOR_LITTLE_BOY_BLUE: (108, 160, 220) + COLOR_MALIBU: (125, 200, 247) + COLOR_MAXIMUM_BLUE: (71, 171, 204) + COLOR_MAYA_BLUE: (115, 194, 251) + COLOR_MEDIUM_SKY_BLUE: (128, 218, 235) + COLOR_MEDIUM_SLATE_BLUE: (123, 104, 238) + COLOR_MELROSE: (199, 193, 255) + COLOR_MORNING_GLORY: (158, 222, 224) + COLOR_NON_PHOTO_BLUE: (164, 221, 237) + COLOR_PALE_CERULEAN: (155, 196, 226) + COLOR_PALE_CORNFLOWER_BLUE: (171, 205, 239) + COLOR_PALE_CYAN: (135, 211, 248) + COLOR_PERANO: (169, 190, 242) + COLOR_PERIWINKLE_GRAY: (195, 205, 230) + COLOR_PICTON_BLUE: (69, 177, 232) + COLOR_PORTAGE: (139, 159, 238) + COLOR_POWDER_BLUE: (176, 224, 230) + COLOR_REGENT_ST_BLUE: (170, 214, 230) + COLOR_SAIL: (184, 224, 249) + COLOR_SCOOTER: (46, 191, 212) + COLOR_SEA_SERPENT: (75, 199, 207) + COLOR_SEAGULL: (128, 204, 234) + COLOR_SHAKESPEARE: (78, 171, 209) + COLOR_SKY_BLUE: (135, 206, 235) + COLOR_SPINDLE: (182, 209, 234) + COLOR_SPIRO_DISCO_BALL: (15, 192, 252) + COLOR_SPRAY: (121, 222, 236) + COLOR_TROPICAL_BLUE: (195, 221, 249) + COLOR_UNITED_NATIONS_BLUE: (91, 146, 229) + COLOR_VERY_LIGHT_AZURE: (116, 187, 251) + COLOR_VERY_LIGHT_BLUE: (102, 102, 255) + COLOR_VIKING: (100, 204, 219) + COLOR_VISTA_BLUE: (124, 158, 217) + COLOR_VIVID_SKY_BLUE: (0, 204, 255) + COLOR_WATERSPOUT: (164, 244, 249) + COLOR_WINTER_WIZARD: (160, 230, 255) + COLOR_ZIGGURAT: (191, 219, 226) + COLOR_AQUA_ISLAND: (161, 218, 215) + COLOR_AQUAMARINE: (127, 255, 212) + COLOR_BERMUDA: (125, 216, 198) + COLOR_BRIGHT_TURQUOISE: (8, 232, 222) + COLOR_CRUISE: (181, 236, 223) + COLOR_DOWNY: (111, 208, 197) + COLOR_EUCALYPTUS: (68, 215, 168) + COLOR_ICE_COLD: (177, 244, 231) + COLOR_JAGGED_ICE: (194, 232, 229) + COLOR_MAGIC_MINT: (170, 240, 209) + COLOR_MEDIUM_TURQUOISE: (72, 209, 204) + COLOR_MINT_TULIP: (196, 244, 235) + COLOR_MONTE_CARLO: (131, 208, 198) + COLOR_PALE_ROBIN_EGG_BLUE: (150, 222, 209) + COLOR_PEARL_AQUA: (136, 216, 192) + COLOR_RIPTIDE: (139, 230, 216) + COLOR_SINBAD: (159, 215, 211) + COLOR_TURQUOISE: (64, 224, 208) + COLOR_TURQUOISE_BLUE: (0, 255, 239) + COLOR_WATER_LEAF: (161, 233, 222) + COLOR_AERO_BLUE: (201, 255, 229) + COLOR_HUMMING_BIRD: (207, 249, 243) + COLOR_ONAHAU: (205, 244, 255) + COLOR_PERIWINKLE: (204, 204, 255) + COLOR_SCANDAL: (207, 250, 244) + COLOR_CELESTE: (178, 255, 255) + COLOR_CYAN: (0, 255, 255) + COLOR_LIGHT_TURQUOISE: (175, 238, 238) + disappointment: + COLOR_AFRICAN_VIOLET: (178, 132, 190) + COLOR_AIR_SUPERIORITY_BLUE: (114, 160, 193) + COLOR_ALUMINIUM: (169, 172, 182) + COLOR_AMETHYST: (153, 102, 204) + COLOR_AMETHYST_SMOKE: (163, 151, 180) + COLOR_BALI_HAI: (133, 159, 175) + COLOR_BLUE_BELL: (162, 162, 208) + COLOR_BLUE_GRAY: (102, 153, 204) + COLOR_BLUE_HAZE: (191, 190, 216) + COLOR_BOMBAY: (175, 177, 184) + COLOR_BRIGHT_LAVENDER: (191, 148, 228) + COLOR_CADET_GREY: (145, 163, 176) + COLOR_CASPER: (173, 190, 209) + COLOR_CEIL: (146, 161, 207) + COLOR_CERULEAN_FROST: (109, 155, 195) + COLOR_CHATELLE: (189, 179, 199) + COLOR_CHETWODE_BLUE: (133, 129, 217) + COLOR_COLD_PURPLE: (171, 160, 217) + COLOR_COOL_GREY: (140, 146, 172) + COLOR_DARK_PASTEL_BLUE: (119, 158, 203) + COLOR_DARK_PASTEL_PURPLE: (150, 111, 214) + COLOR_DULL_LAVENDER: (168, 153, 230) + COLOR_EAST_SIDE: (172, 145, 206) + COLOR_FRENCH_GRAY: (189, 189, 198) + COLOR_GLACIER: (128, 179, 196) + COLOR_GLOSSY_GRAPE: (171, 146, 179) + COLOR_GRAY_CHATEAU: (162, 170, 179) + COLOR_GRAY_SUIT: (193, 190, 205) + COLOR_GULL_GRAY: (157, 172, 183) + COLOR_GUMBO: (124, 161, 166) + COLOR_HEATHER: (183, 195, 208) + COLOR_HIT_GRAY: (161, 173, 181) + COLOR_JUNGLE_MIST: (180, 207, 211) + COLOR_LAVENDER: (181, 126, 220) + COLOR_LAVENDER_GRAY: (196, 195, 208) + COLOR_LAVENDER_INDIGO: (148, 87, 235) + COLOR_LAVENDER_PURPLE: (150, 123, 182) + COLOR_LENURPLE: (186, 147, 216) + COLOR_LIGHT_PASTEL_PURPLE: (177, 156, 217) + COLOR_LILAC_BUSH: (152, 116, 211) + COLOR_LOBLOLLY: (189, 201, 206) + COLOR_LOGAN: (170, 169, 205) + COLOR_LONDON_HUE: (190, 166, 195) + COLOR_MANATEE: (151, 154, 170) + COLOR_MEDIUM_PURPLE: (147, 112, 219) + COLOR_MOODY_BLUE: (127, 118, 211) + COLOR_MOONSTONE_BLUE: (115, 169, 194) + COLOR_MOUNTAIN_MIST: (149, 147, 150) + COLOR_NEPAL: (142, 171, 193) + COLOR_NEPTUNE: (124, 183, 187) + COLOR_PASTEL_BLUE: (174, 198, 207) + COLOR_PASTEL_PURPLE: (179, 158, 181) + COLOR_PEWTER_BLUE: (139, 168, 183) + COLOR_PIGEON_POST: (175, 189, 217) + COLOR_POLO_BLUE: (141, 168, 204) + COLOR_PURPLE_MOUNTAIN_MAJESTY: (150, 120, 182) + COLOR_RICH_LAVENDER: (167, 107, 207) + COLOR_ROCK_BLUE: (158, 177, 205) + COLOR_SANTAS_GRAY: (159, 160, 177) + COLOR_SILVER_SAND: (191, 193, 194) + COLOR_SPUN_PEARL: (170, 171, 183) + COLOR_SUBMARINE: (186, 199, 201) + COLOR_TOWER_GRAY: (169, 189, 191) + COLOR_TRUE_V: (138, 115, 214) + COLOR_UBE: (136, 120, 195) + COLOR_WELDON_BLUE: (124, 152, 171) + COLOR_WILD_BLUE_YONDER: (162, 173, 208) + COLOR_WISTERIA: (201, 160, 220) + COLOR_WISTFUL: (164, 166, 211) + COLOR_AKAROA: (212, 196, 168) + COLOR_ANTIQUE_BRASS: (205, 149, 117) + COLOR_APACHE: (223, 190, 111) + COLOR_ARYLIDE_YELLOW: (233, 214, 107) + COLOR_ASH: (198, 195, 181) + COLOR_BISON_HIDE: (193, 183, 164) + COLOR_BLACK_SHADOWS: (191, 175, 178) + COLOR_BOUQUET: (174, 128, 158) + COLOR_BRANDY: (222, 193, 150) + COLOR_BRANDY_ROSE: (187, 137, 131) + COLOR_BRONCO: (171, 161, 150) + COLOR_BROWN_YELLOW: (204, 153, 102) + COLOR_BURLYWOOD: (222, 184, 135) + COLOR_BURNING_SAND: (217, 147, 118) + COLOR_CAMEO: (217, 185, 155) + COLOR_CAN_CAN: (213, 145, 164) + COLOR_CAREYS_PINK: (210, 158, 170) + COLOR_CHENIN: (223, 205, 111) + COLOR_CHINO: (206, 199, 167) + COLOR_CLAM_SHELL: (212, 182, 175) + COLOR_CLOUD: (199, 196, 191) + COLOR_CLOUDY: (172, 165, 159) + COLOR_COLD_TURKEY: (206, 186, 186) + COLOR_CORAL_REEF: (199, 188, 162) + COLOR_COTTON_SEED: (194, 189, 182) + COLOR_DARK_KHAKI: (189, 183, 107) + COLOR_DARK_VANILLA: (209, 190, 168) + COLOR_DAWN: (166, 162, 154) + COLOR_DEL_RIO: (176, 154, 149) + COLOR_DUSTY_GRAY: (168, 152, 155) + COLOR_ECRU: (194, 178, 128) + COLOR_ENGLISH_LAVENDER: (180, 131, 149) + COLOR_EQUATOR: (225, 188, 100) + COLOR_EUNRY: (207, 163, 157) + COLOR_FALLOW: (193, 154, 107) + COLOR_FAWN: (229, 170, 112) + COLOR_FOGGY_GRAY: (203, 202, 182) + COLOR_GIMBLET: (184, 181, 106) + COLOR_GRAY_OLIVE: (169, 164, 145) + COLOR_GRULLO: (169, 154, 134) + COLOR_HEATHERED_GRAY: (182, 176, 149) + COLOR_HELIOTROPE_GRAY: (170, 152, 169) + COLOR_HILLARY: (172, 165, 134) + COLOR_KHAKI: (195, 176, 145) + COLOR_LASER: (200, 181, 104) + COLOR_LIGHT_FRENCH_BEIGE: (200, 173, 127) + COLOR_LIGHT_MEDIUM_ORCHID: (211, 155, 203) + COLOR_LILAC_LUSTER: (174, 152, 170) + COLOR_LILY: (200, 170, 191) + COLOR_MALTA: (189, 178, 161) + COLOR_MARTINI: (175, 160, 158) + COLOR_MISTY_MOSS: (187, 180, 119) + COLOR_MONGOOSE: (181, 162, 127) + COLOR_MY_PINK: (214, 145, 136) + COLOR_NAPA: (172, 164, 148) + COLOR_NOBEL: (183, 177, 177) + COLOR_NOMAD: (186, 177, 162) + COLOR_OLD_ROSE: (192, 128, 129) + COLOR_OLIVE_GREEN: (181, 179, 92) + COLOR_OPERA_MAUVE: (183, 132, 167) + COLOR_ORIENTAL_PINK: (198, 145, 145) + COLOR_PALE_SILVER: (201, 192, 187) + COLOR_PALE_SLATE: (195, 191, 193) + COLOR_PALE_TAUPE: (188, 152, 126) + COLOR_PASTEL_PINK: (222, 165, 164) + COLOR_PASTEL_VIOLET: (203, 153, 201) + COLOR_PAVLOVA: (215, 196, 152) + COLOR_PEARLY_PURPLE: (183, 104, 162) + COLOR_PETITE_ORCHID: (219, 150, 144) + COLOR_PINK_SWAN: (190, 181, 183) + COLOR_PUCE: (204, 136, 153) + COLOR_QUICKSAND: (189, 151, 142) + COLOR_ROB_ROY: (234, 198, 116) + COLOR_RODEO_DUST: (201, 178, 155) + COLOR_ROSY_BROWN: (188, 143, 143) + COLOR_SAGE: (188, 184, 138) + COLOR_SHADY_LADY: (170, 165, 169) + COLOR_SILK: (189, 177, 168) + COLOR_SILVER_PINK: (196, 174, 173) + COLOR_SOFT_AMBER: (209, 198, 180) + COLOR_SORRELL_BROWN: (206, 185, 143) + COLOR_STRAW: (228, 217, 111) + COLOR_SUNDANCE: (201, 179, 91) + COLOR_TACHA: (214, 197, 98) + COLOR_TALLOW: (168, 165, 137) + COLOR_TAN: (210, 180, 140) + COLOR_TEA: (193, 186, 176) + COLOR_THATCH: (182, 157, 152) + COLOR_THISTLE_GREEN: (204, 202, 168) + COLOR_TIDE: (191, 184, 176) + COLOR_TONYS_PINK: (231, 159, 140) + COLOR_TUMBLEWEED: (222, 170, 136) + COLOR_TUSCANY: (192, 153, 153) + COLOR_VEGAS_GOLD: (197, 179, 88) + COLOR_VIOLA: (203, 143, 169) + COLOR_WHISKEY: (213, 154, 111) + COLOR_WINTER_HAZEL: (213, 209, 149) + COLOR_YUMA: (206, 194, 145) + COLOR_ZORBA: (165, 155, 145) + COLOR_ACAPULCO: (124, 176, 161) + COLOR_ALGAE_GREEN: (147, 223, 184) + COLOR_ASH_GREY: (178, 190, 181) + COLOR_BOOGER_BUSTER: (221, 226, 106) + COLOR_BUD: (168, 174, 156) + COLOR_CAMBRIDGE_BLUE: (163, 193, 173) + COLOR_CASCADE: (139, 169, 165) + COLOR_CELADON: (172, 225, 175) + COLOR_CELERY: (184, 194, 93) + COLOR_CHINOOK: (168, 227, 189) + COLOR_CLAY_ASH: (189, 200, 179) + COLOR_CONIFER: (172, 221, 77) + COLOR_CORIANDER: (196, 208, 176) + COLOR_DARK_SEA_GREEN: (143, 188, 143) + COLOR_DE_YORK: (122, 196, 136) + COLOR_DECO: (210, 218, 151) + COLOR_EAGLE: (182, 186, 164) + COLOR_EDWARD: (162, 174, 171) + COLOR_ENVY: (139, 166, 144) + COLOR_ETON_BLUE: (150, 200, 162) + COLOR_FEIJOA: (159, 221, 140) + COLOR_FRINGY_FLOWER: (177, 226, 193) + COLOR_GRANNY_SMITH_APPLE: (168, 228, 160) + COLOR_GREEN_MIST: (203, 211, 176) + COLOR_GREEN_SMOKE: (164, 175, 110) + COLOR_GREEN_SPRING: (184, 193, 177) + COLOR_GULF_STREAM: (128, 179, 174) + COLOR_GUM_LEAF: (182, 211, 191) + COLOR_INCHWORM: (178, 236, 93) + COLOR_JET_STREAM: (181, 210, 206) + COLOR_JUNE_BUD: (189, 218, 87) + COLOR_KANGAROO: (198, 200, 189) + COLOR_LAUREL_GREEN: (169, 186, 157) + COLOR_LEMON_GRASS: (155, 158, 143) + COLOR_LIGHT_GREEN: (144, 238, 144) + COLOR_LIGHT_MOSS_GREEN: (173, 223, 173) + COLOR_LOCUST: (168, 175, 142) + COLOR_MADANG: (183, 240, 190) + COLOR_MEDIUM_AQUAMARINE: (102, 221, 170) + COLOR_MEDIUM_SPRING_BUD: (201, 220, 135) + COLOR_MINT_GREEN: (152, 255, 152) + COLOR_NORWAY: (168, 189, 159) + COLOR_OLIVINE: (154, 185, 115) + COLOR_OPAL: (169, 198, 194) + COLOR_PADUA: (173, 230, 196) + COLOR_PALE_GREEN: (152, 251, 152) + COLOR_PALE_LEAF: (192, 211, 185) + COLOR_PASTEL_GREEN: (119, 221, 119) + COLOR_PEWTER: (150, 168, 161) + COLOR_PINE_GLADE: (199, 205, 144) + COLOR_PISTACHIO: (147, 197, 114) + COLOR_PIXIE_GREEN: (192, 216, 182) + COLOR_POWDER_ASH: (188, 201, 194) + COLOR_PUMICE: (194, 202, 196) + COLOR_RAINEE: (185, 200, 172) + COLOR_REEF: (201, 255, 162) + COLOR_SCHIST: (169, 180, 151) + COLOR_SCREAMIN_GREEN: (102, 255, 102) + COLOR_SHADOW_GREEN: (154, 194, 184) + COLOR_SPRING_RAIN: (172, 203, 177) + COLOR_SPROUT: (193, 215, 176) + COLOR_SULU: (193, 240, 124) + COLOR_SUMMER_GREEN: (150, 187, 171) + COLOR_SURF: (187, 215, 193) + COLOR_SWAMP_GREEN: (172, 183, 142) + COLOR_TEAL_DEER: (153, 230, 179) + COLOR_TRADEWIND: (95, 179, 172) + COLOR_TURQUOISE_GREEN: (160, 214, 180) + COLOR_VERY_LIGHT_MALACHITE_GREEN: (100, 233, 134) + COLOR_WILD_WILLOW: (185, 196, 106) + COLOR_LIGHT_GRAYISH_MAGENTA: (204, 153, 204) + COLOR_LILAC: (200, 162, 200) + COLOR_DARK_MEDIUM_GRAY: (169, 169, 169) + COLOR_DELTA: (164, 164, 157) + COLOR_GRAY_NICKEL: (195, 195, 189) + COLOR_MIST_GRAY: (196, 196, 188) + COLOR_QUICK_SILVER: (166, 166, 166) + COLOR_SILVER: (192, 192, 192) + COLOR_SILVER_CHALICE: (172, 172, 172) + COLOR_SPANISH_GRAY: (152, 152, 152) + COLOR_STAR_DUST: (159, 159, 156) + COLOR_TIARA: (195, 209, 209) + COLOR_GRANNY_SMITH: (132, 160, 160) + pride: + COLOR_AIR_FORCE_BLUE: (0, 48, 143) + COLOR_ARAPAWA: (17, 12, 108) + COLOR_ASTRONAUT: (40, 58, 119) + COLOR_ASTRONAUT_BLUE: (1, 62, 98) + COLOR_BAY_OF_MANY: (39, 58, 129) + COLOR_BIG_STONE: (22, 42, 64) + COLOR_BISCAY: (27, 49, 98) + COLOR_BLACK_ROCK: (13, 3, 50) + COLOR_BLACKCURRANT: (50, 41, 58) + COLOR_BLEACHED_CEDAR: (44, 33, 51) + COLOR_BLUE_DIAMOND: (56, 4, 116) + COLOR_BLUE_GEM: (44, 14, 140) + COLOR_BLUE_WHALE: (4, 46, 76) + COLOR_BLUE_ZODIAC: (19, 38, 77) + COLOR_BUNTING: (21, 31, 76) + COLOR_CATALINA_BLUE: (6, 42, 120) + COLOR_CELLO: (30, 56, 91) + COLOR_CHARADE: (41, 41, 55) + COLOR_CHERRY_PIE: (42, 3, 89) + COLOR_CHRISTALLE: (51, 3, 107) + COLOR_CLOUD_BURST: (32, 46, 84) + COLOR_COOL_BLACK: (0, 46, 99) + COLOR_COSMIC_COBALT: (46, 45, 136) + COLOR_CYPRUS: (0, 62, 64) + COLOR_DARK_BLUE: (0, 0, 139) + COLOR_DARK_MIDNIGHT_BLUE: (0, 51, 102) + COLOR_DARK_PURPLE: (48, 25, 52) + COLOR_DEEP_BLUE: (34, 8, 120) + COLOR_DEEP_COVE: (5, 16, 64) + COLOR_DEEP_SAPPHIRE: (8, 37, 103) + COLOR_DEEP_VIOLET: (51, 0, 102) + COLOR_DOWNRIVER: (9, 34, 86) + COLOR_DUKE_BLUE: (0, 0, 156) + COLOR_EBONY_CLAY: (38, 40, 59) + COLOR_EGYPTIAN_BLUE: (16, 52, 166) + COLOR_ELEPHANT: (18, 52, 71) + COLOR_GOVERNOR_BAY: (47, 60, 179) + COLOR_GREEN_VOGUE: (3, 43, 82) + COLOR_GULF_BLUE: (5, 22, 87) + COLOR_HAITI: (27, 16, 53) + COLOR_IMPERIAL_BLUE: (0, 35, 149) + COLOR_INDIGO_DYE: (9, 31, 146) + COLOR_INTERNATIONAL_KLEIN_BLUE: (0, 47, 167) + COLOR_JACARTA: (58, 42, 106) + COLOR_JACKSONS_PURPLE: (32, 32, 141) + COLOR_JAGGER: (53, 14, 87) + COLOR_KINGFISHER_DAISY: (62, 4, 128) + COLOR_LUCKY_POINT: (26, 26, 104) + COLOR_MADISON: (9, 37, 93) + COLOR_METEORITE: (60, 31, 118) + COLOR_MIDNIGHT_BLUE: (25, 25, 112) + COLOR_NAVY: (0, 0, 128) + COLOR_NILE_BLUE: (25, 55, 81) + COLOR_OXFORD_BLUE: (0, 33, 71) + COLOR_PANTONE_BLUE: (0, 24, 168) + COLOR_PARIS_M: (38, 5, 106) + COLOR_PAUA: (38, 3, 104) + COLOR_PERSIAN_BLUE: (28, 57, 187) + COLOR_PERSIAN_INDIGO: (50, 18, 122) + COLOR_PHTHALO_BLUE: (0, 15, 137) + COLOR_PIGMENT_BLUE: (51, 51, 153) + COLOR_PIXIE_POWDER: (57, 18, 133) + COLOR_PORT_GORE: (37, 31, 79) + COLOR_PRUSSIAN_BLUE: (0, 49, 83) + COLOR_REGAL_BLUE: (1, 63, 106) + COLOR_RESOLUTION_BLUE: (0, 35, 135) + COLOR_REVOLVER: (44, 22, 50) + COLOR_ROYAL_AZURE: (0, 56, 168) + COLOR_RUSSIAN_VIOLET: (50, 23, 77) + COLOR_SMALT: (0, 51, 153) + COLOR_SPACE_CADET: (29, 41, 81) + COLOR_ST_PATRICKS_BLUE: (35, 41, 122) + COLOR_STEEL_GRAY: (38, 35, 53) + COLOR_STRATOS: (0, 7, 65) + COLOR_TANGAROA: (3, 22, 60) + COLOR_TARAWERA: (7, 58, 80) + COLOR_TIBER: (6, 53, 55) + COLOR_TOLOPEA: (27, 2, 69) + COLOR_TOREA_BAY: (15, 45, 158) + COLOR_UA_BLUE: (0, 51, 170) + COLOR_VALENTINO: (53, 14, 66) + COLOR_VALHALLA: (43, 25, 79) + COLOR_VIOLENT_VIOLET: (41, 12, 94) + COLOR_WINDSOR: (60, 8, 120) + COLOR_YANKEES_BLUE: (28, 40, 65) + COLOR_ZAFFRE: (0, 20, 168) + COLOR_BALTIC_SEA: (42, 38, 48) + COLOR_BASTILLE: (41, 33, 48) + COLOR_BLACK: (0, 0, 0) + COLOR_BLACK_PEARL: (4, 19, 34) + COLOR_BLACK_RUSSIAN: (10, 0, 28) + COLOR_BLUE_CHARCOAL: (1, 13, 26) + COLOR_BUNKER: (13, 17, 23) + COLOR_CHARLESTON_GREEN: (35, 43, 43) + COLOR_CINDER: (14, 14, 24) + COLOR_COD_GRAY: (11, 11, 11) + COLOR_DAINTREE: (1, 39, 49) + COLOR_DARK_GUNMETAL: (31, 38, 42) + COLOR_EERIE_BLACK: (27, 27, 27) + COLOR_FIREFLY: (14, 42, 48) + COLOR_JAGUAR: (8, 1, 16) + COLOR_MIRAGE: (22, 25, 40) + COLOR_RAISIN_BLACK: (36, 33, 36) + COLOR_SHARK: (37, 39, 44) + COLOR_SWAMP: (0, 27, 28) + COLOR_VULCAN: (16, 18, 29) + COLOR_WOODSMOKE: (12, 13, 15) + COLOR_RICH_BLACK: (0, 64, 64) + neutral: + COLOR_AURO_METAL_SAURUS: (110, 127, 128) + COLOR_BERMUDA_GRAY: (107, 139, 162) + COLOR_BLUE_MARGUERITE: (118, 102, 198) + COLOR_BLUE_YONDER: (80, 114, 167) + COLOR_CADET_BLUE: (95, 158, 160) + COLOR_CHINESE_VIOLET: (133, 96, 136) + COLOR_CYAN_AZURE: (78, 130, 180) + COLOR_CYAN_BLUE_AZURE: (70, 130, 191) + COLOR_DARK_BLUE_GRAY: (102, 102, 153) + COLOR_DEEP_LILAC: (153, 85, 187) + COLOR_DELUGE: (117, 99, 168) + COLOR_FRENCH_LILAC: (134, 96, 142) + COLOR_FUCHSIA_BLUE: (122, 88, 193) + COLOR_GLAUCOUS: (96, 130, 182) + COLOR_GOTHIC: (109, 146, 161) + COLOR_HAN_BLUE: (68, 108, 207) + COLOR_HIPPIE_BLUE: (88, 154, 175) + COLOR_HOKI: (101, 134, 159) + COLOR_HORIZON: (90, 135, 160) + COLOR_IRIS: (90, 79, 207) + COLOR_JUMBO: (124, 123, 130) + COLOR_KIMBERLY: (115, 108, 159) + COLOR_LIGHT_SLATE_GRAY: (119, 136, 153) + COLOR_LYNCH: (105, 126, 154) + COLOR_MAJORELLE_BLUE: (96, 80, 220) + COLOR_MAMBA: (142, 129, 144) + COLOR_MOBSTER: (127, 117, 137) + COLOR_OSLO_GRAY: (135, 141, 145) + COLOR_PALE_SKY: (110, 119, 131) + COLOR_PURPLE_PLUM: (156, 81, 182) + COLOR_PURPUREUS: (154, 78, 174) + COLOR_RAVEN: (114, 123, 137) + COLOR_REGENT_GRAY: (134, 148, 159) + COLOR_RHYTHM: (119, 118, 150) + COLOR_ROLLING_STONE: (116, 125, 131) + COLOR_ROMAN_SILVER: (131, 137, 150) + COLOR_ROYAL_AIR_FORCE_BLUE: (93, 138, 168) + COLOR_ROYAL_BLUE: (65, 105, 225) + COLOR_ROYAL_PURPLE: (120, 81, 169) + COLOR_RUM: (121, 105, 137) + COLOR_SCAMPI: (103, 95, 166) + COLOR_SHADOW_BLUE: (119, 139, 165) + COLOR_SHIP_COVE: (120, 139, 186) + COLOR_SILVER_LAKE_BLUE: (93, 137, 186) + COLOR_SLATE_BLUE: (106, 90, 205) + COLOR_SLATE_GRAY: (112, 128, 144) + COLOR_SMALT_BLUE: (81, 128, 143) + COLOR_STEEL_BLUE: (70, 130, 180) + COLOR_STEEL_TEAL: (95, 138, 139) + COLOR_STORM_GRAY: (113, 116, 134) + COLOR_STUDIO: (113, 74, 178) + COLOR_TOOLBOX: (116, 108, 192) + COLOR_TRENDY_PINK: (140, 100, 149) + COLOR_TUFTS_BLUE: (65, 125, 193) + COLOR_WAIKAWA_GRAY: (90, 110, 156) + COLOR_WATERLOO_: (123, 124, 148) + COLOR_WEDGEWOOD: (78, 127, 158) + COLOR_ALMOND_FROST: (144, 123, 113) + COLOR_AMERICANO: (135, 117, 110) + COLOR_ANTIQUE_FUCHSIA: (145, 92, 131) + COLOR_ARROWTOWN: (148, 135, 113) + COLOR_BANDICOOT: (133, 132, 112) + COLOR_BARLEY_CORN: (166, 139, 91) + COLOR_BAZAAR: (152, 119, 123) + COLOR_BEAVER: (159, 129, 112) + COLOR_BLAST_OFF_BRONZE: (165, 113, 100) + COLOR_BRASS: (181, 166, 66) + COLOR_BROWN_SUGAR: (175, 110, 77) + COLOR_BURNISHED_BROWN: (161, 122, 116) + COLOR_CADILLAC: (176, 76, 106) + COLOR_CEMENT: (141, 118, 98) + COLOR_CHAMOISEE: (160, 120, 90) + COLOR_CHINA_ROSE: (168, 81, 110) + COLOR_CINEREOUS: (152, 129, 123) + COLOR_CLAY_CREEK: (138, 131, 96) + COLOR_CONCORD: (124, 123, 122) + COLOR_COPPER_PENNY: (173, 111, 105) + COLOR_COPPER_ROSE: (153, 102, 102) + COLOR_CORAL_TREE: (168, 107, 107) + COLOR_DARK_CHESTNUT: (152, 105, 96) + COLOR_DARK_TAN: (145, 129, 81) + COLOR_DEEP_PUCE: (169, 92, 104) + COLOR_DEER: (186, 135, 89) + COLOR_DIRT: (155, 118, 83) + COLOR_DOMINO: (142, 119, 94) + COLOR_DRIFTWOOD: (175, 135, 81) + COLOR_EMPRESS: (129, 115, 119) + COLOR_FRIAR_GRAY: (128, 126, 121) + COLOR_GIANTS_CLUB: (176, 92, 82) + COLOR_GRANITE_GREEN: (141, 137, 116) + COLOR_GURKHA: (154, 149, 119) + COLOR_HEMP: (144, 120, 116) + COLOR_HURRICANE: (135, 124, 123) + COLOR_HUSK: (183, 164, 88) + COLOR_IRRESISTIBLE: (179, 68, 108) + COLOR_LEATHER: (150, 112, 89) + COLOR_LIGHT_TAUPE: (179, 139, 109) + COLOR_LIMED_OAK: (172, 138, 86) + COLOR_LIVER_CHESTNUT: (152, 116, 86) + COLOR_MAKARA: (137, 125, 109) + COLOR_MATRIX: (176, 93, 84) + COLOR_MONSOON: (138, 131, 137) + COLOR_MOUNTBATTEN_PINK: (153, 122, 141) + COLOR_MUDDY_WATERS: (183, 142, 92) + COLOR_MUESLI: (170, 139, 91) + COLOR_MYSTIC_MAROON: (173, 67, 121) + COLOR_NATURAL_GRAY: (139, 134, 128) + COLOR_OLIVE_HAZE: (139, 132, 112) + COLOR_OPIUM: (142, 111, 112) + COLOR_PALE_BROWN: (152, 118, 84) + COLOR_PALE_OYSTER: (152, 141, 119) + COLOR_PHARLAP: (163, 128, 123) + COLOR_RAZZMIC_BERRY: (141, 78, 133) + COLOR_ROCKET_METALLIC: (138, 127, 128) + COLOR_ROSE_DUST: (158, 94, 111) + COLOR_ROSE_GOLD: (183, 110, 121) + COLOR_SANDAL: (170, 141, 111) + COLOR_SANDRIFT: (171, 145, 122) + COLOR_SANTA_FE: (177, 109, 82) + COLOR_SCHOONER: (139, 132, 126) + COLOR_SHADOW: (138, 121, 93) + COLOR_SPICY_PINK: (129, 110, 113) + COLOR_SQUIRREL: (143, 129, 118) + COLOR_STONEWALL: (146, 133, 115) + COLOR_STRIKEMASTER: (149, 99, 135) + COLOR_SUVA_GRAY: (136, 131, 135) + COLOR_TAPA: (123, 120, 116) + COLOR_TAPESTRY: (176, 94, 129) + COLOR_TAUPE_GRAY: (139, 133, 137) + COLOR_TEAK: (177, 148, 97) + COLOR_TOAST: (154, 110, 97) + COLOR_TURKISH_ROSE: (181, 114, 129) + COLOR_TUSCAN_TAN: (166, 123, 91) + COLOR_TWINE: (194, 149, 93) + COLOR_VENUS: (146, 133, 144) + COLOR_AMULET: (123, 159, 128) + COLOR_ANDROID_GREEN: (164, 198, 57) + COLOR_AQUA_FOREST: (95, 167, 119) + COLOR_ARTICHOKE: (143, 151, 121) + COLOR_ASPARAGUS: (135, 169, 107) + COLOR_BATTLESHIP_GRAY: (130, 143, 114) + COLOR_BAY_LEAF: (125, 169, 141) + COLOR_BITTER: (134, 137, 116) + COLOR_BLUE_SMOKE: (116, 136, 129) + COLOR_BREAKER_BAY: (93, 161, 159) + COLOR_BUD_GREEN: (123, 182, 97) + COLOR_CAMOUFLAGE_GREEN: (120, 134, 107) + COLOR_CHELSEA_CUCUMBER: (131, 170, 93) + COLOR_DOLLAR_BILL: (133, 187, 101) + COLOR_EMERALD: (80, 200, 120) + COLOR_FERN: (99, 183, 108) + COLOR_FLAX_SMOKE: (123, 130, 101) + COLOR_GREEN_SHEEN: (110, 174, 161) + COLOR_GUNSMOKE: (130, 134, 133) + COLOR_HIGHLAND: (111, 142, 99) + COLOR_LAUREL: (116, 147, 120) + COLOR_MANTIS: (116, 195, 101) + COLOR_MANTLE: (139, 156, 144) + COLOR_MOSS_GREEN: (138, 154, 91) + COLOR_MUMMYS_TOMB: (130, 142, 132) + COLOR_OCEAN_GREEN: (72, 191, 145) + COLOR_OXLEY: (119, 158, 134) + COLOR_PATINA: (99, 154, 143) + COLOR_POLISHED_PINE: (93, 164, 147) + COLOR_RUSSIAN_GREEN: (103, 146, 103) + COLOR_SEA_NYMPH: (120, 163, 156) + COLOR_SHINY_SHAMROCK: (95, 167, 120) + COLOR_SILVER_TREE: (102, 181, 143) + COLOR_SMOKE: (115, 130, 118) + COLOR_STACK: (138, 143, 138) + COLOR_SUSHI: (135, 171, 57) + COLOR_VERDIGRIS: (67, 179, 174) + COLOR_XANADU: (115, 134, 120) + COLOR_YELLOW_GREEN: (154, 205, 50) + COLOR_BOULDER: (122, 122, 122) + COLOR_GRAY: (128, 128, 128) + COLOR_OLD_SILVER: (132, 132, 130) + COLOR_SONIC_SILVER: (117, 117, 117) + COLOR_DESATURATED_CYAN: (102, 153, 153) + COLOR_JUNIPER: (109, 146, 146) + COLOR_SIROCCO: (113, 128, 128) + sadness: + COLOR_AZURE: (0, 127, 255) + COLOR_BLUE: (0, 0, 255) + COLOR_BLUE_RIBBON: (0, 102, 255) + COLOR_BLUEBONNET: (28, 28, 240) + COLOR_BRANDEIS_BLUE: (0, 112, 255) + COLOR_CRAYOLA_BLUE: (31, 117, 254) + COLOR_ELECTRIC_INDIGO: (111, 0, 255) + COLOR_HAN_PURPLE: (82, 24, 250) + COLOR_MEDIUM_BLUE: (0, 0, 205) + COLOR_PALATINATE_BLUE: (39, 59, 226) + COLOR_RYB_BLUE: (2, 71, 254) + COLOR_ULTRAMARINE: (63, 0, 255) + COLOR_ULTRAMARINE_BLUE: (65, 102, 245) + COLOR_VIOLET: (127, 0, 255) + disapproval: + COLOR_BLUE_VIOLET: (138, 43, 226) + COLOR_CLAIRVOYANT: (72, 6, 86) + COLOR_DAISY_BUSH: (79, 35, 152) + COLOR_DARK_ORCHID: (153, 50, 204) + COLOR_DARK_VIOLET: (148, 0, 211) + COLOR_EMINENCE: (108, 48, 130) + COLOR_FRENCH_VIOLET: (136, 6, 206) + COLOR_GRAPE: (111, 45, 168) + COLOR_HELIOTROPE_MAGENTA: (170, 0, 187) + COLOR_HONEY_FLOWER: (79, 28, 112) + COLOR_INDIGO: (75, 0, 130) + COLOR_MUNSELL_PURPLE: (159, 0, 197) + COLOR_PURPLE_HEART: (105, 53, 156) + COLOR_RYB_VIOLET: (134, 1, 175) + COLOR_REBECCA_PURPLE: (102, 51, 153) + COLOR_REGALIA: (82, 45, 128) + COLOR_RIPE_PLUM: (65, 0, 86) + COLOR_SCARLET_GUM: (67, 21, 96) + COLOR_SEANCE: (115, 30, 143) + COLOR_SPANISH_VIOLET: (76, 40, 130) + COLOR_BOYSENBERRY: (135, 50, 96) + COLOR_BYZANTIUM: (112, 41, 99) + COLOR_CARDINAL_PINK: (140, 5, 94) + COLOR_DARK_RASPBERRY: (135, 38, 87) + COLOR_DISCO: (135, 21, 80) + COLOR_FANDANGO: (181, 51, 137) + COLOR_FLIRT: (162, 0, 109) + COLOR_FRENCH_PLUM: (129, 20, 83) + COLOR_FRESH_EGGPLANT: (153, 0, 102) + COLOR_HIBISCUS: (182, 49, 108) + COLOR_JAZZBERRY_JAM: (165, 11, 94) + COLOR_LIPSTICK: (171, 5, 99) + COLOR_LOULOU: (70, 11, 65) + COLOR_MARDI_GRAS: (136, 0, 133) + COLOR_PALATINATE_PURPLE: (104, 40, 96) + COLOR_PANSY_PURPLE: (120, 24, 74) + COLOR_PICTORIAL_CARMINE: (195, 11, 78) + COLOR_POMPADOUR: (102, 0, 69) + COLOR_RICH_MAROON: (176, 48, 96) + COLOR_ROSE_BUD_CHERRY: (128, 11, 71) + COLOR_ROSE_RED: (194, 30, 86) + COLOR_ROUGE: (162, 59, 108) + COLOR_ROYAL_HEATH: (171, 52, 114) + COLOR_DARK_MAGENTA: (139, 0, 139) + COLOR_MIDNIGHT: (112, 38, 112) + COLOR_PURPLE: (128, 0, 128) + COLOR_VIOLET_EGGPLANT: (153, 17, 153) + embarrassment: + COLOR_BOTTICELLI: (199, 221, 229) + COLOR_BRIGHT_LILAC: (216, 145, 239) + COLOR_BRIGHT_UBE: (209, 159, 232) + COLOR_BRILLIANT_LAVENDER: (244, 187, 255) + COLOR_GHOST: (199, 201, 213) + COLOR_MAUVE: (224, 176, 255) + COLOR_PALE_VIOLET: (204, 153, 255) + COLOR_PERFUME: (208, 190, 248) + COLOR_PRELUDE: (208, 192, 229) + COLOR_RICH_BRILLIANT_LAVENDER: (241, 167, 254) + COLOR_SOAP: (206, 200, 239) + COLOR_TROPICAL_VIOLET: (205, 164, 222) + COLOR_AMARANTH_PINK: (241, 156, 187) + COLOR_APRICOT: (251, 206, 177) + COLOR_ASTRA: (250, 234, 185) + COLOR_AZALEA: (247, 200, 218) + COLOR_BANANA_MANIA: (250, 231, 181) + COLOR_BEAUTY_BUSH: (238, 193, 190) + COLOR_BEESWAX: (254, 242, 199) + COLOR_BISQUE: (255, 228, 196) + COLOR_BLOND: (250, 240, 190) + COLOR_BLOSSOM: (220, 180, 188) + COLOR_BUBBLE_GUM: (255, 193, 204) + COLOR_BUFF: (240, 220, 130) + COLOR_BUTTERMILK: (255, 241, 181) + COLOR_CALICO: (224, 192, 149) + COLOR_CAMEO_PINK: (239, 187, 204) + COLOR_CAPE_HONEY: (254, 229, 172) + COLOR_CARAMEL: (255, 221, 175) + COLOR_CARNATION_PINK: (255, 166, 201) + COLOR_CASHMERE: (230, 190, 165) + COLOR_CAVERN_PINK: (227, 190, 190) + COLOR_CHALKY: (238, 215, 148) + COLOR_CHAMOIS: (237, 220, 177) + COLOR_CHANTILLY: (248, 195, 223) + COLOR_CHARDONNAY: (255, 205, 140) + COLOR_CHEROKEE: (252, 218, 152) + COLOR_CHERRY_BLOSSOM_PINK: (255, 183, 197) + COLOR_COLONIAL_WHITE: (255, 237, 188) + COLOR_CORNFLOWER_LILAC: (255, 176, 172) + COLOR_CORVETTE: (250, 211, 162) + COLOR_COTTON_CANDY: (255, 188, 217) + COLOR_CRAYOLA_YELLOW: (252, 232, 131) + COLOR_CREAM_BRULEE: (255, 229, 160) + COLOR_CUPID: (251, 190, 218) + COLOR_DAIRY_CREAM: (249, 228, 188) + COLOR_DESERT_SAND: (237, 201, 175) + COLOR_DOUBLE_COLONIAL_WHITE: (238, 227, 173) + COLOR_DOUBLE_SPANISH_WHITE: (230, 215, 185) + COLOR_DROVER: (253, 247, 173) + COLOR_DUTCH_WHITE: (239, 223, 187) + COLOR_EGG_WHITE: (255, 239, 193) + COLOR_FESTIVAL: (251, 233, 108) + COLOR_FLAVESCENT: (247, 233, 142) + COLOR_FLAX: (238, 220, 130) + COLOR_FRANGIPANI: (255, 222, 179) + COLOR_GIVRY: (248, 228, 191) + COLOR_GOLDEN_GLOW: (253, 226, 149) + COLOR_GOLDEN_SAND: (240, 219, 125) + COLOR_GRAIN_BROWN: (228, 213, 183) + COLOR_GRANDIS: (255, 211, 140) + COLOR_HAMPTON: (229, 216, 175) + COLOR_HIT_PINK: (255, 171, 129) + COLOR_ILLUSION: (246, 164, 201) + COLOR_JASMINE: (248, 222, 126) + COLOR_JUST_RIGHT: (236, 205, 185) + COLOR_KOBI: (231, 159, 196) + COLOR_KOURNIKOVA: (255, 231, 114) + COLOR_LAVENDER_PINK: (251, 174, 210) + COLOR_LAVENDER_ROSE: (251, 160, 227) + COLOR_LEMON_MERINGUE: (246, 234, 190) + COLOR_LIGHT_APRICOT: (253, 213, 177) + COLOR_LIGHT_HOT_PINK: (255, 179, 222) + COLOR_LIGHT_KHAKI: (240, 230, 140) + COLOR_LIGHT_ORCHID: (230, 168, 215) + COLOR_LIGHT_PINK: (255, 182, 193) + COLOR_MACARONI_AND_CHEESE: (255, 185, 123) + COLOR_MANDYS_PINK: (242, 195, 178) + COLOR_MANHATTAN: (245, 201, 153) + COLOR_MARIGOLD_YELLOW: (251, 232, 112) + COLOR_MARZIPAN: (248, 219, 157) + COLOR_MAVERICK: (216, 194, 213) + COLOR_MELANIE: (228, 194, 213) + COLOR_MELLOW_APRICOT: (248, 184, 120) + COLOR_MELON: (253, 188, 180) + COLOR_MINT_JULEP: (241, 238, 193) + COLOR_MOCCASIN: (255, 228, 181) + COLOR_MONA_LISA: (255, 161, 148) + COLOR_NADESHIKO_PINK: (246, 173, 198) + COLOR_NAVAJO_WHITE: (255, 222, 173) + COLOR_NEGRONI: (255, 226, 197) + COLOR_NEW_ORLEANS: (243, 214, 157) + COLOR_ORANGE_YELLOW: (248, 213, 104) + COLOR_ORCHID_PINK: (242, 189, 205) + COLOR_PALE_CHESTNUT: (221, 173, 175) + COLOR_PALE_GOLD: (230, 190, 138) + COLOR_PALE_GOLDENROD: (238, 232, 170) + COLOR_PALE_MAGENTA_PINK: (255, 153, 204) + COLOR_PALE_SPRING_BUD: (236, 235, 189) + COLOR_PANCHO: (237, 205, 171) + COLOR_PARIS_DAISY: (255, 244, 110) + COLOR_PASTEL_MAGENTA: (244, 154, 194) + COLOR_PEACH: (255, 203, 164) + COLOR_PEACH_ORANGE: (255, 204, 153) + COLOR_PEACH_PUFF: (255, 218, 185) + COLOR_PEACH_YELLOW: (250, 223, 173) + COLOR_PEARL: (234, 224, 200) + COLOR_PICASSO: (255, 243, 157) + COLOR_PINK: (255, 192, 203) + COLOR_PINK_FLARE: (225, 192, 200) + COLOR_PINK_LAVENDER: (216, 178, 209) + COLOR_PINK_PEARL: (231, 172, 207) + COLOR_PRIMROSE: (237, 234, 153) + COLOR_PUTTY: (231, 205, 140) + COLOR_RAFFIA: (234, 218, 184) + COLOR_ROMANTIC: (255, 210, 183) + COLOR_ROSE_BUD: (251, 178, 163) + COLOR_ROSE_FOG: (231, 188, 180) + COLOR_SAHARA_SAND: (241, 231, 136) + COLOR_SALOMIE: (254, 219, 141) + COLOR_SANDWISP: (245, 231, 162) + COLOR_SANDY_BEACH: (255, 234, 200) + COLOR_SAPLING: (222, 212, 164) + COLOR_SHILO: (232, 185, 179) + COLOR_SIDECAR: (243, 231, 187) + COLOR_SISAL: (211, 203, 186) + COLOR_SPANISH_PINK: (247, 191, 190) + COLOR_STARK_WHITE: (229, 215, 189) + COLOR_SUNDOWN: (255, 177, 179) + COLOR_SUNSET: (250, 214, 165) + COLOR_SWEET_CORN: (251, 234, 140) + COLOR_SWEET_PINK: (253, 159, 162) + COLOR_SWIRL: (211, 205, 197) + COLOR_TACAO: (237, 179, 129) + COLOR_TEA_ROSE: (244, 194, 194) + COLOR_TEQUILA: (255, 230, 199) + COLOR_TOPAZ: (255, 200, 124) + COLOR_VANILLA: (243, 229, 171) + COLOR_VERY_LIGHT_TANGELO: (255, 176, 119) + COLOR_VERY_PALE_ORANGE: (255, 223, 191) + COLOR_VIS_VIS: (255, 239, 161) + COLOR_WAFER: (222, 203, 198) + COLOR_WAX_FLOWER: (255, 192, 168) + COLOR_WEWAK: (241, 155, 171) + COLOR_WHEAT: (245, 222, 179) + COLOR_WILD_RICE: (236, 224, 144) + COLOR_WITCH_HAZE: (255, 252, 153) + COLOR_YOUR_PINK: (255, 195, 192) + COLOR_ZINNWALDITE: (235, 194, 175) + COLOR_ZOMBIE: (228, 214, 155) + COLOR_AUSTRALIAN_MINT: (245, 255, 190) + COLOR_BERYL_GREEN: (222, 229, 192) + COLOR_CAPER: (220, 237, 180) + COLOR_CHIFFON: (241, 255, 200) + COLOR_DOLLY: (249, 255, 139) + COLOR_EDGEWATER: (200, 227, 215) + COLOR_GOSSIP: (210, 248, 176) + COLOR_HONEYSUCKLE: (237, 252, 132) + COLOR_KEY_LIME: (232, 244, 140) + COLOR_MANZ: (238, 239, 120) + COLOR_MILAN: (250, 255, 164) + COLOR_MINDARO: (227, 249, 136) + COLOR_PALE_PRIM: (253, 254, 184) + COLOR_SEA_MIST: (197, 219, 202) + COLOR_SHALIMAR: (251, 255, 186) + COLOR_TAHUNA_SANDS: (238, 240, 200) + COLOR_TANA: (217, 220, 193) + COLOR_TEA_GREEN: (208, 240, 192) + COLOR_TEXAS: (248, 249, 156) + COLOR_TIDAL: (241, 255, 173) + COLOR_TUSK: (238, 243, 195) + COLOR_PALE_CANARY: (255, 255, 153) + COLOR_PASTEL_GRAY: (207, 207, 196) + COLOR_PASTEL_YELLOW: (253, 253, 150) + COLOR_PORTAFINO: (255, 255, 180) + COLOR_SUNNY: (242, 242, 122) + COLOR_VERY_PALE_YELLOW: (255, 255, 191) + COLOR_PALE_PLUM: (221, 160, 221) + COLOR_THISTLE: (216, 191, 216) + COLOR_ALABASTER: (250, 250, 250) + COLOR_ALBESCENT_WHITE: (245, 233, 211) + COLOR_ALICE_BLUE: (240, 248, 255) + COLOR_ALMOND: (239, 222, 205) + COLOR_ALTO: (219, 219, 219) + COLOR_AMOUR: (249, 234, 243) + COLOR_ANTI_FLASH_WHITE: (242, 243, 244) + COLOR_ANTIQUE_WHITE: (250, 235, 215) + COLOR_APRICOT_WHITE: (255, 254, 236) + COLOR_AQUA_HAZE: (237, 245, 245) + COLOR_AQUA_SPRING: (234, 249, 245) + COLOR_AQUA_SQUEEZE: (232, 245, 242) + COLOR_ATHENS_GRAY: (238, 240, 243) + COLOR_ATHS_SPECIAL: (236, 235, 206) + COLOR_AZURE_MIST: (240, 255, 255) + COLOR_AZUREISH_WHITE: (219, 233, 244) + COLOR_BABY_POWDER: (254, 254, 250) + COLOR_BAJA_WHITE: (255, 248, 209) + COLOR_BARLEY_WHITE: (255, 244, 206) + COLOR_BEIGE: (245, 245, 220) + COLOR_BIANCA: (252, 251, 243) + COLOR_BIZARRE: (238, 222, 218) + COLOR_BLACK_HAZE: (246, 247, 247) + COLOR_BLACK_SQUEEZE: (242, 250, 250) + COLOR_BLACK_WHITE: (255, 254, 246) + COLOR_BLANCHED_ALMOND: (255, 235, 205) + COLOR_BLEACH_WHITE: (254, 243, 216) + COLOR_BLUE_CHALK: (241, 233, 255) + COLOR_BLUE_ROMANCE: (210, 246, 222) + COLOR_BON_JOUR: (229, 224, 225) + COLOR_BONE: (227, 218, 201) + COLOR_BRIDAL_HEATH: (255, 250, 244) + COLOR_BRIDESMAID: (254, 240, 236) + COLOR_BUBBLES: (231, 254, 255) + COLOR_BUTTERY_WHITE: (255, 252, 234) + COLOR_CARARRA: (238, 238, 232) + COLOR_CARLA: (243, 255, 216) + COLOR_CAROUSEL_PINK: (249, 224, 237) + COLOR_CATSKILL_WHITE: (238, 246, 247) + COLOR_CERAMIC: (252, 255, 249) + COLOR_CHABLIS: (255, 244, 243) + COLOR_CHAMPAGNE: (247, 231, 206) + COLOR_CHARDON: (255, 243, 241) + COLOR_CHERUB: (248, 217, 233) + COLOR_CHILEAN_HEATH: (255, 253, 230) + COLOR_CHINA_IVORY: (252, 255, 231) + COLOR_CHROME_WHITE: (232, 241, 212) + COLOR_CINDERELLA: (253, 225, 220) + COLOR_CITRINE_WHITE: (250, 247, 214) + COLOR_CLASSIC_ROSE: (251, 204, 231) + COLOR_CLEAR_DAY: (233, 255, 253) + COLOR_COCONUT_CREAM: (248, 247, 220) + COLOR_CONCH: (201, 217, 210) + COLOR_CONCRETE: (242, 242, 242) + COLOR_CORN_FIELD: (248, 250, 205) + COLOR_CORNSILK: (255, 248, 220) + COLOR_COSMIC_LATTE: (255, 248, 231) + COLOR_COSMOS: (255, 216, 217) + COLOR_CREAM: (255, 253, 208) + COLOR_CUMULUS: (253, 255, 213) + COLOR_DAWN_PINK: (243, 233, 229) + COLOR_DERBY: (255, 238, 216) + COLOR_DESERT_STORM: (248, 248, 247) + COLOR_DEW: (234, 255, 254) + COLOR_DOUBLE_PEARL_LUSTA: (252, 244, 208) + COLOR_DUST_STORM: (229, 204, 201) + COLOR_EARLY_DAWN: (255, 249, 230) + COLOR_EBB: (233, 227, 227) + COLOR_ECRU_WHITE: (245, 243, 229) + COLOR_EGG_SOUR: (255, 244, 221) + COLOR_EGGSHELL: (240, 234, 214) + COLOR_FAIR_PINK: (255, 239, 236) + COLOR_FANTASY: (250, 243, 240) + COLOR_FETA: (240, 252, 234) + COLOR_FLORAL_WHITE: (255, 250, 240) + COLOR_FOAM: (216, 252, 250) + COLOR_FOG: (215, 208, 255) + COLOR_FORGET_ME_NOT: (255, 241, 238) + COLOR_FROST: (237, 245, 221) + COLOR_FROSTED_MINT: (219, 255, 248) + COLOR_FROSTEE: (228, 246, 231) + COLOR_GAINSBORO: (220, 220, 220) + COLOR_GALLERY: (239, 239, 239) + COLOR_GEYSER: (212, 223, 226) + COLOR_GHOST_WHITE: (248, 248, 255) + COLOR_GIN: (232, 242, 235) + COLOR_GIN_FIZZ: (255, 249, 226) + COLOR_GLITTER: (230, 232, 250) + COLOR_GRANNY_APPLE: (213, 246, 227) + COLOR_GRAY_NURSE: (231, 236, 230) + COLOR_GREEN_WHITE: (232, 235, 224) + COLOR_HALF_COLONIAL_WHITE: (253, 246, 211) + COLOR_HALF_DUTCH_WHITE: (254, 247, 222) + COLOR_HALF_SPANISH_WHITE: (254, 244, 219) + COLOR_HALF_AND_HALF: (255, 254, 225) + COLOR_HARP: (230, 242, 234) + COLOR_HAWKES_BLUE: (212, 226, 252) + COLOR_HINT_OF_GREEN: (230, 255, 233) + COLOR_HINT_OF_RED: (251, 249, 249) + COLOR_HINT_OF_YELLOW: (250, 253, 228) + COLOR_HONEYDEW: (240, 255, 240) + COLOR_IRON: (212, 215, 217) + COLOR_ISABELLINE: (244, 240, 236) + COLOR_ISLAND_SPICE: (255, 252, 238) + COLOR_IVORY: (255, 255, 240) + COLOR_JANNA: (244, 235, 211) + COLOR_KARRY: (255, 234, 212) + COLOR_KIDNAPPER: (225, 234, 212) + COLOR_LANGUID_LAVENDER: (214, 202, 221) + COLOR_LAVENDER_BLUSH: (255, 240, 245) + COLOR_LAVENDER_MIST: (230, 230, 250) + COLOR_LEMON_CHIFFON: (255, 250, 205) + COLOR_LIGHT_CYAN: (224, 255, 255) + COLOR_LIGHT_GOLDENROD_YELLOW: (250, 250, 210) + COLOR_LIGHT_GRAY: (211, 211, 211) + COLOR_LIGHT_YELLOW: (255, 255, 224) + COLOR_LILY_WHITE: (231, 248, 255) + COLOR_LINEN: (250, 240, 230) + COLOR_LINK_WATER: (217, 228, 245) + COLOR_LOAFER: (238, 244, 222) + COLOR_LOLA: (223, 207, 219) + COLOR_LUMBER: (255, 228, 205) + COLOR_MABEL: (217, 247, 255) + COLOR_MAGNOLIA: (248, 244, 255) + COLOR_MERCURY: (229, 229, 229) + COLOR_MERINO: (246, 240, 230) + COLOR_MILK_PUNCH: (255, 246, 212) + COLOR_MIMOSA: (248, 253, 211) + COLOR_MINT_CREAM: (245, 255, 250) + COLOR_MISCHKA: (209, 210, 221) + COLOR_MISTY_ROSE: (255, 228, 225) + COLOR_MOON_GLOW: (252, 254, 218) + COLOR_MOON_MIST: (220, 221, 204) + COLOR_MOON_RAKER: (214, 206, 246) + COLOR_NARVIK: (237, 249, 241) + COLOR_NEBULA: (203, 219, 214) + COLOR_NYANZA: (233, 255, 219) + COLOR_OASIS: (254, 239, 206) + COLOR_OFF_GREEN: (230, 248, 243) + COLOR_OFF_YELLOW: (254, 249, 227) + COLOR_OLD_LACE: (253, 245, 230) + COLOR_ORANGE_WHITE: (254, 252, 237) + COLOR_ORCHID_WHITE: (255, 253, 243) + COLOR_ORINOCO: (243, 251, 212) + COLOR_OTTOMAN: (233, 248, 237) + COLOR_OYSTER_BAY: (218, 250, 255) + COLOR_OYSTER_PINK: (233, 206, 205) + COLOR_PALE_LAVENDER: (220, 208, 255) + COLOR_PALE_PINK: (250, 218, 221) + COLOR_PALE_ROSE: (255, 225, 242) + COLOR_PAMPAS: (244, 242, 238) + COLOR_PANACHE: (234, 246, 238) + COLOR_PAPAYA_WHIP: (255, 239, 213) + COLOR_PARCHMENT: (241, 233, 210) + COLOR_PARIS_WHITE: (202, 220, 212) + COLOR_PATTENS_BLUE: (222, 245, 255) + COLOR_PEACH_CREAM: (255, 240, 219) + COLOR_PEACH_SCHNAPPS: (255, 220, 214) + COLOR_PEARL_BUSH: (232, 224, 213) + COLOR_PEARL_LUSTA: (252, 244, 220) + COLOR_PEPPERMINT: (227, 245, 225) + COLOR_PERIGLACIAL_BLUE: (225, 230, 214) + COLOR_PIG_PINK: (253, 215, 228) + COLOR_PIGGY_PINK: (253, 221, 230) + COLOR_PINK_LACE: (255, 221, 244) + COLOR_PINK_LADY: (255, 241, 216) + COLOR_PIPI: (254, 244, 204) + COLOR_PIPPIN: (255, 225, 223) + COLOR_PLATINUM: (229, 228, 226) + COLOR_POLAR: (229, 249, 246) + COLOR_PORCELAIN: (239, 242, 243) + COLOR_POT_POURRI: (245, 231, 226) + COLOR_PRIM: (240, 226, 236) + COLOR_PROVINCIAL_PINK: (254, 245, 241) + COLOR_QUARTER_PEARL_LUSTA: (255, 253, 244) + COLOR_QUARTER_SPANISH_WHITE: (247, 242, 225) + COLOR_QUEEN_PINK: (232, 204, 215) + COLOR_QUILL_GRAY: (214, 214, 209) + COLOR_REMY: (254, 235, 243) + COLOR_RICE_CAKE: (255, 254, 240) + COLOR_RICE_FLOWER: (238, 255, 226) + COLOR_ROMANCE: (255, 254, 253) + COLOR_ROSE_WHITE: (255, 246, 245) + COLOR_RUM_SWIZZLE: (249, 248, 228) + COLOR_SALTPAN: (241, 247, 242) + COLOR_SATIN_LINEN: (230, 228, 212) + COLOR_SAUVIGNON: (255, 245, 243) + COLOR_SAZERAC: (255, 244, 224) + COLOR_SCOTCH_MIST: (255, 251, 220) + COLOR_SEASHELL: (255, 245, 238) + COLOR_SELAGO: (240, 238, 253) + COLOR_SERENADE: (255, 244, 232) + COLOR_SHAMPOO: (255, 207, 241) + COLOR_SKEPTIC: (202, 230, 218) + COLOR_SNOW: (255, 250, 250) + COLOR_SNOW_DRIFT: (247, 250, 247) + COLOR_SNOW_FLURRY: (228, 255, 209) + COLOR_SNOWY_MINT: (214, 255, 219) + COLOR_SNUFF: (226, 216, 237) + COLOR_SOAPSTONE: (255, 251, 249) + COLOR_SOFT_PEACH: (245, 237, 239) + COLOR_SOLITAIRE: (254, 248, 226) + COLOR_SOLITUDE: (234, 246, 255) + COLOR_SPRING_SUN: (246, 255, 220) + COLOR_SPRING_WOOD: (248, 246, 241) + COLOR_SUGAR_CANE: (249, 255, 246) + COLOR_SURF_CREST: (207, 229, 210) + COLOR_SWANS_DOWN: (220, 240, 234) + COLOR_SWISS_COFFEE: (221, 214, 213) + COLOR_TARA: (225, 246, 232) + COLOR_TASMAN: (207, 220, 207) + COLOR_TIMBERWOLF: (219, 215, 210) + COLOR_TITAN_WHITE: (240, 238, 255) + COLOR_TRANQUIL: (230, 255, 255) + COLOR_TRAVERTINE: (255, 253, 232) + COLOR_TUFT_BUSH: (255, 221, 205) + COLOR_TUTU: (255, 241, 249) + COLOR_TWILIGHT: (228, 207, 222) + COLOR_TWILIGHT_BLUE: (238, 253, 255) + COLOR_UNBLEACHED_SILK: (255, 221, 202) + COLOR_UNDERAGE_PINK: (249, 230, 244) + COLOR_VARDEN: (255, 246, 223) + COLOR_VISTA_WHITE: (252, 248, 247) + COLOR_WATUSI: (255, 221, 207) + COLOR_WE_PEEP: (247, 219, 230) + COLOR_WESTAR: (220, 217, 210) + COLOR_WHEATFIELD: (243, 237, 207) + COLOR_WHISPER: (247, 245, 250) + COLOR_WHITE: (255, 255, 255) + COLOR_WHITE_ICE: (221, 249, 241) + COLOR_WHITE_LILAC: (248, 247, 252) + COLOR_WHITE_LINEN: (248, 240, 232) + COLOR_WHITE_POINTER: (254, 248, 255) + COLOR_WHITE_ROCK: (234, 232, 212) + COLOR_WHITE_SMOKE: (245, 245, 245) + COLOR_WILD_SAND: (244, 244, 244) + COLOR_WILLOW_BROOK: (223, 236, 218) + COLOR_WISP_PINK: (254, 244, 248) + COLOR_ZANAH: (218, 236, 214) + COLOR_ZIRCON: (244, 248, 255) + COLOR_ZUMTHOR: (237, 246, 255) + confusion: + COLOR_ELECTRIC_PURPLE: (191, 0, 255) + COLOR_ELECTRIC_VIOLET: (139, 0, 255) + COLOR_PSYCHEDELIC_PURPLE: (223, 0, 255) + COLOR_VERONICA: (160, 32, 240) + COLOR_VIVID_MULBERRY: (184, 12, 227) + COLOR_VIVID_ORCHID: (204, 0, 255) + COLOR_VIVID_VIOLET: (159, 0, 255) + COLOR_HOT_MAGENTA: (255, 29, 206) + COLOR_METAL_PINK: (255, 0, 253) + COLOR_DEEP_MAGENTA: (204, 0, 204) + COLOR_FUCHSIA: (255, 0, 255) + surprise: + COLOR_HELIOTROPE: (223, 115, 255) + COLOR_MEDIUM_ORCHID: (186, 85, 211) + COLOR_PINK_FLAMINGO: (252, 116, 253) + COLOR_RICH_LILAC: (182, 102, 210) + COLOR_BAKER_MILLER_PINK: (255, 145, 175) + COLOR_BLUSH: (222, 93, 131) + COLOR_BRILLIANT_ROSE: (255, 85, 163) + COLOR_BRINK_PINK: (251, 96, 127) + COLOR_CARISSMA: (234, 136, 168) + COLOR_CHARM: (212, 116, 148) + COLOR_CHARM_PINK: (230, 143, 172) + COLOR_CINNAMON_SATIN: (205, 96, 126) + COLOR_CYCLAMEN: (245, 111, 161) + COLOR_DARK_PINK: (231, 84, 128) + COLOR_DEEP_BLUSH: (228, 118, 152) + COLOR_FANDANGO_PINK: (222, 82, 133) + COLOR_FLAMINGO_PINK: (252, 142, 172) + COLOR_FRENCH_PINK: (253, 108, 158) + COLOR_FRENCH_ROSE: (246, 74, 138) + COLOR_FROLY: (245, 117, 132) + COLOR_GERALDINE: (251, 137, 137) + COLOR_HOPBUSH: (208, 109, 161) + COLOR_HOT_PINK: (255, 105, 180) + COLOR_LIGHT_CORAL: (240, 128, 128) + COLOR_LIGHT_CRIMSON: (245, 105, 145) + COLOR_LIGHT_DEEP_PINK: (255, 92, 205) + COLOR_LIGHT_FUCHSIA_PINK: (249, 132, 239) + COLOR_LIGHT_SALMON_PINK: (255, 153, 153) + COLOR_MAUVELOUS: (239, 152, 170) + COLOR_MULBERRY: (197, 75, 140) + COLOR_MYSTIC: (214, 82, 130) + COLOR_NEW_YORK_PINK: (215, 131, 127) + COLOR_ORCHID: (218, 112, 214) + COLOR_PALE_MAGENTA: (249, 132, 229) + COLOR_PALE_RED_VIOLET: (219, 112, 147) + COLOR_PANTONE_PINK: (215, 72, 148) + COLOR_PERSIAN_PINK: (247, 127, 190) + COLOR_PINK_SHERBET: (247, 143, 167) + COLOR_PRINCESS_PERFUME: (255, 133, 207) + COLOR_PURPLE_PIZZAZZ: (254, 78, 218) + COLOR_RASPBERRY_PINK: (226, 80, 152) + COLOR_RAZZLE_DAZZLE_ROSE: (255, 51, 204) + COLOR_ROSE_BONBON: (249, 66, 158) + COLOR_ROSE_PINK: (255, 102, 204) + COLOR_RUDDY_PINK: (225, 142, 150) + COLOR_SALMON_PINK: (255, 145, 164) + COLOR_SEA_PINK: (237, 152, 158) + COLOR_SHIMMERING_BLUSH: (217, 134, 149) + COLOR_SHOCKING: (226, 146, 192) + COLOR_SKY_MAGENTA: (207, 113, 175) + COLOR_STRAWBERRY: (252, 90, 141) + COLOR_SUPER_PINK: (207, 107, 169) + COLOR_THULIAN_PINK: (222, 111, 161) + COLOR_TICKLE_ME_PINK: (252, 137, 172) + COLOR_TULIP: (255, 135, 141) + COLOR_VANILLA_ICE: (243, 143, 169) + COLOR_VIOLET_RED: (247, 83, 148) + COLOR_VIVID_TANGERINE: (255, 160, 137) + COLOR_WILD_ORCHID: (212, 112, 162) + COLOR_WILD_STRAWBERRY: (255, 67, 164) + COLOR_WILD_WATERMELON: (252, 108, 133) + COLOR_DEEP_FUCHSIA: (193, 84, 193) + COLOR_FRENCH_MAUVE: (212, 115, 212) + COLOR_FUCHSIA_PINK: (255, 119, 255) + COLOR_LAVENDER_MAGENTA: (238, 130, 238) + COLOR_STEEL_PINK: (204, 51, 204) + COLOR_ULTRA_PINK: (255, 111, 255) + nervousness: + COLOR_ALABAMA_CRIMSON: (175, 0, 42) + COLOR_AUBERGINE: (59, 9, 16) + COLOR_BARN_RED: (124, 10, 2) + COLOR_BAROSSA: (68, 1, 45) + COLOR_BLACK_BEAN: (61, 12, 2) + COLOR_BLACK_ROSE: (103, 3, 45) + COLOR_BLACKBERRY: (77, 1, 53) + COLOR_BORDEAUX: (92, 1, 32) + COLOR_BRACKEN: (74, 42, 4) + COLOR_BRIGHT_RED: (177, 0, 0) + COLOR_BROWN_BRAMBLE: (89, 40, 4) + COLOR_BROWN_DERBY: (73, 38, 21) + COLOR_BROWN_POD: (64, 24, 1) + COLOR_BROWN_TUMBLEWEED: (55, 41, 14) + COLOR_BULGARIAN_ROSE: (72, 6, 7) + COLOR_BURGUNDY: (128, 0, 32) + COLOR_BURNT_MAROON: (66, 3, 3) + COLOR_CAB_SAV: (77, 10, 24) + COLOR_CARMINE: (150, 0, 24) + COLOR_CASTRO: (82, 0, 31) + COLOR_CEDAR: (62, 28, 20) + COLOR_CEDAR_WOOD_FINISH: (113, 26, 0) + COLOR_CHERRYWOOD: (101, 26, 20) + COLOR_CIOCCOLATO: (85, 40, 12) + COLOR_CLARET: (127, 23, 52) + COLOR_CLINKER: (55, 29, 9) + COLOR_COCOA_BEAN: (72, 28, 28) + COLOR_COLA: (63, 37, 0) + COLOR_CRATER_BROWN: (70, 36, 37) + COLOR_CRIMSON_GLORY: (190, 0, 50) + COLOR_CRIMSON_RED: (153, 0, 0) + COLOR_DARK_BURGUNDY: (119, 15, 5) + COLOR_DARK_CANDY_APPLE_RED: (164, 0, 0) + COLOR_DARK_EBONY: (60, 32, 5) + COLOR_DARK_RED: (139, 0, 0) + COLOR_DARK_SCARLET: (86, 3, 25) + COLOR_DARK_SIENNA: (60, 20, 20) + COLOR_DEEP_MAROON: (130, 0, 0) + COLOR_DEEP_OAK: (65, 32, 16) + COLOR_DEEP_RED: (133, 1, 1) + COLOR_ENGINEERING_INTERNATIONAL_ORANGE: (186, 22, 12) + COLOR_FALU_RED: (128, 24, 24) + COLOR_FRENCH_PUCE: (78, 22, 9) + COLOR_GUARDSMAN_RED: (186, 1, 1) + COLOR_HEATH: (84, 16, 18) + COLOR_INDIAN_TAN: (77, 30, 1) + COLOR_JAPANESE_MAPLE: (120, 1, 9) + COLOR_JON: (59, 31, 31) + COLOR_KENYAN_COPPER: (124, 28, 5) + COLOR_LONESTAR: (109, 1, 1) + COLOR_MAROON: (128, 0, 0) + COLOR_MAROON_OAK: (82, 12, 23) + COLOR_MERLOT: (131, 25, 35) + COLOR_MILANO_RED: (184, 17, 4) + COLOR_MOCCACCINO: (110, 29, 20) + COLOR_MONARCH: (139, 7, 35) + COLOR_MORDANT_RED: (174, 12, 0) + COLOR_MOROCCO_BROWN: (68, 29, 0) + COLOR_MULBERRY_WOOD: (92, 5, 54) + COLOR_PACO: (65, 31, 16) + COLOR_PAPRIKA: (141, 2, 38) + COLOR_PERSIAN_PLUM: (112, 28, 28) + COLOR_PINK_RASPBERRY: (152, 0, 54) + COLOR_POHUTUKAWA: (143, 2, 28) + COLOR_REBEL: (60, 18, 6) + COLOR_RED_BERRY: (142, 0, 0) + COLOR_RED_DEVIL: (134, 1, 17) + COLOR_RED_OXIDE: (110, 9, 2) + COLOR_ROSEWOOD: (101, 0, 11) + COLOR_RUBY_RED: (155, 17, 30) + COLOR_RUFOUS: (168, 28, 7) + COLOR_RUSTIC_RED: (72, 4, 4) + COLOR_SAMBUCA: (58, 32, 16) + COLOR_SANGRIA: (146, 0, 10) + COLOR_SCARLETT: (149, 0, 21) + COLOR_SEAL_BROWN: (89, 38, 11) + COLOR_SHIRAZ: (178, 9, 49) + COLOR_SIREN: (122, 1, 58) + COLOR_SPARTAN_CRIMSON: (158, 19, 22) + COLOR_TAMARILLO: (153, 22, 19) + COLOR_TAMARIND: (52, 21, 21) + COLOR_TEMPTRESS: (59, 0, 11) + COLOR_TOLEDO: (58, 0, 32) + COLOR_TOTEM_POLE: (153, 27, 7) + COLOR_TREEHOUSE: (59, 40, 32) + COLOR_TYRIAN_PURPLE: (102, 2, 60) + COLOR_UP_MAROON: (123, 17, 19) + COLOR_VAN_CLEEF: (73, 23, 12) + COLOR_WINE_BERRY: (89, 29, 53) + COLOR_ACADIA: (27, 20, 4) + COLOR_ASPHALT: (19, 10, 6) + COLOR_CANNON_BLACK: (37, 23, 6) + COLOR_COFFEE_BEAN: (42, 20, 14) + COLOR_CREOLE: (30, 15, 4) + COLOR_CROWSHEAD: (28, 18, 8) + COLOR_DIESEL: (19, 0, 0) + COLOR_ECLIPSE: (49, 28, 23) + COLOR_EL_PASO: (30, 23, 8) + COLOR_ETERNITY: (33, 26, 14) + COLOR_GONDOLA: (38, 20, 20) + COLOR_GRAPHITE: (37, 22, 7) + COLOR_JACARANDA: (46, 3, 41) + COLOR_JACKO_BEAN: (46, 25, 5) + COLOR_KARAKA: (30, 22, 9) + COLOR_KILAMANJARO: (36, 12, 2) + COLOR_LICORICE: (26, 17, 16) + COLOR_MELANZANE: (48, 5, 41) + COLOR_MIKADO: (45, 37, 16) + COLOR_NERO: (20, 6, 0) + COLOR_NIGHT_RIDER: (31, 18, 15) + COLOR_OIL: (40, 30, 21) + COLOR_ONION: (47, 39, 14) + COLOR_SEPIA_BLACK: (43, 2, 2) + COLOR_SMOKY_BLACK: (16, 12, 8) + COLOR_WOOD_BARK: (38, 17, 5) + COLOR_WOODRUSH: (48, 42, 15) + COLOR_ZEUS: (41, 35, 25) + COLOR_ZINNWALDITE_BROWN: (44, 22, 8) + desire: + COLOR_ALIZARIN_CRIMSON: (227, 38, 54) + COLOR_BAMBOO: (218, 99, 4) + COLOR_BLAZE_ORANGE: (255, 103, 0) + COLOR_BURNT_ORANGE: (204, 85, 0) + COLOR_COQUELICOT: (255, 56, 0) + COLOR_FERRARI_RED: (255, 40, 0) + COLOR_GIANTS_ORANGE: (254, 90, 29) + COLOR_GRENADIER: (213, 70, 0) + COLOR_INTERNATIONAL_ORANGE: (255, 79, 0) + COLOR_LIGHT_BRILLIANT_RED: (254, 46, 46) + COLOR_ORANGE_RED: (255, 69, 0) + COLOR_ORIOLES_ORANGE: (251, 79, 20) + COLOR_PANTONE_ORANGE: (255, 88, 0) + COLOR_PERMANENT_GERANIUM_LAKE: (225, 44, 44) + COLOR_PERSIMMON: (236, 88, 0) + COLOR_POMEGRANATE: (243, 71, 35) + COLOR_RYB_RED: (254, 39, 18) + COLOR_RED_STAGE: (208, 95, 4) + COLOR_SCARLET: (255, 36, 0) + COLOR_SINOPIA: (203, 65, 11) + COLOR_SPANISH_ORANGE: (232, 97, 0) + COLOR_TANGELO: (249, 77, 0) + COLOR_TENNE: (205, 87, 0) + COLOR_TRINIDAD: (230, 78, 3) + COLOR_VERMILION: (217, 56, 30) + COLOR_VIVID_ORANGE: (255, 95, 0) + COLOR_WILLPOWER_ORANGE: (253, 88, 0) + remorse: + COLOR_ALLOY_ORANGE: (196, 98, 16) + COLOR_ALPINE: (175, 143, 44) + COLOR_AMARANTH_PURPLE: (171, 39, 79) + COLOR_ANTIQUE_BRONZE: (102, 93, 30) + COLOR_ANTIQUE_RUBY: (132, 27, 45) + COLOR_APPLE_BLOSSOM: (175, 77, 67) + COLOR_AUBURN: (165, 42, 42) + COLOR_BIG_DIP_ORUBY: (156, 37, 66) + COLOR_BIRCH: (55, 48, 33) + COLOR_BISTRE: (61, 43, 31) + COLOR_BLACK_MARLIN: (62, 44, 28) + COLOR_BOLE: (121, 68, 59) + COLOR_BOURBON: (186, 111, 30) + COLOR_BRIGHT_MAROON: (195, 33, 72) + COLOR_BRONZE_OLIVE: (78, 66, 12) + COLOR_BRONZE_YELLOW: (115, 112, 0) + COLOR_BRONZETONE: (77, 64, 15) + COLOR_BROWN: (150, 75, 0) + COLOR_BROWN_RUST: (175, 89, 62) + COLOR_BUCCANEER: (98, 47, 48) + COLOR_BULL_SHOT: (134, 77, 30) + COLOR_BURNT_UMBER: (138, 51, 36) + COLOR_BUTTERED_RUM: (161, 117, 13) + COLOR_CAFE_NOIR: (75, 54, 33) + COLOR_CAFE_ROYALE: (111, 68, 12) + COLOR_CAMOUFLAGE: (60, 57, 16) + COLOR_CAPE_PALLISER: (162, 102, 69) + COLOR_CAPUT_MORTUUM: (89, 39, 32) + COLOR_CARDINAL: (196, 30, 58) + COLOR_CARNABY_TAN: (92, 46, 1) + COLOR_CARNELIAN: (179, 27, 27) + COLOR_CATAWBA: (112, 54, 66) + COLOR_CHELSEA_GEM: (158, 83, 2) + COLOR_CHESTNUT: (149, 69, 53) + COLOR_CHINESE_RED: (170, 56, 30) + COLOR_CHOCOLATE: (123, 63, 0) + COLOR_COCONUT: (150, 90, 62) + COLOR_COFFEE: (111, 78, 55) + COLOR_COGNAC: (159, 56, 29) + COLOR_CONGO_BROWN: (89, 55, 55) + COLOR_COPPER: (184, 115, 51) + COLOR_COPPER_CANYON: (126, 58, 21) + COLOR_COPPER_RUST: (148, 71, 71) + COLOR_CORDOVAN: (137, 63, 69) + COLOR_CORK: (64, 41, 29) + COLOR_CORN_HARVEST: (139, 107, 11) + COLOR_COSTA_DEL_SOL: (97, 93, 48) + COLOR_COWBOY: (77, 40, 45) + COLOR_CRAIL: (185, 81, 64) + COLOR_CROWN_OF_THORNS: (119, 31, 31) + COLOR_CUMIN: (146, 67, 33) + COLOR_DALLAS: (110, 75, 38) + COLOR_DARK_BROWN: (101, 67, 33) + COLOR_DARK_PASTEL_RED: (194, 59, 34) + COLOR_DARK_YELLOW: (155, 135, 12) + COLOR_DEEP_BRONZE: (74, 48, 4) + COLOR_DEEP_CARMINE: (169, 32, 62) + COLOR_DEEP_CHESTNUT: (185, 78, 72) + COLOR_DESERT: (174, 96, 32) + COLOR_DOGS: (184, 109, 41) + COLOR_DONKEY_BROWN: (102, 76, 40) + COLOR_EL_SALVA: (143, 62, 51) + COLOR_ENGLISH_RED: (171, 75, 82) + COLOR_ENGLISH_WALNUT: (62, 43, 35) + COLOR_ESPRESSO: (97, 39, 24) + COLOR_FIELD_DRAB: (108, 84, 30) + COLOR_FIERY_ORANGE: (179, 82, 19) + COLOR_FIRE: (170, 66, 3) + COLOR_FIREBRICK: (178, 34, 34) + COLOR_FRENCH_WINE: (172, 30, 68) + COLOR_GAMBOGE_ORANGE: (153, 102, 0) + COLOR_GINGER: (176, 101, 0) + COLOR_GOLDEN_BROWN: (153, 101, 21) + COLOR_GOLDEN_GATE_BRIDGE: (192, 54, 44) + COLOR_GRIZZLY: (136, 88, 24) + COLOR_HACIENDA: (152, 129, 27) + COLOR_HAIRY_HEATH: (107, 42, 20) + COLOR_HAWAIIAN_TAN: (157, 86, 22) + COLOR_HIMALAYA: (106, 93, 27) + COLOR_HORSES: (84, 61, 55) + COLOR_HORSES_NECK: (96, 73, 19) + COLOR_HOT_TODDY: (179, 128, 7) + COLOR_INDOCHINE: (194, 107, 3) + COLOR_IRISH_COFFEE: (95, 61, 38) + COLOR_IROKO: (67, 49, 32) + COLOR_IRONSTONE: (134, 72, 60) + COLOR_JAMBALAYA: (91, 48, 19) + COLOR_JAPANESE_CARMINE: (157, 41, 51) + COLOR_JUDGE_GRAY: (84, 67, 51) + COLOR_KOBICHA: (107, 68, 35) + COLOR_KORMA: (143, 75, 14) + COLOR_KUMERA: (136, 98, 33) + COLOR_LIGHT_BROWN: (181, 101, 29) + COLOR_LISBON_BROWN: (66, 57, 33) + COLOR_LIVID_BROWN: (77, 40, 46) + COLOR_LOTUS: (134, 60, 60) + COLOR_LUXOR_GOLD: (167, 136, 44) + COLOR_MADRAS: (63, 48, 2) + COLOR_MAHOGANY: (192, 64, 0) + COLOR_MAI_TAI: (176, 102, 8) + COLOR_MANDALAY: (173, 120, 27) + COLOR_METALLIC_BRONZE: (73, 55, 27) + COLOR_METALLIC_COPPER: (113, 41, 29) + COLOR_METALLIC_SUNBURST: (156, 124, 56) + COLOR_MEXICAN_RED: (167, 37, 37) + COLOR_MILLBROOK: (89, 68, 51) + COLOR_MOCHA: (120, 45, 25) + COLOR_MOJO: (192, 71, 55) + COLOR_MONDO: (74, 60, 48) + COLOR_MULE_FAWN: (140, 71, 47) + COLOR_NIGHT_SHADZ: (170, 55, 90) + COLOR_NUTMEG: (129, 66, 44) + COLOR_NUTMEG_WOOD_FINISH: (104, 54, 0) + COLOR_OLD_BRICK: (144, 30, 30) + COLOR_OLD_BURGUNDY: (67, 48, 46) + COLOR_OLD_COPPER: (114, 74, 47) + COLOR_OLIVE_DRAB_SEVEN: (60, 52, 31) + COLOR_OLIVETONE: (113, 110, 16) + COLOR_ORANGE_ROUGHY: (196, 87, 25) + COLOR_OREGON: (155, 71, 3) + COLOR_ORGAN: (108, 46, 31) + COLOR_PAARL: (166, 85, 41) + COLOR_PALE_CARMINE: (175, 64, 53) + COLOR_PEANUT: (120, 47, 22) + COLOR_PERU_TAN: (127, 58, 2) + COLOR_PESTO: (124, 118, 49) + COLOR_PICKLED_BEAN: (110, 72, 38) + COLOR_POTTERS_CLAY: (140, 87, 56) + COLOR_PRAIRIE_SAND: (154, 56, 32) + COLOR_PUEBLO: (125, 44, 20) + COLOR_PULLMAN_BROWN: (100, 65, 23) + COLOR_PULLMAN_GREEN: (59, 51, 28) + COLOR_PUMPKIN_SKIN: (177, 97, 11) + COLOR_PUNGA: (77, 61, 20) + COLOR_QUINCY: (98, 63, 45) + COLOR_RED_BEECH: (123, 56, 1) + COLOR_RED_ROBIN: (128, 52, 31) + COLOR_REEF_GOLD: (159, 130, 28) + COLOR_RENO_SAND: (168, 101, 21) + COLOR_RICH_GOLD: (168, 83, 7) + COLOR_ROAST_COFFEE: (112, 66, 65) + COLOR_ROCK: (77, 56, 51) + COLOR_ROCK_SPRAY: (186, 69, 12) + COLOR_ROOF_TERRACOTTA: (166, 47, 32) + COLOR_ROPE: (142, 77, 30) + COLOR_ROSE_VALE: (171, 78, 82) + COLOR_ROSE_OF_SHARON: (191, 85, 0) + COLOR_RUDDY_BROWN: (187, 101, 40) + COLOR_RUSSET: (128, 70, 27) + COLOR_RUST: (183, 65, 14) + COLOR_RUSTY_NAIL: (134, 86, 10) + COLOR_SADDLE: (76, 48, 36) + COLOR_SADDLE_BROWN: (139, 69, 19) + COLOR_SAND_DUNE: (150, 113, 23) + COLOR_SANGUINE_BROWN: (141, 61, 56) + COLOR_SEPIA: (112, 66, 20) + COLOR_SEPIA_SKIN: (158, 91, 64) + COLOR_SHINGLE_FAWN: (107, 78, 49) + COLOR_SIENNA: (136, 45, 23) + COLOR_SMOKEY_TOPAZ: (131, 42, 13) + COLOR_SMOKY_TOPAZ: (147, 61, 65) + COLOR_SOLID_PINK: (137, 56, 67) + COLOR_SPANISH_BISTRE: (128, 117, 50) + COLOR_SPICE: (106, 68, 46) + COLOR_SPICY_MUSTARD: (116, 100, 13) + COLOR_STILETTO: (156, 51, 54) + COLOR_SWEET_BROWN: (168, 55, 49) + COLOR_TABASCO: (160, 39, 18) + COLOR_TALL_POPPY: (179, 45, 41) + COLOR_TAUPE: (72, 60, 50) + COLOR_TAWNY_PORT: (105, 37, 69) + COLOR_THATCH_GREEN: (64, 61, 25) + COLOR_THUNDER: (51, 41, 47) + COLOR_THUNDERBIRD: (192, 43, 24) + COLOR_TIA_MARIA: (193, 68, 14) + COLOR_TOSCA: (141, 63, 63) + COLOR_UNIVERSITY_OF_CALIFORNIA_GOLD: (183, 135, 39) + COLOR_UPSDELL_RED: (174, 32, 41) + COLOR_VAN_DYKE_BROWN: (102, 66, 40) + COLOR_VESUVIUS: (177, 74, 11) + COLOR_VIVID_AUBURN: (146, 39, 36) + COLOR_VIVID_BURGUNDY: (159, 29, 53) + COLOR_WALNUT: (119, 63, 26) + COLOR_WELL_READ: (180, 51, 50) + COLOR_WEST_COAST: (98, 81, 25) + COLOR_WINDSOR_TAN: (167, 85, 2) + COLOR_WINE: (114, 47, 55) + COLOR_WOODY_BROWN: (72, 49, 49) + COLOR_YUKON_GOLD: (123, 102, 8) + COLOR_APPLE_GREEN: (141, 182, 0) + COLOR_ARMY_GREEN: (75, 83, 32) + COLOR_AVOCADO: (86, 130, 3) + COLOR_CHRISTI: (103, 167, 18) + COLOR_CRETE: (115, 120, 41) + COLOR_DARK_MOSS_GREEN: (74, 93, 35) + COLOR_FERN_FROND: (101, 114, 32) + COLOR_LIMEADE: (111, 157, 2) + COLOR_PACIFIKA: (119, 129, 32) + COLOR_RANGITOTO: (46, 50, 34) + COLOR_SARATOGA: (85, 91, 16) + COLOR_TRENDY_GREEN: (124, 136, 26) + COLOR_VERDUN_GREEN: (73, 84, 0) + COLOR_WAIOURU: (54, 60, 13) + COLOR_WASABI: (120, 138, 37) + COLOR_WOODLAND: (77, 83, 40) + COLOR_OLIVE: (128, 128, 0) + annoyance: + COLOR_AMARANTH: (229, 43, 80) + COLOR_AZTEC_GOLD: (195, 153, 83) + COLOR_BIG_FOOT_FEET: (232, 142, 90) + COLOR_BITTERSWEET: (254, 111, 94) + COLOR_BITTERSWEET_SHIMMER: (191, 79, 81) + COLOR_BRICK_RED: (203, 65, 84) + COLOR_BRONZE: (205, 127, 50) + COLOR_BURNING_ORANGE: (255, 112, 52) + COLOR_BURNT_SIENNA: (233, 116, 81) + COLOR_CG_RED: (224, 60, 49) + COLOR_CABARET: (217, 73, 114) + COLOR_CADMIUM_ORANGE: (237, 135, 45) + COLOR_CARMINE_PINK: (235, 76, 66) + COLOR_CARNATION: (249, 90, 97) + COLOR_CEDAR_CHEST: (201, 90, 73) + COLOR_CINNABAR: (227, 66, 52) + COLOR_COCOA_BROWN: (210, 105, 30) + COLOR_CONGO_PINK: (248, 131, 121) + COLOR_CONTESSA: (198, 114, 107) + COLOR_COPPER_RED: (203, 109, 81) + COLOR_CORAL: (255, 127, 80) + COLOR_CORAL_RED: (255, 64, 64) + COLOR_CRANBERRY: (219, 80, 121) + COLOR_CRAYOLA_ORANGE: (255, 117, 56) + COLOR_CRAYOLA_RED: (238, 32, 77) + COLOR_CRUSTA: (253, 123, 51) + COLOR_DARK_CORAL: (205, 91, 69) + COLOR_DARK_SALMON: (233, 150, 122) + COLOR_DARK_TERRA_COTTA: (204, 78, 92) + COLOR_DEEP_CARMINE_PINK: (239, 48, 56) + COLOR_DEEP_CARROT_ORANGE: (233, 105, 44) + COLOR_DESIRE: (234, 60, 83) + COLOR_DI_SERRIA: (219, 153, 94) + COLOR_DINGY_DUNGEON: (197, 49, 81) + COLOR_EARTH_YELLOW: (225, 169, 95) + COLOR_ENGLISH_VERMILLION: (204, 71, 75) + COLOR_FIERY_ROSE: (255, 84, 112) + COLOR_FLAME: (226, 88, 34) + COLOR_FLAME_PEA: (218, 91, 56) + COLOR_FLAMINGO: (242, 85, 42) + COLOR_FLUSH_MAHOGANY: (202, 52, 53) + COLOR_FRENCH_RASPBERRY: (199, 44, 72) + COLOR_FUZZY_WUZZY: (204, 102, 102) + COLOR_FUZZY_WUZZY_BROWN: (196, 86, 85) + COLOR_IMPERIAL_RED: (237, 41, 57) + COLOR_INDIAN_RED: (205, 92, 92) + COLOR_INDIAN_YELLOW: (227, 168, 87) + COLOR_JAFFA: (239, 134, 63) + COLOR_JAPONICA: (216, 124, 99) + COLOR_JASPER: (215, 59, 62) + COLOR_JELLY_BEAN: (218, 97, 78) + COLOR_KOROMIKO: (255, 189, 95) + COLOR_LIGHT_CARMINE_PINK: (230, 103, 113) + COLOR_LIGHT_SALMON: (255, 160, 122) + COLOR_MAGIC_POTION: (255, 68, 102) + COLOR_MANDARIN: (243, 122, 72) + COLOR_MANDY: (226, 84, 101) + COLOR_MANGO_TANGO: (255, 130, 67) + COLOR_MEDIUM_VERMILION: (217, 96, 59) + COLOR_NEON_CARROT: (255, 163, 67) + COLOR_NEON_FUCHSIA: (254, 65, 100) + COLOR_OGRE_ODOR: (253, 82, 64) + COLOR_ORANGE_SODA: (250, 91, 61) + COLOR_OUTRAGEOUS_ORANGE: (255, 110, 74) + COLOR_PALE_COPPER: (218, 138, 103) + COLOR_PARADISE_PINK: (230, 62, 98) + COLOR_PASTEL_RED: (255, 105, 97) + COLOR_PERSIAN_ORANGE: (217, 144, 88) + COLOR_PERSIAN_RED: (204, 51, 51) + COLOR_PERU: (205, 133, 63) + COLOR_PINK_ORANGE: (255, 153, 102) + COLOR_PIPER: (201, 99, 35) + COLOR_POPSTAR: (190, 79, 98) + COLOR_PORSCHE: (234, 174, 105) + COLOR_PORTLAND_ORANGE: (255, 90, 54) + COLOR_PUNCH: (220, 67, 51) + COLOR_RADICAL_RED: (255, 53, 94) + COLOR_RAJAH: (251, 171, 96) + COLOR_RAW_SIENNA: (214, 138, 89) + COLOR_RED_DAMASK: (218, 106, 65) + COLOR_RED_ORANGE: (255, 83, 73) + COLOR_RED_SALSA: (253, 58, 74) + COLOR_ROMAN: (222, 99, 96) + COLOR_ROTI: (198, 168, 75) + COLOR_RUBER: (206, 70, 118) + COLOR_RUSTY_RED: (218, 44, 67) + COLOR_SALMON: (250, 128, 114) + COLOR_SANDY_BROWN: (244, 164, 96) + COLOR_SIZZLING_RED: (255, 56, 85) + COLOR_SMASHED_PUMPKIN: (255, 109, 58) + COLOR_SUNBURNT_CYCLOPS: (255, 64, 76) + COLOR_SUNGLO: (225, 104, 101) + COLOR_SUNRAY: (227, 171, 87) + COLOR_SUNSET_ORANGE: (253, 94, 83) + COLOR_TAN_HIDE: (250, 157, 90) + COLOR_TANGO_PINK: (228, 113, 122) + COLOR_TART_ORANGE: (251, 77, 70) + COLOR_TERRA_COTTA: (226, 114, 91) + COLOR_TEXAS_ROSE: (255, 181, 85) + COLOR_TIGERS_EYE: (224, 141, 60) + COLOR_TOMATO: (255, 99, 71) + COLOR_TUSSOCK: (197, 153, 75) + COLOR_VALENCIA: (216, 68, 55) + COLOR_VIVID_RED_TANGELO: (223, 97, 36) + COLOR_VIVID_TANGELO: (240, 116, 39) + COLOR_VIVID_VERMILION: (229, 96, 36) + love: + COLOR_AMARANTH_RED: (211, 33, 45) + COLOR_AMERICAN_ROSE: (255, 3, 62) + COLOR_BOSTON_UNIVERSITY_RED: (204, 0, 0) + COLOR_CADMIUM_RED: (227, 0, 34) + COLOR_CANDY_APPLE_RED: (255, 8, 0) + COLOR_CARMINE_RED: (255, 0, 56) + COLOR_CRIMSON: (220, 20, 60) + COLOR_ELECTRIC_CRIMSON: (255, 0, 63) + COLOR_FIRE_ENGINE_RED: (206, 32, 41) + COLOR_HARVARD_CRIMSON: (201, 0, 22) + COLOR_KU_CRIMSON: (232, 0, 13) + COLOR_LAVA: (207, 16, 32) + COLOR_LUST: (230, 32, 32) + COLOR_MEDIUM_CANDY_APPLE_RED: (226, 6, 44) + COLOR_MONZA: (199, 3, 30) + COLOR_MUNSELL_RED: (242, 0, 60) + COLOR_NCS_RED: (196, 2, 51) + COLOR_PIGMENT_RED: (237, 28, 36) + COLOR_RED: (255, 0, 0) + COLOR_RED_RIBBON: (237, 10, 63) + COLOR_RICH_CARMINE: (215, 0, 64) + COLOR_ROSSO_CORSA: (212, 0, 0) + COLOR_RUDDY: (255, 0, 40) + COLOR_SPANISH_CARMINE: (209, 0, 71) + COLOR_SPANISH_RED: (230, 0, 38) + COLOR_TRACTOR_RED: (253, 14, 53) + COLOR_UTAH_CRIMSON: (211, 0, 63) + COLOR_VENETIAN_RED: (200, 8, 21) + COLOR_VIVID_CRIMSON: (204, 0, 51) + COLOR_VIVID_RED: (247, 13, 26) + admiration: + COLOR_AMBER: (255, 191, 0) + COLOR_BANANA_YELLOW: (255, 225, 53) + COLOR_BARBERRY: (222, 215, 23) + COLOR_BIRD_FLOWER: (212, 205, 22) + COLOR_BRIGHT_SUN: (254, 211, 60) + COLOR_CANDLELIGHT: (252, 217, 23) + COLOR_CITRINE: (228, 208, 10) + COLOR_CONFETTI: (233, 215, 90) + COLOR_CORN: (231, 191, 5) + COLOR_CREAM_CAN: (245, 200, 92) + COLOR_CYBER_YELLOW: (255, 211, 0) + COLOR_DANDELION: (240, 225, 48) + COLOR_DEEP_LEMON: (245, 199, 26) + COLOR_ENERGY_YELLOW: (248, 221, 92) + COLOR_GARGOYLE_GAS: (255, 223, 70) + COLOR_GOLDEN: (255, 215, 0) + COLOR_GOLDEN_DREAM: (240, 213, 45) + COLOR_GOLDEN_POPPY: (252, 194, 0) + COLOR_GOLDEN_TAINOI: (255, 204, 92) + COLOR_GOLDEN_YELLOW: (255, 223, 0) + COLOR_JONQUIL: (244, 202, 22) + COLOR_LIGHTNING_YELLOW: (252, 192, 30) + COLOR_MIKADO_YELLOW: (255, 196, 12) + COLOR_MINION_YELLOW: (245, 224, 80) + COLOR_MUNSELL_YELLOW: (239, 204, 0) + COLOR_MUSTARD: (255, 219, 88) + COLOR_NAPLES_YELLOW: (250, 218, 94) + COLOR_PANTONE_YELLOW: (254, 223, 0) + COLOR_PERIDOT: (230, 226, 0) + COLOR_PORTICA: (249, 230, 99) + COLOR_RIPE_LEMON: (244, 216, 28) + COLOR_RONCHI: (236, 197, 78) + COLOR_SAFETY_YELLOW: (238, 210, 2) + COLOR_SAFFRON: (244, 196, 48) + COLOR_SAFFRON_MANGO: (249, 191, 88) + COLOR_SANDSTORM: (236, 213, 64) + COLOR_SCHOOL_BUS_YELLOW: (255, 216, 0) + COLOR_SIZZLING_SUNRISE: (255, 219, 0) + COLOR_SUNFLOWER: (228, 212, 34) + COLOR_SUNGLOW: (255, 204, 51) + COLOR_SUPERNOVA: (255, 201, 1) + COLOR_TANGERINE_YELLOW: (255, 204, 0) + COLOR_TITANIUM_YELLOW: (238, 230, 0) + COLOR_TURBO: (250, 230, 0) + COLOR_VIVID_YELLOW: (255, 227, 2) + COLOR_WATTLE: (220, 215, 71) + COLOR_ACID_GREEN: (176, 191, 26) + COLOR_ALIEN_ARMPIT: (132, 222, 2) + COLOR_ATLANTIS: (151, 205, 45) + COLOR_BAHIA: (165, 203, 12) + COLOR_BITTER_LEMON: (202, 224, 13) + COLOR_CITRUS: (161, 197, 10) + COLOR_FUEGO: (190, 222, 13) + COLOR_INCH_WORM: (176, 227, 19) + COLOR_KEY_LIME_PIE: (191, 201, 33) + COLOR_LA_RIOJA: (179, 193, 16) + COLOR_LAS_PALMAS: (198, 230, 16) + COLOR_LIMERICK: (157, 194, 9) + COLOR_PEAR: (209, 226, 49) + COLOR_RIO_GRANDE: (187, 208, 9) + COLOR_SHEEN_GREEN: (143, 212, 0) + COLOR_VIVID_LIME_GREEN: (166, 214, 8) + realization: + COLOR_ANZAC: (224, 182, 70) + COLOR_BRIGHT_YELLOW: (255, 170, 29) + COLOR_BUDDHA_GOLD: (193, 160, 4) + COLOR_BUTTERCUP: (243, 173, 22) + COLOR_CALIFORNIA: (254, 157, 4) + COLOR_CASABLANCA: (248, 184, 83) + COLOR_CHROME_YELLOW: (255, 167, 0) + COLOR_DARK_TANGERINE: (255, 168, 18) + COLOR_DEEP_SAFFRON: (255, 153, 51) + COLOR_EARLS_GREEN: (201, 185, 59) + COLOR_FIRE_BUSH: (232, 153, 40) + COLOR_FUEL_YELLOW: (236, 169, 39) + COLOR_GALLIANO: (220, 178, 12) + COLOR_GAMBOGE: (228, 155, 15) + COLOR_GOLD_TIPS: (222, 186, 19) + COLOR_GOLDENROD: (218, 165, 32) + COLOR_HOKEY_POKEY: (200, 165, 40) + COLOR_LEMON_CURRY: (204, 160, 29) + COLOR_LEMON_GINGER: (172, 158, 34) + COLOR_LUCKY: (175, 159, 28) + COLOR_MARIGOLD: (234, 162, 33) + COLOR_MEAT_BROWN: (229, 183, 59) + COLOR_METALLIC_GOLD: (212, 175, 55) + COLOR_MY_SIN: (255, 179, 31) + COLOR_NUGGET: (197, 153, 34) + COLOR_OLD_GOLD: (207, 181, 59) + COLOR_ORANGE_PEEL: (255, 159, 0) + COLOR_PASTEL_ORANGE: (255, 179, 71) + COLOR_RYB_ORANGE: (251, 153, 2) + COLOR_SAHARA: (183, 162, 20) + COLOR_SATIN_SHEEN_GOLD: (203, 161, 53) + COLOR_SEA_BUCKTHORN: (251, 161, 41) + COLOR_SELECTIVE_YELLOW: (255, 186, 0) + COLOR_SUN: (251, 172, 19) + COLOR_SUNSHADE: (255, 158, 44) + COLOR_TREE_POPPY: (252, 156, 29) + COLOR_TULIP_TREE: (234, 179, 59) + COLOR_TURMERIC: (202, 187, 72) + COLOR_UCLA_GOLD: (255, 179, 0) + COLOR_UROBILIN: (225, 173, 33) + COLOR_VIVID_AMBER: (204, 153, 0) + COLOR_VIVID_GAMBOGE: (255, 153, 0) + COLOR_VIVID_ORANGE_PEEL: (255, 160, 0) + COLOR_WEB_ORANGE: (255, 165, 0) + COLOR_YELLOW_ORANGE: (255, 174, 66) + COLOR_YELLOW_SEA: (254, 169, 4) + COLOR_CITRON: (159, 169, 31) + optimism: + COLOR_AUREOLIN: (253, 238, 0) + COLOR_BROOM: (255, 236, 19) + COLOR_CADMIUM_YELLOW: (255, 246, 0) + COLOR_CANARY_YELLOW: (255, 239, 0) + COLOR_GORSE: (255, 241, 79) + COLOR_ICTERINE: (252, 247, 94) + COLOR_LEMON: (255, 247, 0) + COLOR_LEMON_YELLOW: (255, 244, 79) + COLOR_MAIZE: (251, 236, 93) + COLOR_YELLOW_ROSE: (255, 240, 0) + COLOR_ARCTIC_LIME: (208, 255, 20) + COLOR_CANARY: (243, 251, 98) + COLOR_CHARTREUSE: (223, 255, 0) + COLOR_ELECTRIC_LIME: (204, 255, 0) + COLOR_FRENCH_LIME: (158, 253, 56) + COLOR_GOLDEN_FIZZ: (245, 251, 61) + COLOR_GREEN_LIZARD: (167, 244, 50) + COLOR_GREEN_YELLOW: (173, 255, 47) + COLOR_LEMON_GLACIER: (253, 255, 0) + COLOR_LEMON_LIME: (227, 255, 0) + COLOR_LIME: (191, 255, 0) + COLOR_SPRING_BUD: (167, 252, 0) + COLOR_SPRING_FROST: (135, 255, 42) + COLOR_STARSHIP: (236, 242, 69) + COLOR_VOLT: (206, 255, 0) + COLOR_DAFFODIL: (255, 255, 49) + COLOR_ELECTRIC_YELLOW: (255, 255, 51) + COLOR_MAXIMUM_YELLOW: (250, 250, 55) + COLOR_RYB_YELLOW: (254, 254, 51) + COLOR_UNMELLOW_YELLOW: (255, 255, 102) + COLOR_YELLOW: (255, 255, 0) + excitement: + COLOR_BARBIE_PINK: (224, 33, 138) + COLOR_BYZANTINE: (189, 51, 164) + COLOR_CERISE: (222, 49, 99) + COLOR_CERISE_PINK: (236, 59, 131) + COLOR_DEBIAN_RED: (215, 10, 83) + COLOR_DEEP_CERISE: (218, 50, 135) + COLOR_DEEP_PINK: (255, 20, 147) + COLOR_DOGWOOD_ROSE: (215, 24, 104) + COLOR_FASHION_FUCHSIA: (244, 0, 161) + COLOR_FOLLY: (255, 0, 79) + COLOR_FRENCH_FUCHSIA: (253, 63, 146) + COLOR_FROSTBITE: (233, 54, 167) + COLOR_FUCHSIA_PURPLE: (204, 57, 123) + COLOR_FUCHSIA_ROSE: (199, 67, 117) + COLOR_MAGENTA: (202, 31, 123) + COLOR_MAGENTA_PINK: (204, 51, 139) + COLOR_MEDIUM_RED_VIOLET: (187, 51, 133) + COLOR_MEXICAN_PINK: (228, 0, 124) + COLOR_PANTONE_MAGENTA: (208, 65, 126) + COLOR_PERSIAN_ROSE: (254, 40, 162) + COLOR_PROCESS_MAGENTA: (255, 0, 144) + COLOR_RASPBERRY: (227, 11, 93) + COLOR_RAZZMATAZZ: (227, 37, 107) + COLOR_RED_PURPLE: (228, 0, 120) + COLOR_RED_VIOLET: (199, 21, 133) + COLOR_ROSE: (255, 0, 127) + COLOR_ROYAL_FUCHSIA: (202, 44, 146) + COLOR_RUBINE_RED: (209, 0, 86) + COLOR_RUBY: (224, 17, 95) + COLOR_SASQUATCH_SOCKS: (255, 70, 129) + COLOR_SHOCKING_PINK: (252, 15, 192) + COLOR_SMITTEN: (200, 65, 134) + COLOR_SPANISH_CRIMSON: (229, 26, 76) + COLOR_TELEMAGENTA: (207, 52, 118) + COLOR_UA_RED: (217, 0, 76) + COLOR_VIVID_CERISE: (218, 29, 129) + COLOR_VIVID_RASPBERRY: (255, 0, 108) + COLOR_WINTER_SKY: (255, 0, 124) + amusement: + COLOR_BRANDY_PUNCH: (205, 132, 41) + COLOR_CARROT_ORANGE: (237, 145, 33) + COLOR_CHILEAN_FIRE: (247, 119, 3) + COLOR_CHRISTINE: (231, 115, 10) + COLOR_CLEMENTINE: (233, 110, 0) + COLOR_DARK_GOLDENROD: (184, 134, 11) + COLOR_DARK_ORANGE: (255, 140, 0) + COLOR_DIXIE: (226, 148, 24) + COLOR_ECSTASY: (250, 120, 20) + COLOR_FLAMENCO: (255, 125, 7) + COLOR_FULVOUS: (228, 132, 0) + COLOR_GEEBUNG: (209, 143, 27) + COLOR_GOLD_DROP: (241, 130, 0) + COLOR_GOLDEN_BELL: (226, 137, 19) + COLOR_HARVEST_GOLD: (218, 145, 0) + COLOR_HEAT_WAVE: (255, 122, 0) + COLOR_METEOR: (208, 125, 18) + COLOR_OCHRE: (204, 119, 34) + COLOR_ORANGE: (255, 127, 0) + COLOR_PIRATE_GOLD: (186, 127, 3) + COLOR_PIZAZZ: (255, 144, 0) + COLOR_PIZZA: (201, 148, 21) + COLOR_PRINCETON_ORANGE: (245, 128, 37) + COLOR_PUMPKIN: (255, 117, 24) + COLOR_SAE_ECE_AMBER: (255, 126, 0) + COLOR_SAFETY_ORANGE: (255, 120, 0) + COLOR_SORBUS: (253, 124, 7) + COLOR_TAHITI_GOLD: (233, 124, 7) + COLOR_TANGERINE: (242, 133, 0) + COLOR_TANGO: (237, 122, 28) + COLOR_UNIVERSITY_OF_TENNESSEE_ORANGE: (247, 127, 0) + COLOR_WEST_SIDE: (255, 145, 15) + COLOR_ZEST: (229, 132, 27) + gratitude: + COLOR_AO: (0, 128, 0) + COLOR_BILBAO: (50, 124, 20) + COLOR_BLACK_LEATHER_JACKET: (37, 53, 41) + COLOR_BRITISH_RACING_GREEN: (0, 66, 37) + COLOR_BRUNSWICK_GREEN: (27, 77, 62) + COLOR_CADMIUM_GREEN: (0, 107, 60) + COLOR_CAL_POLY_GREEN: (30, 77, 43) + COLOR_CAMARONE: (0, 88, 26) + COLOR_CARDIN_GREEN: (1, 54, 28) + COLOR_CASTLETON_GREEN: (0, 86, 59) + COLOR_CELTIC: (22, 50, 34) + COLOR_CLOVER: (56, 73, 16) + COLOR_COUNTY_GREEN: (1, 55, 26) + COLOR_CRUSOE: (0, 72, 22) + COLOR_DARK_FERN: (10, 72, 13) + COLOR_DARK_GREEN: (1, 50, 32) + COLOR_DARTMOUTH_GREEN: (0, 112, 60) + COLOR_DEEP_GREEN: (5, 102, 8) + COLOR_DEEP_TEAL: (0, 53, 50) + COLOR_DELL: (57, 100, 19) + COLOR_EVERGLADE: (28, 64, 46) + COLOR_FOREST_GREEN: (34, 139, 34) + COLOR_FUN_GREEN: (1, 109, 57) + COLOR_GABLE_GREEN: (22, 53, 49) + COLOR_GREEN_HOUSE: (36, 80, 15) + COLOR_GREEN_LEAF: (67, 106, 13) + COLOR_HEAVY_METAL: (43, 50, 40) + COLOR_INDIA_GREEN: (19, 136, 8) + COLOR_ISLAMIC_GREEN: (0, 144, 0) + COLOR_JAPANESE_LAUREL: (10, 105, 6) + COLOR_JEWEL: (18, 107, 64) + COLOR_KAITOKE_GREEN: (0, 70, 32) + COLOR_KELLY_GREEN: (76, 187, 23) + COLOR_LA_PALMA: (54, 135, 22) + COLOR_LA_SALLE_GREEN: (8, 120, 48) + COLOR_LINCOLN_GREEN: (25, 89, 5) + COLOR_MSU_GREEN: (24, 69, 59) + COLOR_MALLARD: (35, 52, 24) + COLOR_MEDIUM_JUNGLE_GREEN: (28, 53, 45) + COLOR_MUGHAL_GREEN: (48, 96, 48) + COLOR_NAPIER_GREEN: (42, 128, 0) + COLOR_NORTH_TEXAS_GREEN: (5, 144, 51) + COLOR_PAKISTAN_GREEN: (0, 102, 0) + COLOR_PALM_LEAF: (25, 51, 14) + COLOR_PARSLEY: (19, 79, 25) + COLOR_PHTHALO_GREEN: (18, 53, 36) + COLOR_SACRAMENTO_STATE_GREEN: (4, 57, 39) + COLOR_SAN_FELIX: (11, 98, 7) + COLOR_SHERWOOD_GREEN: (2, 64, 44) + COLOR_SLIMY_GREEN: (41, 150, 23) + COLOR_TE_PAPA_GREEN: (30, 67, 60) + COLOR_TIMBER_GREEN: (22, 50, 44) + COLOR_TURTLE_GREEN: (42, 56, 11) + COLOR_UP_FOREST_GREEN: (1, 68, 33) + COLOR_VIDA_LOCA: (84, 144, 25) + COLOR_ZUCCINI: (4, 64, 34) + COLOR_AZTEC: (13, 28, 25) + COLOR_BLACK_FOREST: (11, 19, 4) + COLOR_BURNHAM: (0, 46, 32) + COLOR_BUSH: (13, 46, 28) + COLOR_DARK_JUNGLE_GREEN: (26, 36, 33) + COLOR_DEEP_FIR: (0, 41, 0) + COLOR_DEEP_FOREST_GREEN: (24, 45, 9) + COLOR_ENGLISH_HOLLY: (2, 45, 21) + COLOR_GORDONS_GREEN: (11, 17, 7) + COLOR_GREEN_KELP: (37, 49, 28) + COLOR_GREEN_WATERLOO: (16, 20, 5) + COLOR_HOLLY: (1, 29, 19) + COLOR_LOG_CABIN: (36, 42, 29) + COLOR_MARSHLAND: (11, 15, 8) + COLOR_MIDNIGHT_MOSS: (4, 16, 4) + COLOR_PALM_GREEN: (9, 35, 15) + COLOR_PINE_TREE: (23, 31, 4) + COLOR_RACING_GREEN: (12, 25, 17) + COLOR_RANGOON_GREEN: (28, 30, 19) + COLOR_SEAWEED: (27, 47, 17) + relief: + COLOR_BRIGHT_GREEN: (102, 255, 0) + COLOR_CHLOROPHYLL_GREEN: (74, 255, 0) + COLOR_DARK_PASTEL_GREEN: (3, 192, 60) + COLOR_GREEN: (0, 255, 0) + COLOR_HARLEQUIN: (63, 255, 0) + COLOR_HARLEQUIN_GREEN: (70, 203, 24) + COLOR_LAWN_GREEN: (124, 252, 0) + COLOR_LIME_GREEN: (50, 205, 50) + COLOR_MALACHITE: (11, 218, 81) + COLOR_NEON_GREEN: (57, 255, 20) + COLOR_SPRING_GREEN: (0, 255, 127) + COLOR_VIVID_MALACHITE: (0, 204, 51) + COLOR_WEB_CHARTREUSE: (127, 255, 0) + class: EmotionAnalysis diff --git a/modules/neopixel/emotion_analysis.py b/src/modules/neopixel/emotion_analysis/emotion_analysis.py similarity index 100% rename from modules/neopixel/emotion_analysis.py rename to src/modules/neopixel/emotion_analysis/emotion_analysis.py diff --git a/src/modules/neopixel/emotion_analysis/tests/__init__.py b/src/modules/neopixel/emotion_analysis/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/neopixel/emotion_analysis/tests/test_emotion_analysis.py b/src/modules/neopixel/emotion_analysis/tests/test_emotion_analysis.py new file mode 100644 index 00000000..6731e6e7 --- /dev/null +++ b/src/modules/neopixel/emotion_analysis/tests/test_emotion_analysis.py @@ -0,0 +1,24 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_transformers = MagicMock() +sys.modules['transformers'] = mock_transformers + +from modules.neopixel.emotion_analysis.emotion_analysis import EmotionAnalysis + +class TestEmotionAnalysis(unittest.TestCase): + + def test_init(self): + ea = EmotionAnalysis(colors={'happy': 'green', 'sad': 'blue'}) + self.assertIsNotNone(ea.emotion_analyzer) + self.assertIsNotNone(ea.emotion_to_keyword) + + def test_setup_messaging(self): + ea = EmotionAnalysis() + with patch.object(ea, 'subscribe') as mock_sub: + ea.setup_messaging() + mock_sub.assert_called_with('speech', ea.analyze_text) + +if __name__ == '__main__': + unittest.main() diff --git a/docs/Neopixel.md b/src/modules/neopixel/neopx/README.md similarity index 100% rename from docs/Neopixel.md rename to src/modules/neopixel/neopx/README.md diff --git a/src/modules/neopixel/neopx/__init__.py b/src/modules/neopixel/neopx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/neopixel/neopx/config.yml b/src/modules/neopixel/neopx/config.yml new file mode 100644 index 00000000..2fa78195 --- /dev/null +++ b/src/modules/neopixel/neopx/config.yml @@ -0,0 +1,40 @@ +neopixel: + config: + pin: 12 + protocol: I2C + count: 12 + positions: + status1: 0 + status2: 1 + status3: 2 + status4: 3 + status5: 4 + right: 5 + top_right: 6 + top_left: 7 + left: 8 + bottom_left: 9 + bottom_right: 10 + middle: 11 + brightness: + - 0.2 + - 0.2 + - 0.2 + - 0.2 + - 0.2 + - 1 + - 1 + - 1 + - 1 + - 1 + - 1 + - 1 + dependencies: + python: + - colour + - adafruit-circuitpython-seesaw + - adafruit-blinka + - adafruit-circuitpython-neopixel-spi + emotion_analysis: + enable: false + class: NeoPx diff --git a/modules/neopixel/neopx.py b/src/modules/neopixel/neopx/neopx.py similarity index 100% rename from modules/neopixel/neopx.py rename to src/modules/neopixel/neopx/neopx.py diff --git a/src/modules/neopixel/neopx/tests/__init__.py b/src/modules/neopixel/neopx/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/neopixel/neopx/tests/test_neopx.py b/src/modules/neopixel/neopx/tests/test_neopx.py new file mode 100644 index 00000000..39080da2 --- /dev/null +++ b/src/modules/neopixel/neopx/tests/test_neopx.py @@ -0,0 +1,32 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_board = MagicMock() +mock_colour = MagicMock() +mock_seesaw = MagicMock() +sys.modules['board'] = mock_board +sys.modules['colour'] = mock_colour +sys.modules['adafruit_seesaw'] = mock_seesaw + +# Mock Color class to return empty list for range_to +mock_colour.Color.return_value.range_to.return_value = [] + +from modules.neopixel.neopx.neopx import NeoPx + +class TestNeoPx(unittest.TestCase): + + def test_color_map_exists(self): + self.assertIn('red', NeoPx.COLOR_MAP) + self.assertIn('green', NeoPx.COLOR_MAP) + self.assertIn('blue', NeoPx.COLOR_MAP) + self.assertIn('off', NeoPx.COLOR_MAP) + + def test_setup_messaging(self): + px = NeoPx.__new__(NeoPx) + with patch.object(px, 'subscribe') as mock_sub: + px.setup_messaging() + mock_sub.assert_any_call('led', px.set) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/network/__init__.py b/src/modules/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/arduinoserial/README.md b/src/modules/network/arduinoserial/README.md new file mode 100644 index 00000000..7215baff --- /dev/null +++ b/src/modules/network/arduinoserial/README.md @@ -0,0 +1,30 @@ +# ArduinoSerial + +## Overview + +Arduino serial communication module using robust-serial protocol. + +Communicate with Arduino over Serial + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +serial: + enabled: false # Set to true to enable this module + path: modules.network.arduinoserial.arduinoserial.ArduinoSerial + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/network/arduinoserial/__init__.py b/src/modules/network/arduinoserial/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/arduino_sketch/Config.h b/src/modules/network/arduinoserial/arduino_sketch/Config.h similarity index 100% rename from arduino_sketch/Config.h rename to src/modules/network/arduinoserial/arduino_sketch/Config.h diff --git a/arduino_sketch/InverseKinematics.h b/src/modules/network/arduinoserial/arduino_sketch/InverseKinematics.h similarity index 100% rename from arduino_sketch/InverseKinematics.h rename to src/modules/network/arduinoserial/arduino_sketch/InverseKinematics.h diff --git a/arduino_sketch/Mpu6050.h b/src/modules/network/arduinoserial/arduino_sketch/Mpu6050.h similarity index 100% rename from arduino_sketch/Mpu6050.h rename to src/modules/network/arduinoserial/arduino_sketch/Mpu6050.h diff --git a/arduino_sketch/Order.h b/src/modules/network/arduinoserial/arduino_sketch/Order.h similarity index 100% rename from arduino_sketch/Order.h rename to src/modules/network/arduinoserial/arduino_sketch/Order.h diff --git a/arduino_sketch/PiConnect.h b/src/modules/network/arduinoserial/arduino_sketch/PiConnect.h similarity index 100% rename from arduino_sketch/PiConnect.h rename to src/modules/network/arduinoserial/arduino_sketch/PiConnect.h diff --git a/arduino_sketch/ServoManager.h b/src/modules/network/arduinoserial/arduino_sketch/ServoManager.h similarity index 100% rename from arduino_sketch/ServoManager.h rename to src/modules/network/arduinoserial/arduino_sketch/ServoManager.h diff --git a/arduino_sketch/arduino_sketch.ino b/src/modules/network/arduinoserial/arduino_sketch/arduino_sketch.ino similarity index 100% rename from arduino_sketch/arduino_sketch.ino rename to src/modules/network/arduinoserial/arduino_sketch/arduino_sketch.ino diff --git a/modules/network/arduinoserial.py b/src/modules/network/arduinoserial/arduinoserial.py similarity index 100% rename from modules/network/arduinoserial.py rename to src/modules/network/arduinoserial/arduinoserial.py diff --git a/src/modules/network/arduinoserial/config.yml b/src/modules/network/arduinoserial/config.yml new file mode 100644 index 00000000..2a2a3e27 --- /dev/null +++ b/src/modules/network/arduinoserial/config.yml @@ -0,0 +1,5 @@ +serial: + config: + port: /dev/ttyAMA0 + baudrate: '115200' + class: ArduinoSerial diff --git a/src/modules/network/arduinoserial/tests/__init__.py b/src/modules/network/arduinoserial/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/arduinoserial/tests/test_arduinoserial.py b/src/modules/network/arduinoserial/tests/test_arduinoserial.py new file mode 100644 index 00000000..c000521c --- /dev/null +++ b/src/modules/network/arduinoserial/tests/test_arduinoserial.py @@ -0,0 +1,27 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_robust_serial = MagicMock() +sys.modules['modules.network.robust_serial'] = mock_robust_serial +sys.modules['modules.network.robust_serial.robust_serial'] = mock_robust_serial.robust_serial +sys.modules['modules.network.robust_serial.utils'] = mock_robust_serial.utils +mock_robust_serial.utils.open_serial_port.side_effect = Exception("No serial port") + +from modules.network.arduinoserial.arduinoserial import ArduinoSerial + +class TestArduinoSerial(unittest.TestCase): + + def test_init_no_port(self): + with self.assertRaises(Exception): + ArduinoSerial(port='/dev/ttyAMA0') + + def test_setup_messaging(self): + arduino = ArduinoSerial.__new__(ArduinoSerial) + arduino.serial_file = None + with patch.object(arduino, 'subscribe') as mock_sub: + arduino.setup_messaging() + mock_sub.assert_called_with('serial', arduino.send) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/network/discordbot.py b/src/modules/network/discordbot.py similarity index 100% rename from modules/network/discordbot.py rename to src/modules/network/discordbot.py diff --git a/docs/DiscordBot.md b/src/modules/network/discordbot/README.md similarity index 100% rename from docs/DiscordBot.md rename to src/modules/network/discordbot/README.md diff --git a/src/modules/network/discordbot/__init__.py b/src/modules/network/discordbot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/discordbot/config.yml b/src/modules/network/discordbot/config.yml new file mode 100644 index 00000000..f3723137 --- /dev/null +++ b/src/modules/network/discordbot/config.yml @@ -0,0 +1,27 @@ +discordbot: + class: DiscordBot + config: + prompt: > + You are a helpful assistant for the modular-biped open-source robotics + project. Answer questions clearly and concisely, using the provided + knowledge sources where relevant. Response MUST BE under 1500 characters. If you are unsure of an answer, say so + and direct the user to the appropriate knowledge source. Reference the knowledge sources by name (e.g. "Knowledge source: Electronics") when citing information and include links where possible. Do not make up answers if you are unsure; instead, say "I don't know" or "I'm not sure" and suggest where the user might find the answer in the knowledge sources. + # Maximum characters to extract from each knowledge source URL. + # Increase for more context (higher token usage) or decrease to save tokens. + max_chars_per_source: 20000 + # Footer appended to every AI response (Markdown supported). + footer: | + *I am a bot and currently in beta.* + *If you want to help support the project, buy me a coffee: https://buymeacoffee.com/makerforgetech* + knowledge_sources: + - https://github.com/makerforgetech/modular-biped/wiki + - https://github.com/makerforgetech/modular-biped/wiki/Files + - https://github.com/makerforgetech/modular-biped/wiki/Electronics + - https://github.com/makerforgetech/modular-biped/wiki/Raspberry-Pi + - https://github.com/makerforgetech/modular-biped/wiki/Arduino + - https://github.com/makerforgetech/modular-biped/wiki/Software + dependencies: + python: + - discord.py + additional: + - https://discord.com/developers/applications diff --git a/src/modules/network/discordbot/discordbot.py b/src/modules/network/discordbot/discordbot.py new file mode 100644 index 00000000..2a965775 --- /dev/null +++ b/src/modules/network/discordbot/discordbot.py @@ -0,0 +1,393 @@ +import asyncio +import os +import threading +import urllib.request +from html.parser import HTMLParser + +import discord +from modules.base_module import BaseModule + +# Discord thread names are limited to 100 characters. We reserve 3 for the +# trailing ellipsis ("...") so the effective content limit is 97 characters. +_MAX_THREAD_NAME_CONTENT = 97 + +# Default character limit applied to each fetched knowledge-source URL. +_DEFAULT_MAX_CHARS_PER_SOURCE = 5000 + + +class _TextExtractor(HTMLParser): + """Minimal HTML-to-text converter for extracting knowledge-source content.""" + + # Tags whose content adds no meaningful text (scripts, navigation, etc.) + _SKIP_TAGS = {"script", "style", "nav", "header", "footer", "aside"} + + def __init__(self): + super().__init__() + self._chunks = [] + self._skip_depth = 0 + + def handle_starttag(self, tag, attrs): + if tag in self._SKIP_TAGS: + self._skip_depth += 1 + + def handle_endtag(self, tag): + if tag in self._SKIP_TAGS and self._skip_depth > 0: + self._skip_depth -= 1 + + def handle_data(self, data): + if self._skip_depth == 0: + text = data.strip() + if text: + self._chunks.append(text) + + def get_text(self): + return " ".join(self._chunks) + + +class DiscordBot(BaseModule): + """ + Discord Bot module. + + Hosts a Discord bot that monitors for mentions and answers questions + using the ChatGPT module directly (via ``ai_instance``) with + GitHub-hosted knowledge sources specified in the config YAML. + + At startup the text content of each ``knowledge_sources`` URL is fetched + and embedded directly into the system prompt so that ChatGPT can draw on + the actual information rather than just seeing bare hyperlinks. Each + source is truncated to ``max_chars_per_source`` characters (default + 5,000) to keep prompt size manageable. + + The bot responds to: + - Private messages (DMs): every message in a DM channel is processed + without requiring the bot to be @mentioned. + - Server channels / threads: the bot must be explicitly @mentioned. + + Any time the bot is triggered it will: + 1. Collect the thread context (if the post is already inside a thread). + 2. Forward the question together with the system prompt (which now + contains the fetched knowledge) to the AI via + ``ai_instance.completion()``. + 3. Post the reply back: + - In a DM: directly in the DM channel. + - In a thread: directly in that thread. + - In a regular channel: create a new thread from the original post. + + Requires the environment variable ``DISCORD_BOT_TOKEN``. + The ``ai_instance`` attribute must be set to a ``ChatGPT`` instance + (e.g. via ``main.py``) before the bot will be able to answer questions. + + Subscribes to: + - ``discord/send`` – allows other modules to send a message to a + specific channel (kwarg: channel_id, message) + - ``system/exit`` – gracefully shuts down the bot + + Publishes to: + - ``log/*`` – standard logging (via BaseModule.log) + + Example config (config/discordbot.yml): + + discordbot: + enabled: true + path: modules.network.discordbot.DiscordBot + config: + prompt: "You are a helpful assistant for the modular-biped project..." + max_chars_per_source: 5000 + footer: "*I am a bot and currently in beta.*" + knowledge_sources: + - https://github.com/makerforgetech/modular-biped/wiki + - https://github.com/makerforgetech/modular-biped/discussions + """ + + def __init__(self, **kwargs): + """ + Initialise the Discord bot. + + :kwarg prompt: System prompt sent to the AI as the ``persona`` argument + for every request. + :kwarg knowledge_sources: List of URLs whose content will be fetched + and embedded in the system prompt. + :kwarg max_chars_per_source: Maximum characters to include from each + fetched URL (default 5,000). + :kwarg footer: Text appended to every AI response before posting to + Discord. Supports Markdown. + :kwarg token: Bot token (falls back to DISCORD_BOT_TOKEN env var). + """ + self.token = os.getenv("DISCORD_BOT_TOKEN", kwargs.get("token", None)) + if not self.token: + raise RuntimeError( + "DiscordBot: No DISCORD_BOT_TOKEN provided. " + "Set it as an environment variable or pass it as a keyword argument." + ) + + self.ai_instance = None # Will be set via set_chatgpt() + + self.prompt = kwargs.get( + "prompt", + "You are a helpful assistant. Answer questions clearly and concisely.", + ) + self.knowledge_sources = kwargs.get("knowledge_sources", []) + self.max_chars_per_source = kwargs.get( + "max_chars_per_source", _DEFAULT_MAX_CHARS_PER_SOURCE + ) + self.footer = kwargs.get("footer", "") + + # Build the system prompt, embedding fetched content from each URL so + # ChatGPT can draw on the actual text rather than bare hyperlinks. + if self.knowledge_sources: + parts = [ + self.prompt, + "\nKnowledge base (use this content to answer questions accurately):", + ] + for url in self.knowledge_sources: + content = self._fetch_knowledge_source_content( + url, self.max_chars_per_source + ) + if content: + parts.append(f"\n### Source: {url}\n{content}") + else: + parts.append(f"\n### Source: {url}\n(Content could not be loaded)") + self.system_prompt = "\n".join(parts) + else: + self.system_prompt = self.prompt + + # Serialise AI requests so that concurrent Discord mentions do not + # interleave their pubsub publish/subscribe calls. + self._ai_lock = threading.Lock() + self._current_response = None + + # Set up Discord gateway intents. + intents = discord.Intents.default() + intents.message_content = True + self._bot = discord.Client(intents=intents) + + # Register event handlers via decorators. + @self._bot.event + async def on_ready(): + self.log(f"Discord bot logged in as {self._bot.user}.") + self.log(f"GPT instance set: {self.ai_instance is not None}") + + @self._bot.event + async def on_message(message): + await self._on_message(message) + + # Run the Discord client in a dedicated background thread so it does + # not block the main system loop. + self._loop = asyncio.new_event_loop() + self._bot_thread = threading.Thread( + target=self._run_bot, + name="DiscordBotLoop", + daemon=True, + ) + self._bot_thread.start() + + # ------------------------------------------------------------------ + # BaseModule interface + # ------------------------------------------------------------------ + + def setup_messaging(self): + """Subscribe to pubsub topics after the messaging service is set.""" + # self.subscribe("ai/response", self.handle_ai_response) + self.subscribe("discord/send", self.handle_send) + self.subscribe("system/exit", self._shutdown_wrapper) + + # ------------------------------------------------------------------ + # Bot lifecycle + # ------------------------------------------------------------------ + + def _run_bot(self): + """Entry point for the background thread running the Discord event loop.""" + asyncio.set_event_loop(self._loop) + self._loop.run_until_complete(self._bot.start(self.token)) + + def _shutdown_wrapper(self, *args, **kwargs): + """Schedule a clean shutdown of the Discord client.""" + self.log("Shutting down Discord bot...", level="info") + asyncio.run_coroutine_threadsafe(self._bot.close(), self._loop) + + # ------------------------------------------------------------------ + # Incoming Discord messages + # ------------------------------------------------------------------ + + async def _on_message(self, message): + """ + Called by discord.py for every message the bot can see. + + Responds to: + - Any message in a private DM channel (no @mention required). + - Messages in servers/threads where the bot is explicitly @mentioned. + Does not respond to its own messages. + """ + # print(f"Received message: {message.content} (mentions: {[m.display_name for m in message.mentions]})") + + if message.author == self._bot.user: + return + + # Private messages: respond to everything without requiring a mention. + is_dm = isinstance(message.channel, discord.DMChannel) + + if not is_dm and self._bot.user not in message.mentions: + return + + self.log(f"Processing message from {message.author}: {message.content}", level="info") + + # Strip @mentions from content (only relevant for channel/thread messages). + content = message.content + if not is_dm: + for mention in message.mentions: + content = content.replace(f"<@{mention.id}>", "") + content = content.replace(f"<@!{mention.id}>", "") + content = content.strip() + + # If the message is already inside a thread, collect recent context. + if isinstance(message.channel, discord.Thread): + thread_lines = [] + async for msg in message.channel.history(limit=20, oldest_first=True): + if msg.id != message.id: + thread_lines.append( + f"{msg.author.display_name}: {msg.content}" + ) + if thread_lines: + context = "\n".join(thread_lines) + full_text = ( + f"Thread context:\n{context}\n\n" + f"Question: {content}" + ) + else: + full_text = f"Question: {content}" + else: + full_text = f"Question: {content}" + + # Offload the synchronous AI call to a thread-pool executor so the + # Discord event loop is not blocked while waiting for the AI. + + # print(f"Full text sent to AI:\n{full_text}") + + loop = asyncio.get_running_loop() + response = await loop.run_in_executor( + None, self._get_ai_response, full_text + ) + + if response: + if self.footer: + response += f"\n\n{self.footer}" + # print (f"AI response:\n{response}") + await self._send_response(message, response) + + # log the timestamp and full content of the message for debugging + self.log(f"Received message at {message.created_at} from {message.author}: {message.content}\n\nFull text sent to AI:\n\n{full_text}\n\nResponse:\n\n{response}\n\nPrompt:{self.system_prompt}", level="file") + + # ------------------------------------------------------------------ + # AI integration (pubsub) + # ------------------------------------------------------------------ + + def _get_ai_response(self, text, prompt=None): + """ + Call the ChatGPT module's completion() method directly. + """ + if prompt is None: + prompt = self.system_prompt + if not self.ai_instance: + self.log("ChatGPT instance not set in DiscordBot.", level="error") + return "[Error: ChatGPT module not available]" + try: + return self.ai_instance.text_completion(text, persona=prompt) + except Exception as e: + self.log(f"Error calling ChatGPT completion: {e}", level="error") + return f"[Error: {e}]" + + @staticmethod + def _fetch_knowledge_source_content(url, max_chars=_DEFAULT_MAX_CHARS_PER_SOURCE): + """ + Fetch *url* and return its text content, stripped of HTML markup. + + The result is truncated to *max_chars* characters. Returns ``None`` + on any network or parsing error so callers can fall back gracefully. + """ + try: + req = urllib.request.Request( + url, + headers={"User-Agent": "Mozilla/5.0 (compatible; DiscordBot/1.0)"}, + ) + with urllib.request.urlopen(req, timeout=10) as resp: + raw = resp.read().decode("utf-8", errors="replace") + extractor = _TextExtractor() + extractor.feed(raw) + # Collapse repeated whitespace for a cleaner prompt. + text = " ".join(extractor.get_text().split()) + if len(text) > max_chars: + text = text[:max_chars] + "..." + return text or None + except Exception: + return None + + def handle_ai_response(self, response): + """ + Receive an AI-generated response via the ``ai/response`` pubsub topic. + + :param response: The text generated by the AI module. + """ + self._current_response = response + + # ------------------------------------------------------------------ + # Sending responses back to Discord + # ------------------------------------------------------------------ + + async def _send_response(self, message, response): + """ + Send *response* back to Discord. + + - DM channels and existing threads: reply directly in the channel. + - Regular server channels: create a new thread from the message so + the conversation stays organised. + """ + if len(response) > 2000: + # split into chunks of 2000 characters and send as multiple messages to avoid Discord limits + for i in range(0, len(response), 2000): + chunk = response[i:i+2000] + await self._send_response(message, chunk) + return + try: + if isinstance(message.channel, (discord.Thread, discord.DMChannel)): + await message.channel.send(response) + else: + thread_name = (message.content[:_MAX_THREAD_NAME_CONTENT] + "...") if len(message.content) > _MAX_THREAD_NAME_CONTENT else message.content + thread_name = thread_name.strip() or "Discussion" + try: + thread = await message.create_thread(name=thread_name) + await thread.send(response) + except discord.HTTPException as e: + # Fallback: reply inline if thread creation fails. + self.log(f"Failed to create thread or send response in thread: {e}", level="error") + try: + await message.reply(response) + except Exception as e2: + self.log(f"Failed to reply inline after thread failure: {e2}", level="error") + except Exception as e: + self.log(f"Failed to send response: {e}", level="error") + + def handle_send(self, channel_id=None, message=None): + """ + Send a message to a Discord channel from another module via pubsub. + + Publish ``discord/send`` with kwargs ``channel_id`` (int) and + ``message`` (str) to use this handler. + """ + if channel_id is None or message is None: + self.log( + "handle_send: channel_id and message are required", + level="warning", + ) + return + asyncio.run_coroutine_threadsafe( + self._send_to_channel(int(channel_id), message), + self._loop, + ) + + async def _send_to_channel(self, channel_id, message): + """Send *message* to the Discord channel identified by *channel_id*.""" + channel = self._bot.get_channel(channel_id) + if channel: + await channel.send(message) + else: + self.log(f"Channel {channel_id} not found", level="warning") diff --git a/src/modules/network/discordbot/tests/__init__.py b/src/modules/network/discordbot/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_discordbot.py b/src/modules/network/discordbot/tests/test_discordbot.py similarity index 97% rename from tests/test_discordbot.py rename to src/modules/network/discordbot/tests/test_discordbot.py index bf4eb704..f879fc8f 100644 --- a/tests/test_discordbot.py +++ b/src/modules/network/discordbot/tests/test_discordbot.py @@ -63,7 +63,7 @@ async def close(self): # --------------------------------------------------------------------------- # Now import the module under test (with discord already stubbed). # --------------------------------------------------------------------------- -from modules.network.discordbot import DiscordBot, _TextExtractor # noqa: E402 +from modules.network.discordbot.discordbot import DiscordBot, _TextExtractor # noqa: E402 # --------------------------------------------------------------------------- @@ -86,7 +86,7 @@ def _make_bot(prompt="Test prompt", knowledge_sources=None, token="fake-token", urlopen_mock = _mock_urlopen(b"") with patch("threading.Thread") as mock_thread, \ patch.dict("os.environ", {"DISCORD_BOT_TOKEN": token}), \ - patch("modules.network.discordbot.urllib.request.urlopen", urlopen_mock): + patch("modules.network.discordbot.discordbot.urllib.request.urlopen", urlopen_mock): mock_thread.return_value = Mock() bot = DiscordBot( prompt=prompt, @@ -185,7 +185,7 @@ def test_footer_from_kwarg(self): def test_background_thread_started(self): with patch("threading.Thread") as mock_thread_cls, \ patch.dict("os.environ", {"DISCORD_BOT_TOKEN": "tok"}), \ - patch("modules.network.discordbot.urllib.request.urlopen", + patch("modules.network.discordbot.discordbot.urllib.request.urlopen", _mock_urlopen(b"")): mock_thread = Mock() mock_thread_cls.return_value = mock_thread @@ -224,7 +224,7 @@ def test_content_truncated_to_max_chars(self): html = b"

" + long_text + b"

" with patch("threading.Thread"), \ patch.dict("os.environ", {"DISCORD_BOT_TOKEN": "tok"}), \ - patch("modules.network.discordbot.urllib.request.urlopen", + patch("modules.network.discordbot.discordbot.urllib.request.urlopen", _mock_urlopen(html)): bot = DiscordBot( prompt="p", @@ -252,7 +252,7 @@ def test_navigation_stripped_from_fetched_content(self): def test_fetch_knowledge_source_content_returns_none_on_error(self): """Static method returns None when the URL cannot be fetched.""" - with patch("modules.network.discordbot.urllib.request.urlopen", + with patch("modules.network.discordbot.discordbot.urllib.request.urlopen", side_effect=Exception("timeout")): result = DiscordBot._fetch_knowledge_source_content("https://example.com") self.assertIsNone(result) @@ -260,7 +260,7 @@ def test_fetch_knowledge_source_content_returns_none_on_error(self): def test_fetch_knowledge_source_content_strips_html(self): """Static method returns plain text, not raw HTML.""" html = b"

Title

Body text.

" - with patch("modules.network.discordbot.urllib.request.urlopen", + with patch("modules.network.discordbot.discordbot.urllib.request.urlopen", _mock_urlopen(html)): result = DiscordBot._fetch_knowledge_source_content( "https://example.com", max_chars=1000 @@ -282,7 +282,7 @@ def urlopen_side_effect(req, timeout=10): with patch("threading.Thread"), \ patch.dict("os.environ", {"DISCORD_BOT_TOKEN": "tok"}), \ - patch("modules.network.discordbot.urllib.request.urlopen", + patch("modules.network.discordbot.discordbot.urllib.request.urlopen", side_effect=urlopen_side_effect): bot = DiscordBot( prompt="p", diff --git a/docs/MessagingService.md b/src/modules/network/messaging_service/README.md similarity index 100% rename from docs/MessagingService.md rename to src/modules/network/messaging_service/README.md diff --git a/src/modules/network/messaging_service/__init__.py b/src/modules/network/messaging_service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/messaging_service/config.yml b/src/modules/network/messaging_service/config.yml new file mode 100644 index 00000000..8dbdbcc6 --- /dev/null +++ b/src/modules/network/messaging_service/config.yml @@ -0,0 +1,22 @@ +messaging_service: + config: + # Messaging backend(s) to activate. + # + # pubsub — in-process pypubsub only (default, no broker required) + # mqtt — MQTT broker only + # both — pypubsub + MQTT available simultaneously + # + # Routing (all modes): + # publish() → primary backend (pubsub when "pubsub"/"both"; mqtt when "mqtt") + # publish_mqtt() → MQTT backend only (no-op if MQTT not active) + # subscribe() → registered on ALL active backends + protocol: pubsub + mqtt_host: localhost + mqtt_port: 1883 + dependencies: + python: + - paho-mqtt + - pypubsub + unix: + - mosquitto + class: MessagingService diff --git a/src/modules/network/messaging_service/messaging_service.py b/src/modules/network/messaging_service/messaging_service.py new file mode 100755 index 00000000..e5ab856d --- /dev/null +++ b/src/modules/network/messaging_service/messaging_service.py @@ -0,0 +1,183 @@ +import json +import paho.mqtt.client as mqtt +from pubsub import pub + +VALID_PROTOCOLS = ('pubsub', 'mqtt', 'both') + +class MessagingService: + """ + Unified messaging façade that supports pypubsub, MQTT, or both. + + Config options (via config.yml or environment override): + protocol: pubsub # in-process pypubsub only (default) + protocol: mqtt # MQTT broker only + protocol: both # pypubsub + MQTT available simultaneously + mqtt_host: localhost + mqtt_port: 1883 + + Routing: + subscribe() — registers the callback on ALL active backends so it + receives messages from either path. + publish() — sends via the *primary* backend only: + pubsub / both → pypubsub + mqtt → MQTT + publish_mqtt() — sends to the MQTT backend only (no-op when MQTT is + not active). Use this when you need to push data to + an external broker without also triggering local + pypubsub listeners. + """ + + def __init__(self, **kwargs): + self.protocol = kwargs.get('protocol', 'pubsub') + if self.protocol not in VALID_PROTOCOLS: + raise ValueError( + f"Invalid protocol '{self.protocol}'. " + f"Must be one of: {', '.join(VALID_PROTOCOLS)}" + ) + self._pubsub = None + self._mqtt = None + + if self.protocol in ('pubsub', 'both'): + self._pubsub = PubSubMessagingService() + + if self.protocol in ('mqtt', 'both'): + host = kwargs.get('mqtt_host', 'localhost') + try: + port = int(kwargs.get('mqtt_port', 1883)) + except (TypeError, ValueError) as exc: + raise ValueError( + f"Invalid mqtt_port '{kwargs.get('mqtt_port')}': must be an integer." + ) from exc + self._mqtt = MQTTMessagingService(broker=host, port=port) + + self.messaging_service = self._mqtt if self.protocol == 'mqtt' else self._pubsub + + def subscribe(self, topic, callback, **kwargs): + """ + Subscribe to a topic on all active messaging backends. + + When ``protocol`` is ``both``, the callback is registered on both + backends so it receives messages published via either path. + """ + if self._pubsub: + self._pubsub.subscribe(topic, callback, **kwargs) + if self._mqtt: + self._mqtt.subscribe(topic, callback, **kwargs) + + def publish(self, topic, *args, **kwargs): + """ + Publish a message via the primary backend. + + - ``pubsub`` (default) and ``both`` → publishes to pypubsub only. + - ``mqtt`` → publishes to MQTT only. + + Use :meth:`publish_mqtt` to target the MQTT backend explicitly when + ``protocol`` is ``both``. + """ + if self._pubsub: + self._pubsub.publish(topic, *args, **kwargs) + elif self._mqtt: + self._mqtt.publish(topic, *args, **kwargs) + + def publish_mqtt(self, topic, *args, **kwargs): + """Publish only to the MQTT backend (no-op if MQTT is not active).""" + if self._mqtt: + self._mqtt.publish(topic, *args, **kwargs) + + +class PubSubMessagingService: + """pypubsub-based messaging implementation.""" + + def __init__(self): + self.protocol = 'pubsub' + print("[PubSubMessagingService] Initialized") + + def subscribe(self, topic, callback, **kwargs): + """ + Subscribe to a topic. + + :param topic: Topic string (e.g., 'system/log'). + :param callback: Callback function. + + Example:: + + def on_message(message): + print(message) + + messaging_service.subscribe('system/log', on_message) + """ + if hasattr(callback, '__self__') and hasattr(callback, '__name__'): + cb_name = f"{callback.__self__.__class__.__name__}.{callback.__name__}" + else: + cb_name = getattr(callback, '__name__', repr(callback)) + print(f"[PubSubMessagingService] Subscribing to {topic} to call {cb_name}") + pub.subscribe(callback, topic, **kwargs) + + def publish(self, topic, *args, **kwargs): + """ + Publish a message to a topic. + + - Single positional argument → sent as the ``message`` keyword. + - Keyword arguments → forwarded directly to pypubsub. + """ + if len(args) == 1 and not kwargs: + pub.sendMessage(topic, message=args[0]) + else: + pub.sendMessage(topic, **kwargs) + + +class MQTTMessagingService: + """MQTT-based messaging implementation.""" + + def __init__(self, broker="localhost", port=1883): + self.protocol = 'mqtt' + print(f"[MQTTMessagingService] Connecting to {broker}:{port}") + self.client = mqtt.Client() + self.client.connect(broker, port, 60) + self.subscriptions = {} + self.client.loop_start() + + def __del__(self): + try: + self.client.loop_stop() + except Exception: + pass + + def subscribe(self, topic, callback, **kwargs): + """Subscribe to an MQTT topic and call the callback on each message.""" + def on_message(client, userdata, msg): + try: + payload = msg.payload.decode('utf-8') + try: + data = json.loads(payload) + except Exception: + data = payload + if isinstance(data, dict): + callback(**data) + else: + callback(data) + except Exception as e: + print(f"[MQTTMessagingService] Error in on_message: {e}") + + qos = kwargs.get('qos', 1) + self.client.subscribe(topic, qos=qos) + self.client.message_callback_add(topic, on_message) + print(f"[MQTTMessagingService] Subscribed to topic: {topic}") + + def publish(self, topic, *args, **kwargs): + """ + Publish a message to an MQTT topic. + + - Single positional argument → sent as a raw string/bytes payload. + - Keyword arguments → serialised as JSON. + """ + qos = kwargs.pop('qos', 1) + if len(args) == 1 and not kwargs: + payload = args[0] + if isinstance(payload, bytes): + pass # send raw bytes as-is + elif not isinstance(payload, str): + payload = json.dumps(payload) + self.client.publish(topic, payload, qos=qos) + else: + self.client.publish(topic, json.dumps(kwargs), qos=qos) diff --git a/src/modules/network/messaging_service/tests/__init__.py b/src/modules/network/messaging_service/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/messaging_service/tests/test_messaging_service.py b/src/modules/network/messaging_service/tests/test_messaging_service.py new file mode 100644 index 00000000..4eaa6ff0 --- /dev/null +++ b/src/modules/network/messaging_service/tests/test_messaging_service.py @@ -0,0 +1,124 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch, call + +mock_paho = MagicMock() +mock_pubsub = MagicMock() +sys.modules['paho'] = mock_paho +sys.modules['paho.mqtt'] = mock_paho.mqtt +sys.modules['paho.mqtt.client'] = mock_paho.mqtt.client +sys.modules['pubsub'] = mock_pubsub +sys.modules['pubsub.pub'] = mock_pubsub.pub + +from modules.network.messaging_service.messaging_service import ( + MessagingService, + PubSubMessagingService, + MQTTMessagingService, +) + + +class TestMessagingServiceInit(unittest.TestCase): + + def test_init_pubsub(self): + ms = MessagingService(protocol='pubsub') + self.assertEqual(ms.protocol, 'pubsub') + self.assertIsInstance(ms._pubsub, PubSubMessagingService) + self.assertIsNone(ms._mqtt) + + def test_init_mqtt(self): + ms = MessagingService(protocol='mqtt', mqtt_host='localhost', mqtt_port=1883) + self.assertEqual(ms.protocol, 'mqtt') + self.assertIsNone(ms._pubsub) + self.assertIsInstance(ms._mqtt, MQTTMessagingService) + + def test_init_both(self): + ms = MessagingService(protocol='both', mqtt_host='localhost', mqtt_port=1883) + self.assertEqual(ms.protocol, 'both') + self.assertIsInstance(ms._pubsub, PubSubMessagingService) + self.assertIsInstance(ms._mqtt, MQTTMessagingService) + + def test_init_invalid_protocol(self): + with self.assertRaises(ValueError) as ctx: + MessagingService(protocol='invalid') + self.assertIn('invalid', str(ctx.exception)) + + +class TestMessagingServicePubSub(unittest.TestCase): + + def setUp(self): + self.ms = MessagingService(protocol='pubsub') + + def test_subscribe_delegates_to_pubsub(self): + with patch.object(self.ms._pubsub, 'subscribe') as mock_sub: + cb = MagicMock() + self.ms.subscribe('test/topic', cb) + mock_sub.assert_called_once_with('test/topic', cb) + + def test_publish_delegates_to_pubsub(self): + with patch.object(self.ms._pubsub, 'publish') as mock_pub: + self.ms.publish('test/topic', message='hello') + mock_pub.assert_called_once_with('test/topic', message='hello') + + def test_publish_mqtt_is_noop_when_no_mqtt(self): + # Should not raise even when MQTT is not configured + self.ms.publish_mqtt('test/topic', message='hello') + + +class TestMessagingServiceBoth(unittest.TestCase): + + def setUp(self): + self.ms = MessagingService(protocol='both', mqtt_host='localhost', mqtt_port=1883) + + def test_subscribe_calls_both_backends(self): + with patch.object(self.ms._pubsub, 'subscribe') as mock_ps_sub, \ + patch.object(self.ms._mqtt, 'subscribe') as mock_mqtt_sub: + cb = MagicMock() + self.ms.subscribe('test/topic', cb) + mock_ps_sub.assert_called_once_with('test/topic', cb) + mock_mqtt_sub.assert_called_once_with('test/topic', cb) + + def test_publish_uses_pubsub_when_both_available(self): + with patch.object(self.ms._pubsub, 'publish') as mock_ps_pub, \ + patch.object(self.ms._mqtt, 'publish') as mock_mqtt_pub: + self.ms.publish('test/topic', message='hello') + mock_ps_pub.assert_called_once_with('test/topic', message='hello') + mock_mqtt_pub.assert_not_called() + + def test_publish_mqtt_only_calls_mqtt(self): + with patch.object(self.ms._pubsub, 'publish') as mock_ps_pub, \ + patch.object(self.ms._mqtt, 'publish') as mock_mqtt_pub: + self.ms.publish_mqtt('test/topic', message='hello') + mock_ps_pub.assert_not_called() + mock_mqtt_pub.assert_called_once_with('test/topic', message='hello') + + +class TestPubSubMessagingService(unittest.TestCase): + + def test_subscribe_and_publish(self): + ms = PubSubMessagingService() + callback = MagicMock() + ms.subscribe('test/topic', callback) + ms.publish('test/topic', message='hello') + + +class TestMQTTMessagingService(unittest.TestCase): + + def test_publish_single_arg(self): + ms = MQTTMessagingService(broker='localhost', port=1883) + ms.publish('test/topic', 'payload') + ms.client.publish.assert_called() + + def test_publish_kwargs(self): + ms = MQTTMessagingService(broker='localhost', port=1883) + ms.publish('test/topic', key='value') + ms.client.publish.assert_called() + + def test_subscribe(self): + ms = MQTTMessagingService(broker='localhost', port=1883) + cb = MagicMock() + ms.subscribe('test/topic', cb) + ms.client.subscribe.assert_called() + + +if __name__ == '__main__': + unittest.main() diff --git a/modules/network/robust_serial/__init__.py b/src/modules/network/robust_serial/__init__.py similarity index 100% rename from modules/network/robust_serial/__init__.py rename to src/modules/network/robust_serial/__init__.py diff --git a/modules/network/robust_serial/robust_serial.py b/src/modules/network/robust_serial/robust_serial.py similarity index 100% rename from modules/network/robust_serial/robust_serial.py rename to src/modules/network/robust_serial/robust_serial.py diff --git a/modules/network/robust_serial/threads.py b/src/modules/network/robust_serial/threads.py similarity index 100% rename from modules/network/robust_serial/threads.py rename to src/modules/network/robust_serial/threads.py diff --git a/modules/network/robust_serial/utils.py b/src/modules/network/robust_serial/utils.py similarity index 100% rename from modules/network/robust_serial/utils.py rename to src/modules/network/robust_serial/utils.py diff --git a/docs/RTLSDR.md b/src/modules/network/rtlsdr/README.md similarity index 100% rename from docs/RTLSDR.md rename to src/modules/network/rtlsdr/README.md diff --git a/src/modules/network/rtlsdr/__init__.py b/src/modules/network/rtlsdr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/rtlsdr/config.yml b/src/modules/network/rtlsdr/config.yml new file mode 100644 index 00000000..d868dbc2 --- /dev/null +++ b/src/modules/network/rtlsdr/config.yml @@ -0,0 +1,11 @@ +rtl_sdr: + config: + udp_host: 127.0.0.1 + udp_port: 8433 + timeout: 70 + dependencies: + unix: + - rtl-433 + python: + - requests + class: RTLSDR diff --git a/modules/network/rtlsdr.py b/src/modules/network/rtlsdr/rtlsdr.py similarity index 100% rename from modules/network/rtlsdr.py rename to src/modules/network/rtlsdr/rtlsdr.py diff --git a/src/modules/network/rtlsdr/tests/__init__.py b/src/modules/network/rtlsdr/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/rtlsdr/tests/test_rtlsdr.py b/src/modules/network/rtlsdr/tests/test_rtlsdr.py new file mode 100644 index 00000000..99103aeb --- /dev/null +++ b/src/modules/network/rtlsdr/tests/test_rtlsdr.py @@ -0,0 +1,21 @@ +import unittest +from unittest.mock import MagicMock, patch +from modules.network.rtlsdr.rtlsdr import RTLSDR + +class TestRTLSDR(unittest.TestCase): + + def test_init_defaults(self): + sdr = RTLSDR() + self.assertEqual(sdr.udp_host, '127.0.0.1') + self.assertEqual(sdr.udp_port, 8433) + self.assertIsNone(sdr.rtl_process) + + def test_setup_messaging(self): + sdr = RTLSDR() + with patch.object(sdr, 'subscribe') as mock_sub: + sdr.setup_messaging() + mock_sub.assert_any_call('sdr/start', sdr.start_rtl_433) + mock_sub.assert_any_call('sdr/listen', sdr.listen_once) + +if __name__ == '__main__': + unittest.main() diff --git a/docs/TelegramBot.md b/src/modules/network/telegrambot/README.md similarity index 100% rename from docs/TelegramBot.md rename to src/modules/network/telegrambot/README.md diff --git a/src/modules/network/telegrambot/__init__.py b/src/modules/network/telegrambot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/telegrambot/config.yml b/src/modules/network/telegrambot/config.yml new file mode 100644 index 00000000..edfdf4d0 --- /dev/null +++ b/src/modules/network/telegrambot/config.yml @@ -0,0 +1,9 @@ +telegram: + config: + user_whitelist: [] + dependencies: + python: + - python-telegram-bot + additional: + - https://core.telegram.org/bots/tutorial#obtain-your-bot-token + class: TelegramBot diff --git a/modules/network/telegrambot.py b/src/modules/network/telegrambot/telegrambot.py similarity index 100% rename from modules/network/telegrambot.py rename to src/modules/network/telegrambot/telegrambot.py diff --git a/src/modules/network/telegrambot/tests/__init__.py b/src/modules/network/telegrambot/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/network/telegrambot/tests/test_telegrambot.py b/src/modules/network/telegrambot/tests/test_telegrambot.py new file mode 100644 index 00000000..d0d90069 --- /dev/null +++ b/src/modules/network/telegrambot/tests/test_telegrambot.py @@ -0,0 +1,24 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_telegram = MagicMock() +sys.modules['telegram'] = mock_telegram +sys.modules['telegram.ext'] = mock_telegram.ext + +from modules.network.telegrambot.telegrambot import TelegramBot + +class TestTelegramBot(unittest.TestCase): + + def test_init_no_token(self): + with patch.dict('os.environ', {}, clear=True): + with self.assertRaises(RuntimeError): + TelegramBot() + + def test_init_with_token(self): + with patch.dict('os.environ', {}, clear=True): + bot = TelegramBot(token='test_token_123') + self.assertEqual(bot.token, 'test_token_123') + +if __name__ == '__main__': + unittest.main() diff --git a/docs/Personality.md b/src/modules/personality/README.md similarity index 100% rename from docs/Personality.md rename to src/modules/personality/README.md diff --git a/src/modules/personality/__init__.py b/src/modules/personality/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/personality/config.yml b/src/modules/personality/config.yml new file mode 100644 index 00000000..65113a58 --- /dev/null +++ b/src/modules/personality/config.yml @@ -0,0 +1,5 @@ +personality: + config: + mode: 6 + sleep_timeout: 60 + class: Personality diff --git a/modules/personality.py b/src/modules/personality/personality.py similarity index 100% rename from modules/personality.py rename to src/modules/personality/personality.py diff --git a/src/modules/personality/tests/__init__.py b/src/modules/personality/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/personality/tests/test_personality.py b/src/modules/personality/tests/test_personality.py new file mode 100644 index 00000000..4d0acd61 --- /dev/null +++ b/src/modules/personality/tests/test_personality.py @@ -0,0 +1,27 @@ +import unittest +from unittest.mock import MagicMock, patch +from modules.personality.personality import Personality + +class TestPersonality(unittest.TestCase): + + def test_init_defaults(self): + p = Personality() + self.assertEqual(p.eye, 'blue') + self.assertEqual(p.min_interval, 20) + self.assertEqual(p.max_interval, 60) + self.assertEqual(p.balance_enabled, True) + self.assertIsNotNone(p.next_action_time) + + def test_init_custom(self): + p = Personality(min_interval=10, max_interval=30, balance_enabled=False) + self.assertEqual(p.min_interval, 10) + self.assertEqual(p.max_interval, 30) + self.assertFalse(p.balance_enabled) + + def test_calculate_next_action_time(self): + p = Personality(min_interval=5, max_interval=10) + t = p.calculate_next_action_time() + self.assertIsNotNone(t) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/pitemperature/README.md b/src/modules/pitemperature/README.md new file mode 100644 index 00000000..ba8c3b89 --- /dev/null +++ b/src/modules/pitemperature/README.md @@ -0,0 +1,28 @@ +# PiTemperature + +## Overview + +Monitors Raspberry Pi CPU temperature and throttles/sleeps the system when needed. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +pitemperature: + enabled: false # Set to true to enable this module + path: modules.pitemperature.pitemperature.PiTemperature + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/pitemperature/__init__.py b/src/modules/pitemperature/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/pitemperature/config.yml b/src/modules/pitemperature/config.yml new file mode 100644 index 00000000..9c30db2f --- /dev/null +++ b/src/modules/pitemperature/config.yml @@ -0,0 +1,2 @@ +pitemperature: + class: PiTemperature diff --git a/modules/pitemperature.py b/src/modules/pitemperature/pitemperature.py similarity index 100% rename from modules/pitemperature.py rename to src/modules/pitemperature/pitemperature.py diff --git a/src/modules/pitemperature/tests/__init__.py b/src/modules/pitemperature/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_pitemperature.py b/src/modules/pitemperature/tests/test_pitemperature.py similarity index 74% rename from tests/test_pitemperature.py rename to src/modules/pitemperature/tests/test_pitemperature.py index 759cbc66..4c06445a 100644 --- a/tests/test_pitemperature.py +++ b/src/modules/pitemperature/tests/test_pitemperature.py @@ -1,15 +1,15 @@ import unittest from unittest import mock -from modules.pitemperature import PiTemperature +from modules.pitemperature.pitemperature import PiTemperature class TestPiTemperature(unittest.TestCase): - - @mock.patch('modules.pitemperature.os.popen') + @mock.patch('modules.pitemperature.pitemperature.os.popen') def test_monitor_critical_temp(self, mock_popen): mock_popen.return_value.readline.return_value = "temp=86'C" temp_module = PiTemperature() - temp_module.messaging_service = mock.MagicMock() + with mock.patch('modules.pitemperature.pitemperature.threading.Thread'): + temp_module.messaging_service = mock.MagicMock() with mock.patch.object(temp_module, 'publish') as mock_publish, \ mock.patch.object(temp_module, 'log') as mock_log: temp_module.monitor() @@ -20,11 +20,12 @@ def test_monitor_critical_temp(self, mock_popen): mock_log.assert_called_with('Temperature is critical: 86°C', 'critical') - @mock.patch('modules.pitemperature.os.popen') + @mock.patch('modules.pitemperature.pitemperature.os.popen') def test_monitor_high_temp(self, mock_popen): mock_popen.return_value.readline.return_value = "temp=81'C" temp_module = PiTemperature() - temp_module.messaging_service = mock.MagicMock() + with mock.patch('modules.pitemperature.pitemperature.threading.Thread'): + temp_module.messaging_service = mock.MagicMock() with mock.patch.object(temp_module, 'publish') as mock_publish, \ mock.patch.object(temp_module, 'log') as mock_log: temp_module.monitor() @@ -35,11 +36,12 @@ def test_monitor_high_temp(self, mock_popen): mock_log.assert_called_with('Temperature is high: 81°C', 'warning') - @mock.patch('modules.pitemperature.os.popen') + @mock.patch('modules.pitemperature.pitemperature.os.popen') def test_monitor_normal_temp(self, mock_popen): mock_popen.return_value.readline.return_value = "temp=50'C" temp_module = PiTemperature() - temp_module.messaging_service = mock.MagicMock() + with mock.patch('modules.pitemperature.pitemperature.threading.Thread'): + temp_module.messaging_service = mock.MagicMock() with mock.patch.object(temp_module, 'publish') as mock_publish, \ mock.patch.object(temp_module, 'log') as mock_log: temp_module.monitor() @@ -49,12 +51,13 @@ def test_monitor_normal_temp(self, mock_popen): # Should log only the debug message mock_log.assert_called_with('Temperature: 50°C', 'debug') - @mock.patch('modules.pitemperature.os.popen') - @mock.patch('modules.pitemperature.sys.exit') + @mock.patch('modules.pitemperature.pitemperature.os.popen') + @mock.patch('modules.pitemperature.pitemperature.sys.exit') def test_monitor_shutdown_temp(self, mock_exit, mock_popen): mock_popen.return_value.readline.return_value = "temp=91'C" temp_module = PiTemperature() - temp_module.messaging_service = mock.MagicMock() + with mock.patch('modules.pitemperature.pitemperature.threading.Thread'): + temp_module.messaging_service = mock.MagicMock() with mock.patch.object(temp_module, 'publish') as mock_publish, \ mock.patch.object(temp_module, 'log') as mock_log: temp_module.monitor() diff --git a/src/modules/sensor/__init__.py b/src/modules/sensor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/sensor/imu/__init__.py b/src/modules/sensor/imu/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/BNO055.md b/src/modules/sensor/imu/bno055/README.md similarity index 100% rename from docs/BNO055.md rename to src/modules/sensor/imu/bno055/README.md diff --git a/src/modules/sensor/imu/bno055/__init__.py b/src/modules/sensor/imu/bno055/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/sensor/imu/bno055.py b/src/modules/sensor/imu/bno055/bno055.py similarity index 100% rename from modules/sensor/imu/bno055.py rename to src/modules/sensor/imu/bno055/bno055.py diff --git a/src/modules/sensor/imu/bno055/config.yml b/src/modules/sensor/imu/bno055/config.yml new file mode 100644 index 00000000..b5265790 --- /dev/null +++ b/src/modules/sensor/imu/bno055/config.yml @@ -0,0 +1,6 @@ +bno055: + class: BNO055 + dependencies: + python: + - adafruit-circuitpython-bno055 + - adafruit-blinka diff --git a/src/modules/sensor/imu/bno055/tests/__init__.py b/src/modules/sensor/imu/bno055/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_bno055.py b/src/modules/sensor/imu/bno055/tests/test_bno055.py similarity index 98% rename from tests/test_bno055.py rename to src/modules/sensor/imu/bno055/tests/test_bno055.py index e43ed298..a8c32934 100644 --- a/tests/test_bno055.py +++ b/src/modules/sensor/imu/bno055/tests/test_bno055.py @@ -14,7 +14,7 @@ sys.modules['pubsub'] = mock_pubsub sys.modules['pubsub.pub'] = mock_pubsub.pub -from modules.sensor.imu.bno055 import BNO055 +from modules.sensor.imu.bno055.bno055 import BNO055 def make_sensor(**kwargs): @@ -75,7 +75,7 @@ def test_init_test_on_boot(self): def fake_read_data(self_inner): call_count['n'] += 1 - with patch('modules.sensor.imu.bno055.time.sleep', side_effect=StopIteration), \ + with patch('modules.sensor.imu.bno055.bno055.time.sleep', side_effect=StopIteration), \ patch.object(BNO055, 'read_data', fake_read_data): with self.assertRaises(StopIteration): make_sensor(test_on_boot=True) diff --git a/docs/MPU6050.md b/src/modules/sensor/imu/mpu6050/README.md similarity index 100% rename from docs/MPU6050.md rename to src/modules/sensor/imu/mpu6050/README.md diff --git a/src/modules/sensor/imu/mpu6050/__init__.py b/src/modules/sensor/imu/mpu6050/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/sensor/imu/mpu6050/config.yml b/src/modules/sensor/imu/mpu6050/config.yml new file mode 100644 index 00000000..b2ebcfab --- /dev/null +++ b/src/modules/sensor/imu/mpu6050/config.yml @@ -0,0 +1,7 @@ +mpu6050: + config: + test_on_boot: true + dependencies: + unix: + - python3-smbus + class: MPU6050 diff --git a/modules/sensor/imu/mpu6050.py b/src/modules/sensor/imu/mpu6050/mpu6050.py similarity index 100% rename from modules/sensor/imu/mpu6050.py rename to src/modules/sensor/imu/mpu6050/mpu6050.py diff --git a/src/modules/sensor/imu/mpu6050/tests/__init__.py b/src/modules/sensor/imu/mpu6050/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/sensor/imu/mpu6050/tests/test_mpu6050.py b/src/modules/sensor/imu/mpu6050/tests/test_mpu6050.py new file mode 100644 index 00000000..9ca4818b --- /dev/null +++ b/src/modules/sensor/imu/mpu6050/tests/test_mpu6050.py @@ -0,0 +1,23 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +mock_smbus = MagicMock() +mock_pubsub = MagicMock() +sys.modules['smbus'] = mock_smbus +sys.modules['pubsub'] = mock_pubsub +sys.modules['pubsub.pub'] = mock_pubsub.pub + +with patch('time.sleep', return_value=None): + from modules.sensor.imu.mpu6050.mpu6050 import MPU6050 + +class TestMPU6050(unittest.TestCase): + + def test_init(self): + with patch('time.sleep', return_value=None): + sensor = MPU6050() + self.assertEqual(sensor.Device_Address, 0x68) + mock_smbus.SMBus.assert_called_with(1) + +if __name__ == '__main__': + unittest.main() diff --git a/src/modules/translator/README.md b/src/modules/translator/README.md new file mode 100644 index 00000000..f3fa9f8c --- /dev/null +++ b/src/modules/translator/README.md @@ -0,0 +1,28 @@ +# Translator + +## Overview + +Text translation module. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +translator: + enabled: false # Set to true to enable this module + path: modules.translator.translator.Translator + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/translator/__init__.py b/src/modules/translator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/translator/config.yml b/src/modules/translator/config.yml new file mode 100644 index 00000000..dc640263 --- /dev/null +++ b/src/modules/translator/config.yml @@ -0,0 +1,9 @@ +translator: + config: + default: + src: en + dest: en + dependencies: + python: + - googletrans==3.1.0a0 + class: Translator diff --git a/src/modules/translator/tests/__init__.py b/src/modules/translator/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/translator/tests/test_translator.py b/src/modules/translator/tests/test_translator.py new file mode 100644 index 00000000..2e2899fb --- /dev/null +++ b/src/modules/translator/tests/test_translator.py @@ -0,0 +1,34 @@ +import unittest +from unittest.mock import MagicMock, patch + +mock_googletrans = MagicMock() +mock_translator_instance = MagicMock() +mock_googletrans.Translator.return_value = mock_translator_instance + +import sys +sys.modules['googletrans'] = mock_googletrans + +from modules.translator.translator import Translator + +class TestTranslator(unittest.TestCase): + + def test_init_defaults(self): + t = Translator() + self.assertEqual(t.src, 'en') + self.assertEqual(t.dest, 'en') + + def test_same_language_no_translation(self): + t = Translator(src='en', dest='en') + result = t.request('hello') + self.assertEqual(result, 'hello') + + def test_request(self): + t = Translator(src='en', dest='es') + mock_translation = MagicMock() + mock_translation.text = 'hola' + mock_translator_instance.translate.return_value = mock_translation + result = t.request('hello') + self.assertEqual(result, 'hola') + +if __name__ == '__main__': + unittest.main() diff --git a/modules/translator.py b/src/modules/translator/translator.py similarity index 100% rename from modules/translator.py rename to src/modules/translator/translator.py diff --git a/src/modules/vision/__init__.py b/src/modules/vision/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/imx500/__init__.py b/src/modules/vision/imx500/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/vision/imx500/calibration.py b/src/modules/vision/imx500/calibration.py similarity index 100% rename from modules/vision/imx500/calibration.py rename to src/modules/vision/imx500/calibration.py diff --git a/src/modules/vision/imx500/tracking/README.md b/src/modules/vision/imx500/tracking/README.md new file mode 100644 index 00000000..b4ebefe4 --- /dev/null +++ b/src/modules/vision/imx500/tracking/README.md @@ -0,0 +1,28 @@ +# Tracking + +## Overview + +IMX500 AI camera tracking module for object tracking. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +tracking: + enabled: false # Set to true to enable this module + path: modules.vision.imx500.tracking.tracking.Tracking + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/vision/imx500/tracking/__init__.py b/src/modules/vision/imx500/tracking/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/imx500/tracking/config.yml b/src/modules/vision/imx500/tracking/config.yml new file mode 100644 index 00000000..e8dd3cd1 --- /dev/null +++ b/src/modules/vision/imx500/tracking/config.yml @@ -0,0 +1,6 @@ +tracking: + config: + active: true + name: tracking + filter: person + class: Tracking diff --git a/src/modules/vision/imx500/tracking/tests/__init__.py b/src/modules/vision/imx500/tracking/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/imx500/tracking/tests/test_tracking.py b/src/modules/vision/imx500/tracking/tests/test_tracking.py new file mode 100644 index 00000000..c3896fd2 --- /dev/null +++ b/src/modules/vision/imx500/tracking/tests/test_tracking.py @@ -0,0 +1,36 @@ +import unittest +from unittest.mock import MagicMock, patch +from modules.vision.imx500.tracking.tracking import Tracking + +class TestTracking(unittest.TestCase): + + def test_init_defaults(self): + t = Tracking() + self.assertTrue(t.active) + self.assertFalse(t.moving) + self.assertEqual(t.filter, 'person') + + def test_init_custom(self): + t = Tracking(active=False, filter='car') + self.assertFalse(t.active) + self.assertEqual(t.filter, 'car') + + def test_set_state(self): + t = Tracking() + t.set_state(False) + self.assertFalse(t.active) + + def test_unfreeze(self): + t = Tracking() + t.moving = True + t.unfreeze() + self.assertFalse(t.moving) + + def test_setup_messaging(self): + t = Tracking() + with patch.object(t, 'subscribe') as mock_sub: + t.setup_messaging() + mock_sub.assert_any_call('vision/detections', t.handle) + +if __name__ == '__main__': + unittest.main() diff --git a/modules/vision/imx500/tracking.py b/src/modules/vision/imx500/tracking/tracking.py similarity index 100% rename from modules/vision/imx500/tracking.py rename to src/modules/vision/imx500/tracking/tracking.py diff --git a/src/modules/vision/imx500/vision/README.md b/src/modules/vision/imx500/vision/README.md new file mode 100644 index 00000000..474091de --- /dev/null +++ b/src/modules/vision/imx500/vision/README.md @@ -0,0 +1,28 @@ +# Vision + +## Overview + +IMX500 AI camera vision module for object detection. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +vision_imx500: + enabled: false # Set to true to enable this module + path: modules.vision.imx500.vision.vision.Vision + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/vision/imx500/vision/__init__.py b/src/modules/vision/imx500/vision/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/imx500/vision/config.yml b/src/modules/vision/imx500/vision/config.yml new file mode 100644 index 00000000..9c76bef2 --- /dev/null +++ b/src/modules/vision/imx500/vision/config.yml @@ -0,0 +1,9 @@ +vision_imx500: + config: + preview: false + dependencies: + unix: + - imx500-all + python: + - opencv-python + class: Vision diff --git a/src/modules/vision/imx500/vision/tests/__init__.py b/src/modules/vision/imx500/vision/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/imx500/vision/tests/test_vision.py b/src/modules/vision/imx500/vision/tests/test_vision.py new file mode 100644 index 00000000..41393016 --- /dev/null +++ b/src/modules/vision/imx500/vision/tests/test_vision.py @@ -0,0 +1,29 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +# Mock hardware-specific modules +for mod in ['cv2', 'numpy', 'picamera2', 'libcamera', 'argparse']: + if mod not in sys.modules: + sys.modules[mod] = MagicMock() +sys.modules['picamera2.devices'] = MagicMock() +sys.modules['picamera2.devices.imx500'] = MagicMock() +sys.modules['picamera2.devices.imx500.imx500_postprocess'] = MagicMock() +sys.modules['numpy'] = MagicMock() + +from modules.vision.imx500.vision.vision import Vision + +class TestIMX500Vision(unittest.TestCase): + + def test_class_exists(self): + self.assertTrue(hasattr(Vision, '__init__')) + + def test_setup_messaging(self): + v = Vision.__new__(Vision) + with patch.object(v, 'subscribe') as mock_sub, \ + patch.object(v, 'publish') as mock_pub: + v.setup_messaging() + # Should subscribe to system topics + +if __name__ == '__main__': + unittest.main() diff --git a/modules/vision/imx500/vision.py b/src/modules/vision/imx500/vision/vision.py similarity index 99% rename from modules/vision/imx500/vision.py rename to src/modules/vision/imx500/vision/vision.py index 9070178f..9555119b 100644 --- a/modules/vision/imx500/vision.py +++ b/src/modules/vision/imx500/vision/vision.py @@ -373,6 +373,7 @@ def get_args(): help="Path to the labels file") parser.add_argument("--print-self.intrinsics", action="store_true", help="Print JSON network_intrinsics then exit") + parser.add_argument("--env", default='cody', help="Set the environment (e.g. archie, buddy, cody, server, laptop)") return parser.parse_args() diff --git a/src/modules/vision/opencv/__init__.py b/src/modules/vision/opencv/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/opencv/vision/README.md b/src/modules/vision/opencv/vision/README.md new file mode 100644 index 00000000..d53700a2 --- /dev/null +++ b/src/modules/vision/opencv/vision/README.md @@ -0,0 +1,28 @@ +# Vision + +## Overview + +OpenCV-based vision module for computer vision processing. + +## Configuration + +Configuration is defined in `config.yml` within this directory: + +```yaml +vision_opencv: + enabled: false # Set to true to enable this module + path: modules.vision.opencv.vision.vision.Vision + config: + # See config.yml for available options +``` + +## Usage + +Enable the module in `config.yml` and it will be automatically loaded by the `ModuleLoader`. + +## Testing + +Run the module tests with: +```bash +python3 -m unittest discover -s modules -p 'test_*.py' +``` diff --git a/src/modules/vision/opencv/vision/__init__.py b/src/modules/vision/opencv/vision/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/opencv/vision/config.yml b/src/modules/vision/opencv/vision/config.yml new file mode 100644 index 00000000..0299296f --- /dev/null +++ b/src/modules/vision/opencv/vision/config.yml @@ -0,0 +1,4 @@ +vision_opencv: + config: + preview: false + class: Vision diff --git a/src/modules/vision/opencv/vision/tests/__init__.py b/src/modules/vision/opencv/vision/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/vision/opencv/vision/tests/test_vision.py b/src/modules/vision/opencv/vision/tests/test_vision.py new file mode 100644 index 00000000..645c965c --- /dev/null +++ b/src/modules/vision/opencv/vision/tests/test_vision.py @@ -0,0 +1,29 @@ +import sys +import unittest +from unittest.mock import MagicMock, patch + +for mod in ['cv2', 'numpy', 'picamera2', 'argparse']: + if mod not in sys.modules: + sys.modules[mod] = MagicMock() + +from modules.vision.opencv.vision.vision import Vision + +class TestOpenCVVision(unittest.TestCase): + + def test_class_exists(self): + self.assertTrue(hasattr(Vision, '__init__')) + + def test_setup_messaging(self): + v = Vision.__new__(Vision) + with patch.object(v, 'subscribe') as mock_sub: + v.setup_messaging() + mock_sub.assert_not_called() + + def test_loop_calls_scan(self): + v = Vision.__new__(Vision) + with patch.object(v, 'scan') as mock_scan: + v.loop() + mock_scan.assert_called_once() + +if __name__ == '__main__': + unittest.main() diff --git a/modules/vision/opencv/vision.py b/src/modules/vision/opencv/vision/vision.py similarity index 97% rename from modules/vision/opencv/vision.py rename to src/modules/vision/opencv/vision/vision.py index 6bf43946..a31d23c7 100644 --- a/modules/vision/opencv/vision.py +++ b/src/modules/vision/opencv/vision/vision.py @@ -62,7 +62,11 @@ def __init__(self, **kwargs): self.moving = False def setup_messaging(self): - self.subscribe('system/loop', self.scan) + pass + + def loop(self): + """Called every system loop cycle to run object detection.""" + self.scan() def scan(self): self.last_results = self.parse_detections() diff --git a/docs/XboxController.md b/src/modules/xbox_controller/README.md similarity index 100% rename from docs/XboxController.md rename to src/modules/xbox_controller/README.md diff --git a/src/modules/xbox_controller/__init__.py b/src/modules/xbox_controller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/xbox_controller/config.yml b/src/modules/xbox_controller/config.yml new file mode 100644 index 00000000..29a3e621 --- /dev/null +++ b/src/modules/xbox_controller/config.yml @@ -0,0 +1,31 @@ +xbox_controller: + config: + normalised: true + debug: false + device: /dev/input/js0 + deadzone: 4000 + button_names: + 0: BTN_A + 1: BTN_B + 2: BTN_X + 3: BTN_Y + 4: BTN_TL + 5: BTN_TR + 6: BTN_SELECT + 7: BTN_START + 8: BTN_MODE + 9: BTN_THUMBL + 10: BTN_THUMBR + 11: KEY_RECORD + axis_names: + 0: ABS_X + 1: ABS_Y + 2: ABS_Z + 3: ABS_RZ + 4: ABS_RX + 5: ABS_RY + 6: ABS_HAT0X + 7: ABS_HAT0Y + axis_max: 32767.0 + axis_min: -32767.0 + class: XboxController diff --git a/src/modules/xbox_controller/tests/__init__.py b/src/modules/xbox_controller/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_controller.py b/src/modules/xbox_controller/tests/test_xbox_controller.py old mode 100755 new mode 100644 similarity index 98% rename from tests/test_controller.py rename to src/modules/xbox_controller/tests/test_xbox_controller.py index 785bb06a..396e9802 --- a/tests/test_controller.py +++ b/src/modules/xbox_controller/tests/test_xbox_controller.py @@ -1,10 +1,8 @@ import sys -import os import unittest from unittest.mock import MagicMock, patch sys.modules['yaml'] = MagicMock() -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -from modules.xbox_controller import XboxController +from modules.xbox_controller.xbox_controller import XboxController class TestXboxController(unittest.TestCase): diff --git a/modules/xbox_controller.py b/src/modules/xbox_controller/xbox_controller.py similarity index 100% rename from modules/xbox_controller.py rename to src/modules/xbox_controller/xbox_controller.py diff --git a/system_loop.py b/src/system_loop.py similarity index 95% rename from system_loop.py rename to src/system_loop.py index 3fe96088..367284bd 100644 --- a/system_loop.py +++ b/src/system_loop.py @@ -8,9 +8,9 @@ class SystemLoop: STATE_RUNNING = 3 DEFAULT_SLEEP_INTERVAL = 0.01 # 10ms - def __init__(self, messaging_service, personality): + def __init__(self, messaging_service, module_instances=None): self.messaging_service = messaging_service - self.personality = personality + self._modules = list((module_instances or {}).values()) self._state = SystemLoop.STATE_RUNNING self._state_requests = {} self._motion_state = self._state # To remember previous state before sleeping due to motion @@ -118,9 +118,9 @@ def run(self): time.sleep(self.sleep_interval) if self._state == SystemLoop.STATE_SLEEPING: continue - self.messaging_service.publish('system/loop') - if self.personality is not None: - self.personality.loop() # Call personality loop every cycle for more responsive updates + for module in self._modules: + if hasattr(module, 'loop') and callable(module.loop): + module.loop() now = time.time() if now - self._second_loop > 1: self._second_loop = now diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_base_module.py b/src/tests/test_base_module.py similarity index 73% rename from tests/test_base_module.py rename to src/tests/test_base_module.py index fad38079..5865baf1 100644 --- a/tests/test_base_module.py +++ b/src/tests/test_base_module.py @@ -23,6 +23,16 @@ def test_publish_with_messaging_service(self): self.module.publish('test/topic', arg1='value1') self.mock_messaging_service.publish.assert_called_with('test/topic', arg1='value1') + def test_publish_mqtt_with_messaging_service(self): + self.module.publish_mqtt('test/topic', arg1='value1') + self.mock_messaging_service.publish_mqtt.assert_called_with('test/topic', arg1='value1') + + def test_publish_mqtt_no_messaging_service(self): + self.module.messaging_service = None + with self.assertRaises(ValueError) as context: + self.module.publish_mqtt('test/topic') + self.assertEqual(str(context.exception), "Messaging service not set.") + def test_subscribe_no_messaging_service(self): self.module.messaging_service = None with self.assertRaises(ValueError) as context: @@ -42,5 +52,10 @@ def test_log(self): self.assertTrue('log/debug' in args) self.assertTrue('[BaseModule.test_log' in kwargs['message']) + def test_loop_is_noop(self): + """loop() is a no-op placeholder — should return None without raising.""" + result = self.module.loop() + self.assertIsNone(result) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/src/tests/test_module_loader.py b/src/tests/test_module_loader.py new file mode 100644 index 00000000..0cb1f60f --- /dev/null +++ b/src/tests/test_module_loader.py @@ -0,0 +1,393 @@ + +import unittest +from unittest.mock import Mock, patch, MagicMock +import sys +import os +# Mock pubsub before importing module_loader +import types +sys.modules['pubsub'] = types.ModuleType('pubsub') +sys.modules['pubsub'].pub = MagicMock() +# mock yaml before importing module_loader +sys.modules['yaml'] = MagicMock() +sys.modules['yaml'].safe_load = MagicMock() +import yaml +from module_loader import ModuleLoader + + +class TestModuleLoader(unittest.TestCase): + """Test cases for ModuleLoader class""" + + def setUp(self): + self.patcher_os_walk = patch('os.walk') + self.patcher_open = patch('builtins.open', create=True) + self.patcher_yaml = patch('yaml.safe_load') + self.patcher_importlib_spec = patch('importlib.util.spec_from_file_location') + self.patcher_importlib_mod = patch('importlib.util.module_from_spec') + # Patch _load_environment_config to control enabled/override state per test + self.patcher_env = patch.object(ModuleLoader, '_load_environment_config') + self.mock_walk = self.patcher_os_walk.start() + self.mock_open = self.patcher_open.start() + self.mock_yaml = self.patcher_yaml.start() + self.mock_spec = self.patcher_importlib_spec.start() + self.mock_mod = self.patcher_importlib_mod.start() + self.mock_env = self.patcher_env.start() + # Default: no modules enabled + self.mock_env.return_value = {} + + def tearDown(self): + patch.stopall() + + def _setup_module_config(self, class_name='TestModule', config=None, instances=None): + """Build a module config dict as it would appear in config.yml.""" + module_config = { + 'class': class_name, + } + if config is not None: + module_config['config'] = config + if instances is not None: + module_config['instances'] = instances + return {'test_module': module_config} + + def test_initialization_default_env(self): + # Should default to 'laptop' environment + self.mock_walk.return_value = [] + loader = ModuleLoader(config_folder='modules') + self.assertEqual(loader.environment, 'laptop') + + def test_load_yaml_files_enabled(self): + # Module enabled in environment config is loaded + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + self.mock_yaml.return_value = self._setup_module_config() + self.mock_env.return_value = {'test_module': {'enabled': True}} + loader = ModuleLoader(config_folder='modules', environment='laptop') + self.assertEqual(len(loader.modules), 1) + + def test_load_yaml_files_disabled(self): + # Module not in environment config (or explicitly disabled) is skipped + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + self.mock_yaml.return_value = self._setup_module_config() + self.mock_env.return_value = {'test_module': {'enabled': False}} + loader = ModuleLoader(config_folder='modules', environment='laptop') + self.assertEqual(len(loader.modules), 0) + + def test_load_yaml_files_not_in_env(self): + # Module absent from environment file is not loaded + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + self.mock_yaml.return_value = self._setup_module_config() + self.mock_env.return_value = {} # module not listed + loader = ModuleLoader(config_folder='modules', environment='laptop') + self.assertEqual(len(loader.modules), 0) + + def test_load_yaml_files_env_config_override(self): + # Environment config values are merged over module config values + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + self.mock_yaml.return_value = self._setup_module_config(config={'pin': 5, 'test_on_boot': False}) + self.mock_env.return_value = {'test_module': {'enabled': True, 'config': {'pin': 9}}} + loader = ModuleLoader(config_folder='modules', environment='laptop') + self.assertEqual(len(loader.modules), 1) + # pin should be overridden to 9, test_on_boot should still be False + self.assertEqual(loader.modules[0]['config']['pin'], 9) + self.assertEqual(loader.modules[0]['config']['test_on_boot'], False) + + def test_load_yaml_files_env_instances_override(self): + # Environment instances list replaces module instances list + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + module_instances = [{'name': 'original', 'id': 0}] + env_instances = [{'name': 'env_override', 'id': 99}] + self.mock_yaml.return_value = self._setup_module_config(instances=module_instances) + self.mock_env.return_value = {'test_module': {'enabled': True, 'instances': env_instances}} + loader = ModuleLoader(config_folder='modules', environment='laptop') + self.assertEqual(len(loader.modules), 1) + self.assertEqual(loader.modules[0]['instances'], env_instances) + + @patch.dict('sys.modules', {'yaml': MagicMock()}) + def test_load_yaml_files_yaml_error(self): + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + self.mock_yaml.side_effect = yaml.YAMLError('YAML error') + self.mock_env.return_value = {'test_module': {'enabled': True}} + loader = ModuleLoader(config_folder='modules', environment='laptop') + # Should not raise, just print error + self.assertEqual(loader.modules, []) + self.mock_yaml.side_effect = None + + def test_load_modules_success_and_instance_naming(self): + # Setup for dynamic loading with two instances + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + instances = [ + {'name': 'foo', 'param': 1}, + {'name': 'bar', 'param': 2} + ] + self.mock_yaml.return_value = self._setup_module_config(instances=instances) + self.mock_env.return_value = {'test_module': {'enabled': True}} + mock_spec = MagicMock() + self.mock_spec.return_value = mock_spec + mock_mod = MagicMock() + self.mock_mod.return_value = mock_mod + + class Dummy: + def __init__(self, **kwargs): + self.kwargs = kwargs + setattr(mock_mod, 'TestModule', Dummy) + mock_spec.loader.exec_module.side_effect = lambda mod: mod.__setattr__('TestModule', Dummy) + + loader = ModuleLoader(config_folder='modules', environment='laptop') + with patch('importlib.util.module_from_spec', return_value=mock_mod): + instances_dict = loader.load_modules() + self.assertIn('TestModule_foo', instances_dict) + self.assertIn('TestModule_bar', instances_dict) + self.assertIsInstance(instances_dict['TestModule_foo'], Dummy) + self.assertEqual(instances_dict['TestModule_foo'].kwargs['name'], 'foo') + + def test_load_modules_no_config(self): + # Module with no config section uses empty dict + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + module_cfg = self._setup_module_config() # no config + self.mock_yaml.return_value = module_cfg + self.mock_env.return_value = {'test_module': {'enabled': True}} + mock_spec = MagicMock() + self.mock_spec.return_value = mock_spec + mock_mod = MagicMock() + + class Dummy: + def __init__(self, **kwargs): + self.kwargs = kwargs + setattr(mock_mod, 'TestModule', Dummy) + mock_spec.loader.exec_module.side_effect = lambda mod: mod.__setattr__('TestModule', Dummy) + + loader = ModuleLoader(config_folder='modules', environment='laptop') + with patch('importlib.util.module_from_spec', return_value=mock_mod): + instances_dict = loader.load_modules() + self.assertIn('TestModule', instances_dict) + self.assertIsInstance(instances_dict['TestModule'], Dummy) + + def test_load_modules_import_error(self): + # Import error is caught and logged, does not raise + self.mock_walk.return_value = [('modules/test', [], ['config.yml'])] + self.mock_open.return_value.__enter__.return_value = MagicMock() + self.mock_yaml.return_value = self._setup_module_config() + self.mock_env.return_value = {'test_module': {'enabled': True}} + mock_spec = MagicMock() + self.mock_spec.return_value = mock_spec + mock_mod = MagicMock() + setattr(mock_mod, 'TestModule', lambda **kwargs: None) + + def raise_import(mod): + raise Exception('Import error') + mock_spec.loader.exec_module.side_effect = raise_import + + loader = ModuleLoader(config_folder='modules', environment='laptop') + with patch('importlib.util.module_from_spec', return_value=mock_mod): + instances_dict = loader.load_modules() + self.assertIsInstance(instances_dict, dict) + + def test_set_messaging_service(self): + class Dummy: + def __init__(self): + self.messaging_service = None + modules = { + 'TestModule': Dummy(), + 'MessagingService': Dummy() + } + ms = object() + ModuleLoader().set_messaging_service(modules, ms) + self.assertIs(modules['TestModule'].messaging_service, ms) + # MessagingService itself should not be set + self.assertIsNot(modules['MessagingService'].messaging_service, ms) + + def test_load_environment_config_missing_file(self): + # Missing environment file returns empty dict (with warning) + with patch('os.path.exists', return_value=False): + loader = ModuleLoader.__new__(ModuleLoader) + loader.config_folder = 'modules' + loader.environment = 'nonexistent' + result = loader._load_environment_config() + self.assertEqual(result, {}) + + # ── inject_dependencies tests ───────────────────────────────────────────── + + def _make_loader_with_module(self, module_key='my_module', class_name='MyClass'): + """Create a ModuleLoader with a single pre-loaded module (bypassing file I/O).""" + loader = ModuleLoader.__new__(ModuleLoader) + loader.config_folder = 'modules' + loader.environment = 'laptop' + loader.modules = [{'_module_key': module_key, '_class': class_name}] + return loader + + def test_inject_simple_attribute(self): + # inject: attr: TargetKey sets target.attr = instances['TargetKey'] + class Target: + vision = None + class Source: + pass + + loader = self._make_loader_with_module() + target_inst = Target() + source_inst = Source() + instances = {'MyClass': target_inst, 'Vision': source_inst} + self.mock_env.return_value = { + 'my_module': {'enabled': True, 'inject': {'vision': 'Vision'}} + } + loader.inject_dependencies(instances) + self.assertIs(target_inst.vision, source_inst) + + def test_inject_dict_path(self): + # inject: attr.subkey: TargetKey sets target.attr['subkey'] = instances['TargetKey'] + class Target: + imu = {} + class IMU: + pass + + loader = self._make_loader_with_module() + target_inst = Target() + imu_inst = IMU() + instances = {'MyClass': target_inst, 'BNO055_imu_head': imu_inst} + self.mock_env.return_value = { + 'my_module': {'enabled': True, 'inject': {'imu.head': 'BNO055_imu_head'}} + } + loader.inject_dependencies(instances) + self.assertIs(target_inst.imu['head'], imu_inst) + + def test_inject_wildcard_pattern(self): + # inject: servos: "Servo_*" builds a dict of {identifier: instance} + class Target: + servos = {} + class ServoInst: + def __init__(self, name): + self.identifier = name + + loader = self._make_loader_with_module() + target_inst = Target() + servo_a = ServoInst('leg_l') + servo_b = ServoInst('leg_r') + instances = { + 'MyClass': target_inst, + 'Servo_leg_l': servo_a, + 'Servo_leg_r': servo_b, + } + self.mock_env.return_value = { + 'my_module': {'enabled': True, 'inject': {'servos': 'Servo_*'}} + } + loader.inject_dependencies(instances) + self.assertIn('leg_l', target_inst.servos) + self.assertIn('leg_r', target_inst.servos) + self.assertIs(target_inst.servos['leg_l'], servo_a) + self.assertIs(target_inst.servos['leg_r'], servo_b) + + def test_inject_missing_source_logs_and_skips(self): + # Missing source should not raise; attribute is left unchanged + class Target: + vision = None + + loader = self._make_loader_with_module() + target_inst = Target() + instances = {'MyClass': target_inst} # 'Vision' not present + self.mock_env.return_value = { + 'my_module': {'enabled': True, 'inject': {'vision': 'Vision'}} + } + loader.inject_dependencies(instances) + self.assertIsNone(target_inst.vision) # unchanged + + def test_on_inject_calls_method(self): + # on_inject: [start] calls target.start() after injection + class Target: + started = False + def start(self): + self.started = True + + loader = self._make_loader_with_module() + target_inst = Target() + instances = {'MyClass': target_inst} + self.mock_env.return_value = { + 'my_module': {'enabled': True, 'on_inject': ['start']} + } + loader.inject_dependencies(instances) + self.assertTrue(target_inst.started) + + def test_on_inject_string_form(self): + # on_inject: start (string, not list) also works + class Target: + called = False + def start(self): + self.called = True + + loader = self._make_loader_with_module() + target_inst = Target() + instances = {'MyClass': target_inst} + self.mock_env.return_value = { + 'my_module': {'enabled': True, 'on_inject': 'start'} + } + loader.inject_dependencies(instances) + self.assertTrue(target_inst.called) + + def test_inject_injects_messaging_service_automatically(self): + # inject_dependencies always injects messaging service for all non-MessagingService modules + class MessagingServiceModule: + messaging_service = object() + class OtherModule: + messaging_service = None + + loader = self._make_loader_with_module() + ms_inst = MessagingServiceModule() + other_inst = OtherModule() + instances = {'MessagingService': ms_inst, 'MyClass': other_inst} + self.mock_env.return_value = {} + loader.inject_dependencies(instances) + self.assertIs(other_inst.messaging_service, ms_inst.messaging_service) + + def test_resolve_inject_source_simple(self): + loader = ModuleLoader.__new__(ModuleLoader) + instances = {'Vision': object()} + result = loader._resolve_inject_source('Vision', instances) + self.assertIs(result, instances['Vision']) + + def test_resolve_inject_source_wildcard(self): + loader = ModuleLoader.__new__(ModuleLoader) + class Inst: + def __init__(self, id_): + self.identifier = id_ + a, b = Inst('neck_pan'), Inst('neck_tilt') + instances = {'Servo_neck_pan': a, 'Servo_neck_tilt': b, 'Other': object()} + result = loader._resolve_inject_source('Servo_*', instances) + self.assertEqual(set(result.keys()), {'neck_pan', 'neck_tilt'}) + + def test_resolve_inject_source_wildcard_no_match(self): + loader = ModuleLoader.__new__(ModuleLoader) + result = loader._resolve_inject_source('Servo_*', {'Other': object()}) + self.assertIsNone(result) + + def test_set_attr_simple(self): + loader = ModuleLoader.__new__(ModuleLoader) + class TestTarget: + vision = None + target = TestTarget() + loader._set_attr(target, 'vision', 'val') + self.assertEqual(target.vision, 'val') + + def test_set_attr_dict_path(self): + loader = ModuleLoader.__new__(ModuleLoader) + class TestTarget: + imu = {} + target = TestTarget() + loader._set_attr(target, 'imu.head', 'val') + self.assertEqual(target.imu['head'], 'val') + + def test_set_attr_creates_dict_if_absent(self): + loader = ModuleLoader.__new__(ModuleLoader) + class TestTarget: + pass + target = TestTarget() + target.imu = None # not a dict + loader._set_attr(target, 'imu.head', 'val') + self.assertEqual(target.imu, {'head': 'val'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/startup.sh b/startup.sh index 0f53c637..9f2df2f0 100755 --- a/startup.sh +++ b/startup.sh @@ -9,6 +9,16 @@ BASE_DIR="$(dirname "$(realpath "$0")")" # Ensure any existing instance of main.py is terminated sudo pkill -f "$BASE_DIR/main.py" +# Source /home/$USER/.myenv to set environment variables for this session +if [ -f "/home/$USER/.myenv" ]; then + echo "Sourcing environment variables from /home/$USER/.myenv" + set -a # Automatically export all variables + source "/home/$USER/.myenv" + set +a # Disable automatic export +else + echo "No environment file found at /home/$USER/.myenv. Continuing without sourcing environment variables." +fi + # Start necessary services # sudo modprobe bcm2835-v4l2 # Enable camera (if needed) sudo pigpiod # Start the GPIO daemon. @@ -30,5 +40,5 @@ if [ -n "$1" ]; then else ENV_ARG="" fi -"$BASE_DIR/myenv/bin/python3" "$BASE_DIR/main.py" $ENV_ARG +"$BASE_DIR/myenv/bin/python3" "$BASE_DIR/src/main.py" $ENV_ARG diff --git a/test.sh b/test.sh index ab45c77c..b88a7e89 100755 --- a/test.sh +++ b/test.sh @@ -1 +1,2 @@ -python3 -m unittest discover -s tests -p "test_*.py" \ No newline at end of file +PYTHONPATH=src python3 -m unittest discover -s src/tests -p "test_*.py" -t src +PYTHONPATH=src python3 -m unittest discover -s src/modules -p "test_*.py" -t src diff --git a/tests/test_module_loader.py b/tests/test_module_loader.py deleted file mode 100644 index 47645b94..00000000 --- a/tests/test_module_loader.py +++ /dev/null @@ -1,204 +0,0 @@ - -import unittest -from unittest.mock import Mock, patch, MagicMock -import sys -import os -# Mock pubsub before importing module_loader -import types -sys.modules['pubsub'] = types.ModuleType('pubsub') -sys.modules['pubsub'].pub = MagicMock() -from module_loader import ModuleLoader - -# Add parent directory to path to import module_loader -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - - - - -class TestModuleLoader(unittest.TestCase): - """Test cases for ModuleLoader class""" - - def setUp(self): - self.patcher_os_listdir = patch('os.listdir') - self.patcher_open = patch('builtins.open', create=True) - self.patcher_yaml = patch('yaml.safe_load') - self.patcher_importlib_spec = patch('importlib.util.spec_from_file_location') - self.patcher_importlib_mod = patch('importlib.util.module_from_spec') - self.mock_listdir = self.patcher_os_listdir.start() - self.mock_open = self.patcher_open.start() - self.mock_yaml = self.patcher_yaml.start() - self.mock_spec = self.patcher_importlib_spec.start() - self.mock_mod = self.patcher_importlib_mod.start() - - def tearDown(self): - patch.stopall() - - def _setup_module_config(self, enabled=True, env=None, name='TestModule', path='modules.test.TestModule', config=None, instances=None): - module_config = { - 'enabled': enabled, - 'path': path, - 'config': config or {'name': name}, - } - if env is not None: - module_config['environment'] = env - if instances is not None: - module_config['instances'] = instances - return {'test_module': module_config} - - def test_initialization_default_env(self): - # Should default to 'robot' environment - self.mock_listdir.return_value = [] - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config') - self.assertEqual(loader.environment, 'robot') - - def test_load_yaml_files_enabled_and_env_string(self): - # Only enabled modules with matching env are loaded - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - self.mock_yaml.return_value = self._setup_module_config(enabled=True, env='robot') - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - modules = loader.load_yaml_files() - self.assertEqual(len(modules), 1) - # Should skip if env does not match - self.mock_yaml.return_value = self._setup_module_config(enabled=True, env='dev') - loader = ModuleLoader(config_folder='config', environment='robot') - modules = loader.load_yaml_files() - self.assertEqual(len(modules), 0) - - def test_load_yaml_files_env_list(self): - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - self.mock_yaml.return_value = self._setup_module_config(enabled=True, env=['robot', 'dev']) - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - modules = loader.load_yaml_files() - self.assertEqual(len(modules), 1) - loader = ModuleLoader(config_folder='config', environment='other') - modules = loader.load_yaml_files() - self.assertEqual(len(modules), 0) - - def test_load_yaml_files_disabled(self): - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - self.mock_yaml.return_value = self._setup_module_config(enabled=False) - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - modules = loader.load_yaml_files() - self.assertEqual(len(modules), 0) - - def test_load_yaml_files_yaml_error(self): - import yaml - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - self.mock_yaml.side_effect = yaml.YAMLError('YAML error') - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - # Should not raise, just print error - modules = loader.load_yaml_files() - self.assertEqual(modules, []) - self.mock_yaml.side_effect = None - - def test_load_modules_success_and_instance_naming(self): - # Setup for dynamic loading - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - # One module, with two instances - instances = [ - {'name': 'foo', 'param': 1}, - {'name': 'bar', 'param': 2} - ] - self.mock_yaml.return_value = self._setup_module_config(enabled=True, instances=instances) - # Mock importlib - mock_spec = MagicMock() - self.mock_spec.return_value = mock_spec - mock_mod = MagicMock() - self.mock_mod.return_value = mock_mod - # The class to instantiate - class Dummy: - def __init__(self, **kwargs): - self.kwargs = kwargs - setattr(mock_mod, 'TestModule', Dummy) - mock_spec.loader.exec_module.side_effect = lambda mod: mod.__setattr__('TestModule', Dummy) - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - loader.modules = loader.load_yaml_files() - # Patch module_from_spec to return our mock_mod - self.mock_mod.return_value = mock_mod - self.mock_mod.side_effect = lambda spec: mock_mod - # Actually test load_modules - with patch('importlib.util.module_from_spec', return_value=mock_mod): - instances_dict = loader.load_modules() - self.assertIn('TestModule_foo', instances_dict) - self.assertIn('TestModule_bar', instances_dict) - self.assertIsInstance(instances_dict['TestModule_foo'], Dummy) - self.assertEqual(instances_dict['TestModule_foo'].kwargs['name'], 'foo') - - def test_load_modules_no_config(self): - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - # config is None - self.mock_yaml.return_value = self._setup_module_config(enabled=True, config=None) - mock_spec = MagicMock() - self.mock_spec.return_value = mock_spec - mock_mod = MagicMock() - class Dummy: - def __init__(self, **kwargs): - self.kwargs = kwargs - setattr(mock_mod, 'TestModule', Dummy) - mock_spec.loader.exec_module.side_effect = lambda mod: mod.__setattr__('TestModule', Dummy) - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - loader.modules = loader.load_yaml_files() - with patch('importlib.util.module_from_spec', return_value=mock_mod): - instances_dict = loader.load_modules() - # When config is None, instance_name becomes 'TestModule_TestModule' (module_name + '_' + None) - self.assertIn('TestModule_TestModule', instances_dict) - self.assertIsInstance(instances_dict['TestModule_TestModule'], Dummy) - - def test_load_modules_import_error(self): - self.mock_listdir.return_value = ['mod1.yml'] - mock_file = MagicMock() - self.mock_open.return_value.__enter__.return_value = mock_file - self.mock_yaml.return_value = self._setup_module_config(enabled=True) - mock_spec = MagicMock() - self.mock_spec.return_value = mock_spec - mock_mod = MagicMock() - setattr(mock_mod, 'TestModule', lambda **kwargs: None) - # Simulate import error - def raise_import(mod): - raise Exception('Import error') - mock_spec.loader.exec_module.side_effect = raise_import - from module_loader import ModuleLoader - loader = ModuleLoader(config_folder='config', environment='robot') - loader.modules = loader.load_yaml_files() - with patch('importlib.util.module_from_spec', return_value=mock_mod): - # Should not raise, just print error - instances_dict = loader.load_modules() - self.assertIsInstance(instances_dict, dict) - - def test_set_messaging_service(self): - from module_loader import ModuleLoader - class Dummy: - def __init__(self): - self.messaging_service = None - modules = { - 'TestModule': Dummy(), - 'MessagingService': Dummy() - } - ms = object() - ModuleLoader().set_messaging_service(modules, ms) - self.assertIs(modules['TestModule'].messaging_service, ms) - # MessagingService should not be set - self.assertIsNot(modules['MessagingService'].messaging_service, ms) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file