diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d191acd..76de3154 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: build: docker: # specify the version you desire here - - image: coderbot/python-gpac:3.5-tf2 + - image: coderbot/coderbot-ci:3.9-bullseye-ffmpeg working_directory: ~/repo diff --git a/.gitignore b/.gitignore index ed0fd582..d59dc0e5 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,6 @@ Thumbs.db # Swap files *.swp - # Python3 Virtual Environment folders bin/ @@ -94,3 +93,7 @@ photos/metadata.json # Uploaded updates folder updatePackages/ + +# firmware +firmware/ + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..31ed3caa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,336 @@ +That's correct, at least for linking existing material and, overall, providing and overview of the project and the (now many) components. + +As for the "CONTRIBUTING" document, I will prepare something inspired by [this](), according to these [general guidelines](https://mozillascience.github.io/working-open-workshop/contributing/). + +# Contributing to CoderBot + +:+1::robot: Thanks for your interest in the CoderBot project! :robot::+1: + +The following is a set of guidelines for contributing to CoderBot and its modules, which are hosted in the [CoderBot Organization](https://github.com/CoderBotOrg) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +#### Table Of Contents + +[Code of Conduct](#code-of-conduct) + +[What should I know before I get started?](#what-should-i-know-before-i-get-started) + * [CoderBot project description](#coderbot-project-description) + * [CoderBot architecture](#coderbot-architecture) + +[How Can I Contribute?](#how-can-i-contribute) + * [Reporting Bugs](#reporting-bugs) + * [Suggesting Enhancements](#suggesting-enhancements) + * [Your First Code Contribution](#your-first-code-contribution) + * [Pull Requests](#pull-requests) + +[Styleguides](#styleguides) + * [Git Commit Messages](#git-commit-messages) + * [JavaScript Styleguide](#javascript-styleguide) + * [CoffeeScript Styleguide](#coffeescript-styleguide) + * [Specs Styleguide](#specs-styleguide) + * [Documentation Styleguide](#documentation-styleguide) + +[Additional Notes](#additional-notes) + * [Issue and Pull Request Labels](#issue-and-pull-request-labels) + +## Code of Conduct + +This project and everyone participating in it is governed by the [CoderBot Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [info@coderbot.org](info@coderbot.org). + +## What should I know before I get started? + +### CoderBot project description + +CoderBot is an open source project with the objective of providing the hardware and software used in the educational robot with the same name. + +It is composed by several [repositories](https://github.com/CoderBotOrg). When you initially consider contributing to CoderBot, you might be unsure about which of those repositories implements the functionality you want to change or report a bug for. This section should help you with that. + +#### Package Conventions + +### CoderBot architecture + +## How Can I Contribute? + +### Reporting Bugs + +This section guides you through submitting a bug report for CoderBot. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/CoderBotOrg/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md), the information it asks for helps us resolve issues faster. + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before Submitting A Bug Report + +* TODO + +#### How Do I Submit A (Good) Bug Report? + +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#coderbot-and-repos) your bug is related to, create an issue on that repository and provide the following information by filling in [the template](https://github.com/CoderBotOrg/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md). + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. + +Provide more context by answering these questions: + +° TODO + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for CoderBot, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. + +Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](https://github.com/CoderBotOrg/.github/blob/master/.github/ISSUE_TEMPLATE/feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed. + +#### Before Submitting An Enhancement Suggestion + +#### How Do I Submit A (Good) Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#coderbot-and-repos) your enhancement suggestion is related to, create an issue on that repository and provide the following information: + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. +* **Explain why this enhancement would be useful** to most CoderBot users and isn't something that can or should be implemented as a [community package](#coderbot-and-repos). +* **List some other text editors or applications where this enhancement exists.** + +### Your First Code Contribution + +Unsure where to begin contributing to CoderBot? You can start by looking through these `beginner` and `help-wanted` issues: + +* [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two. +* [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. + +Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. + +#### Local development + +* TODO + +### Pull Requests + +The process described here has several goals: + +- Maintain CoderBot's quality +- Fix problems that are important to users +- Engage the community in working toward the best possible CoderBot +- Enable a sustainable system for CoderBot's maintainers to review contributions + +Please follow these steps to have your contribution considered by the maintainers: + +1. Follow all instructions in [the template](PULL_REQUEST_TEMPLATE.md) +2. Follow the [styleguides](#styleguides) +3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing
What if the status checks are failing?If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
+ +While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. + +## Styleguides + +### Git Commit Messages + +* Use the present tense ("Add feature" not "Added feature") +* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +* Limit the first line to 72 characters or less +* Reference issues and pull requests liberally after the first line +* When only changing documentation, include `[ci skip]` in the commit title + +### JavaScript Styleguide + +All JavaScript code is linted with [Prettier](https://prettier.io/). + +* Prefer the object spread operator (`{...anotherObj}`) to `Object.assign()` +* Inline `export`s with expressions whenever possible + ```js + // Use this: + export default class ClassName { + + } + + // Instead of: + class ClassName { + + } + export default ClassName + ``` +* Place requires in the following order: + * Built in Node Modules (such as `path`) + * Local Modules (using relative paths) +* Place class properties in the following order: + * Class methods and properties (methods starting with `static`) + * Instance methods and properties + +### Documentation Styleguide + +* Use [Markdown](https://daringfireball.net/projects/markdown). +* Reference methods and classes in markdown with the custom `{}` notation: + * Reference classes with `{ClassName}` + * Reference instance methods with `{ClassName::methodName}` + * Reference class methods with `{ClassName.methodName}` + +#### Example + +* TODO + +## Additional Notes + +### Issue and Pull Request Labels + +This section lists the labels we use to help us track and manage issues and pull requests. Most labels are used across all CoderBot repositories. + +[GitHub search](https://help.github.com/articles/searching-issues/) makes it easy to use labels for finding groups of issues or pull requests you're interested in. +The labels are loosely grouped by their purpose, but it's not required that every issue has a label from every group or that an issue can't have more than one label from the same group. + +#### Type of Issue and Issue State + +| Label name | `CoderBotOrg` :mag_right: | `CoderBot`‑org :mag_right: | Description | +| --- | --- | --- | --- | +| `enhancement` | [search][search-coderbot-repo-label-enhancement] | [search][search-coderbot-org-label-enhancement] | Feature requests. | +| `bug` | [search][search-coderbot-repo-label-bug] | [search][search-coderbot-org-label-bug] | Confirmed bugs or reports that are very likely to be bugs. | +| `question` | [search][search-coderbot-repo-label-question] | [search][search-coderbot-org-label-question] | Questions more than bug reports or feature requests (e.g. how do I do X). | +| `feedback` | [search][search-coderbot-repo-label-feedback] | [search][search-coderbot-org-label-feedback] | General feedback more than bug reports or feature requests. | +| `help-wanted` | [search][search-coderbot-repo-label-help-wanted] | [search][search-coderbot-org-label-help-wanted] | The CoderBot core team would appreciate help from the community in resolving these issues. | +| `beginner` | [search][search-coderbot-repo-label-beginner] | [search][search-coderbot-org-label-beginner] | Less complex issues which would be good first issues to work on for users who want to contribute to CoderBot. | +| `more-information-needed` | [search][search-coderbot-repo-label-more-information-needed] | [search][search-coderbot-org-label-more-information-needed] | More information needs to be collected about these problems or feature requests (e.g. steps to reproduce). | +| `needs-reproduction` | [search][search-coderbot-repo-label-needs-reproduction] | [search][search-coderbot-org-label-needs-reproduction] | Likely bugs, but haven't been reliably reproduced. | +| `blocked` | [search][search-coderbot-repo-label-blocked] | [search][search-coderbot-org-label-blocked] | Issues blocked on other issues. | +| `duplicate` | [search][search-coderbot-repo-label-duplicate] | [search][search-coderbot-org-label-duplicate] | Issues which are duplicates of other issues, i.e. they have been reported before. | +| `wontfix` | [search][search-coderbot-repo-label-wontfix] | [search][search-coderbot-org-label-wontfix] | The CoderBot core team has decided not to fix these issues for now, either because they're working as intended or for some other reason. | +| `invalid` | [search][search-coderbot-repo-label-invalid] | [search][search-coderbot-org-label-invalid] | Issues which aren't valid (e.g. user errors). | +| `package-idea` | [search][search-coderbot-repo-label-package-idea] | [search][search-coderbot-org-label-package-idea] | Feature request which might be good candidates for new packages, instead of extending CoderBot or core CoderBot packages. | +| `wrong-repo` | [search][search-coderbot-repo-label-wrong-repo] | [search][search-coderbot-org-label-wrong-repo] | | + +#### Topic Categories + +| Label name | `CoderBotOrg/backend` :mag_right: | `CoderBotOrg`‑org :mag_right: | Description | +| --- | --- | --- | --- | +| `windows` | [search][search-coderbot-repo-label-windows] | [search][search-coderbot-org-label-windows] | Related to CoderBot running on Windows. | +| `linux` | [search][search-coderbot-repo-label-linux] | [search][search-coderbot-org-label-linux] | Related to CoderBot running on Linux. | +| `mac` | [search][search-coderbot-repo-label-mac] | [search][search-coderbot-org-label-mac] | Related to CoderBot running on macOS. | +| `documentation` | [search][search-coderbot-repo-label-documentation] | [search][search-coderbot-org-label-documentation] | Related to any type of documentation (e.g. [API documentation]() and the [flight manual]()). | +| `performance` | [search][search-coderbot-repo-label-performance] | [search][search-coderbot-org-label-performance] | Related to performance. | +| `security` | [search][search-coderbot-repo-label-security] | [search][search-coderbot-org-label-security] | Related to security. | +| `ui` | [search][search-coderbot-repo-label-ui] | [search][search-coderbot-org-label-ui] | Related to visual design. | +| `api` | [search][search-coderbot-repo-label-api] | [search][search-coderbot-org-label-api] | Related to CoderBot's public APIs. | +| `uncaught-exception` | [search][search-coderbot-repo-label-uncaught-exception] | [search][search-coderbot-org-label-uncaught-exception] | Issues about uncaught exceptions. | +| `crash` | [search][search-coderbot-repo-label-crash] | [search][search-coderbot-org-label-crash] | Reports of CoderBot completely crashing. | +| `auto-indent` | [search][search-coderbot-repo-label-auto-indent] | [search][search-coderbot-org-label-auto-indent] | Related to auto-indenting text. | +| `encoding` | [search][search-coderbot-repo-label-encoding] | [search][search-coderbot-org-label-encoding] | Related to character encoding. | +| `network` | [search][search-coderbot-repo-label-network] | [search][search-coderbot-org-label-network] | Related to network problems or working with remote files (e.g. on network drives). | +| `git` | [search][search-coderbot-repo-label-git] | [search][search-coderbot-org-label-git] | Related to Git functionality (e.g. problems with gitignore files or with showing the correct file status). | + +#### `CoderBotOrg/backend` Topic Categories + +| Label name | `CoderBotOrg/backend` :mag_right: | `CoderBotOrg`‑org :mag_right: | Description | +| --- | --- | --- | --- | +| `editor-rendering` | [search][search-coderbot-repo-label-editor-rendering] | [search][search-coderbot-org-label-editor-rendering] | Related to language-independent aspects of rendering text (e.g. scrolling, soft wrap, and font rendering). | +| `build-error` | [search][search-coderbot-repo-label-build-error] | [search][search-coderbot-org-label-build-error] | Related to problems with building CoderBot from source. | +| `error-from-pathwatcher` | [search][search-coderbot-repo-label-error-from-pathwatcher] | [search][search-coderbot-org-label-error-from-pathwatcher] | | +| `error-from-save` | [search][search-coderbot-repo-label-error-from-save] | [search][search-coderbot-org-label-error-from-save] | Related to errors thrown when saving files. | +| `error-from-open` | [search][search-coderbot-repo-label-error-from-open] | [search][search-coderbot-org-label-error-from-open] | Related to errors thrown when opening files. | +| `installer` | [search][search-coderbot-repo-label-installer] | [search][search-coderbot-org-label-installer] | Related to the CoderBot installers for different OSes. | +| `auto-updater` | [search][search-coderbot-repo-label-auto-updater] | [search][search-coderbot-org-label-auto-updater] | Related to the auto-updater for different OSes. | +| `deprecation-help` | [search][search-coderbot-repo-label-deprecation-help] | [search][search-coderbot-org-label-deprecation-help] | Issues for helping package authors remove usage of deprecated APIs in packages. | +| `electron` | [search][search-coderbot-repo-label-electron] | [search][search-coderbot-org-label-electron] | | + +#### Pull Request Labels + +| Label name | `CoderBotOrg/backend` :mag_right: | `CoderBotOrg`‑org :mag_right: | Description +| --- | --- | --- | --- | +| `work-in-progress` | [search][search-coderbot-repo-label-work-in-progress] | [search][search-coderbot-org-label-work-in-progress] | Pull requests which are still being worked on, more changes will follow. | +| `needs-review` | [search][search-coderbot-repo-label-needs-review] | [search][search-coderbot-org-label-needs-review] | Pull requests which need code review, and approval from maintainers or CoderBot core team. | +| `under-review` | [search][search-coderbot-repo-label-under-review] | [search][search-coderbot-org-label-under-review] | Pull requests being reviewed by maintainers or CoderBot core team. | +| `requires-changes` | [search][search-coderbot-repo-label-requires-changes] | [search][search-coderbot-org-label-requires-changes] | Pull requests which need to be updated based on review comments and then reviewed again. | +| `needs-testing` | [search][search-coderbot-repo-label-needs-testing] | [search][search-coderbot-org-label-needs-testing] | Pull requests which need manual testing. | + +[search-coderbot-repo-label-enhancement]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aenhancement +[search-coderbot-org-label-enhancement]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aenhancement +[search-coderbot-repo-label-bug]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Abug +[search-coderbot-org-label-bug]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Abug +[search-coderbot-repo-label-question]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aquestion +[search-coderbot-org-label-question]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aquestion +[search-coderbot-repo-label-feedback]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Afeedback +[search-coderbot-org-label-feedback]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Afeedback +[search-coderbot-repo-label-help-wanted]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Ahelp-wanted +[search-coderbot-org-label-help-wanted]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Ahelp-wanted +[search-coderbot-repo-label-beginner]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Abeginner +[search-coderbot-org-label-beginner]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Abeginner +[search-coderbot-repo-label-more-information-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Amore-information-needed +[search-coderbot-org-label-more-information-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Amore-information-needed +[search-coderbot-repo-label-needs-reproduction]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aneeds-reproduction +[search-coderbot-org-label-needs-reproduction]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aneeds-reproduction +[search-coderbot-repo-label-triage-help-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Atriage-help-needed +[search-coderbot-org-label-triage-help-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Atriage-help-needed +[search-coderbot-repo-label-windows]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Awindows +[search-coderbot-org-label-windows]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Awindows +[search-coderbot-repo-label-linux]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Alinux +[search-coderbot-org-label-linux]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Alinux +[search-coderbot-repo-label-mac]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Amac +[search-coderbot-org-label-mac]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Amac +[search-coderbot-repo-label-documentation]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Adocumentation +[search-coderbot-org-label-documentation]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Adocumentation +[search-coderbot-repo-label-performance]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aperformance +[search-coderbot-org-label-performance]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aperformance +[search-coderbot-repo-label-security]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Asecurity +[search-coderbot-org-label-security]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Asecurity +[search-coderbot-repo-label-ui]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aui +[search-coderbot-org-label-ui]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aui +[search-coderbot-repo-label-api]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aapi +[search-coderbot-org-label-api]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aapi +[search-coderbot-repo-label-crash]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Acrash +[search-coderbot-org-label-crash]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Acrash +[search-coderbot-repo-label-auto-indent]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aauto-indent +[search-coderbot-org-label-auto-indent]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aauto-indent +[search-coderbot-repo-label-encoding]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aencoding +[search-coderbot-org-label-encoding]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aencoding +[search-coderbot-repo-label-network]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Anetwork +[search-coderbot-org-label-network]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Anetwork +[search-coderbot-repo-label-uncaught-exception]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Auncaught-exception +[search-coderbot-org-label-uncaught-exception]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Auncaught-exception +[search-coderbot-repo-label-git]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Agit +[search-coderbot-org-label-git]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Agit +[search-coderbot-repo-label-blocked]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Ablocked +[search-coderbot-org-label-blocked]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Ablocked +[search-coderbot-repo-label-duplicate]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aduplicate +[search-coderbot-org-label-duplicate]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aduplicate +[search-coderbot-repo-label-wontfix]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Awontfix +[search-coderbot-org-label-wontfix]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Awontfix +[search-coderbot-repo-label-invalid]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Ainvalid +[search-coderbot-org-label-invalid]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Ainvalid +[search-coderbot-repo-label-package-idea]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Apackage-idea +[search-coderbot-org-label-package-idea]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Apackage-idea +[search-coderbot-repo-label-wrong-repo]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Awrong-repo +[search-coderbot-org-label-wrong-repo]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Awrong-repo +[search-coderbot-repo-label-editor-rendering]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aeditor-rendering +[search-coderbot-org-label-editor-rendering]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aeditor-rendering +[search-coderbot-repo-label-build-error]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Abuild-error +[search-coderbot-org-label-build-error]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Abuild-error +[search-coderbot-repo-label-error-from-pathwatcher]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aerror-from-pathwatcher +[search-coderbot-org-label-error-from-pathwatcher]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aerror-from-pathwatcher +[search-coderbot-repo-label-error-from-save]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aerror-from-save +[search-coderbot-org-label-error-from-save]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aerror-from-save +[search-coderbot-repo-label-error-from-open]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aerror-from-open +[search-coderbot-org-label-error-from-open]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aerror-from-open +[search-coderbot-repo-label-installer]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Ainstaller +[search-coderbot-org-label-installer]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Ainstaller +[search-coderbot-repo-label-auto-updater]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Aauto-updater +[search-coderbot-org-label-auto-updater]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aauto-updater +[search-coderbot-repo-label-deprecation-help]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3ACoderBotOrg%2Fbackend+label%3Adeprecation-help +[search-coderbot-org-label-deprecation-help]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Adeprecation-help +[search-coderbot-repo-label-electron]: https://github.com/search?q=is%3Aissue+repo%3ACoderBotOrg%2Fbackend+is%3Aopen+label%3Aelectron +[search-coderbot-org-label-electron]: https://github.com/search?q=is%3Aopen+is%3Aissue+user%3ACoderBotOrg+label%3Aelectron +[search-coderbot-repo-label-work-in-progress]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3ACoderBotOrg%2Fbackend+label%3Awork-in-progress +[search-coderbot-org-label-work-in-progress]: https://github.com/search?q=is%3Aopen+is%3Apr+user%3ACoderBotOrg+label%3Awork-in-progress +[search-coderbot-repo-label-needs-review]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3ACoderBotOrg%2Fbackend+label%3Aneeds-review +[search-coderbot-org-label-needs-review]: https://github.com/search?q=is%3Aopen+is%3Apr+user%3ACoderBotOrg+label%3Aneeds-review +[search-coderbot-repo-label-under-review]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3ACoderBotOrg%2Fbackend+label%3Aunder-review +[search-coderbot-org-label-under-review]: https://github.com/search?q=is%3Aopen+is%3Apr+user%3ACoderBotOrg+label%3Aunder-review +[search-coderbot-repo-label-requires-changes]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3ACoderBotOrg%2Fbackend+label%3Arequires-changes +[search-coderbot-org-label-requires-changes]: https://github.com/search?q=is%3Aopen+is%3Apr+user%3ACoderBotOrg+label%3Arequires-changes +[search-coderbot-repo-label-needs-testing]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3ACoderBotOrg%2Fbackend+label%3Aneeds-testing +[search-coderbot-org-label-needs-testing]: https://github.com/search?q=is%3Aopen+is%3Apr+user%3ACoderBotOrg+label%3Aneeds-testing + +[beginner]:https://github.com/search?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3Ahelp-wanted+user%3ACoderBotOrg+sort%3Acomments-desc +[help-wanted]:https://github.com/search?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3ACoderBotOrg+sort%3Acomments-desc+-label%3Abeginner +[contributing-to-official-coderbot-packages]: todo +[hacking-on-coderbot-core]: todo + diff --git a/README.md b/README.md index 9f21cece..b618a798 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # backend +[![CoderBotOrg](https://circleci.com/gh/CoderBotOrg/backend.svg?style=svg)](https://circleci.com/gh/CoderBotOrg/backend/tree/master) > CoderBot is a RaspberryPI-based programmable robot for educational purposes. Check the [project website](https://www.coderbot.org) for more information. > diff --git a/activity.py b/activity.py new file mode 100644 index 00000000..7c46ac88 --- /dev/null +++ b/activity.py @@ -0,0 +1,45 @@ +from tinydb import TinyDB, Query +from tinydb.operations import delete +import json + +# Programs and Activities databases +class Activities(): + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance == None: + cls._instance = Activities() + return cls._instance + + def __init__(self): + self.activities = TinyDB("data/activities.json") + self.query = Query() + + def load(self, name, default): + if name: + return self.activities.search(self.query.name == name)[0] + elif default is not None: + default_Activities = self.activities.search(self.query.default == True) + if len(self.activities.search(self.query.default == True)) > 0: + return self.activities.search(self.query.default == True)[0] + else: + return None + + def save(self, activity): + if self.activities.search(self.query.name == activity["name"]) == []: + self.activities.insert(activity) + else: + if activity.get("default", False) == True: + self.activities.update({'default': False}) + self.activities.update(activity, self.query.name == activity["name"]) + + def delete(self, activity): + activity = self.activities.search(self.query.name == activity["name"])[0] + if activity.get("default", False) == True: + self.activities.update({'default': True}, self.query.stock == True) + self.activities.remove(self.query.name == activity["name"]) + + def list(self): + return self.activities.all() + diff --git a/api.py b/api.py index 08c22c1d..f7035e3c 100644 --- a/api.py +++ b/api.py @@ -6,26 +6,26 @@ import os import subprocess import json +import logging import connexion -from tinydb import TinyDB, Query -from tinydb.operations import delete +import pigpio from cachetools import cached, TTLCache from coderbot import CoderBot from program import ProgramEngine, Program from config import Config +from activity import Activities from coderbotTestUnit import run_test as runCoderbotTestUnit -import pigpio +from cnn_manager import CNNManager +from musicPackages import MusicPackageManager BUTTON_PIN = 16 bot_config = Config.get() bot = CoderBot.get_instance( motor_trim_factor=float(bot_config.get("move_motor_trim", 1.0)), - encoder=bool(bot_config.get("encoder")) + hw_version=bot_config.get("hw_version") ) -query = Query() - def get_serial(): """ Extract serial from cpuinfo file @@ -107,8 +107,7 @@ def get_info(): prog = None prog_engine = ProgramEngine.get_instance() -# Programs and Activities databases -activities = TinyDB("data/activities.json") +activities = Activities.get_instance() ## Robot control @@ -132,7 +131,8 @@ def turn(data): def exec(data): program = prog_engine.create(data["name"], data["code"]) - return json.dumps(program.execute()) + options = data["options"] + return json.dumps(program.execute(options)) ## System @@ -172,6 +172,7 @@ def restoreSettings(): Config.get() return "ok" + def updateFromPackage(): os.system('sudo bash /home/pi/clean-update.sh') file_to_upload = connexion.request.files['file_to_upload'] @@ -179,11 +180,47 @@ def updateFromPackage(): os.system('sudo reboot') return 200 +def listMusicPackages(): + """ + list available music packages + """ + musicPkg = MusicPackageManager.get_instance() + response = musicPkg.listPackages() + return json.dumps(response) +def updateMusicPackages(): + """ + Add a musical package an save the list of available packages on disk + also add sounds and directory + """ + """zipName = request.args.get("zipname") + """ + file_to_upload = connexion.request.files['file_to_upload'] + print("adding " +str(file_to_upload)) + print("adding " + file_to_upload.filename) + file_to_upload.save(os.path.join('./updatePackages/', file_to_upload.filename)) + musicPkg = MusicPackageManager.get_instance() + response = musicPkg.addPackage(file_to_upload.filename) + if response == 1: + return 200 + elif response == 2: + return 400 + elif response == 3: + return 400 + +def deleteMusicPackage(package_data): + """ + Delete a musical package an save the list of available packages on disk + also delete package sounds and directory + """ + musicPkg = MusicPackageManager.get_instance() + musicPkg.deletePackage(package_data['package_name']) + return 200 ## Programs -def saveProgram(data, overwrite): +def saveProgram(data): + overwrite = data["overwrite"] existing_program = prog_engine.load(data["name"]) if existing_program and not overwrite: return "askOverwrite" @@ -207,23 +244,17 @@ def listPrograms(): ## Activities def saveActivity(data): - data = data["activity"] - if activities.search(query.name == data["name"]) == []: - activities.insert(data) - return 200 - else: - activities.update(data, query.name == data["name"]) - return 200 + activity = data["activity"] + activities.save(activity) -def loadActivity(name): - return activities.search(query.name == name)[0], 200 +def loadActivity(name=None, default=None): + return activities.load(name, default) def deleteActivity(data): - activities.remove(query.name == data["name"]) - + activities.delete(data), 200 def listActivities(): - return activities.all() + return activities.list() def resetDefaultPrograms(): """ @@ -252,4 +283,10 @@ def reset(): def testCoderbot(data): # taking first JSON key value (varargin) tests_state = runCoderbotTestUnit(data[list(data.keys())[0]]) - return tests_state \ No newline at end of file + return tests_state + +def list_cnn_models(): + cnn = CNNManager.get_instance() + logging.info("cnn_models_list") + return json.dumps(cnn.get_models()) + diff --git a/atmega328p.py b/atmega328p.py new file mode 100644 index 00000000..76c3965b --- /dev/null +++ b/atmega328p.py @@ -0,0 +1,84 @@ +# RPi PINOUTS +# MOSI -> GPIO10 +# MISO -> GPIO9 +# SCK -> GPIO11 +# CE1 -> GPIO7 +# CE1 -> GPIO8 + +# get the GPIO Library and SPI Library +import spidev +import time + +BAUDRATE_MAX = 250000 +BAUDRATE = 10000 + +START = 0xff +CMD_RESET = 0x00 +CMD_SET_DATA = 0x01 +CMD_GET_DATA = 0x02 +CMD_SET_MODE = 0x03 +CMD_SET_LED = 0x04 + +ADDR_AI_FIRST = 0x00 +ADDR_AI_LAST = 0x01 +ADDR_DI_FIRST = 0x02 +ADDR_DI_LAST = 0x05 +ADDR_DO_FIRST = 0x00 +ADDR_DO_LAST = 0x0a + +class ATMega328(): + + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = ATMega328() + return cls._instance + + def __init__(self): + # Initialze the SPI + self.spi = spidev.SpiDev() + self.spi.open(0,0) + self.spi.max_speed_hz = BAUDRATE_MAX + + def close(self): + self.spi.close() + + def digitalWrite(self, addr, value): + resp = self.spi.xfer([START, CMD_SET_DATA, addr, value, 0], BAUDRATE) + + def digitalRead(self, addr): + resp = self.spi.xfer([START, CMD_GET_DATA, addr, 0, 0], BAUDRATE) + return resp[3] + + def analogRead(self, addr): + resp = self.spi.xfer([START, CMD_GET_DATA, addr, 0, 0], BAUDRATE) + return resp[3] + + def setLed(self, begin_led, end_led, red, green, blue): + resp = self.spi.xfer([START, CMD_SET_LED, + min(max(begin_led, 0), 60), + min(max(end_led, 0), 60), + min(max(red, 0), 254), + min(max(green, 0), 254), + min(max(blue, 0), 254)], BAUDRATE) + return resp[3] + + def set_led(self, begin_led, end_led, red, green, blue): + begin = begin_led - 1 + end = end_led - 1 + red = int(red * 255 / 100) + green = int(green * 255 / 100) + blue = int(blue * 255 / 100) + return self.setLed(begin, end, red, green, blue) + + def get_input(self, addr): + if addr >= ADDR_AI_FIRST and addr <= ADDR_AI_LAST: + return self.analogRead(addr) + elif addr >= ADDR_DI_FIRST and addr <= ADDR_DI_LAST: + return self.digitalRead(addr) + + def set_output(self, addr, value): + if addr >= ADDR_DO_FIRST and addr <= ADDR_DO_LAST: + self.digitalWrite(addr, value) diff --git a/audio.py b/audio.py index 1d7798c1..6841d651 100644 --- a/audio.py +++ b/audio.py @@ -35,7 +35,7 @@ # [END import_libraries] # Audio recording parameters -RATE = 16000 +RATE = 44100 CHUNK = int(RATE / 10) # 100ms FORMAT = pyaudio.paInt16 diff --git a/audioControls.py b/audioControls.py new file mode 100644 index 00000000..be87d899 --- /dev/null +++ b/audioControls.py @@ -0,0 +1,52 @@ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# MUSICAL EXTENTION for CoderBot +# This extention is develop by: +# Michele Carbonera - miki_992@hotmail.it - m.carbonera@campus.unimib.it - michele.carbonera@unimib.it +# Antonino Tramontana - a.tramontana1@campus.unimib.it +# Copyright (C) 2020 +############################################################################ + +import os +import alsaaudio + +class AudioCtrl: + mixer = None + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = AudioCtrl() + return cls._instance + + def __init__(self): + self.mixer = alsaaudio.Mixer('Headphone') + + def getVolume(self): + print(self.mixer.getvolume()) + + def setVolume(self,valueVolume): + self.mixer.setvolume(valueVolume) + +# if __name__ == "__main__": +# a = AudioCtrl() +# a.setVolume(20) diff --git a/camera.py b/camera.py index 48ef2f16..e56af812 100644 --- a/camera.py +++ b/camera.py @@ -49,7 +49,6 @@ class Camera(object): # pylint: disable=too-many-public-methods _instance = None - _img_template = image.Image.load("static/media/coderdojo-logo.png") @classmethod def get_instance(cls): @@ -252,18 +251,6 @@ def find_line(self): self.set_image_cv(img) return coords - def find_signal(self): - angle = None - ts = time.time() - img = self.get_image() - signals = img.find_template(self._img_template) - - logging.info("signal: %s", str(time.time() - ts)) - if signals: - angle = signals[0].angle - - return angle - def find_face(self): face_x = face_y = face_size = None img = self.get_image() diff --git a/cnn_classifier.py b/cnn_classifier.py index 0b80453f..d8e61ce8 100644 --- a/cnn_classifier.py +++ b/cnn_classifier.py @@ -23,7 +23,15 @@ import logging import numpy as np -from tensorflow.lite.python.interpreter import Interpreter +try: + from tensorflow.lite.python.interpreter import Interpreter +except: + logging.warning("tensorflow not available (for inference)") +try: + from tflite_runtime.interpreter import Interpreter +except: + logging.warning("tflite not available") + import cv2 logger = logging.getLogger(__name__) @@ -31,8 +39,7 @@ class CNNClassifier(object): def __init__(self, model_file, label_file): logger.info(model_file) - self._interpreter = Interpreter(model_path=model_file) - self._interpreter.set_num_threads(4) + self._interpreter = Interpreter(model_path=model_file, num_threads=4) self._interpreter.allocate_tensors() self._labels = self.load_labels(label_file) self._input_details = self._interpreter.get_input_details() diff --git a/cnn_manager.py b/cnn_manager.py index f8bfc7dd..0294c9b1 100644 --- a/cnn_manager.py +++ b/cnn_manager.py @@ -26,7 +26,11 @@ import json import threading -from cnn_train import CNNTrainer +try: + from cnn_train import CNNTrainer +except: + logging.warning("tensorflow not available (for training)") + from cnn_classifier import CNNClassifier MODEL_PATH = "./cnn_models" @@ -117,6 +121,7 @@ def load_model(self, model_name): return CNNClassifier(model_file=MODEL_PATH + "/" + model_name + ".tflite", label_file=MODEL_PATH + "/" + model_name + ".txt") return None + class TrainThread(threading.Thread): def __init__(self, manager, model_name, architecture, image_tags, photos_metadata, training_steps, learning_rate): diff --git a/coderbot-copy.py b/coderbot-copy.py deleted file mode 100644 index b7295dfa..00000000 --- a/coderbot-copy.py +++ /dev/null @@ -1,431 +0,0 @@ -############################################################################ -# CoderBot, a didactical programmable robot. -# Copyright (C) 2014, 2015 Roberto Previtera -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -############################################################################ - -import os -import time -import threading -import logging -import pigpio -import sonar -import mpu - -PIN_MOTOR_ENABLE = 22 -PIN_LEFT_FORWARD = 25 -PIN_LEFT_BACKWARD = 24 -PIN_RIGHT_FORWARD = 4 -PIN_RIGHT_BACKWARD = 17 -PIN_PUSHBUTTON = 11 -PIN_SERVO_3 = 9 -PIN_SERVO_4 = 10 -PIN_SONAR_1_TRIGGER = 18 -PIN_SONAR_1_ECHO = 7 -PIN_SONAR_2_TRIGGER = 18 -PIN_SONAR_2_ECHO = 8 -PIN_SONAR_3_TRIGGER = 18 -PIN_SONAR_3_ECHO = 23 -PIN_ENCODER_LEFT = 15 -PIN_ENCODER_RIGHT = 14 - -PWM_FREQUENCY = 100 #Hz -PWM_RANGE = 100 #0-100 - -class CoderBot(object): - - # pylint: disable=too-many-instance-attributes - - _pin_out = [PIN_MOTOR_ENABLE, PIN_LEFT_FORWARD, PIN_RIGHT_FORWARD, PIN_LEFT_BACKWARD, PIN_RIGHT_BACKWARD, PIN_SERVO_3, PIN_SERVO_4] - - def __init__(self, servo=False, motor_trim_factor=1.0, encoder=False): - self.pi = pigpio.pi('localhost') - self.pi.set_mode(PIN_PUSHBUTTON, pigpio.INPUT) - self._cb = dict() - self._cb_last_tick = dict() - self._cb_elapse = dict() - self._servo = servo - self._encoder = encoder - self._motor_trim_factor = motor_trim_factor - if self._servo: - self.motor_control = self._servo_motor - elif self._encoder: - self._twin_motors_enc = self.TwinMotorsEncoder( - self.pi, - pin_enable=PIN_MOTOR_ENABLE, - pin_forward_left=PIN_LEFT_FORWARD, - pin_backward_left=PIN_LEFT_BACKWARD, - pin_encoder_left=PIN_ENCODER_LEFT, - pin_forward_right=PIN_RIGHT_FORWARD, - pin_backward_right=PIN_RIGHT_BACKWARD, - pin_encoder_right=PIN_ENCODER_RIGHT) - self.motor_control = self._dc_enc_motor - else: - self.motor_control = self._dc_motor - - self._cb1 = self.pi.callback(PIN_PUSHBUTTON, pigpio.EITHER_EDGE, self._cb_button) - - for pin in self._pin_out: - self.pi.set_PWM_frequency(pin, PWM_FREQUENCY) - self.pi.set_PWM_range(pin, PWM_RANGE) - - self.sonar = [sonar.Sonar(self.pi, PIN_SONAR_1_TRIGGER, PIN_SONAR_1_ECHO), - sonar.Sonar(self.pi, PIN_SONAR_2_TRIGGER, PIN_SONAR_2_ECHO), - sonar.Sonar(self.pi, PIN_SONAR_3_TRIGGER, PIN_SONAR_3_ECHO)] - - try: - self._ag = mpu.AccelGyro() - except IOError: - logging.info("MPU not available") - - self.stop() - self._is_moving = False - - the_bot = None - - def exit(self): - self._cb1.cancel() - if self._encoder: - self._twin_motors_enc.exit() - for s in self.sonar: - s.cancel() - - @classmethod - def get_instance(cls, servo=False, motor_trim_factor=1.0): - if not cls.the_bot: - cls.the_bot = CoderBot(servo, motor_trim_factor) - return cls.the_bot - - def move(self, speed=100, elapse=-1, steps=-1): - speed_left = min(100, max(-100, speed * self._motor_trim_factor)) - speed_right = min(100, max(-100, speed / self._motor_trim_factor)) - self.motor_control(speed_left=speed_left, speed_right=speed_right, elapse=elapse, steps_left=steps, steps_right=steps) - - def turn(self, speed=100, elapse=-1, steps=-1): - speed_left = min(100, max(-100, speed * self._motor_trim_factor)) - speed_right = -min(100, max(-100, speed / self._motor_trim_factor)) - self.motor_control(speed_left=speed_left, speed_right=speed_right, elapse=elapse, steps_left=steps, steps_right=steps) - - def turn_angle(self, speed=100, angle=0): - z = self._ag.get_gyro_data()['z'] - self.turn(speed, elapse=-1) - while abs(z - self._ag.get_gyro_data()['z']) < angle: - time.sleep(0.05) - logging.info(self._ag.get_gyro_data()['z']) - self.stop() - - def forward(self, speed=100, elapse=-1): - self.move(speed=speed, elapse=elapse) - - def backward(self, speed=100, elapse=-1): - self.move(speed=-speed, elapse=elapse) - - def left(self, speed=100, elapse=-1): - self.turn(speed=-speed, elapse=elapse) - - def right(self, speed=100, elapse=-1): - self.turn(speed=speed, elapse=elapse) - - def servo3(self, angle): - self._servo_control(PIN_SERVO_3, angle) - - def servo4(self, angle): - self._servo_control(PIN_SERVO_4, angle) - - def get_sonar_distance(self, sonar_id=0): - return self.sonar[sonar_id].get_distance() - - def _dc_enc_motor(self, speed_left=100, speed_right=100, elapse=-1, steps_left=-1, steps_right=-1): - self._twin_motors_enc.control(power_left=speed_left, power_right=speed_right, - elapse=elapse, speed_left=speed_left, speed_right=speed_right, - steps_left=steps_left, steps_right=steps_right) - - def _dc_motor(self, speed_left=100, speed_right=100, elapse=-1, steps_left=-1, steps_right=-1): - - # pylint: disable=too-many-instance-attributes - - self._encoder_cur_left = 0 - self._encoder_cur_right = 0 - self._encoder_target_left = steps_left - self._encoder_target_right = steps_right - self._encoder_dir_left = (speed_left > 0) - (speed_left < 0) - self._encoder_dir_right = (speed_right > 0) - (speed_right < 0) - self._encoder_last_tick_time_left = 0 - self._encoder_last_tick_time_right = 0 - self._encoder_motor_stopping_left = False - self._encoder_motor_stopping_right = False - self._encoder_motor_stopped_left = False - self._encoder_motor_stopped_right = False - - self._is_moving = True - if speed_left < 0: - speed_left = abs(speed_left) - self.pi.write(PIN_LEFT_FORWARD, 0) - self.pi.set_PWM_dutycycle(PIN_LEFT_BACKWARD, speed_left) - else: - self.pi.write(PIN_LEFT_BACKWARD, 0) - self.pi.set_PWM_dutycycle(PIN_LEFT_FORWARD, speed_left) - - if speed_right < 0: - speed_right = abs(speed_right) - self.pi.write(PIN_RIGHT_FORWARD, 0) - self.pi.set_PWM_dutycycle(PIN_RIGHT_BACKWARD, speed_right) - else: - self.pi.write(PIN_RIGHT_BACKWARD, 0) - self.pi.set_PWM_dutycycle(PIN_RIGHT_FORWARD, speed_right) - - self.pi.write(PIN_MOTOR_ENABLE, 1) - if elapse > 0: - time.sleep(elapse) - self.stop() - - def _servo_motor(self, speed_left=100, speed_right=100, elapse=-1, steps_left=-1, steps_right=-1): - self._is_moving = True - speed_left = -speed_left - - steps_left - steps_right - - self.pi.write(PIN_MOTOR_ENABLE, 1) - self.pi.write(PIN_RIGHT_BACKWARD, 0) - self.pi.write(PIN_LEFT_BACKWARD, 0) - - self._servo_motor_control(PIN_LEFT_FORWARD, speed_left) - self._servo_motor_control(PIN_RIGHT_FORWARD, speed_right) - if elapse > 0: - time.sleep(elapse) - self.stop() - - - def _servo_motor_control(self, pin, speed): - self._is_moving = True - speed = ((speed + 100) * 50 / 200) + 52 - - self.pi.set_PWM_range(pin, 1000) - self.pi.set_PWM_frequency(pin, 50) - self.pi.set_PWM_dutycycle(pin, speed) - - def _servo_control(self, pin, angle): - duty = ((angle + 90) * 100 / 180) + 25 - - self.pi.set_PWM_range(pin, 1000) - self.pi.set_PWM_frequency(pin, 50) - self.pi.set_PWM_dutycycle(pin, duty) - - def stop(self): - if self._encoder: - self._twin_motors_enc.stop() - else: - for pin in self._pin_out: - self.pi.write(pin, 0) - self._is_moving = False - - def is_moving(self): - return self._is_moving - - def set_callback(self, gpio, callback, elapse): - self._cb_elapse[gpio] = elapse * 1000 - self._cb[gpio] = callback - self._cb_last_tick[gpio] = 0 - - def sleep(self, elapse): - logging.debug("sleep: %s", str(elapse)) - time.sleep(elapse) - - def _cb_button(self, gpio, level, tick): - cb = self._cb.get(gpio) - if cb: - elapse = self._cb_elapse.get(gpio) - if level == 0: - self._cb_last_tick[gpio] = tick - elif tick - self._cb_last_tick[gpio] > elapse: - self._cb_last_tick[gpio] = tick - logging.info("pushed: %d, %d", level, tick) - cb() - - class MotorEncoder(object): - def __init__(self, parent, _pigpio, pin_enable, pin_forward, pin_backward, pin_encoder): - self._parent = parent - self._pigpio = _pigpio - self._pin_enable = pin_enable - self._pin_forward = pin_forward - self._pin_backward = pin_backward - self._pin_encoder = pin_encoder - self._direction = False - self._pin_duty = 0 - self._pin_reverse = 0 - self._power = 0.0 - self._power_actual = 0.0 - self._encoder_dist = 0 - self._encoder_speed = 0.0 - self._encoder_last_tick = 0 - self._encoder_dist_target = 0 - self._encoder_speed_target = 0.0 - self._encoder_k_s_1 = 20 - self._encoder_k_v_1 = 80 - self._motor_stopping = False - self._motor_running = False - self._motor_stop_fast = True - self._pigpio.set_mode(self._pin_encoder, pigpio.INPUT) - self._cb = self._pigpio.callback(self._pin_encoder, pigpio.RISING_EDGE, self._cb_encoder) - self._motor_lock = threading.RLock() - - def exit(self): - self._cb.cancel() - - def _cb_encoder(self, gpio, level, tick): - self._motor_lock.acquire() - self._encoder_dist += 1 - delta_ticks = tick - self._encoder_last_tick if tick > self._encoder_last_tick else tick - self._encoder_last_tick + 4294967295 - self._encoder_last_tick = tick - self._encoder_speed = 1000000.0 / delta_ticks #convert speed in steps per second - #print "pin: " + str(self._pin_forward) + " dist: " + str(self._encoder_dist) + " target: " + str(self._encoder_dist_target) - if self._encoder_dist_target >= 0 and self._motor_stop_fast: - #delta_s is the delta (in steps)before the target to reverse the motor in order to arrive at target - delta_s = max(min(self._encoder_speed / self._encoder_k_s_1, 100), 0) - #print "pin: " + str(self._pin_forward) + " dist: " + str(self._encoder_dist) + " target: " + str(self._encoder_dist_target) + " delta_s: " + str(delta_s) - if (self._encoder_dist >= self._encoder_dist_target - delta_s and - not self._motor_stopping and self._motor_running): - self._motor_stopping = True - self._pigpio.write(self._pin_duty, 0) - self._pigpio.set_PWM_dutycycle(self._pin_reverse, self._power) - elif (self._motor_running and - ((self._motor_stopping and - self._encoder_speed < self._encoder_k_v_1) or - (self._motor_stopping and - self._encoder_dist >= self._encoder_dist_target))): - self.stop() - logging.info("dist: " + str(self._encoder_dist) + " speed: " + str(self._encoder_speed)) - if self._encoder_dist_target >= 0 and not self._motor_stop_fast: - if self._encoder_dist >= self._encoder_dist_target: - self.stop() - self._parent._cb_encoder(self, gpio, level, tick) - self._motor_lock.release() - if not self._motor_running: - self._parent._check_complete() - - def control(self, power=100.0, elapse=-1, speed=100.0, steps=-1): - self._motor_lock.acquire() - self._direction = speed > 0 - self._encoder_dist_target = steps - self._motor_stopping = False - self._motor_running = True - self._encoder_dist = 0 - self._encoder_speed_target = abs(speed) - self._power = abs(power) #TODO: initial power must be a function of desired speed - self._power_actual = abs(power) #TODO: initial power must be a function of desired speed - self._pin_duty = self._pin_forward if self._direction else self._pin_backward - self._pin_reverse = self._pin_backward if self._direction else self._pin_forward - self._pigpio.write(self._pin_reverse, 0) - self._pigpio.set_PWM_dutycycle(self._pin_duty, self._power) - self._pigpio.write(self._pin_enable, True) - self._motor_lock.release() - if elapse > 0: - time.sleep(elapse) - self.stop() - - def stop(self): - self._motor_lock.acquire() - self._motor_stopping = False - self._motor_running = False - self._pigpio.write(self._pin_forward, 0) - self._pigpio.write(self._pin_backward, 0) - self._motor_lock.release() - - def distance(self): - return self._encoder_dist - - def speed(self): - return self._encoder_speed - - def stopping(self): - return self._motor_stopping - - def running(self): - return self._motor_running - - def adjust_power(self, power_delta): - self._power_actual = min(max(self._power + power_delta, 0), 100) - self._pigpio.set_PWM_dutycycle(self._pin_duty, self._power_actual) - - class TwinMotorsEncoder(object): - def __init__(self, apigpio, pin_enable, pin_forward_left, pin_backward_left, pin_encoder_left, pin_forward_right, pin_backward_right, pin_encoder_right): - self._straight = False - self._running = False - self._encoder_sem = threading.Condition() - self._motor_left = CoderBot.MotorEncoder(self, apigpio, pin_enable, pin_forward_left, pin_backward_left, pin_encoder_left) - self._motor_right = CoderBot.MotorEncoder(self, apigpio, pin_enable, pin_forward_right, pin_backward_right, pin_encoder_right) - - def exit(self): - self._motor_left.exit() - self._motor_right.exit() - - def control(self, power_left=100.0, power_right=100.0, elapse=-1, speed_left=-1, speed_right=-1, steps_left=-1, steps_right=-1): - self._straight = power_left == power_right and speed_left == speed_right and steps_left == steps_right - - if steps_left >= 0 or steps_right >= 0: - self._encoder_sem.acquire() - - self._motor_left.control(power=power_left, elapse=-1, speed=speed_left, steps=steps_left) - self._motor_right.control(power=power_right, elapse=-1, speed=speed_right, steps=steps_right) - self._running = True - - if elapse > 0: - time.sleep(elapse) - self.stop() - - if steps_left >= 0 or steps_right >= 0: - self._encoder_sem.wait() - self._encoder_sem.release() - self.stop() - - def stop(self): - self._motor_left.stop() - self._motor_right.stop() - self._running = False - - def distance(self): - return (self._motor_left.distance() + self._motor_right.distance()) / 2 - - def speed(self): - return (self._motor_left.speed() + self._motor_right.speed()) / 2 - - def _cb_encoder(self, motor, gpio, level, tick): - if (self._straight and self._running and not self._motor_left.stopping() and not self._motor_right.stopping() and - abs(self._motor_left.distance() - self._motor_right.distance()) > 2): - distance_delta = self._motor_left.distance() - self._motor_right.distance() - speed_delta = self._motor_left.speed() - self._motor_right.speed() - power_delta = (distance_delta / 2.0) + (speed_delta / 10.0) - #print "power_delta: " + str(power_delta) + " distance_delta: " + str(distance_delta) + " speed_delta: " + str(speed_delta) - if self._motor_left == motor: - self._motor_left.adjust_power(-power_delta) - if self._motor_right == motor: - self._motor_right.adjust_power(power_delta) - - def _check_complete(self): - if self._motor_left.running() is False and self._motor_right.running() is False: - self._encoder_sem.acquire() - self._encoder_sem.notify() - self._encoder_sem.release() - - def halt(self): - os.system('sudo halt') - - def restart(self): - os.system('sudo /etc/init.d/coderbot restart') - - def reboot(self): - os.system('sudo reboot') diff --git a/coderbot.cfg b/coderbot.cfg index 5e8264f9..93b4a617 100644 --- a/coderbot.cfg +++ b/coderbot.cfg @@ -1 +1,46 @@ -{"move_power_angle_3": "60", "cnn_default_model": "generic_fast_low", "prog_maxblocks": "-1", "camera_jpeg_quality": "5", "show_page_control": "true", "camera_framerate": "30", "prog_scrollbars": "true", "move_fw_speed": "100", "prog_level": "adv", "move_motor_trim": "1", "move_motor_mode": "dc", "cv_image_factor": "2", "move_power_angle_1": "45", "camera_path_object_size_min": "4000", "button_func": "none", "camera_color_object_size_min": "4000", "camera_jpeg_bitrate": "1000000", "move_fw_elapse": "1", "show_control_move_commands": "true", "camera_color_object_size_max": "160000", "show_page_prefs": "true", "camera_exposure_mode": "auto", "ctrl_tr_elapse": "-1", "show_page_program": "true", "move_tr_elapse": "0.5", "camera_path_object_size_max": "160000", "sound_shutter": "$shutter.mp3", "ctrl_fw_elapse": "-1", "sound_stop": "$shutdown.mp3", "ctrl_tr_speed": "80", "ctrl_fw_speed": "100", "move_tr_speed": "85", "move_power_angle_2": "60", "ctrl_hud_image": "", "load_at_start": "", "sound_start": "$startup.mp3", "encoder": "True", "wifi_mode": "ap", "wifi_ssid": "", "wifi_psk": ""} \ No newline at end of file +{ + "move_power_angle_3":"60", + "cnn_default_model":"generic_fast_low", + "prog_maxblocks":"-1", + "camera_jpeg_quality":"5", + "show_page_control":"true", + "camera_framerate":"30", + "prog_scrollbars":"true", + "move_fw_speed":"100", + "prog_level":"adv", + "move_motor_trim":"1", + "move_motor_mode":"dc", + "cv_image_factor":"2", + "move_power_angle_1":"45", + "camera_path_object_size_min":"4000", + "button_func":"none", + "camera_color_object_size_min":"4000", + "camera_jpeg_bitrate":"1000000", + "move_fw_elapse":"1", + "show_control_move_commands":"true", + "camera_color_object_size_max":"160000", + "show_page_prefs":"true", + "camera_exposure_mode":"auto", + "ctrl_tr_elapse":"-1", + "show_page_program":"true", + "move_tr_elapse":"0.5", + "camera_path_object_size_max":"160000", + "sound_shutter":"$shutter.wav", + "ctrl_fw_elapse":"-1", + "sound_stop":"$shutdown.wav", + "ctrl_tr_speed":"80", + "ctrl_fw_speed":"100", + "move_tr_speed":"85", + "move_power_angle_2":"60", + "ctrl_hud_image":"", + "load_at_start":"", + "sound_start":"$startup.wav", + "hw_version":"5", + "audio_volume_level":"100", + "wifi_mode":"ap", + "wifi_ssid":"coderbot_CHANGEMEATFIRSTRUN", + "wifi_psk":"coderbot", + "packages_installed":"", + "admin_password":"", + "hardware_version":"5" +} diff --git a/coderbot.py b/coderbot.py index 93f2e6a6..67cecc5e 100644 --- a/coderbot.py +++ b/coderbot.py @@ -46,7 +46,7 @@ class GPIO_CODERBOT_V_4(): PIN_SONAR_3_TRIGGER = 18 PIN_SONAR_3_ECHO = 23 PIN_SONAR_4_TRIGGER = 18 - PIN_SONAR_4_ECHO = None + PIN_SONAR_4_ECHO = 13 # encoder PIN_ENCODER_LEFT_A = 14 @@ -54,6 +54,8 @@ class GPIO_CODERBOT_V_4(): PIN_ENCODER_RIGHT_A = 15 PIN_ENCODER_RIGHT_B = 12 + HAS_ENCODER = False + class GPIO_CODERBOT_V_5(): # motors PIN_MOTOR_ENABLE = None #22 @@ -82,30 +84,36 @@ class GPIO_CODERBOT_V_5(): PIN_ENCODER_RIGHT_A = 24 #15 PIN_ENCODER_RIGHT_B = 25 #12 + HAS_ENCODER = True + # PWM PWM_FREQUENCY = 100 #Hz PWM_RANGE = 100 #0-100 +HW_VERSIONS = { + "4": GPIO_CODERBOT_V_4(), + "5": GPIO_CODERBOT_V_5() +} + class CoderBot(object): # pylint: disable=too-many-instance-attributes - def __init__(self, motor_trim_factor=1.0, encoder=True): + def __init__(self, motor_trim_factor=1.0, hw_version="5"): try: self._mpu = mpu.AccelGyroMag() - self.GPIOS = GPIO_CODERBOT_V_5() logging.info("MPU available") except: logging.info("MPU not available") - self.GPIOS = GPIO_CODERBOT_V_4() + self.GPIOS = HW_VERSIONS.get(hw_version, GPIO_CODERBOT_V_5()) self._pin_out = [self.GPIOS.PIN_LEFT_FORWARD, self.GPIOS.PIN_RIGHT_FORWARD, self.GPIOS.PIN_LEFT_BACKWARD, self.GPIOS.PIN_RIGHT_BACKWARD, self.GPIOS.PIN_SERVO_1, self.GPIOS.PIN_SERVO_2] self.pi = pigpio.pi('localhost') self.pi.set_mode(self.GPIOS.PIN_PUSHBUTTON, pigpio.INPUT) self._cb = dict() self._cb_last_tick = dict() self._cb_elapse = dict() - self._encoder = encoder + self._encoder = self.GPIOS.HAS_ENCODER self._motor_trim_factor = motor_trim_factor self._twin_motors_enc = WheelsAxel( self.pi, @@ -144,9 +152,9 @@ def exit(self): s.cancel() @classmethod - def get_instance(cls, motor_trim_factor=1.0, encoder=True, servo=False): + def get_instance(cls, motor_trim_factor=1.0, hw_version="5", servo=False): if not cls.the_bot: - cls.the_bot = CoderBot(motor_trim_factor=motor_trim_factor, encoder=encoder) + cls.the_bot = CoderBot(motor_trim_factor=motor_trim_factor, hw_version=hw_version) return cls.the_bot def move(self, speed=100, elapse=0, distance=0): diff --git a/cv/camera.py b/cv/camera.py index 570d0a37..f2b1b68f 100644 --- a/cv/camera.py +++ b/cv/camera.py @@ -29,7 +29,7 @@ class Camera(object): - FFMPEG_CMD = 'MP4Box' + FFMPEG_CMD = 'ffmpeg' PHOTO_FILE_EXT = ".jpg" VIDEO_FILE_EXT = ".mp4" VIDEO_FILE_EXT_H264 = '.h264' @@ -90,7 +90,10 @@ def video_stop(self): self.camera.stop_recording(2) # pack in mp4 container - params = " -fps " + str(self.camera.framerate) + " -add " + self.video_filename + self.VIDEO_FILE_EXT_H264 + " " + self.video_filename + self.VIDEO_FILE_EXT + params = " -loglevel quiet -stats -framerate " + str(self.camera.framerate) + \ + " -i " + self.video_filename + self.VIDEO_FILE_EXT_H264 + \ + " -c copy " + self.video_filename + self.VIDEO_FILE_EXT + os.system(self.FFMPEG_CMD + params) # remove h264 file os.remove(self.video_filename + self.VIDEO_FILE_EXT_H264) diff --git a/data/activities.json b/data/activities.json deleted file mode 100644 index e9b18176..00000000 --- a/data/activities.json +++ /dev/null @@ -1 +0,0 @@ -{"_default": {}} \ No newline at end of file diff --git a/data/defaults/config.json b/data/defaults/config.json index 55bfcee1..c5ce7f84 100644 --- a/data/defaults/config.json +++ b/data/defaults/config.json @@ -1,38 +1,46 @@ { - "move_power_angle_3": "60", - "cnn_default_model": "fruit_025_128_1", - "prog_maxblocks": "-1", - "camera_jpeg_quality": "5", - "show_page_control": "true", - "camera_framerate": "30", - "prog_scrollbars": "true", - "move_fw_speed": "100", - "prog_level": "adv", - "move_motor_trim": "1", - "move_motor_mode": "dc", - "cv_image_factor": "2", - "move_power_angle_1": "45", - "camera_path_object_size_min": "4000", - "button_func": "none", - "camera_color_object_size_min": "4000", - "camera_jpeg_bitrate": "1000000", - "move_fw_elapse": "1", - "show_control_move_commands": "true", - "camera_color_object_size_max": "160000", - "show_page_prefs": "true", - "camera_exposure_mode": "auto", - "ctrl_tr_elapse": "-1", - "show_page_program": "true", - "move_tr_elapse": "0.5", - "camera_path_object_size_max": "160000", - "sound_shutter": "$shutter.mp3", - "ctrl_fw_elapse": "-1", - "sound_stop": "$shutdown.mp3", - "ctrl_tr_speed": "80", - "ctrl_fw_speed": "100", - "move_tr_speed": "85", - "move_power_angle_2": "60", - "ctrl_hud_image": "", - "load_at_start": "", - "sound_start": "$startup.mp3" + "move_power_angle_3":"60", + "cnn_default_model":"generic_fast_low", + "prog_maxblocks":"-1", + "camera_jpeg_quality":"5", + "show_page_control":"true", + "camera_framerate":"30", + "prog_scrollbars":"true", + "move_fw_speed":"100", + "prog_level":"adv", + "move_motor_trim":"1", + "move_motor_mode":"dc", + "cv_image_factor":"2", + "move_power_angle_1":"45", + "camera_path_object_size_min":"4000", + "button_func":"none", + "camera_color_object_size_min":"4000", + "camera_jpeg_bitrate":"1000000", + "move_fw_elapse":"1", + "show_control_move_commands":"true", + "camera_color_object_size_max":"160000", + "show_page_prefs":"true", + "camera_exposure_mode":"auto", + "ctrl_tr_elapse":"-1", + "show_page_program":"true", + "move_tr_elapse":"0.5", + "camera_path_object_size_max":"160000", + "sound_shutter":"$shutter.wav", + "ctrl_fw_elapse":"-1", + "sound_stop":"$shutdown.wav", + "ctrl_tr_speed":"80", + "ctrl_fw_speed":"100", + "move_tr_speed":"85", + "move_power_angle_2":"60", + "ctrl_hud_image":"", + "load_at_start":"", + "sound_start":"$startup.wav", + "hw_version":"5", + "audio_volume_level":"100", + "wifi_mode":"ap", + "wifi_ssid":"coderbot_CHANGEMEATFIRSTRUN", + "wifi_psk":"coderbot", + "packages_installed":"", + "admin_password":"", + "hardware_version": "5" } \ No newline at end of file diff --git a/data/defaults/programs/program_demo_cat_follower.json b/data/defaults/programs/program_demo_cat_follower.json new file mode 100644 index 00000000..376d1fb8 --- /dev/null +++ b/data/defaults/programs/program_demo_cat_follower.json @@ -0,0 +1 @@ +{"name": "cat_follower", "dom_code": "objectclasspositionpos_xWHILETRUEobjectGETFIRSTgeneric_object_detectclassGETFIRSTobjectEQclasscatpositionGETFROM_STARTobject3pos_xDIVIDEADDGETFROM_STARTposition1GETFROM_STARTposition32classLTpos_x40LEFT600.1GTpos_x60RIGHT600.1FORWARD1000.2object", "code": "object2 = None\nclass2 = None\nposition = None\npos_x = None\n\n\nwhile True:\n get_prog_eng().check_end()\n object2 = get_cam().cnn_detect_objects(\"generic_object_detect\")[0]\n class2 = object2[0]\n if class2 == 'cat':\n position = object2[2]\n pos_x = (position[0] + position[2]) / 2\n get_cam().set_text(class2)\n if pos_x < 40:\n get_bot().left(speed=60, elapse=0.1)\n elif pos_x > 60:\n get_bot().right(speed=60, elapse=0.1)\n else:\n get_bot().forward(speed=100, elapse=0.2)\n else:\n get_cam().set_text(object2)\n", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_demo_io_ext.json b/data/defaults/programs/program_demo_io_ext.json new file mode 100644 index 00000000..3d2c518a --- /dev/null +++ b/data/defaults/programs/program_demo_io_ext.json @@ -0,0 +1 @@ +{"name": "test_io_ext", "dom_code": "Analog_Input_1WHILETRUEAnalog_Input_10Analog Input 1: Analog_Input_1GTAnalog_Input_11000TRUE0FALSE", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_test_input.json b/data/defaults/programs/program_test_input.json new file mode 100644 index 00000000..309b3a4f --- /dev/null +++ b/data/defaults/programs/program_test_input.json @@ -0,0 +1 @@ +{"name": "test_input", "dom_code": "WHILETRUEanalog 1: 0 analog 2: 1 digital 1: 2", "code": "while True:\n get_prog_eng().check_end()\n get_cam().set_text(''.join([str(x) for x in ['analog 1: ', get_atmega().get_input(0), ' analog 2: ', get_atmega().get_input(1), ' digital 1: ', get_atmega().get_input(2)]]))\n", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_test_music.json b/data/defaults/programs/program_test_music.json new file mode 100644 index 00000000..9516746c --- /dev/null +++ b/data/defaults/programs/program_test_music.json @@ -0,0 +1 @@ +{"name": "test_music", "dom_code": "C2nonedog1D2nonecat1E2nonepig1F2noneelephant1G2nonesnake1A2noneduck1B2nonecat1", "code": "get_music().play_note(note=\"C2\", alteration=\"none\" ,instrument=\"dog\" ,duration=1)\nget_music().play_note(note=\"D2\", alteration=\"none\" ,instrument=\"cat\" ,duration=1)\nget_music().play_note(note=\"E2\", alteration=\"none\" ,instrument=\"pig\" ,duration=1)\nget_music().play_note(note=\"F2\", alteration=\"none\" ,instrument=\"elephant\" ,duration=1)\nget_music().play_note(note=\"G2\", alteration=\"none\" ,instrument=\"snake\" ,duration=1)\nget_music().play_note(note=\"A2\", alteration=\"none\" ,instrument=\"duck\" ,duration=1)\nget_music().play_note(note=\"B2\", alteration=\"none\" ,instrument=\"cat\" ,duration=1)\n", "default": false} \ No newline at end of file diff --git a/data/defaults/programs/program_test_output.json b/data/defaults/programs/program_test_output.json new file mode 100644 index 00000000..954474ae --- /dev/null +++ b/data/defaults/programs/program_test_output.json @@ -0,0 +1 @@ +{"name": "test_output", "dom_code": "WHILETRUE0TRUE0.11TRUE0.12TRUE0.10FALSE0.11FALSE0.12FALSE0.1", "code": "while True:\n get_prog_eng().check_end()\n get_atmega().set_output(0, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(1, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(2, True)\n get_bot().sleep(0.1)\n get_atmega().set_output(0, False)\n get_bot().sleep(0.1)\n get_atmega().set_output(1, False)\n get_bot().sleep(0.1)\n get_atmega().set_output(2, False)\n get_bot().sleep(0.1)\n", "default": false} \ No newline at end of file diff --git a/data/program_no_name.json b/data/program_no_name.json new file mode 100644 index 00000000..8d987595 --- /dev/null +++ b/data/program_no_name.json @@ -0,0 +1 @@ +{"name": "no_name", "dom_code": "", "code": "", "default": false} \ No newline at end of file diff --git a/data/program_test_led.json b/data/program_test_led.json new file mode 100644 index 00000000..c95f304b --- /dev/null +++ b/data/program_test_led.json @@ -0,0 +1 @@ +{"name": "test_led", "dom_code": "ledsicleds6031leds000i1leds11MINUSi1000ADDi5leds000c11005iADDi5MULTIPLYc0MULTIPLYc0MULTIPLYc31leds000", "code": "leds = None\ni = None\nc = None\n\ndef upRange(start, stop, step):\n while start <= stop:\n yield start\n start += abs(step)\n\ndef downRange(start, stop, step):\n while start >= stop:\n yield start\n start -= abs(step)\n\n\nleds = 60\nfor count in range(3):\n get_prog_eng().check_end()\n get_atmega().set_led(1, leds, 0, 0, 0)\n for i in (1 <= float(leds)) and upRange(1, float(leds), 1) or downRange(1, float(leds), 1):\n get_prog_eng().check_end()\n get_atmega().set_led(1, i - 1, 0, 0, 0)\n get_atmega().set_led(i + 5, leds, 0, 0, 0)\n for c in range(1, 101, 5):\n get_prog_eng().check_end()\n get_atmega().set_led(i, i + 5, c * 0, c * 0, c * 3)\n get_atmega().set_led(1, leds, 0, 0, 0)\n", "default": false} \ No newline at end of file diff --git a/data/programs.json b/data/programs.json index 4e899519..e2968f2b 100644 --- a/data/programs.json +++ b/data/programs.json @@ -1 +1 @@ -{"_default": {"1": {"name": "test_find_code", "filename": "./data/defaults/programs/program_test_find_code.json", "default": "True"}, "2": {"name": "demo_color_seeker", "filename": "./data/defaults/programs/program_demo_color_seeker.json", "default": "True"}, "3": {"name": "demo_sound_clap_control", "filename": "./data/defaults/programs/program_demo_sound_clap_control.json", "default": "True"}, "4": {"name": "test_find_path_ahead", "filename": "./data/defaults/programs/program_test_find_path_ahead.json", "default": "True"}, "5": {"name": "test_sound_hear", "filename": "./data/defaults/programs/program_test_sound_hear.json", "default": "True"}, "6": {"name": "test_find_color", "filename": "./data/defaults/programs/program_test_find_color.json", "default": "True"}, "7": {"name": "test_cnn_classifier", "filename": "./data/defaults/programs/program_test_cnn_classifier.json", "default": "True"}, "8": {"name": "test_sound_rec", "filename": "./data/defaults/programs/program_test_sound_rec.json", "default": "True"}, "9": {"name": "test_find_face", "filename": "./data/defaults/programs/program_test_find_face.json", "default": "True"}, "10": {"name": "demo_obstacle_avoidance", "filename": "./data/defaults/programs/program_demo_obstacle_avoidance.json", "default": "True"}, "11": {"name": "test_sonars", "filename": "./data/defaults/programs/program_test_sonars.json", "default": "True"}, "12": {"name": "test_img_average", "filename": "./data/defaults/programs/program_test_img_average.json", "default": "True"}, "13": {"name": "demo_ar_tags", "filename": "./data/defaults/programs/program_demo_ar_tags.json", "default": "True"}, "14": {"name": "test_cnn_object_detect", "filename": "./data/defaults/programs/program_test_cnn_object_detect.json", "default": "True"}, "15": {"name": "demo_line_follower", "filename": "./data/defaults/programs/program_demo_line_follower.json", "default": "True"}}} \ No newline at end of file +{"_default": {"1": {"name": "test_find_code", "filename": "./data/defaults/programs/program_test_find_code.json", "default": "True"}, "2": {"name": "demo_color_seeker", "filename": "./data/defaults/programs/program_demo_color_seeker.json", "default": "True"}, "3": {"name": "demo_sound_clap_control", "filename": "./data/defaults/programs/program_demo_sound_clap_control.json", "default": "True"}, "4": {"name": "test_find_path_ahead", "filename": "./data/defaults/programs/program_test_find_path_ahead.json", "default": "True"}, "5": {"name": "test_sound_hear", "filename": "./data/defaults/programs/program_test_sound_hear.json", "default": "True"}, "6": {"name": "test_find_color", "filename": "./data/defaults/programs/program_test_find_color.json", "default": "True"}, "7": {"name": "test_cnn_classifier", "filename": "./data/defaults/programs/program_test_cnn_classifier.json", "default": "True"}, "8": {"name": "test_sound_rec", "filename": "./data/defaults/programs/program_test_sound_rec.json", "default": "True"}, "9": {"name": "test_find_face", "filename": "./data/defaults/programs/program_test_find_face.json", "default": "True"}, "10": {"name": "demo_obstacle_avoidance", "filename": "./data/defaults/programs/program_demo_obstacle_avoidance.json", "default": "True"}, "11": {"name": "test_img_average", "filename": "./data/defaults/programs/program_test_img_average.json", "default": "True"}, "12": {"name": "demo_ar_tags", "filename": "./data/defaults/programs/program_demo_ar_tags.json", "default": "True"}, "13": {"name": "test_cnn_object_detect", "filename": "./data/defaults/programs/program_test_cnn_object_detect.json", "default": "True"}, "14": {"name": "demo_line_follower", "filename": "./data/defaults/programs/program_demo_line_follower.json", "default": "True"}, "15": {"name": "cat_follower", "filename": "./data/defaults/programs/program_demo_cat_follower.json", "default": "True"}, "16": {"name": "test_music", "filename": "./data/defaults/programs/program_test_music.json", "default": "True"}, "17": {"name": "demo_cat_follower", "filename": "./data/defaults/programs/program_demo_cat_follower.json", "default": "True"}, "18": {"name": "test_input", "filename": "./data/defaults/programs/program_test_input.json", "default": "True"}, "19": {"name": "test_output", "filename": "./data/defaults/programs/program_test_output.json", "default": "True"}, "20": {"name": "demo_io_ext", "filename": "./data/defaults/programs/program_demo_io_ext.json", "default": "True"}, "21": {"name": "test_sonars", "filename": "./data/defaults/programs/program_test_sonars.json", "default": "True"}, "22": {"name": "test_led", "dom_code": "ledsicleds6031leds000i1leds11MINUSi1000ADDi5leds000c11005iADDi5MULTIPLYc0MULTIPLYc0MULTIPLYc31leds000", "code": "leds = None\ni = None\nc = None\n\ndef upRange(start, stop, step):\n while start <= stop:\n yield start\n start += abs(step)\n\ndef downRange(start, stop, step):\n while start >= stop:\n yield start\n start -= abs(step)\n\n\nleds = 60\nfor count in range(3):\n get_prog_eng().check_end()\n get_atmega().set_led(1, leds, 0, 0, 0)\n for i in (1 <= float(leds)) and upRange(1, float(leds), 1) or downRange(1, float(leds), 1):\n get_prog_eng().check_end()\n get_atmega().set_led(1, i - 1, 0, 0, 0)\n get_atmega().set_led(i + 5, leds, 0, 0, 0)\n for c in range(1, 101, 5):\n get_prog_eng().check_end()\n get_atmega().set_led(i, i + 5, c * 0, c * 0, c * 3)\n get_atmega().set_led(1, leds, 0, 0, 0)\n", "default": false, "filename": "./data/program_test_led.json"}, "23": {"name": "no_name", "dom_code": "", "code": "", "default": false, "filename": "./data/program_no_name.json"}}} \ No newline at end of file diff --git a/main.py b/main.py index fa80e5bf..563e684a 100644 --- a/main.py +++ b/main.py @@ -30,6 +30,7 @@ from config import Config from cnn_manager import CNNManager from event import EventManager +from audioControls import AudioCtrl # Logging configuration logger = logging.getLogger() @@ -42,12 +43,12 @@ #logger.addHandler(sh) logger.addHandler(fh) - ## (Connexion) Flask app configuration # Serve a custom version of the swagger ui (Jinja2 templates) based on the default one # from the folder 'swagger-ui'. Clone the 'swagger-ui' repository inside the backend folder -connexionApp = connexion.App(__name__, swagger_ui=True, swagger_path='swagger-ui/') +options = {"swagger_ui": False} +connexionApp = connexion.App(__name__, options=options) # Connexion wraps FlaskApp, so app becomes connexionApp.app app = connexionApp.app @@ -56,7 +57,6 @@ babel = Babel(app) app.debug = False app.prog_engine = ProgramEngine.get_instance() -app.prog = None app.shutdown_requested = False ## New API and web application @@ -153,6 +153,8 @@ def handle_config(): """ Overwrite configuration file on disk and reload it """ + audioCtrl = AudioCtrl.get_instance() + audioCtrl.setVolume(int(request.form['audio_volume_level'])) Config.write(updateDict(app.bot_config, request.form)) app.bot_config = Config.get() return "ok" @@ -176,9 +178,9 @@ def handle_wifi(): psk = request.form.get("wifi_psk") logging.info("mode " + mode +" ssid: " + ssid + " psk: " + psk) - client_params = " \"" + ssid + "\" \"" + psk + "\"" if ssid != "" and psk != "" else "" + client_params = " --ssid \"" + ssid + "\" --pwd \"" + psk + "\"" if ssid != "" and psk != "" else "" logging.info(client_params) - os.system("sudo ./wifi.py updatecfg " + mode + client_params) + os.system("sudo ./wifi.py updatecfg --mode " + mode + client_params) os.system("sudo reboot") if mode == "ap": return "http://coder.bot" @@ -333,8 +335,8 @@ def handle_program_load(): """ logging.debug("program_load") name = request.args.get('name') - app.prog = app.prog_engine.load(name) - return jsonify(app.prog.as_dict()) + prog = app.prog_engine.load(name) + return jsonify(prog.as_dict()) @app.route("/program/save", methods=["POST"]) def handle_program_save(): @@ -367,8 +369,8 @@ def handle_program_exec(): logging.debug("program_exec") name = request.form.get('name') code = request.form.get('code') - app.prog = app.prog_engine.create(name, code) - return json.dumps(app.prog.execute()) + prog = app.prog_engine.create(name, code) + return json.dumps(prog.execute()) @app.route("/program/end", methods=["POST"]) def handle_program_end(): @@ -376,9 +378,9 @@ def handle_program_end(): Stop the program execution """ logging.debug("program_end") - if app.prog: - app.prog.end() - app.prog = None + prog = app.prog_engine.get_current_program() + if prog: + prog.end() return "ok" @app.route("/program/status", methods=["GET"]) @@ -387,9 +389,9 @@ def handle_program_status(): Expose the program status """ logging.debug("program_status") - prog = Program("") - if app.prog: - prog = app.prog + prog = app.prog_engine.get_current_program() + if prog is None: + prog = Program("") return json.dumps({'name': prog.name, "running": prog.is_running(), "log": app.prog_engine.get_log()}) @app.route("/cnnmodels", methods=["GET"]) @@ -446,10 +448,11 @@ def execute(command): def button_pushed(): if app.bot_config.get('button_func') == "startstop": - if app.prog and app.prog.is_running(): - app.prog.end() - elif app.prog and not app.prog.is_running(): - app.prog.execute() + prog = app.prog_engine.get_current_prog() + if prog and prog.is_running(): + prog.end() + elif prog and not prog.is_running(): + prog.execute() def remove_doreset_file(): try: @@ -457,6 +460,14 @@ def remove_doreset_file(): except OSError: pass +def align_wifi_config(): + if app.bot_config["wifi_ssid"] == "coderbot_CHANGEMEATFIRSTRUN": + out = os.popen("sudo ./wifi.py getcfg --ssid").read() + if "coderbot_" in out: + app.bot_config["wifi_ssid"] = out.split()[0] + Config.write(app.bot_config) + app.bot_config = Config.get() + # Finally, get the server running def run_server(): bot = None @@ -464,10 +475,15 @@ def run_server(): try: try: app.bot_config = Config.read() + align_wifi_config() bot = CoderBot.get_instance(motor_trim_factor=float(app.bot_config.get('move_motor_trim', 1.0)), - encoder=bool(app.bot_config.get('encoder'))) + hw_version=app.bot_config.get('hardware_version')) audio = Audio.get_instance() audio.say(app.bot_config.get("sound_start")) + + audioCtrl = AudioCtrl.get_instance() + audioCtrl.setVolume(int(app.bot_config.get('audio_volume_level'))) + try: cam = Camera.get_instance() Motion.get_instance() @@ -478,8 +494,8 @@ def run_server(): EventManager.get_instance("coderbot") if app.bot_config.get('load_at_start') and app.bot_config.get('load_at_start'): - app.prog = app.prog_engine.load(app.bot_config.get('load_at_start')) - app.prog.execute() + prog = app.prog_engine.load(app.bot_config.get('load_at_start')) + prog.execute() except ValueError as e: app.bot_config = {} logging.error(e) diff --git a/music.py b/music.py new file mode 100644 index 00000000..0544b468 --- /dev/null +++ b/music.py @@ -0,0 +1,152 @@ +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# MUSICAL EXTENTION for CoderBot +# This extention is develop by: +# Michele Carbonera - miki_992@hotmail.it - m.carbonera@campus.unimib.it - michele.carbonera@unimib.it +# Antonino Tramontana - a.tramontana1@campus.unimib.it +# Copyright (C) 2020 +############################################################################ + +import os +import sox +import time + +class Music: + _instance = None + managerPackage = None + + noteDict = { + 'C2': -7.0, 'D2' : -5.0, 'E2' : -3.0, 'F2' : -2.0, 'F#2' : -1.0, 'G2' : 0.0, + 'A2' : 2.0, 'Bb2' : 3.0, 'B2' : 4.0, 'C3' : 5.0, 'D3' : 7.0, 'E3' : 9.0, + 'F3' : 10.0, 'G3' : 12.0 + } + + + @classmethod + def get_instance(cls,managerPackage): + if cls._instance is None: + cls._instance = Music(managerPackage) + return cls._instance + + def __init__(self,managerPackage): + + #os.putenv('AUDIODRIVER', 'alsa') + #os.putenv('AUDIODEV', 'hw:1,0') + self.managerPackage = managerPackage + print("We have create a class: MUSICAL") + + def test(self): + tfm = sox.Transformer() + tfm.preview('cat.wav') + tfm.build('cat.wav', 'outMusicDemo.wav') + + #play a pause + # @param duration: duration of the pause in seconds + def play_pause(self, duration): + duration = float(duration) + time.sleep(duration) + + #play a given note for a given instrument + # @param instrument: name of the instrument to be used + # @param note: name of the note in the following format "A2" + # @para alteration: if it is a diesis or a bemolle + # @param time: duration of the note in seconds + def play_note(self, note, instrument='piano', alteration='none', duration=1.0): + print(note) + tfm = sox.Transformer() + + duration = float(duration) + + alt = 0.0 + if alteration == 'bmolle': + alt = -1.0 + elif alteration == 'diesis': + alt = 1.0 + + if note in self.noteDict : + shift = self.noteDict[note]+ alt + else: + print('note not exist') + return + + tfm.pitch(shift, quick=False) + tfm.trim(0.0, end_time=0.5*duration) + if self.managerPackage.isPackageAvailable(instrument): + tfm.preview('./sounds/notes/' + instrument + '/audio.wav') + else: + print("no instrument:"+str(instrument)+" present in this coderbot!") + + def play_animal(self, instrument, note='G2', alteration='none', duration=1.0): + tfm = sox.Transformer() + + duration = float(duration) + + alt = 0.0 + if alteration == 'bmolle': + alt = -1.0 + elif alteration == 'diesis': + alt = 1.0 + + if note == 'C2': + shift = -7.0 + alt + elif note == 'D2': + shift = -5.0 + alt + elif note == 'E2': + shift = -3.0 + alt + elif note == 'F2': + shift = -2.0 + alt + elif note == 'F#2': + shift = -1.0 + alt + elif note == 'G2': + shift = 0.0 + alt + elif note == 'A2': + shift = 2.0 + alt + elif note == 'Bb2': + shift = 3.0 + alt + elif note == 'B2': + shift = 4.0 + alt + elif note == 'C3': + shift = 5.0 + alt + elif note == 'D3': + shift = 7.0 + alt + elif note == 'E3': + shift = 9.0 + alt + elif note == 'F3': + shift = 10.0 + alt + elif note == 'G3': + shift = 12.0 + alt + + if note in self.noteDict : + shift = self.noteDict[note]+ alt + else: + print('note not exist') + return + + if self.managerPackage.isPackageAvailable(instrument): + tfm.preview('./sounds/notes/' + instrument + '/audio.wav') + else: + print("no animal verse:"+str(instrument)+" present in this coderbot!") + return + tfm.pitch(shift, quick=False) + tfm.trim(0.0, end_time=0.5*duration) + #tfm.stretch(time, window=20) + tfm.preview('./sounds/notes/' + instrument + '/audio.wav') diff --git a/musicPackages.py b/musicPackages.py new file mode 100644 index 00000000..051b8738 --- /dev/null +++ b/musicPackages.py @@ -0,0 +1,221 @@ +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +############################################################################ +# CoderBot, a didactical programmable robot. +# Copyright (C) 2014, 2015 Roberto Previtera +# +# MUSICAL EXTENTION for CoderBot +# This extention is develop by: +# Michele Carbonera - miki_992@hotmail.it - m.carbonera@campus.unimib.it - michele.carbonera@unimib.it +# Antonino Tramontana - a.tramontana1@campus.unimib.it +# Copyright (C) 2020 +############################################################################ + +import json +import os +import logging +import copy + +class MusicPackage: + + def __init__(self, nameID, category, name_IT, name_EN, version, date): + self.nameID = nameID + self.category = category + self.name_IT = name_IT + self.name_EN = name_EN + self.version = version + self.date = date + self.interfaces = list() + + def getNameID(self): + return self.nameID + + def getCategory(self): + return self.category + + def getNameIT(self): + return self.name_IT + + def getNameEN(self): + return self.name_EN + + def getVersion(self): + return self.version + + def getDate(self): + return self.date + + def getInterfaces(self): + return self.interfaces + + def addInterface(self, musicPackageInterface): + self.interfaces.append(musicPackageInterface) + +class MusicPackageInterface: + + def __init__(self,interfaceName,available,icon): + self.interfaceName = interfaceName + self.available = available + self.icon = icon + + def getInterfaceName(self): + return self.interfaceName + + def getAvailable(self): + return self.available + + def getIcon(self): + return self.icon + +class MusicPackageManager: + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = MusicPackageManager() + return cls._instance + + def __init__(self): + self.packages = dict() + with open('./sounds/notes/music_package.json') as json_file: + data = json.load(json_file) + for p in data['packages']: + + package = data['packages'][p] + mp = MusicPackage(p,package['category'],package['name_IT'],package['name_EN'],package['version'],package['date']) + for i in package['interface']: + interfaceItem = package['interface'][i] + mpi = MusicPackageInterface(i,interfaceItem['available'],interfaceItem['icon']) + mp.addInterface(mpi) + + if p not in self.packages: + self.packages[p] = mp + + def listPackages(self): + packages_serializable = dict() + for name, package in self.packages.items(): + package_copy = copy.deepcopy(package) + packages_serializable[name] = package_copy.__dict__ + packages_serializable[name]['interfaces'] = [] + for i in package.interfaces: + packages_serializable[name]['interfaces'].append(i.__dict__) + return packages_serializable + + def updatePackages(self): + newdict = { 'packages': {} } + for element in self.packages: + nameID = self.packages[element].getNameID() + newdict['packages'][nameID] = { } + newdict['packages'][nameID]['category']= self.packages[element].getCategory() + newdict['packages'][nameID]['name_IT']= self.packages[element].getNameIT() + newdict['packages'][nameID]['name_EN']= self.packages[element].getNameEN() + newdict['packages'][nameID]['version']= self.packages[element].getVersion() + newdict['packages'][nameID]['date']= self.packages[element].getDate() + newdict['packages'][nameID]['interface']= {'base':{}, 'intermediate':{}, 'advanced': {}} + newdict['packages'][nameID]['interface']['base']['available'] = self.packages[element].getInterfaces()[0].getAvailable() + newdict['packages'][nameID]['interface']['base']['icon'] = self.packages[element].getInterfaces()[0].getIcon() + newdict['packages'][nameID]['interface']['intermediate']['available'] = self.packages[element].getInterfaces()[1].getAvailable() + newdict['packages'][nameID]['interface']['intermediate']['icon'] = self.packages[element].getInterfaces()[1].getIcon() + newdict['packages'][nameID]['interface']['advanced']['available'] = self.packages[element].getInterfaces()[2].getAvailable() + newdict['packages'][nameID]['interface']['advanced']['icon'] = self.packages[element].getInterfaces()[2].getIcon() + + #json_packages = json.dumps(newdict) + with open('sounds/notes/music_package.json', 'w', encoding='utf-8') as json_file: + json.dump(newdict, json_file, ensure_ascii=False, indent=4) + + + def deletePackage(self, packageName): + logging.info("packageName: " + packageName) + if packageName in self.packages: + del self.packages[packageName] + self.updatePackages() + else: + logging.error("errore, il pacchetto " + packageName + " non è stato trovato") + return 2 + + if os.path.exists('./sounds/notes/' + packageName): + os.system('rm -rf ./sounds/notes/' + packageName) + return 1 + + + def verifyVersion(self, packageName, version): + logging.info("verifica pacchetto") + #newversionList = version.split('.') + if packageName not in self.packages: + return True + + newVersionList = [int(x) for x in version.split('.')] + #for i in ragen(0,len(newversionList) -1): + #newversionList[i] = int(newLversionList[i]) + + oldVersion = self.packages[packageName].getVersion() + oldVersionList = [int(x) for x in oldVersion.split('.')] + + for i in range(0,len(newVersionList) -1): + if(newVersionList[i] > oldVersionList[i] ): + return True + elif(newVersionList[i] < oldVersionList[i] ): + return False + + return False + + def addPackage(self, filename): + pkgnames = filename.split('_') + version = pkgnames[1].replace('.zip', '') + logging.info("Music Package version: " + version) + pkgname = pkgnames[0] + pkgpath = './sounds/notes/' + pkgname + if not self.verifyVersion(pkgname, version): + if (version == self.packages[pkgname].getVersion()): + logging.error("errore, il pacchetto " + pkgname + " ha versione identica a quello attualmente installato") + return 3 + else: + logging.info("errore, il pacchetto " + pkgname + " ha versione precendente a quello attualmente installato") + return 2 + else: + + os.system('unzip -o ' + './updatePackages/' + filename + " -d ./updatePackages") + + os.system('mkdir ' + pkgpath) + os.system('mv ./updatePackages/' + pkgname + "/" + 'audio.wav ' + pkgpath + '/') + + with open('./updatePackages/' + pkgname + '/' + pkgname + '.json') as json_file: + logging.info("adding " + pkgname + " package") + data = json.load(json_file) + for p in data['packages']: + package = data['packages'][p] + mp = MusicPackage(p,package['category'],package['name_IT'],package['name_EN'],package['version'],package['date']) + for i in package['interface']: + interfaceItem = package['interface'][i] + mpi = MusicPackageInterface(i,interfaceItem['available'],interfaceItem['icon']) + mp.addInterface(mpi) + + self.packages[p] = mp + + self.updatePackages() + + os.system('rm -rf ./updatePackages/' + pkgname) + return 1 + + + def isPackageAvailable(self,namePackage): + if namePackage in self.packages: + return True + else: + return False diff --git a/photos/0.txt b/photos/0.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/photos/metadata.json b/photos/metadata.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/photos/metadata.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/program.py b/program.py index b66efde9..15629115 100644 --- a/program.py +++ b/program.py @@ -31,13 +31,16 @@ import config import audio import event - - +import music +import musicPackages +import atmega328p PROGRAM_PATH = "./data/" PROGRAM_PREFIX = "program_" PROGRAM_SUFFIX = ".json" +musicPackageManager = musicPackages.MusicPackageManager.get_instance() + def get_cam(): return camera.Camera.get_instance() @@ -56,6 +59,12 @@ def get_prog_eng(): def get_event(): return event.EventManager.get_instance() +def get_music(): + return music.Music.get_instance(musicPackageManager) + +def get_atmega(): + return atmega328p.ATMega328.get_instance() + class ProgramEngine: # pylint: disable=exec-used @@ -105,7 +114,8 @@ def load(self, name): logging.info(program_db_entries[0]) f = open(program_db_entries[0]["filename"], 'r') self._program = Program.from_dict(json.load(f)) - return self._program + return self._program + return None def delete(self, name): query = Query() @@ -131,6 +141,12 @@ def log(self, text): def get_log(self): return self._log + def set_log(self, log): + self._log = "" + + def get_current_program(self): + return self._program + class Program: _running = False @@ -139,21 +155,20 @@ def dom_code(self): return self._dom_code def __init__(self, name, code=None, dom_code=None, default=False): - #super(Program, self).__init__() self._thread = None self.name = name self._dom_code = dom_code self._code = code self._default = default - def execute(self): + def execute(self, options={}): if self._running: raise RuntimeError('already running') + ProgramEngine.get_instance().set_log("") self._running = True - try: - self._thread = threading.Thread(target=self.run) + self._thread = threading.Thread(target=self.run, args=(options,)) self._thread.start() except RuntimeError as re: logging.error("RuntimeError: %s", str(re)) @@ -178,15 +193,16 @@ def is_running(self): def is_default(self): return self._default - def run(self): + def run(self, *args): + options = args[0] try: program = self try: - if config.Config.get().get("prog_video_rec") == "true": - get_cam().video_rec(program.name) + if options.get("autoRecVideo") == True: + get_cam().video_rec(program.name.replace(" ", "_")) logging.debug("starting video") - except Exception: - logging.error("Camera not available") + except Exception as e: + logging.error("Camera not available: " + str(e)) self._log = "" #clear log imports = "import json\n" diff --git a/requirements_stub.txt b/requirements_stub.txt index e8301ef3..6fd067b3 100644 --- a/requirements_stub.txt +++ b/requirements_stub.txt @@ -1,47 +1,49 @@ # Basic set of required packages. If you need to run on a real system (NOT in STUB mode) # install the packages in `requirements.txt` too. -absl-py==0.9.0 +absl-py==1.0.0 astor==0.8.1 -Babel==2.8.0 -certifi==2018.4.16 +Babel==2.10.1 +certifi==2022.5.18.1 chardet==3.0.4 -click==7.0 -clickclick==1.2.2 -connexion==1.4.2 -Flask==1.1.1 -Flask-Babel==0.12.2 -Flask-Cors==3.0.8 -gast==0.2.2 -grpcio==1.26.0 -idna==2.8 -pybind11==2.4.3 -inflection==0.3.1 -itsdangerous==0.24 -Jinja2==2.11.1 -jsonschema==2.6.0 -Markdown==3.1.1 -MarkupSafe==1.1.1 -numpy==1.17.4 -opencv-contrib-python==4.1.1.26 -pigpio==1.45 -Pillow==7.0.0 -protobuf==3.11.3 -Pypubsub==4.0.0 -pytz==2018.4 -pyyaml>=4.2b1 -pyzbar==0.1.7 -requests==2.22.0 -six==1.14.0 -swagger-spec-validator==2.3.1 +click==8.1.3 +clickclick==20.10.2 +connexion==2.13.1 +Flask==2.1.2 +Flask-Babel==2.0.0 +Flask-Cors==3.0.10 +gast==0.5.3 +grpcio==1.46.3 +idna==3.3 +pybind11==2.9.2 +inflection==0.5.1 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jsonschema==4.5.1 +Markdown==3.3.7 +MarkupSafe==2.1.1 +numpy==1.22.4 +opencv-contrib-python==4.5.3.56 +pigpio==1.78 +Pillow==9.1.1 +protobuf==4.21.1 +Pypubsub==4.0.3 +pytz==2022.1 +pyyaml==6.0 +pyzbar==0.1.9 +requests==2.27.1 +six==1.16.0 +swagger-spec-validator==2.7.4 termcolor==1.1.0 -tinydb==3.12.1 -tensorflow==2.1.0 -tensorflow_hub>=0.7.0 -urllib3==1.24.2 -Werkzeug==0.15.3 -setuptools==42.0.1 -smbus2==0.3.0 -spidev==3.4 -cachetools==3.0.0 -pytesseract==0.3.4 +tinydb==4.7.0 +tflite_runtime==2.8.0 +urllib3==1.26.9 +Werkzeug==2.1.2 +setuptools==62.3.2 +smbus2==0.4.1 +spidev==3.5 +cachetools==5.1.0 +sox==1.4.1 +pyalsaaudio==0.9.2 +pytesseract==0.3.9 +sox==1.4.1 diff --git a/sounds/buzz.mp3 b/sounds/buzz.mp3 deleted file mode 100644 index e28d0f69..00000000 Binary files a/sounds/buzz.mp3 and /dev/null differ diff --git a/sounds/buzz.wav b/sounds/buzz.wav new file mode 100644 index 00000000..dae05b95 Binary files /dev/null and b/sounds/buzz.wav differ diff --git a/sounds/i-see-you.mp3 b/sounds/i-see-you.mp3 deleted file mode 100644 index 9965ad52..00000000 Binary files a/sounds/i-see-you.mp3 and /dev/null differ diff --git a/sounds/i-see-you.wav b/sounds/i-see-you.wav new file mode 100644 index 00000000..a3ab3021 Binary files /dev/null and b/sounds/i-see-you.wav differ diff --git a/sounds/notes/cat/audio.wav b/sounds/notes/cat/audio.wav new file mode 100644 index 00000000..eb20db75 Binary files /dev/null and b/sounds/notes/cat/audio.wav differ diff --git a/sounds/notes/dinosaur.wav b/sounds/notes/dinosaur.wav new file mode 100644 index 00000000..4fe28f48 Binary files /dev/null and b/sounds/notes/dinosaur.wav differ diff --git a/sounds/notes/dog/audio.wav b/sounds/notes/dog/audio.wav new file mode 100644 index 00000000..fa3e4e67 Binary files /dev/null and b/sounds/notes/dog/audio.wav differ diff --git a/sounds/notes/duck/audio.wav b/sounds/notes/duck/audio.wav new file mode 100644 index 00000000..ee416bc0 Binary files /dev/null and b/sounds/notes/duck/audio.wav differ diff --git a/sounds/notes/elephant/audio.wav b/sounds/notes/elephant/audio.wav new file mode 100644 index 00000000..7d313846 Binary files /dev/null and b/sounds/notes/elephant/audio.wav differ diff --git a/sounds/notes/flute/audio.wav b/sounds/notes/flute/audio.wav new file mode 100644 index 00000000..1e14fa57 Binary files /dev/null and b/sounds/notes/flute/audio.wav differ diff --git a/sounds/notes/guitar/audio.wav b/sounds/notes/guitar/audio.wav new file mode 100644 index 00000000..673b5127 Binary files /dev/null and b/sounds/notes/guitar/audio.wav differ diff --git a/sounds/notes/music_package.json b/sounds/notes/music_package.json new file mode 100644 index 00000000..d876c5c3 --- /dev/null +++ b/sounds/notes/music_package.json @@ -0,0 +1,193 @@ +{ + "packages": { + "piano": { + "category": "instrument", + "name_IT": "pianoforte", + "name_EN": "piano", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "piano.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "piano.png" + }, + "advanced": { + "available": "TRUE", + "icon": "piano.png" + } + } + }, + "guitar": { + "category": "instrument", + "name_IT": "chitarra", + "name_EN": "guitar", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "guitar.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "guitar.png" + }, + "advanced": { + "available": "TRUE", + "icon": "guitar.png" + } + } + }, + "flute": { + "category": "instrument", + "name_IT": "flauto", + "name_EN": "flute", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "flute.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "flute.png" + }, + "advanced": { + "available": "TRUE", + "icon": "flute.png" + } + } + }, + "cat": { + "category": "animal", + "name_IT": "gatto", + "name_EN": "cat", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "cat.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "cat.png" + }, + "advanced": { + "available": "TRUE", + "icon": "cat.png" + } + } + }, + "dog": { + "category": "animal", + "name_IT": "cane", + "name_EN": "dog", + "version": "0.1", + "date": "2020-04-08", + "interface": { + "base": { + "available": "TRUE", + "icon": "dog.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "dog.png" + }, + "advanced": { + "available": "TRUE", + "icon": "dog.png" + } + } + }, + "pig": { + "category": "animal", + "name_IT": "maiale", + "name_EN": "pig", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "pig.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "pig.png" + }, + "advanced": { + "available": "TRUE", + "icon": "pig.png" + } + } + }, + "elephant": { + "category": "animal", + "name_IT": "elefante", + "name_EN": "elephant", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "elephant.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "elephant.png" + }, + "advanced": { + "available": "TRUE", + "icon": "elephant.png" + } + } + }, + "snake": { + "category": "animal", + "name_IT": "serpente", + "name_EN": "snake", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "snake.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "snake.png" + }, + "advanced": { + "available": "TRUE", + "icon": "snake.png" + } + } + }, + "duck": { + "category": "animal", + "name_IT": "anatra", + "name_EN": "duck", + "version": "0.1", + "date": "2020-06-01", + "interface": { + "base": { + "available": "TRUE", + "icon": "duck.png" + }, + "intermediate": { + "available": "TRUE", + "icon": "duck.png" + }, + "advanced": { + "available": "TRUE", + "icon": "duck.png" + } + } + } + } +} diff --git a/sounds/notes/piano/audio.wav b/sounds/notes/piano/audio.wav new file mode 100644 index 00000000..5eaabb67 Binary files /dev/null and b/sounds/notes/piano/audio.wav differ diff --git a/sounds/notes/pig/audio.wav b/sounds/notes/pig/audio.wav new file mode 100644 index 00000000..63ddcf4c Binary files /dev/null and b/sounds/notes/pig/audio.wav differ diff --git a/sounds/notes/snake/audio.wav b/sounds/notes/snake/audio.wav new file mode 100644 index 00000000..39775e14 Binary files /dev/null and b/sounds/notes/snake/audio.wav differ diff --git a/sounds/phaser.mp3 b/sounds/phaser.mp3 deleted file mode 100644 index 632a307b..00000000 Binary files a/sounds/phaser.mp3 and /dev/null differ diff --git a/sounds/phaser.wav b/sounds/phaser.wav new file mode 100644 index 00000000..bc3a59ff Binary files /dev/null and b/sounds/phaser.wav differ diff --git a/sounds/scanner.mp3 b/sounds/scanner.mp3 deleted file mode 100644 index 534647f3..00000000 Binary files a/sounds/scanner.mp3 and /dev/null differ diff --git a/sounds/scanner.wav b/sounds/scanner.wav new file mode 100644 index 00000000..9212f29a Binary files /dev/null and b/sounds/scanner.wav differ diff --git a/sounds/shutdown.mp3 b/sounds/shutdown.mp3 deleted file mode 100644 index 0039e395..00000000 Binary files a/sounds/shutdown.mp3 and /dev/null differ diff --git a/sounds/shutdown.wav b/sounds/shutdown.wav new file mode 100644 index 00000000..73627427 Binary files /dev/null and b/sounds/shutdown.wav differ diff --git a/sounds/shutter.mp3 b/sounds/shutter.mp3 deleted file mode 100644 index b376d7c3..00000000 Binary files a/sounds/shutter.mp3 and /dev/null differ diff --git a/sounds/shutter.wav b/sounds/shutter.wav new file mode 100644 index 00000000..96bc89c6 Binary files /dev/null and b/sounds/shutter.wav differ diff --git a/sounds/startup.mp3 b/sounds/startup.mp3 deleted file mode 100644 index a33852fe..00000000 Binary files a/sounds/startup.mp3 and /dev/null differ diff --git a/sounds/startup.wav b/sounds/startup.wav new file mode 100644 index 00000000..707ace6b Binary files /dev/null and b/sounds/startup.wav differ diff --git a/sounds/still-there.mp3 b/sounds/still-there.mp3 deleted file mode 100644 index 9bd92881..00000000 Binary files a/sounds/still-there.mp3 and /dev/null differ diff --git a/sounds/still-there.wav b/sounds/still-there.wav new file mode 100644 index 00000000..35c2f01b Binary files /dev/null and b/sounds/still-there.wav differ diff --git a/sounds/there-you-are.mp3 b/sounds/there-you-are.mp3 deleted file mode 100644 index 92165bc8..00000000 Binary files a/sounds/there-you-are.mp3 and /dev/null differ diff --git a/sounds/there-you-are.wav b/sounds/there-you-are.wav new file mode 100644 index 00000000..bf18c808 Binary files /dev/null and b/sounds/there-you-are.wav differ diff --git a/start.sh b/start.sh index a8ab92dd..b628acf2 100755 --- a/start.sh +++ b/start.sh @@ -1,2 +1,3 @@ #!/bin/bash -LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1.2.0 python3 init.py +[[ -d "firmware" ]] && [[ ! -f "firmware/initialised" ]] && source firmware/upload.sh +AUDIODEV=hw:1 LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1.2.0 python3 init.py diff --git a/static/js/blockly/blocks.js b/static/js/blockly/blocks.js index 5dd1c305..c7e72208 100644 --- a/static/js/blockly/blocks.js +++ b/static/js/blockly/blocks.js @@ -572,27 +572,6 @@ Blockly.Python['coderbot_adv_findLine'] = function(block) { return [code, Blockly.Python.ORDER_ATOMIC]; }; -Blockly.Blocks['coderbot_adv_findSignal'] = { - /** - * Block for findSignal function. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); - this.setColour(250); - this.appendDummyInput() - .appendField(Blockly.Msg.CODERBOT_SENSOR_FINDSIGNAL); - this.setOutput(true, 'Number'); - this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); - } -}; - -Blockly.Python['coderbot_adv_findSignal'] = function(block) { - // Boolean values true and false. - var code = 'get_cam().find_signal()'; - return [code, Blockly.Python.ORDER_ATOMIC]; -}; - Blockly.Blocks['coderbot_adv_findFace'] = { /** * Block for findSignal function. @@ -1206,3 +1185,115 @@ Blockly.Python['coderbot_mpu_get_temp'] = function(block) { var code = 'get_bot().get_mpu_temp()'; return [code, Blockly.Python.ORDER_ATOMIC]; }; + +Blockly.Blocks['coderbot_atmega_get_input'] = { + /** + * Block for get_input function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); + this.setColour(240); + this.appendDummyInput() + .appendField(Blockly.Msg.CODERBOT_ATMEGA_READ) + .appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_ATMEGA_AI_1, "0"], + [Blockly.Msg.CODERBOT_ATMEGA_AI_2, "1"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_3, "2"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_4, "3"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_5, "4"], + [Blockly.Msg.CODERBOT_ATMEGA_DI_6, "5"],]), 'INPUT'); + this.setOutput(true, 'Number'); + this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); + } +}; + +Blockly.Python['coderbot_atmega_get_input'] = function(block) { + // input index: 0, 1 are Analogs, 2..5 are Digital + var input = block.getFieldValue('INPUT'); + var code = 'get_atmega().get_input(' + input + ')'; + return [code, Blockly.Python.ORDER_ATOMIC]; +}; + +Blockly.Blocks['coderbot_atmega_set_output'] = { + /** + * Block for set_output function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); + this.setColour(240); + this.appendDummyInput() + .appendField(Blockly.Msg.CODERBOT_ATMEGA_WRITE) + .appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_ATMEGA_DO_1, "0"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_2, "1"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_3, "2"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_4, "3"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_5, "4"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_6, "5"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_7, "6"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_8, "7"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_9, "8"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_10, "9"], + [Blockly.Msg.CODERBOT_ATMEGA_DO_11, "10"],]), 'OUTPUT'); + this.appendValueInput('VALUE') + .setCheck('Boolean') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_VALUE); + this.setInputsInline(true); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); + } +}; + +Blockly.Python['coderbot_atmega_set_output'] = function(block) { + // input index: 0, 10 are Digital + var output = block.getFieldValue('OUTPUT'); + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '\'\''; + var code = 'get_atmega().set_output(' + output + ', ' + value + ')\n'; + return code; +}; + +Blockly.Blocks['coderbot_atmega_set_led'] = { + /** + * Block for set_output function. + * @this Blockly.Block + */ + init: function() { + this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); + this.setColour(240); + this.appendDummyInput() + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_SET) + this.appendValueInput('BEGIN') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_BEGIN); + this.appendValueInput('END') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_END); + this.appendValueInput('RED') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_RED); + this.appendValueInput('GREEN') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_GREEN); + this.appendValueInput('BLUE') + .setCheck('Number') + .appendField(Blockly.Msg.CODERBOT_ATMEGA_LED_BLUE); + this.setInputsInline(true); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); + } +}; + +Blockly.Python['coderbot_atmega_set_led'] = function(block) { + // input index: 0, 10 are Digital + var begin = Blockly.Python.valueToCode(block, 'BEGIN', Blockly.Python.ORDER_NONE); + var end = Blockly.Python.valueToCode(block, 'END', Blockly.Python.ORDER_NONE); + var red = Blockly.Python.valueToCode(block, 'RED', Blockly.Python.ORDER_NONE); + var green = Blockly.Python.valueToCode(block, 'GREEN', Blockly.Python.ORDER_NONE); + var blue = Blockly.Python.valueToCode(block, 'BLUE', Blockly.Python.ORDER_NONE); + var code = 'get_atmega().set_led(' + begin + ', ' + end + ', ' + red + ', ' + green + ', ' + blue + ')\n'; + + return code; +}; diff --git a/static/js/blockly/bot_en.js b/static/js/blockly/bot_en.js index bc436856..31d77c4c 100644 --- a/static/js/blockly/bot_en.js +++ b/static/js/blockly/bot_en.js @@ -97,4 +97,24 @@ Blockly.Msg.CODERBOT_EVENT_PUBLISH = "publish"; Blockly.Msg.CODERBOT_EVENT_ON_TOPIC = "on topic"; Blockly.Msg.CODERBOT_EVENT_GENERATOR = "event generator"; Blockly.Msg.CODERBOT_CONVERSATION_PARSE = "parse"; +Blockly.Msg.CODERBOT_ATMEGA_READ = "Read"; +Blockly.Msg.CODERBOT_ATMEGA_VALUE = "Value"; +Blockly.Msg.CODERBOT_ATMEGA_AI_1 = "Analog Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_AI_2 = "Analog Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_3 = "Digital Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_DI_4 = "Digital Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_5 = "Digital Input3"; +Blockly.Msg.CODERBOT_ATMEGA_DI_6 = "Digital Input 4"; +Blockly.Msg.CODERBOT_ATMEGA_WRITE = "Write"; +Blockly.Msg.CODERBOT_ATMEGA_DO_1 = "Digital Output 1"; +Blockly.Msg.CODERBOT_ATMEGA_DO_2 = "Digital Output 2"; +Blockly.Msg.CODERBOT_ATMEGA_DO_3 = "Digital Output 3"; +Blockly.Msg.CODERBOT_ATMEGA_DO_4 = "Digital Output 4"; +Blockly.Msg.CODERBOT_ATMEGA_DO_5 = "Digital Output 5"; +Blockly.Msg.CODERBOT_ATMEGA_DO_6 = "Digital Output 6"; +Blockly.Msg.CODERBOT_ATMEGA_DO_7 = "Digital Output 7"; +Blockly.Msg.CODERBOT_ATMEGA_DO_8 = "Digital Output 8"; +Blockly.Msg.CODERBOT_ATMEGA_DO_9 = "Digital Output 9"; +Blockly.Msg.CODERBOT_ATMEGA_DO_10 = "Digital Output 10"; +Blockly.Msg.CODERBOT_ATMEGA_DO_11 = "Digital Output 11"; diff --git a/static/js/blockly/bot_it.js b/static/js/blockly/bot_it.js index 0d7c70e2..d8492b0a 100644 --- a/static/js/blockly/bot_it.js +++ b/static/js/blockly/bot_it.js @@ -97,3 +97,29 @@ Blockly.Msg.CODERBOT_EVENT_PUBLISH = "pubblica"; Blockly.Msg.CODERBOT_EVENT_ON_TOPIC = "sul topic"; Blockly.Msg.CODERBOT_EVENT_GENERATOR = "genera eventi"; Blockly.Msg.CODERBOT_CONVERSATION_PARSE = "interpreta"; +Blockly.Msg.CODERBOT_ATMEGA_READ = "Leggi"; +Blockly.Msg.CODERBOT_ATMEGA_VALUE = "Valore"; +Blockly.Msg.CODERBOT_ATMEGA_AI_1 = "Analog Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_AI_2 = "Analog Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_3 = "Digital Input 1"; +Blockly.Msg.CODERBOT_ATMEGA_DI_4 = "Digital Input 2"; +Blockly.Msg.CODERBOT_ATMEGA_DI_5 = "Digital Input3"; +Blockly.Msg.CODERBOT_ATMEGA_DI_6 = "Digital Input 4"; +Blockly.Msg.CODERBOT_ATMEGA_WRITE = "Scrivi"; +Blockly.Msg.CODERBOT_ATMEGA_DO_1 = "Digital Output 1"; +Blockly.Msg.CODERBOT_ATMEGA_DO_2 = "Digital Output 2"; +Blockly.Msg.CODERBOT_ATMEGA_DO_3 = "Digital Output 3"; +Blockly.Msg.CODERBOT_ATMEGA_DO_4 = "Digital Output 4"; +Blockly.Msg.CODERBOT_ATMEGA_DO_5 = "Digital Output 5"; +Blockly.Msg.CODERBOT_ATMEGA_DO_6 = "Digital Output 6"; +Blockly.Msg.CODERBOT_ATMEGA_DO_7 = "Digital Output 7"; +Blockly.Msg.CODERBOT_ATMEGA_DO_8 = "Digital Output 8"; +Blockly.Msg.CODERBOT_ATMEGA_DO_9 = "Digital Output 9"; +Blockly.Msg.CODERBOT_ATMEGA_DO_10 = "Digital Output 10"; +Blockly.Msg.CODERBOT_ATMEGA_DO_11 = "Digital Output 11"; +Blockly.Msg.CODERBOT_ATMEGA_LED_SET = "Controlla Led"; +Blockly.Msg.CODERBOT_ATMEGA_LED_BEGIN = "Led inizio"; +Blockly.Msg.CODERBOT_ATMEGA_LED_END = "Led fine"; +Blockly.Msg.CODERBOT_ATMEGA_LED_RED = "Intensità Rosso"; +Blockly.Msg.CODERBOT_ATMEGA_LED_GREEN = "Intensità Verde"; +Blockly.Msg.CODERBOT_ATMEGA_LED_BLUE = "Intensità Blu"; diff --git a/static/media/coderdojo-logo.png b/static/media/coderdojo-logo.png deleted file mode 100644 index 93a07e8d..00000000 Binary files a/static/media/coderdojo-logo.png and /dev/null differ diff --git a/templates/blocks_adv.xml b/templates/blocks_adv.xml index 2f2a2030..13b7e50a 100644 --- a/templates/blocks_adv.xml +++ b/templates/blocks_adv.xml @@ -304,7 +304,6 @@ - @@ -331,6 +330,11 @@ + + + + + diff --git a/templates/blocks_std.xml b/templates/blocks_std.xml index b0fb4a00..a5243d97 100644 --- a/templates/blocks_std.xml +++ b/templates/blocks_std.xml @@ -61,7 +61,6 @@ - diff --git a/test/musicPackage_test.py b/test/musicPackage_test.py new file mode 100644 index 00000000..65f4823f --- /dev/null +++ b/test/musicPackage_test.py @@ -0,0 +1,41 @@ +#__import__("../musicPackages") +import json +import sys +sys.path.insert(0, './') +import musicPackages +class MusicPackage_test: + + def test_musicPackage(self): + print("sample Music Package: ") + print(" name_IT = name_it, name_EN = name_en , category = sample_category, version = sample_version, date = sample_date, interfaces = sample_interfaces, nameID = sample_id") + + mpkg = musicPackages.MusicPackage(name_IT = "name_it", name_EN = "name_en", category= "sample_category", version= "sample_version", date="sample_date", nameID="sample_id") + print("name_IT : ", mpkg.getNameIT()) + print("name_EN : ", mpkg.getNameEN()) + print("nameID : ", mpkg.getNameID()) + print("version : ", mpkg.getVersion()) + print("date : ", mpkg.getDate()) + print("category : ", mpkg.getCategory()) + print("interfaces : ", mpkg.getInterfaces()) + + def test_isPackageAvaible(self): + pkg_manager = musicPackages.MusicPackageManager() + for package_name in pkg_manager.packages: + print("Test if " + package_name + " package is available") + result = pkg_manager.isPackageAvailable(package_name) + if(result): + print(package_name + " package is available") + else: + print(package_name + " package is not available") + + print("Test if NONE package is available" ) + result = pkg_manager.isPackageAvailable("NONE") + if(result): + print("NONE package is available") + else: + print("NONE package is not available") + +test = MusicPackage_test() +test.test_musicPackage() +test.test_isPackageAvaible() + diff --git a/test/music_test.py b/test/music_test.py new file mode 100644 index 00000000..b73a6b1b --- /dev/null +++ b/test/music_test.py @@ -0,0 +1,116 @@ +import sys +import sox +import time +import os +sys.path.insert(0, './') +from musicPackages import MusicPackageManager +from music import Music + +class Music_test: + + def test_library(self): + print("testing sound playback:...") + tfm = sox.Transformer() + tfm.preview('cat.wav') + tfm.build('cat.wav', 'outMusicDemo.wav') + +# test each parametr of the function play_note + def test_play_note(self): + musicPkg = MusicPackageManager() + m = Music(musicPkg) + print('test Music.play_note') + print("m.play_note(note='C2')") + m.play_note(note='C2') + print("m.play_note(note='C2',duration=2.0)") + m.play_note(note='C2',duration=2.0) + print("m.play_note(note='C2',instrument='guitar')") + m.play_note(note='C2',instrument='guitar') + print("m.play_note(note='C2',alteration='bmolle')") + m.play_note(note='C2',alteration='bmolle') + print("m.play_note(note='C2',alteration='diesis')") + m.play_note(note='C2',alteration='diesis') + print("m.play_note(note='C2',instrument='guitar')") + m.play_note(note='C2',instrument='guitar') + print("m.play_note(note='C2',alteration='bmolle')") + m.play_note(note='C2',alteration='bmolle') + print("m.play_note(note='C2',instrument='guitar',alteration='diesis')") + m.play_note(note='C2',instrument='guitar',alteration='diesis') + print("m.play_note(note='C2',instrument='guitar',alteration='diesis',duration=2.0)") + m.play_note(note='C2',instrument='guitar',alteration='diesis',duration=2.0) + print("m.play_note(note='G3',duration=2.0)") + m.play_note(note='G3',duration=2.0) + print("m.play_note(note='G3',instrument='guitar')") + m.play_note(note='G3',instrument='guitar') + print("m.play_note(note='G3',alteration='bmolle')") + m.play_note(note='G3',alteration='bmolle') + print("m.play_note(note='G3',alteration='diesis')") + m.play_note(note='G3',alteration='diesis') + print("m.play_note(note='G3',instrument='guitar')") + m.play_note(note='G3',instrument='guitar') + print("m.play_note(note='G3',alteration='bmolle')") + m.play_note(note='G3',alteration='bmolle') + print("m.play_note(note='G3',instrument='guitar',alteration='diesis')") + m.play_note(note='G3',instrument='guitar',alteration='diesis') + print("m.play_note(note='G3',instrument='guitar',alteration='diesis',duration=2.0)") + m.play_note(note='G3',instrument='guitar',alteration='diesis',duration=2.0) + print("it's ok if print: no instrument: coderInstrument present in this coderbot!") + m.play_note(note='C2',instrument='coderInstrument',alteration='diesis',duration=2.0) + print("it's ok if print: note: coderNote not exist") + m.play_note(note='coderNote',instrument='piano',alteration='diesis',duration=2.0) + + +# test each parametr of the function play_note + def test_play_animal(self): + print('test Music.play_animal') + musicPkg = MusicPackageManager() + m = Music(musicPkg) + print("(note='C2',instrument='cat', duration=2.0)") + m.play_animal(note='C2',instrument='cat', duration=2.0) + print("m.play_animal(note='C2',instrument='dog')") + m.play_animal(note='C2',instrument='dog') + print("m.play_animal(note='C2',instrument='dog', alteration='bmolle')") + m.play_animal(note='C2',instrument='dog', alteration='bmolle') + print("m.play_animal(note='C2',instrument='cat', alteration='diesis')") + m.play_animal(note='C2',instrument='cat', alteration='diesis') + print("m.play_animal(note='C2',instrument='dinosaur')") + m.play_animal(note='C2',instrument='dinosaur') + print("m.play_animal(note='C2',alteration='bmolle')") + m.play_animal(note='C2',instrument="dinosaur", alteration='bmolle') + print("m.play_animal(note='C2',instrument='cat',alteration='diesis')") + m.play_animal(note='C2',instrument='cat',alteration='diesis') + print("m.play_animal(note='C2',instrument='cat',alteration='diesis',duration=2.0)") + m.play_animal(note='C2',instrument='cat',alteration='diesis',duration=2.0) + print("m.play_note(note='G3',duration=2.0)") + m.play_note(note='G3',duration=2.0) + print("m.play_note(note='G3',instrument='dinosaur',alteration='bmolle')") + m.play_note(note='G3',instrument='dinosaur',alteration='bmolle') + print("m.play_note(note='G3',instrument='dinosaur',alteration='diesis')") + m.play_note(note='G3',instrument='dinosaur',alteration='diesis') + print("m.play_note(note='G3',alteration='bmolle', instrument= 'cat')") + m.play_note(note='G3',alteration='bmolle', instrument= 'cat') + print("m.play_note(note='G3',instrument='cat',alteration='diesis')") + m.play_note(note='G3',instrument='cat',alteration='diesis') + print("m.play_note(note='G3',instrument='cat',alteration='diesis',duration=2.0)") + m.play_note(note='G3',instrument='cat',alteration='diesis',duration=2.0) + print("it's ok if print: no instrument: coderInstrument present in this coderbot!") + m.play_animal(note='C2',instrument='coderInstrument',alteration='diesis',duration=2.0) + print("it's ok if print: note: coderNote not exist") + m.play_animal(note='coderNote',instrument='cat',alteration='diesis',duration=2.0) + print('test Music.play_note: ENDED') + + + def test_play_pause(self): + print('test Music.play_pause') + musicPkg = MusicPackageManager() + m = Music() + prrint("play pause") + m.play_pause(1.0) + prrint("play pause and note") + m.play_note(note='C2',instrument='guitar') + m.play_pause(2.0) + m.play_note(note='C2',instrument='guitar') + +test = Music_test() +test.test_play_note() +test.test_play_animal() + diff --git a/v2.yml b/v2.yml index 803cac67..b09d8871 100644 --- a/v2.yml +++ b/v2.yml @@ -1,6 +1,6 @@ swagger: "2.0" info: - version: "0.2" + version: "0.3" title: OpenAPI 2.0 definition of Coderbot API v2 consumes: @@ -74,9 +74,35 @@ paths: description: "ok" 400: description: "Failed to save the activity" + /listMusicPackages: + get: + operationId: "api.listMusicPackages" + summary: "List Music Packages" + responses: + 200: + description: "ok" + /deleteMusicPackage: + post: + operationId: "api.deleteMusicPackage" + summary: "Delete Music Package" + parameters: + - name: package_data + in: body + schema: + type: object + properties: + package_name: + type: string + responses: + 200: + description: "ok" + 400: + description: "not found" + /updateFromPackage: post: operationId: "api.updateFromPackage" + summary: "Update CoderBot from package" consumes: - multipart/form-data parameters: @@ -120,7 +146,11 @@ paths: - name: name in: query type: string - required: true + required: false + - name: default + in: query + type: string + required: false tags: - Activity management responses: @@ -133,10 +163,6 @@ paths: tags: - Program management parameters: - - name: overwrite - in: query - required: false - type: string - in: body name: data schema: @@ -191,7 +217,7 @@ paths: description: Components names to be tested schema: type: object - default: {'varargin': ['motors', 'sonar', 'speaker', 'ocr']} + # default: {'varargin': ['motors', 'sonar', 'speaker', 'ocr']} required: - varargin properties: @@ -232,7 +258,7 @@ paths: description: Movement speed and duration schema: type: object - default: {'speed': 100, 'elapse':0, 'distance':0} + # default: {'speed': 100, 'elapse':0, 'distance':0} required: - speed - elapse @@ -276,3 +302,13 @@ paths: responses: 200: description: Sent command to the bot GPIO. + /listCNNModels: + get: + operationId: "api.list_cnn_models" + summary: "list of CNN Models" + tags: + - CNN Models + responses: + 200: + description: "CNN Models as JSON Object" + diff --git a/wifi.py b/wifi.py index 735db240..4fa447e4 100755 --- a/wifi.py +++ b/wifi.py @@ -91,7 +91,7 @@ def get_ipaddr(cls, ifname): return socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x8915, # SIOCGIFADDR - struct.pack('256s', ifname[:15]) + struct.pack('256s', ifname.encode('utf-8')[:15]) )[20:24]) @classmethod @@ -135,9 +135,22 @@ def set_ap_params(cls, wssid=None, wpsk=None): if wpsk: os.system("sudo sed -i s/wpa_passphrase=.*$/wpa_passphrase=" + wpsk + "/ /etc/hostapd/" + cls.hostapds.get(adapter) + ".conf") + @classmethod + def get_ap_params(cls): + adapter = cls.get_adapter_type() + ap_conf = {} + with open("/etc/hostapd/" + cls.hostapds.get(adapter) + ".conf", "r") as hostapd_conf: + for l in hostapd_conf.readlines(): + ls = l.split("=") + ap_conf[ls[0]] = ls[1] + return ap_conf + @classmethod def set_start_as_client(cls): cls._config["wifi_mode"] = "client" + os.system("sudo systemctl disable hostapd") + os.system("sudo systemctl disable dnsmasq") + os.system("sudo cp /etc/dhcpcd.conf.client /etc/dhcpcd.conf") cls.save_config() @classmethod @@ -147,18 +160,11 @@ def set_bot_name(cls, name): @classmethod def start_as_client(cls): - cls.stop_dnsmasq() - cls.stop_hostapd() try: - time.sleep(1.0) - out = os.system("wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf > /dev/null 2>&1") - out += os.system("dhclient -1 wlan0") - print(out) - try: - cls.register_ipaddr(cls.get_macaddr("wlan0"), cls.get_config().get('bot_name', 'CoderBot'), cls.get_ipaddr("wlan0"), "roberto.previtera@gmail.com") - print("registered bot, ip: " + str(cls.get_ipaddr("wlan0") + " name: " + cls.get_config().get('bot_name', 'CoderBot'))) - except: - pass + time.sleep(30.0) + ipaddr = cls.get_ipaddr("wlan0") + if ipaddr is None or "169.254" in ipaddr: + raise Exception() except subprocess.CalledProcessError as e: print(e.output) raise @@ -166,19 +172,11 @@ def start_as_client(cls): @classmethod def set_start_as_ap(cls): cls._config["wifi_mode"] = "ap" + os.system("sudo systemctl enable hostapd") + os.system("sudo systemctl enable dnsmasq") + os.system("sudo cp /etc/dhcpcd.conf.ap /etc/dhcpcd.conf") cls.save_config() - @classmethod - def start_as_ap(cls): - time.sleep(1.0) - out = str(subprocess.check_output(["ip", "link", "set", "dev", "wlan0", "down"])) - out += str(subprocess.check_output(["ip", "a", "add", "10.0.0.1/24", "dev", "wlan0"])) - out += str(subprocess.check_output(["ip", "link", "set", "dev", "wlan0", "up"])) - out += str(subprocess.check_output(["ifconfig"])) - print(out) - cls.start_hostapd() - cls.start_dnsmasq() - @classmethod def start_service(cls): config = cls.load_config() @@ -191,7 +189,8 @@ def start_service(cls): cls.start_as_client() except: print("Unable to register ip, revert to ap mode") - cls.start_as_ap() + cls.set_start_as_ap() + os.system("sudo reboot") @classmethod def get_hostapd_config_file(cls): @@ -234,27 +233,34 @@ def get_serial(cls): def main(): parser = argparse.ArgumentParser(description="CoderBot wifi config manager and daemon initializer", prog="wifi.py") - subparsers = parser.add_subparsers() + subparsers = parser.add_subparsers(dest='subparser_name') up = subparsers.add_parser('updatecfg', help="update configuration") up.add_argument('-m', '--mode', choices=['ap', 'client'], help='wifi mode') up.add_argument('-s', '--ssid', help='wifi ssid') up.add_argument('-p', '--pwd', help='wifi password') up.add_argument('-n', '--name', help='coderbot unique id') + get = subparsers.add_parser('getcfg', help="get configuration") + get.add_argument('-s', '--ssid', nargs="*", help='wifi mode') + get = subparsers.add_parser('setuniquessid', help="set unique ssid") args = vars(parser.parse_args()) - print(args) w = WiFi() if args: - if args['mode'] == 'ap': - w.set_start_as_ap() - w.set_ap_params(args['ssid'], args['pwd']) - elif args['mode'] == 'client': - w.set_start_as_client() - w.set_client_params(args['ssid'], args['pwd']) - if args['name']: - w.set_bot_name(args['name']) - else: - w.set_unique_ssid() - w.start_service() + if args["subparser_name"] == "updatecfg": + if args['mode'] == 'ap': + w.set_start_as_ap() + w.set_ap_params(args['ssid'], args['pwd']) + elif args['mode'] == 'client': + w.set_start_as_client() + w.set_client_params(args['ssid'], args['pwd']) + if 'name' in args: + w.set_bot_name(args['name']) + elif args["subparser_name"] == "getcfg": + if "ssid" in args: + print(w.get_ap_params()["ssid"]) + elif args["subparser_name"] == "setuniquessid": + w.set_unique_ssid() + else: + w.start_service() if __name__ == "__main__": main() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy