diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md deleted file mode 100644 index 9dc8b5c..0000000 --- a/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,85 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity -and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting -via an official social media account, or acting as an appointed representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of actions. - -**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. -Translations are available at https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index ee7b599..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,468 +0,0 @@ -# Contributing to Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss projects - -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss encourages and welcomes code contributions, bug fixes and enhancements from the Github community. - -## Ground rules & expectations - -Before we get started, here are a few things we expect from you (and that you should expect from others): - -- Be kind and thoughtful in your conversations around this project. We all come from different backgrounds and projects, which means we likely have different perspectives on "how open source is done." Try to listen to others rather than convince them that your way is correct. -- This project is released with a [Contributor Code of Conduct](CODE-OF-CONDUCT.md). By participating in this project, you agree to abide by its terms. -- Please ensure that your contribution complies with this document. If it does not, you will need to address and fix all issues before we can merge your contribution. -- When adding content, please consider if it is widely valuable. - -## Overview - -Being an Open Source project, everyone can contribute, provided that you respect the following points: - -- Before contributing any code, the author must make sure all the tests work (see below how to launch the tests). -- Developed code must adhere to the syntax guidelines enforced by the linters. -- Code must be developed following the [SemVer (Semantic Versioning 2.0.0)](https://semver.org/) branching model. -- For any new feature added, unit tests must be provided, following the example of the ones already created. - -## How to contribute - -If you'd like to contribute, start by searching through the issues and pull requests to see whether someone else has raised a similar idea or question. - -If you don't see your idea listed, and you think it fits into the goals of this guide, do one of the following: - -- Bug Report -- Feature Proposal -- Feature Request -- Vulnerability Report - -### Repository Issues - -The first step is to head to our repository issues tab and decide how you would like to contribute. - -![Repository Issues](assets/images/repo-issues.jpg) - -### Bug reports - -![Bug Reports](assets/images/bug-report.jpg) - -If you would like to contribute bug fixes or make the team aware of bugs you have identified in the project, please raise a **Bug report** issue in the [issues section](issues/new/choose) section. A template is provided that will allow you to provide your suggestions for your bug report / bug fix(es) which will be reviewed by the team. - -Bug fix issues are the first step to creating a pull request for bug fixes, once you have created your issue and it has been approved you can proceed with your bug fixes. - -### Feature proposals - -![Feature Proposals](assets/images/feature-proposals.jpg) - -If you would like to contribute new features to the project, please raise a **Feature proposal** issue in the [issues section](issues/new/choose) section. A template is provided that will allow you to provide your suggestions for your feature proposal. - -Feature proposal issues are the first step to creating a pull request for feature proposals, once you have created your issue and it has been approved you can proceed with your feature proposal. - -### Feature requests - -![Feature requests](assets/images/feature-request.jpg) - -If you would like to suggest a new feature/new features for this project, please raise a **Feature request** issue in the [issues section](issues/new/choose) section. A template is provided that will allow you to provide your suggestions for your feature request. - -### Community - -Discussions about the Open Source Guides take place on this repository's -[Issues](issues) and [Pull Requests](pulls) sections, or the [discussions](discussions). Anybody is welcome to join these conversations. - -Wherever possible, do not take these conversations to private channels, including contacting the maintainers directly. Keeping communication public means everybody can benefit and learn from the conversation. - -### Getting Started - -In order to start contributing: - -![Fork](assets/images/fork.jpg) - -1. Fork this repository clicking on the "Fork" button on the upper-right area of the page. - -2. Clone your just forked repository: - -```bash -git clone https://github.com/YourAccount/HIAS-MQTT-IoT-Agen.git -``` - -3. Add the main HIAS MQTT IoT Agent repository as a remote to your forked repository: - -```bash -git remote add origin https://github.com/AIIAL/HIAS-MQTT-IoT-Agen.git -``` - -Before starting your contribution, remember to synchronize the latest `dev` branch in your forked repository with the `dev` branch in the main HIAS-MQTT-IoT-Agen repository as specified by the **CURRENT DEV BRANCH** badge in the repository [README](README.md). To do this, use following these steps - -1. Change to your local `dev` branch (in case you are not in it already): - -```bash -git checkout 1.0.0 -``` - -2. Fetch the remote changes: - -```bash -git fetch origin -``` - -3. Merge them: - -```bash -git rebase origin/1.0.0 -``` - -Contributions following these guidelines will be added to the `dev` branch, and released in the next version. The release process is explained in the _Releasing_ section below. - -### Documentation - -Changes you make to the code in the repository or new projects that you make should be supported with documentation added to the **docs** directory. - -It is the contributor's responsibility to ensure that the documentation is up to date. If you are contributing to an existing repository you will ensure that these documents are updated and/or added to to reflect your changes. - -We use [MKDocs](https://www.mkdocs.org/) along with [Read the Docs](https://docs.readthedocs.io/en/stable/index.html). Use the [Getting Started with MkDocs](https://docs.readthedocs.io/en/stable/intro/getting-started-with-mkdocs.html) guide to find out how to update/create documentation for the project. - -To start the MKDocs server on use the following command: - -``` bash -mkdocs serve -``` -To start the MKDocs on a specific IP and port use the following command, updating your IP and port as required: - -``` bash -mkdocs serve -a 192.168.1.578:8000 -``` -Remember to update the [mkdocs.yml](mkdocs.yml) file to match your documentation strutcture. - -### Repository structure - -Repository structures for HIAS IoT Agents **must be followed exactly** for all contributions. Pull Requests that do not follow this structure will be rejected and closed with no further discussion. - -``` -- Project Root (Directory) - - assets (Directory) - - images (Directory) - - project-banner.jpg (Image) - - bug-report.jpg (Image) - - feature-proposal.jpg (Image) - - feature-request.jpg (Image) - - fork.jpg (Image) - - repo-issues.jpg (Image) - - configuration (Directory) - - config.json (File) - - credentials.json (File) - - docs (Directory) - - img (Directory) - - project-banner.jpg (image) - - installation (Directory) - - ubuntu.md (File) - - usage (Directory) - - ubuntu.md (File) - - index.md (File) - - logs (Directory) - - HIAS MQTT IoT Agent logs - - modules (Directory) - - AbstractAgent.py (File) - - helpers.py (File) - - hiasbch.py (File) - - hiascdi.py (File) - - hiashdi.py (File) - - mqtt.py (File) - - scripts - - install.sh (File) - - service.sh (File) - - agent.py (File) - - CODE-OF-CONDUCT.md (File) - - CONTRIBUTING.md (File) - - LICENSE (File) - - mkdocs.yml (File) - - README.md (File) -``` - -**Directories and files may be added to the above structure as required, but none must be removed.** - -### Installation Scripts - -The default installation script is [install.sh](scripts/install.sh) found in the [scripts](scripts) directory. - -You must include the installation commands for all libraries for the project using apt/pip/make etc. Replace **# DEVELOPER TO ADD INSTALLATION COMMANDS FOR ALL REQUIRED LIBRARIES (apt/pip etc)** with the relevant installation commands. If you are contributing an existing repository you will ensure that these scripts are updated to reflect your changes. - -### Configuration - -The project configuration file [config.json](configuration/config.json) can be found in the [configuration](configuration) directory. - -All configurable variables should be held within this file and used wherever relevant throughout the project. - -The [helpers file](modules/helpers.py) loads the configuration and makes it available as `helpers.confs`. - -You may remove redundent objects/arrays/values. from the configuration and/or add new ones. - -### Project Images - -Images used in the project must be **jpg**. You must own rights to images you upload to the project, or include attribution. Contributors are solely responsible for any images they publish to our Github. - -### Naming Scheme - -The following naming scheme must be used: - -- **Directories:** Snake case (snake_case) -- **Abstract Files:** CamelCase (CamelCase) -- **Files:** Spinal case (spinal-case) -- **Images:** Spinal case (spinal-case) - -Please use descriptive but short names, and make sure to not use spaces in directory and file names. - -### Headers - -All Python files must include the following header, replacing **Module Title** with a short but descriptive title for the module, and **Module Description** with a paragraph explaining what the module is for. - -``` -#!/usr/bin/env python3 -""" Module Title. - -Module Description. - -MIT License - -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files(the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Contributors: - -""" -``` - -#### Attribution - -- When you create a new module you should add your name to the **Contributors** section. -- When you make a change to an existing module you should add your name to the **Contributors** section below existing contributors. You must not remove existing contributors. - -### Footers - -All READMEs and documentation should include the following footer: - -``` -# Contributing -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss encourages and welcomes code contributions, bug fixes and enhancements from the Github community. - -Please read the [CONTRIBUTING](CONTRIBUTING.md "CONTRIBUTING") document for a full guide to forking our repositories and submitting your pull requests. You will also find our code of conduct in the [Code of Conduct](CODE-OF-CONDUCT.md) document. - -## Contributors -- [Adam Milton-Barker](https://www.leukemiaairesearch.com/association/volunteers/adam-milton-barker "Adam Milton-Barker") - [Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss](https://www.leukemiaresearchassociation.ai "Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss") President/Founder & Lead Developer, Sabadell, Spain - -  - -# Versioning -We use [SemVer](https://semver.org/) for versioning. - -  - -# License -This project is licensed under the **MIT License** - see the [LICENSE](LICENSE "LICENSE") file for details. - -  - -# Bugs/Issues -We use the [repo issues](issues "repo issues") to track bugs and general requests related to using this project. See [CONTRIBUTING](CONTRIBUTING.md "CONTRIBUTING") for more info on how to submit bugs, feature requests and proposals. -``` - -Remember to use **relative URLs**, and in the case of footers in the [docs](docs) folder, you must us **absolute URLs**. - -The contributors section should include a list of contributors that have contributed to the related document. In the case of the README footer, the Contributors section should include a list of contributors that have contributed to **any** part of the project. - -You should add your details below existing contributors. Details should include: - -- Name -- Company/University etc -- Position -- City -- Country - -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss Volunteers should use the following: - -- Name -- Association name -- Association Position -- City -- Country - -### Branching model - -There are one special branch in the repository: - -- `main`: contains the tagged and released version -- `1.0.0`: is the current `dev` branch and contains the latest development code. New features and bugfixes are always merged to current `dev` branch. - -In order to start developing a new feature or refactoring, a new branch will be created following the SemVer scheme: - -Given a version number MAJOR.MINOR.PATCH, increment the: - -- `MAJOR` version when you make incompatible code changes, -- `MINOR` version when you add functionality in a backwards compatible manner, and -- `PATCH` version when you make backwards compatible bug fixes. - -- If `MAJOR` is 0, then Minor could mean the version is not backwards compatible -- If `MINOR` is 1, this means the release is stable. - -The branch will be created by our team depending on the nature of your issue. Once the new functionality has been completed, a Pull Request will be created from the feature branch to the relevant branch. Remember to check both the linters, and -the tests before creating the Pull Request. - -In order to contribute to the repository, the same scheme should be replicated in the forked repositories, so the new features or fixes should all come from the current version of `dev` branch and end up in the current `dev` branch again. - -### PEP 8 -- Style Guide for Python Code - -All Python projects must align with the [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/). - -### CII Best Practices - -All projects must align with the [CII Best Practice](https://www.construction-institute.org/resources/knowledgebase/best-practices). - -### Changelog - -The project contains a changelog that is automatically created from the description of the Pull Requests that have been merged into develop, thanks to the [Release Drafter GitHub action](https://github.com/marketplace/actions/release-drafter). - -### Releasing - -The process of making a release simply consists of creating the release in Github and providing the new tag name, this task is carried out by our team. - -### Version numbers - -The version number will change for each release, following the SemVer scheme described previously. - -### Bugfix in releases - -When a bug is found affecting a release, a branch will be created from the `main` branch. As a part of the patch, the release version will be increased in it's last number (Z). The patch then will be merged (via pull request (PR)) to the `main` branch, and a new version will be released. - -### Commits - -Commits should be [atomic](https://en.wikipedia.org/wiki/Atomic_commit), make commits to your fork until you have resolved/completed the work specified in your issue before submitting your PR, this keeps an easy to follow history of what work has been carried out and makes the review process easier and quicker - -When making a commit, the subjects of each commit should be as follows, where XXX represents the issue number: - -#### Commit Title - -##### Bug Fixes - -- fix #xxx Issue Name -- fixes #xxx Issue Name -- fixed #xxx Issue Name - -##### Partial Resolutions - -- partially resolves #xxx Issue Name -- partially resolved #xxx Issue Name - -##### Resolution - -- resolves #xxx Issue Name -- resolved #xxx Issue Name - -##### Alignment - -- aligns with #xxx Issue Name - -##### Closure - -- close #xxx Issue Name -- closes #xxx Issue Name -- closed #xxx Issue Name - -#### Commit Description - -Your commit description should include a detailed description of the changes that have been made. - -#### Committing - -When you are ready to commit, you should do the following: - -##### Show The Status Of All Changed/Added/Deleted files - -``` -git status -``` - -##### Diff - -You may want to check the differences between changed files, you can do this using the following command. - -``` -git diff -``` - -##### Add All Changes - -The following will add all changes shown by git status to your commit. - -``` -git add . -``` - -##### Add One Or More Changes - -``` -git add file1 file2 file5 -``` - -##### Commit Added Changes - -Commit your added changes to your local repository, remember to follow the [Commit Title](#commit-title) & [Commit Description](#commit-description) guides above. - -To create your commit with both a title and description, use the following command which states the commit fixes issue ID 1 and provides a detailed description: - -``` -git commit -m "fixes #1" -m "Fixes the documentation typos described in issue #1" -``` - -### Push Your Changes - -When you have made your changes, ensured you have aligned with the procedures in this document, and made your commits to your local repository aligning with the guide above, you need to push your changes to your forked repository. - -Push changes to your fork by using the following command: - -``` -git push -``` - -### Pull Request protocol - -Contributions to the HIAS MQTT IoT Agent repository are done using a PR. The detailed "protocol" used in such PR is described below: - -* Direct commits to main or develop branches (even single-line modifications) are not allowed. Every modification has to come as a PR to the latest `dev branch` -* PRs implement/fix submitted issues, the issue number has to be referenced in the subject of the relevant commit and PR -* Anybody is welcome to provide comments to the PR (either direct comments or using the review feature offered by Github) -* Use *code line comments* instead of *general comments*, for traceability reasons (see comments lifecycle below) -* Comments lifecycle - * Comment is created, initiating a *comment thread* - * New comments can be added as responses to the original one, starting a discussion - * After discussion, the comment thread ends in one of the following ways: - * `Fixed in ` in case the discussion involves a fix in the PR branch (which commit hash is included as reference) - * `NTC`, if finally nothing needs to be done (NTC = Nothing To Change) -* PR can be merged when the following conditions are met: - * All comment threads are closed - * All the participants in the discussion have provided a `LGTM` general comment (LGTM = Looks good to me) - * All documentation has been updated to reflect your changes. - * No proprietory software or images have been added. - -* Self-merging is not allowed (except in rare and justified circumstances) - -Some additional remarks to take into account when contributing with new PRs: - -* PR must include not only code contributions, but their corresponding pieces of documentation (new or modifications to existing one) and tests -* Documentation must be added to the **docs** folder -* In the case empty directories need to be uploaded, add a `.gitkeep` file inside. -* The project banner is included in all documentation -* Contributing, Versioning, Licensing, Bugs/Issues footer is included in all information -* Contributors have been added to all Contributors footers -* PR modifications must pass full regression based on existing tests in addition to whichever new test added due to the new functionality -* PR should be of an appropriated size that makes review achievable. Too large PRs could be closed with a "please, redo the work in smaller pieces" without any further discussion \ No newline at end of file diff --git a/README.md b/README.md index dc6014f..0cdce6a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss +# Peter Moss MedTech Research Project ## HIAS MQTT IoT Agent -![HIAS MQTT IoT Agent](assets/images/project-banner.jpg) +![HIAS MQTT IoT Agent](assets/img/project-banner.jpg) -[![CURRENT RELEASE](https://img.shields.io/badge/CURRENT%20RELEASE-3.1.1-blue.svg)](https://github.com/aiial/hias-mqtt-iot-agent/tree/release-3.1.1) [![UPCOMING RELEASE](https://img.shields.io/badge/DEV%20BRANCH-develop-blue.svg)](https://github.com/aiial/hias-mqtt-iot-agent/tree/develop) [![Contributions Welcome!](https://img.shields.io/badge/Contributions-Welcome-lightgrey.svg)](CONTRIBUTING.md) [![Issues](https://img.shields.io/badge/Issues-Welcome-lightgrey.svg)](issues) +[![CURRENT RELEASE](https://img.shields.io/badge/CURRENT%20RELEASE-4.0.0-blue.svg)](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/tree/release-4.0.0) [![UPCOMING RELEASE](https://img.shields.io/badge/DEV%20BRANCH-develop-blue.svg)](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/tree/develop) [![Contributions Welcome!](https://img.shields.io/badge/Contributions-Welcome-lightgrey.svg)](CONTRIBUTING.md) [![Issues](https://img.shields.io/badge/Issues-Welcome-lightgrey.svg)](issues) [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/) [![Documentation Status](https://readthedocs.org/projects/hias-mqtt-iot-agent/badge/?version=latest)](https://hias-mqtt-iot-agent.readthedocs.io/en/latest/?badge=latest) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4994/badge)](https://bestpractices.coreinfrastructure.org/projects/4994) @@ -43,7 +43,7 @@ The protocol is publish-subscribe (Pub/Sub) communication protocol that runs ove # HIAS -![HIAS - Hospital Intelligent Automation Server](assets/images/hias-network-v3.jpg) +![HIAS - Hospital Intelligent Automation Server](assets/img/hias-network-v3.jpg) [HIAS - Hospital Intelligent Automation Server](https://github.com/aiial/HIAS-Core) is an open-source server for hospitals and medical centers, designed to control and manage a network of intelligent IoT connected devices and applications. @@ -53,13 +53,13 @@ The HIAS iotJumpWay IoT Agents are based on FIWARE IoT Agents and are a selectio Each IoT Agent provides a North & South Port interface that allows communication to and from the Context Broker. -![SOUTHBOUND TRAFFIC (COMMANDS)](assets/images/southbound.jpg) +![SOUTHBOUND TRAFFIC (COMMANDS)](assets/img/southbound.jpg) __Source: [FIWARE IoT Agents](https://fiware-tutorials.readthedocs.io/en/latest/iot-agent/index.html)__ The North Port interface of an IoT Agent listens to southbound traffic coming from the Context Broker towards the devices and applications. -![NORTHBOUND TRAFFIC (MEASUREMENTS)](assets/images/southbound.jpg) +![NORTHBOUND TRAFFIC (MEASUREMENTS)](assets/img/southbound.jpg) __Source: [FIWARE IoT Agents](https://fiware-tutorials.readthedocs.io/en/latest/iot-agent/index.html)__ @@ -77,17 +77,17 @@ To get started follow the following guides:   # Contributing -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss encourages and welcomes code contributions, bug fixes and enhancements from the Github community. +Peter Moss Leukaemia MedTech Research CIC encourages and welcomes code contributions, bug fixes and enhancements from the Github community. -Please read the [CONTRIBUTING](CONTRIBUTING.md "CONTRIBUTING") document for a full guide to forking our repositories and submitting your pull requests. You will also find our code of conduct in the [Code of Conduct](CODE-OF-CONDUCT.md) document. +Please read the [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for a full guide to contributing to our IoT Agent projects. You will also find our code of conduct in the [CODE OF CONDUCT](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CODE-OF-CONDUCT.md) document. ## Contributors -- [Adam Milton-Barker](https://www.leukemiaairesearch.com/association/volunteers/adam-milton-barker "Adam Milton-Barker") - [Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss](https://www.leukemiaresearchassociation.ai "Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss") President/Founder & Lead Developer, Sabadell, Spain +- [Adam Milton-Barker](https://www.leukaemiamedtechresearch.org.uk/about/volunteers/adam-milton-barker "Adam Milton-Barker") - [Peter Moss Leukaemia MedTech Research CIC](https://www.leukaemiamedtechresearch.org.uk "Peter Moss Leukaemia MedTech Research CIC") Founder & Managing Director.   # Versioning -We use SemVer for versioning. +We use [SemVer](https://semver.org/) for versioning.   @@ -97,4 +97,4 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE "   # Bugs/Issues -We use the [repo issues](issues "repo issues") to track bugs and general requests related to using this project. See [CONTRIBUTING](CONTRIBUTING.md "CONTRIBUTING") for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file +We use the [repo issues](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/issues/new/choose "repo issues") to track bugs and general requests related to using this project. See [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file diff --git a/agent.py b/agent.py index bf0a60e..2871062 100644 --- a/agent.py +++ b/agent.py @@ -7,8 +7,7 @@ MIT License -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial -Para la Leucemia Peter Moss +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal @@ -37,12 +36,11 @@ monkey.patch_all() import json -import os import psutil import signal import sys -import time +from bson import json_util from datetime import datetime from flask import Flask, request, Response from threading import Thread @@ -69,69 +67,37 @@ def status_callback(self, topic, payload): status = payload.decode() split_topic = topic.split("/") - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] - - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] - - self.helpers.logger.info( - "Received " + entity_type + " Status") - - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) if not self.hiasbch.iotjumpway_access_check(bch): return + + update_response = self.hiascdi.update_online_status( + entity, entity_type, status) - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" - - update_response = self.hiascdi.update_entity( - entity, entity_type, { - "networkStatus": {"value": status}, - "networkStatus.metadata": {"timestamp": {"value": datetime.now().isoformat()}}, - "dateModified": {"value": datetime.now().isoformat()} - }) - - if update_response: - _id = self.hiashdi.insert_data("Statuses", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "HIASBCH": entity if entity_type == "HIASBCH" else "NA", - "HIASCDI": entity if entity_type == "HIASCDI" else "NA", - "HIASHDI": entity if entity_type == "HIASHDI" else "NA", - "Agent": entity if entity_type == "Agent" else "NA", - "Application": entity if entity_type == "Application" else "NA", - "Device": entity if entity_type == "Device" else "NA", - "Staff": entity if entity_type == "Staff" else "NA", - "Robotics": entity if entity_type == "Robotics" else "NA", - "Status": status, - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - if _id != False: - self.helpers.logger.info( - entity_type + " " + entity + " status update OK") - - self.mqtt.publish("Integrity", { - "_id": str(_id), - "Status": status - }) - - else: - self.helpers.logger.error( + if update_response == False: + self.helpers.logger.error( entity_type + " " + entity + " status update KO") + return - else: + update_data = self.hiashdi.entity_status_data( + entity, entity_type, location, zone, status) + + _id = self.hiashdi.insert_data( + "Statuses", update_data) + + if _id == False: self.helpers.logger.error( - entity_type + " " + entity + " status update KO") + entity_type + " " + entity + " status data update KO") + return + + update_data["_id"] = _id + self.mqtt.publish( + "Integrity", update_data) + + self.helpers.logger.info( + entity_type + " " + entity + " status data update OK") def life_callback(self, topic, payload): """Called in the event of a life payload @@ -141,98 +107,129 @@ def life_callback(self, topic, payload): payload (:obj:`str`): The payload. """ - data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") + data, split_topic = self.parse_payload( + payload, topic) - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] + if not self.hiasbch.iotjumpway_access_check(bch): + return + + update_response = self.hiascdi.update_entity( + entity, entity_type, self.hiascdi.entity_life_data(data)) + + if update_response == False: + self.helpers.logger.error( + entity_type + " " + entity + " life update KO") + return + + update_data = self.hiashdi.entity_life_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data( + "Life", update_data) + + if _id == False: + self.helpers.logger.error( + entity_type + " " + entity + " life update KO") + return + + self.mqtt.publish("Integrity", { + "_id": str(_id), + "CPU": str(data["CPU"]), + "Memory": str(data["Memory"]), + "Diskspace": str(data["Diskspace"]), + "Temperature": str(data["Temperature"]), + "Latitude": str(data["Latitude"]), + "Longitude": str(data["Longitude"]) + }) self.helpers.logger.info( - "Received " + entity_type + " Life") + entity_type + " " + entity + " life update OK") - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + def comands_callback(self, topic, payload): + """ + iotJumpWay Device Commands Callback + + The callback function that is triggerend in the event of an device + command communication from the iotJumpWay. + """ + + data, split_topic = self.parse_payload( + payload, topic) + + entity_type, entity, location, zone, bch = self.process_request( + split_topic) if not self.hiasbch.iotjumpway_access_check(bch): return + + if "Use" not in data: + self.helpers.logger.error( + "Command not supported yet") + return + + if data["Use"] != "Device": + self.helpers.logger.error( + "Command not supported yet") + return - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" - - update_response = self.hiascdi.update_entity( - entity, entity_type, { - "networkStatus": {"value": "ONLINE"}, - "networkStatus.metadata": {"timestamp": {"value": datetime.now().isoformat()}}, - "dateModified": {"value": datetime.now().isoformat()}, - "cpuUsage": { - "value": float(data["CPU"]) - }, - "memoryUsage": { - "value": float(data["Memory"]) - }, - "hddUsage": { - "value": float(data["Diskspace"]) - }, - "temperature": { - "value": float(data["Temperature"]) - }, - "location": { - "type": "geo:json", - "value": { - "type": "Point", - "coordinates": [float(data["Latitude"]), float(data["Longitude"])] - } - } - }) + entity_data = self.hiascdi.get_entity( + data["Use"], data["To"]) + + if data["Property"] not in entity_data: + self.helpers.logger.error( + "Property not found") + return + + if data["Type"] not in entity_data[data["Property"]]["metadata"]["commands"]["value"]: + self.helpers.logger.error( + "Command not found") + return + + if data["Value"] not in entity_data[data["Property"]]["metadata"]["commands"]["value"][data["Type"]]: + self.helpers.logger.error( + "Command not found") + return + + actuator_data = self.hiascdi.entity_actuator_data( + entity_data, data) - if update_response: - _id = self.hiashdi.insert_data("Life", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "HIASBCH": entity if entity_type == "HIASBCH" else "NA", - "HIASCDI": entity if entity_type == "HIASCDI" else "NA", - "HIASHDI": entity if entity_type == "HIASHDI" else "NA", - "Agent": entity if entity_type == "Agent" else "NA", - "Application": entity if entity_type == "Application" else "NA", - "Device": entity if entity_type == "Device" else "NA", - "Staff": entity if entity_type == "Staff" else "NA", - "Robotics": entity if entity_type == "Robotics" else "NA", - "Data": data, - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.hiascdi.update_entity( + data["To"], data["Use"], { + data["Type"]: actuator_data, + "dateModified": {"value": datetime.now().isoformat()} }) - if _id != False: - self.helpers.logger.info( - entity_type + " " + entity + " life update OK") - - self.mqtt.publish("Integrity", { - "_id": str(_id), - "CPU": str(data["CPU"]), - "Memory": str(data["Memory"]), - "Diskspace": str(data["Diskspace"]), - "Temperature": str(data["Temperature"]), - "Latitude": str(data["Latitude"]), - "Longitude": str(data["Longitude"]) - }) - - else: - self.helpers.logger.error( - entity_type + " " + entity + " life update KO") - else: + pathto = location + "/Devices/" + data["Zone"] \ + + "/" + data["To"] + "/Commands" + + self.mqtt.publish("Custom", { + "Type": data["Type"], + "Property": data["Property"], + "Value": data["Value"], + "Message": data["Message"] + }, pathto) + + update_data = self.hiashdi.entity_actuator_command_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data("Commands", update_data) + + if _id == False: self.helpers.logger.error( - entity_type + " " + entity + " life update KO") + entity_type + " " + entity + " command data update KO") + return + + update_data["_id"] = _id + self.mqtt.publish("Integrity", update_data) - def sensors_callback(self, topic, payload): - """Called in the event of a sensor payload + self.helpers.logger.info( + entity_type + " " + entity + " command data update OK") + + def notifications_callback(self, topic, payload): + """Called in the event of an notifications payload Args: topic (str): The topic the payload was sent to. @@ -240,22 +237,12 @@ def sensors_callback(self, topic, payload): """ data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") - - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] - - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] self.helpers.logger.info( - "Received " + entity_type + " Sensors Data") + "Received " + data["Use"] + " notifications data payload") - attrs = self.get_attributes(entity_type, entity) + attrs = self.get_attributes( + data["FromType"], data["From"]) bch = attrs["blockchain"] if not self.hiasbch.iotjumpway_access_check(bch): @@ -263,74 +250,25 @@ def sensors_callback(self, topic, payload): entity = attrs["id"] location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" - sensors = self.hiascdi.get_sensors( - entity, entity_type) - sensor_data = sensors["sensors"] - - i = 0 - for sensor in sensor_data["value"]: - for prop in sensor["properties"]["value"]: - if data["Type"].lower() in prop: - sensor_data["value"][i]["properties"]["value"][data["Type"].lower()] = { - "value": data["Value"], - "timestamp": { - "value": datetime.now().isoformat() - } - } - i = i + 1 - - update_response = self.hiascdi.update_entity( - entity, entity_type, { - "networkStatus": {"value": "ONLINE"}, - "networkStatus.metadata": {"timestamp": { - "value": datetime.now().isoformat() - }}, - "dateModified": {"value": datetime.now().isoformat()}, - "sensors": sensor_data - }) - - if update_response: - _id = self.hiashdi.insert_data("Sensors", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "HIASBCH": entity if entity_type == "HIASBCH" else "NA", - "HIASCDI": entity if entity_type == "HIASCDI" else "NA", - "HIASHDI": entity if entity_type == "HIASHDI" else "NA", - "Agent": entity if entity_type == "Agent" else "NA", - "Application": entity if entity_type == "Application" else "NA", - "Device": entity if entity_type == "Device" else "NA", - "Staff": entity if entity_type == "Staff" else "NA", - "Robotics": entity if entity_type == "Robotics" else "NA", - "Sensor": data["Sensor"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) + update_data = self.hiashdi.entity_notification_data( + location, data) - if _id != False: - self.helpers.logger.info( - entity_type + " " + entity + " sensors update OK") + _id = self.hiashdi.insert_data( + "Notifications", update_data) + + if _id == False: + self.helpers.logger.error( + data["Use"] + " " + data["To"] + " notification update KO") + return - self.mqtt.publish("Integrity", { - "_id": str(_id), - "Sensor": data["Sensor"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"] - }) + update_data["_id"] = _id + self.mqtt.publish("Integrity", update_data) - else: - self.helpers.logger.error( - entity_type + " " + entity + " sensors update KO") - else: - self.helpers.logger.error( - entity_type + " " + entity + " sensors update KO") + self.helpers.logger.info( + data["Use"] + " " + data["To"] + " notification update OK") - def actuator_callback(self, topic, payload): + def actuators_callback(self, topic, payload): """Called in the event of a actuator payload Args: @@ -338,481 +276,301 @@ def actuator_callback(self, topic, payload): payload (:obj:`str`): The payload. """ - data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") + data, split_topic = self.parse_payload( + payload, topic) - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] - - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] - - self.helpers.logger.info( - "Received " + entity_type + " Actuators Data") - - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) if not self.hiasbch.iotjumpway_access_check(bch): return - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" + entity_data = self.hiascdi.get_entity( + entity_type, entity) + + if data["Type"] not in entity_data: + self.helpers.logger.error( + entity_type + " " + entity + " actuators not found") + return - actuators = self.hiascdi.get_actuators( - entity, entity_type) - actuator_data = actuators["actuators"] - - i = 0 - for actuator in actuator_data["value"]: - exists = True - if data["Name"] in actuator["name"]["value"] and data["Type"].lower() in actuator["commands"]["value"] \ - and data["Value"].lower() in actuator["commands"]["value"][data["Type"].lower()]: - actuator_data["value"][i]["state"] = { - "value": data["Value"], - "metadata":{ - "timestamp": { - "value": datetime.now().isoformat() - } - } - } - i = i + 1 + actuator_data = self.hiascdi.entity_actuator_data( + entity_data, data) - if exists: - update_response = self.hiascdi.update_entity( + update_response = self.hiascdi.update_entity( entity, entity_type, { "networkStatus": {"value": "ONLINE"}, "networkStatus.metadata": {"timestamp": { "value": datetime.now().isoformat() }}, - "dateModified": {"value": datetime.now().isoformat()}, - "actuators": actuator_data + data["Type"]: actuator_data, + "dateModified": {"value": datetime.now().isoformat()} }) - if update_response: - _id = self.hiashdi.insert_data("Actuators", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "HIASBCH": entity if entity_type == "HIASBCH" else "NA", - "HIASCDI": entity if entity_type == "HIASCDI" else "NA", - "HIASHDI": entity if entity_type == "HIASHDI" else "NA", - "Agent": entity if entity_type == "Agent" else "NA", - "Application": entity if entity_type == "Application" else "NA", - "Device": entity if entity_type == "Device" else "NA", - "Staff": entity if entity_type == "Staff" else "NA", - "Robotics": entity if entity_type == "Robotics" else "NA", - "Actuator": data["Name"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - if _id != False: - self.helpers.logger.info( - entity_type + " " + entity + " actuators update OK") - - self.mqtt.publish("Integrity", { - "_id": str(_id), - "Actuator": data["Name"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"] - }) - - else: - self.helpers.logger.error( - entity_type + " " + entity + " actuators update KO") - else: - self.helpers.logger.error( - entity_type + " " + entity + " actuators update KO") - - def comands_callback(self, topic, payload): - """ - iotJumpWay Device Commands Callback + if update_response == False: + self.helpers.logger.error( + entity_type + " " + entity + " actuators update KO") + return - The callback function that is triggerend in the event of an device - command communication from the iotJumpWay. - """ + update_data = self.hiashdi.entity_actuator_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data("Actuators", update_data) + + if _id == False: + self.helpers.logger.error( + entity_type + " " + entity + " actuators update KO") + return - data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") + update_data["_id"] = _id + self.mqtt.publish( + "Integrity", update_data) + + self.helpers.logger.info( + entity_type + " " + entity + " actuators update OK") - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] + def sensors_callback(self, topic, payload): + """Called in the event of a sensor payload - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ - self.helpers.logger.info( - "Received Command Data") + data, split_topic = self.parse_payload( + payload, topic) - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) if not self.hiasbch.iotjumpway_access_check(bch): return - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" - exists = False - - if "Command" in data and data["Command"] == "Verify": - - if data["Use"] == "Device": - - actuators = self.hiascdi.get_actuators( - data["To"], data["Use"]) - actuator_data = actuators["actuators"] - - i = 0 - for actuator in actuator_data["value"]: - exists = True - if data["Name"] in actuator["name"]["value"] and data["Type"].lower() in actuator["commands"]["value"] \ - and data["Value"].lower() in actuator["commands"]["value"][data["Type"].lower()]: - actuator_data["value"][i]["state"] = { - "value": "Processing " + data["Value"], - "metadata":{ - "timestamp": { - "value": datetime.now().isoformat() - } - } - } - i = i + 1 - - if exists: - update_response = self.hiascdi.update_entity( - data["To"], data["Use"], { - "networkStatus": {"value": "ONLINE"}, - "networkStatus.metadata": {"timestamp": { - "value": datetime.now().isoformat() - }}, - "dateModified": {"value": datetime.now().isoformat()}, - "actuators": actuator_data - }) - - pathto = location + "/Devices/" + data["Zone"] \ - + "/" + data["To"] + "/Commands" - - self.mqtt.publish("Custom", { - "From": entity, - "Type": data["Type"], - "Name": data["Name"], - "Value": data["Value"], - "Message": data["Message"] - }, pathto) - - _id = self.hiashdi.insert_data("Commands", { - "Use": data["Use"], - "From": data["From"], - "Location": location, - "Zone": data["Zone"], - "HIASBCH": "NA", - "HIASCDI": "NA", - "HIASHDI": "NA", - "Agent": "NA", - "Application": "NA", - "Device": data["To"], - "Staff":"NA", - "Robotics": "NA", - "Actuator": data["Name"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - self.mqtt.publish("Integrity", { - "_id": str(_id), - "From": data["From"], - "Actuator": data["Name"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"] - }) - - self.helpers.logger.info( - data["Use"] + " " + data["To"] + " command update OK") + entity_data = self.hiascdi.get_entity( + entity_type, entity) + + if data["Type"] not in entity_data: + self.helpers.logger.error( + entity_type + " " + entity + " sensors not found") + return - def bci_callback(self, topic, payload): - """Called in the event of a BCI payload + sensor_data = self.hiascdi.entity_sensor_data( + entity_data, data) + + update_response = self.hiascdi.update_entity( + entity, entity_type, { + "networkStatus": {"value": "ONLINE"}, + "networkStatus.metadata": {"timestamp": { + "value": datetime.now().isoformat() + }}, + "dateModified": {"value": datetime.now().isoformat()}, + data["Type"]: sensor_data + }) + + if update_response == False: + self.helpers.logger.error( + entity_type + " " + entity + " sensors update KO") + return + + update_data = self.hiashdi.entity_sensor_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data("Sensors", update_data) + + if _id == False: + self.helpers.logger.error( + entity_type + " " + entity + " sensors update KO") + return + + update_data["_id"] = _id + self.mqtt.publish("Integrity", update_data) + + self.helpers.logger.info( + entity_type + " " + entity + " sensors data update OK") + + def state_callback(self, topic, payload): + """Called in the event of a state payload Args: topic (str): The topic the payload was sent to. payload (:obj:`str`): The payload. """ - data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") - - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] + data, split_topic = self.parse_payload( + payload, topic) - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] - - self.helpers.logger.info( - "Received " + entity_type + " BCI Data: " + str(data)) - - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) if not self.hiasbch.iotjumpway_access_check(bch): return - - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" - + + update_response = self.hiascdi.update_online_status( + entity, entity_type, "ONLINE") + + entity_data = self.hiascdi.get_entity( + entity_type, entity) + + if data["State"] not in entity_data["states"]["value"]: + self.helpers.logger.error( + entity_type + " " + entity + " state update KO") + return + update_response = self.hiascdi.update_entity( entity, entity_type, { - "network.status": {"value": "ONLINE"}, - "network.status.metadata": {"timestamp": datetime.now().isoformat()}, + "state": {"value": data["State"]}, "dateModified": {"value": datetime.now().isoformat()} }) - if update_response: - _id = self.hiashdi.insert_data("Sensors", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "HIASBCH": entity if entity_type == "HIASBCH" else "NA", - "HIASCDI": entity if entity_type == "HIASCDI" else "NA", - "HIASHDI": entity if entity_type == "HIASHDI" else "NA", - "Agent": entity if entity_type == "Agent" else "NA", - "Application": entity if entity_type == "Application" else "NA", - "Device": entity if entity_type == "Device" else "NA", - "Staff": entity if entity_type == "Staff" else "NA", - "Robotics": entity if entity_type == "Robotics" else "NA", - "Sensor": data["Sensor"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - self.mqtt.publish("Integrity", { - "_id": str(_id), - "Sensor": data["Sensor"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"] - }) - - self.helpers.logger.info( - entity_type + " " + entity + " BCI data update OK") - else: + if update_response == False: self.helpers.logger.error( - entity_type + " " + entity + " BCI data update KO") - - def ai_model_callback(self, topic, payload): - """Called in the event of an AI payload + entity_type + " " + entity + " state update KO") + return + + update_data = self.hiashdi.entity_state_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data( + "State", update_data) + + if _id == False: + self.helpers.logger.error( + entity_type + " " + entity + " state update KO") + return + + update_data["_id"] = _id + self.mqtt.publish( + "Integrity", update_data) + + self.helpers.logger.info( + entity_type + " " + entity + " state update OK") + + def classification_callback(self, topic, payload): + """Called in the event of a classification payload Args: topic (str): The topic the payload was sent to. payload (:obj:`str`): The payload. """ - data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") + data, split_topic = self.parse_payload( + payload, topic) - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] + if not self.hiasbch.iotjumpway_access_check(bch): + return + + update_response = self.hiascdi.update_online_status( + entity, entity_type, "ONLINE") - self.helpers.logger.info( - "Received " + entity_type + " AI Data: " + str(data)) + if update_response == False: + self.helpers.logger.error( + entity_type + " " + entity + " AI model update KO") + return - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + models = self.hiascdi.get_ai_models( + entity, entity_type) + + model_data = models["models"]["value"] + modelExists = False - if not self.hiasbch.iotjumpway_access_check(bch): + newModelData = [] + + if model_data is None: + self.helpers.logger.error( + entity_type + " " + entity + " does not have any models") return - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" + for model in model_data: + modelExists = True + if model["model"] == data["Model"]: + if "State" in data and data["State"] in model["context"]["states"]["value"]: + model["context"]["state"] = { + "value": data["State"], + "timestamp": datetime.now().isoformat() + } + if "Type" in data and data["Type"] in model["context"]["properties"]["value"]: + model["context"]["properties"]["value"][data["Type"]] = { + "value": data["Value"], + "timestamp": datetime.now().isoformat() + } + newModelData.append(model) + + if modelExists == False: + self.helpers.logger.error( + entity_type + " " + entity + " does not have a " + data["Model"] + " model") + return update_response = self.hiascdi.update_entity( entity, entity_type, { - "network.status": {"value": "ONLINE"}, - "network.status.metadata": {"timestamp": datetime.now().isoformat()}, + "models": {"value": newModelData}, "dateModified": {"value": datetime.now().isoformat()} }) - models = self.hiascdi.get_ai_models(entity, entity_type) - model_data = models["models"]["value"] - modelExists = False - - for model in model_data: - modelExists = True - if model == data["Model"] and data["State"] in model_data[data["Model"]]["states"]["value"]: - model_data[data["Model"]]["state"] = { - "value": data["State"], - "timestamp": datetime.now().isoformat() - } - if model == data["Model"] and data["Type"] in model_data[data["Model"]]["properties"]["value"]: - model_data[data["Model"]]["properties"]["value"][data["Type"]] = { - "value": data["Value"], - "timestamp": datetime.now().isoformat() - } - - if modelExists: - update_response = self.hiascdi.update_entity( - entity, entity_type, { - "models": {"value": model_data}, - "dateModified": {"value": datetime.now().isoformat()} - }) - - if update_response: - _id = self.hiashdi.insert_data("AI", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "Agent": entity, - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - self.helpers.logger.info( - entity_type + " " + entity + " AI model data update OK") - else: - self.helpers.logger.error( - entity_type + " " + entity + " AI model data update KO") + if update_response == False: + self.helpers.logger.error( + entity_type + " " + entity + " AI model update KO") + return + + update_data = self.hiashdi.entity_ai_model_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data( + "Classification", update_data) + + if _id == False: + self.helpers.logger.error( + entity_type + " " + entity + " AI model update KO") + return + + update_data["_id"] = _id + self.mqtt.publish("Integrity", update_data) + self.helpers.logger.info( + entity_type + " " + entity + " AI model update OK") - def state_callback(self, topic, payload): - """Called in the event of an state payload + def bci_callback(self, topic, payload): + """Called in the event of a BCI payload Args: topic (str): The topic the payload was sent to. payload (:obj:`str`): The payload. """ - data = json.loads(payload.decode("utf-8")) - split_topic = topic.split("/") + data, split_topic = self.parse_payload( + payload, topic) - if split_topic[1] not in self.ignore_types: - entity_type = split_topic[1][:-1] - else: - entity_type = split_topic[1] - - if entity_type in ["Robotics", "Application", "Staff"]: - entity = split_topic[2] - else: - entity = split_topic[3] - - self.helpers.logger.info( - "Received " + entity_type + " State Data: " + str(data)) - - attrs = self.get_attributes(entity_type, entity) - bch = attrs["blockchain"] + entity_type, entity, location, zone, bch = self.process_request( + split_topic) if not self.hiasbch.iotjumpway_access_check(bch): return + + update_response = self.hiascdi.update_online_status( + entity, entity_type, "ONLINE") - entity = attrs["id"] - location = attrs["location"] - zone = attrs["zone"] if "zone" in attrs else "NA" - - update_response = self.hiascdi.update_entity( - entity, entity_type, { - "network.status": {"value": "ONLINE"}, - "network.status.metadata": {"timestamp": datetime.now().isoformat()}, - "dateModified": { - "type": "DateTime", - "value": datetime.now().isoformat() - } - }) + if update_response == False: + self.helpers.logger.error( + entity_type + " " + entity + " AI model update KO") + return - actuators = self.hiascdi.get_actuators(entity, entity_type) - actuator_data = actuators["actuators"]["value"] - exists = False - - i = 0 - for actuator in actuator_data: - exists = True - if data["Name"] in actuator["name"]["value"] and data["Type"].lower() in actuator["commands"] \ - and data["Value"].lower() in actuator["commands"][data["Type"]]: - actuator_data[i]["state"] = { - "value": data["Value"], - "timestamp": datetime.now().isoformat() - } - i = i + 1 - - if exists: - update_response = self.hiascdi.update_entity( - entity, entity_type, { - "actuators": {"value": actuator_data}, - "dateModified": { - "type": "DateTime", - "value": datetime.now().isoformat() - } - }) - - if update_response: - _id = self.hiashdi.insert_data("Actuators", { - "Use": entity_type, - "Location": location, - "Zone": zone, - "HIASBCH": entity if entity_type == "HIASBCH" else "NA", - "HIASCDI": entity if entity_type == "HIASCDI" else "NA", - "HIASHDI": entity if entity_type == "HIASHDI" else "NA", - "Agent": entity if entity_type == "Agent" else "NA", - "Application": entity if entity_type == "Application" else "NA", - "Device": entity if entity_type == "Device" else "NA", - "Staff": entity if entity_type == "Staff" else "NA", - "Robotics": entity if entity_type == "Robotics" else "NA", - "Actuator": data["Name"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - self.mqtt.publish("Integrity", { - "_id": str(_id), - "From": entity, - "Actuator": data["Name"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"] - }) - - self.helpers.logger.info( - entity_type + " " + entity + " state data update OK") - else: - self.helpers.logger.error( - entity_type + " " + entity + " state data update KO") - - def respond(self, responseCode, response): - """ Returns the request repsonse """ - - return Response(response=json.dumps(response, indent=4), - status=responseCode, - mimetype="application/json") + update_data = self.hiashdi.entity_bci_data( + entity, entity_type, location, zone, data) + + _id = self.hiashdi.insert_data( + "Sensors", update_data) + + if _id == False: + self.helpers.logger.error( + entity_type + " " + entity + " BCI update KO") + return + + update_data["_id"] = _id + self.mqtt.publish( + "Integrity", update_data) + + self.helpers.logger.info( + entity_type + " " + entity + " BCI update OK") def signal_handler(self, signal, frame): self.helpers.logger.info("Disconnecting") @@ -839,6 +597,85 @@ def about(): "Temperature": psutil.sensors_temperatures()['coretemp'][0].current }) +@app.route('/Rules', methods=['POST']) +def rules(): + """ + Returns Agent details + Responds to GET requests sent to the North Port About API endpoint. + """ + + accepted = agent.check_accepts_type(request.headers) + content_type = agent.check_content_type(request.headers) + query = agent.check_body(request) + + if accepted is False: + return agent.respond( + 406, agent.confs["errorMessages"][str(406)], + "application/json") + + if content_type is False: + return agent.respond( + 415, agent.confs["errorMessages"][str(415)], + "application/json") + + if query is False: + return agent.respond( + 400, agent.confs["errorMessages"]["400p"], + accepted) + + entities = query["data"] + entity = agent.hiascdi.get_entity( + entities[0]["type"], entities[0]["id"]) + + for rule in entity["rules"]["value"]: + if rule["subscription"] == query["subscriptionId"]: + break + + if rule["action"]["type"] == 'staff_ui': + message = entity["name"]["value"] + if rule["event"]["type"] == 'sensors': + message += " " + rule["event"]["sensor"] + if rule["event"]["type"] == 'actuators': + message += " " + rule["event"]["actuator"] + message += " is " + rule["event"]["range"] + " " + rule["event"]["value"] + + pathto = entity["networkLocation"]["value"] + "/Staff/" \ + + rule["action"]["user"] + "/Notifications" + + Thread(target=agent.mqtt.publish, args=( + "Custom", { + "Use": "Staff", + "From": entities[0]["id"], + "FromType": entities[0]["type"], + "To": rule["action"]["user"], + "Message": message + }, pathto), daemon=True).start() + + elif rule["action"]["type"] == 'output_device_command': + + device = agent.hiascdi.get_entity( + "Device", rule["action"]["device"]) + + if device[rule["action"]["property"]]["value"] != rule["action"]["value"]: + + pathto = agent.credentials["iotJumpWay"]["location"] + "/Agents/" \ + + agent.credentials["iotJumpWay"]["zone"] + "/" + agent.credentials["iotJumpWay"]["entity"] + "/Commands" + + Thread(target=agent.mqtt.publish, args=( + "Custom", { + "Use": "Device", + "To": device["id"], + "Zone": device["networkZone"]["value"], + "Property": rule["action"]["property"], + "Type": rule["action"]["command"], + "Value": rule["action"]["value"], + "Message": rule["action"]["command"].capitalize() + " " + rule["action"]["value"] + }, pathto), daemon=True).start() + + return agent.respond( + 200, json.dumps(json.loads( + json_util.dumps(entity))), accepted) + def main(): signal.signal(signal.SIGINT, agent.signal_handler) @@ -850,6 +687,7 @@ def main(): agent.mqtt_connection({ "host": agent.credentials["iotJumpWay"]["host"], "port": agent.credentials["iotJumpWay"]["port"], + "security": agent.confs["agent"]["secure"], "location": agent.credentials["iotJumpWay"]["location"], "zone": agent.credentials["iotJumpWay"]["zone"], "entity": agent.credentials["iotJumpWay"]["entity"], @@ -858,11 +696,12 @@ def main(): "up": agent.credentials["iotJumpWay"]["up"] }) - agent.mqtt.actuator_callback = agent.actuator_callback - agent.mqtt.ai_model_callback = agent.ai_model_callback + agent.mqtt.actuators_callback = agent.actuators_callback agent.mqtt.bci_callback = agent.bci_callback agent.mqtt.comands_callback = agent.comands_callback + agent.mqtt.classification_callback = agent.classification_callback agent.mqtt.life_callback = agent.life_callback + agent.mqtt.notifications_callback = agent.notifications_callback agent.mqtt.sensors_callback = agent.sensors_callback agent.mqtt.state_callback = agent.state_callback agent.mqtt.status_callback = agent.status_callback diff --git a/assets/images/project-banner.jpg b/assets/images/project-banner.jpg deleted file mode 100644 index 2b409a2..0000000 Binary files a/assets/images/project-banner.jpg and /dev/null differ diff --git a/assets/images/bug-report.jpg b/assets/img/bug-report.jpg similarity index 100% rename from assets/images/bug-report.jpg rename to assets/img/bug-report.jpg diff --git a/assets/images/feature-proposals.jpg b/assets/img/feature-proposals.jpg similarity index 100% rename from assets/images/feature-proposals.jpg rename to assets/img/feature-proposals.jpg diff --git a/assets/images/feature-request.jpg b/assets/img/feature-request.jpg similarity index 100% rename from assets/images/feature-request.jpg rename to assets/img/feature-request.jpg diff --git a/assets/images/fork.png b/assets/img/fork.png similarity index 100% rename from assets/images/fork.png rename to assets/img/fork.png diff --git a/assets/images/hias-network-v3.jpg b/assets/img/hias-network-v3.jpg similarity index 100% rename from assets/images/hias-network-v3.jpg rename to assets/img/hias-network-v3.jpg diff --git a/assets/images/northbound.jpg b/assets/img/northbound.jpg similarity index 100% rename from assets/images/northbound.jpg rename to assets/img/northbound.jpg diff --git a/assets/img/project-banner.jpg b/assets/img/project-banner.jpg new file mode 100644 index 0000000..440605c Binary files /dev/null and b/assets/img/project-banner.jpg differ diff --git a/assets/images/repo-issues.jpg b/assets/img/repo-issues.jpg similarity index 100% rename from assets/images/repo-issues.jpg rename to assets/img/repo-issues.jpg diff --git a/assets/images/southbound.jpg b/assets/img/southbound.jpg similarity index 100% rename from assets/images/southbound.jpg rename to assets/img/southbound.jpg diff --git a/configuration/config.json b/configuration/config.json index 2083b1f..a207985 100644 --- a/configuration/config.json +++ b/configuration/config.json @@ -1,11 +1,80 @@ { + "acceptTypes": [ + "application/json", + "text/plain" + ], "agent": { + "secure": true, "params": [], "api": { "content": "application/json" }, "proxy": { - "up": "" + "up": "17604jb9L8qKY0tpZi0ECa5d242MJ52Z" } + }, + "contentType": "application/json", + "contentTypes": [ + "application/json", + "text/plain" + ], + "errorMessages": { + "400b": { + "Error": "ParseError", + "Description": "400 Parse Error: Incoming JSON payload cannot be parsed" + }, + "400p": { + "Error": "BadRequest", + "Description": "400 Bad Request: Error in URL parameters or payload" + }, + "404": { + "Error": "NotFound", + "Description": "404 Not Found: Resource identified by the request is not found" + }, + "405": { + "Error": "MethodNotAlowed", + "Description": "405 Method Not Allowed: Requested method not supported" + }, + "406": { + "Error": "NotAcceptable", + "Description": "406 Not Acceptable: Accepted MIME types: application/json, text/plain" + }, + "409": { + "Error": "TooManyResults", + "Description": "409 Too Many Results: Request may refer to several resources" + }, + "413": { + "Error": "NoResourceAvailable", + "Description": "413 No Resource Available: Attemp to exceed spatial index limit results" + }, + "415": { + "Error": "UnsupportedMediaType", + "Description": "415 Unsupported Media Type: Request media type not supported" + }, + "501": { + "Error": "NotImplemented", + "Description": "501 Not Implemented: Request not supported" + } + }, + "methods": [ + "POST", + "GET", + "PUT", + "PATCH", + "DELETE" + ], + "successMessage": { + "200": { + "Description": "200 OK: Request successful" + }, + "201": { + "Description": "200 Created: Resource Created" + }, + "204": { + "Description": "204: Request successful, no content" + } + }, + "ssl": { + "chain": "/etc/ssl/certs/ISRG_Root_X1.pem" } } \ No newline at end of file diff --git a/docs/img/project-banner.jpg b/docs/img/project-banner.jpg index ff4087b..440605c 100644 Binary files a/docs/img/project-banner.jpg and b/docs/img/project-banner.jpg differ diff --git a/docs/index.md b/docs/index.md index ec0a535..d8cca38 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ ![HIAS MQTT IoT Agent](img/project-banner.jpg) -[![CURRENT RELEASE](https://img.shields.io/badge/CURRENT%20RELEASE-3.1.0-blue.svg)](https://github.com/aiial/hias-mqtt-iot-agent/tree/release-3.1.0) [![UPCOMING RELEASE](https://img.shields.io/badge/DEV%20BRANCH-develop-blue.svg)](https://github.com/aiial/hias-mqtt-iot-agent/tree/develop) [![Contributions Welcome!](https://img.shields.io/badge/Contributions-Welcome-lightgrey.svg)](CONTRIBUTING.md) [![Issues](https://img.shields.io/badge/Issues-Welcome-lightgrey.svg)](issues) +[![CURRENT RELEASE](https://img.shields.io/badge/CURRENT%20RELEASE-4.0.0-blue.svg)](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/tree/release-4.0.0) [![UPCOMING RELEASE](https://img.shields.io/badge/DEV%20BRANCH-develop-blue.svg)](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/tree/develop) [![Contributions Welcome!](https://img.shields.io/badge/Contributions-Welcome-lightgrey.svg)](CONTRIBUTING.md) [![Issues](https://img.shields.io/badge/Issues-Welcome-lightgrey.svg)](issues) # Introduction @@ -22,7 +22,7 @@ The protocol is publish-subscribe (Pub/Sub) communication protocol that runs ove ![HIAS - Hospital Intelligent Automation Server](img/hias-network-v3.jpg) -[HIAS - Hospital Intelligent Automation Server](https://github.com/aiial/HIAS-Core) is an open-source server for hospitals and medical centers, designed to control and manage a network of intelligent IoT connected devices and applications. +[HIAS - Hospital Intelligent Automation Server](https://github.com/leukaemiamedtech/HIAS-Core) is an open-source server for hospitals and medical centers, designed to control and manage a network of intelligent IoT connected devices and applications. ## HIAS IoT Agents @@ -54,24 +54,24 @@ To get started, follow the following guides:   # Contributing -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss encourages and welcomes code contributions, bug fixes and enhancements from the Github community. +Peter Moss Leukaemia MedTech Research CIC encourages and welcomes code contributions, bug fixes and enhancements from the Github community. -Please read the [CONTRIBUTING](https://github.com/aiial/hias-mqtt-iot-agent/blob/main/CONTRIBUTING.md "CONTRIBUTING") document for a full guide to forking our repositories and submitting your pull requests. You will also find our code of conduct in the [Code of Conduct](https://github.com/aiial/hias-mqtt-iot-agent/blob/main/CODE-OF-CONDUCT.md) document. +Please read the [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for a full guide to contributing to our IoT Agent projects. You will also find our code of conduct in the [CODE OF CONDUCT](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CODE-OF-CONDUCT.md) document. ## Contributors -- [Adam Milton-Barker](https://www.leukemiaairesearch.com/association/volunteers/adam-milton-barker "Adam Milton-Barker") - [Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss](https://www.leukemiaresearchassociation.ai "Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss") President/Founder & Lead Developer, Sabadell, Spain +- [Adam Milton-Barker](https://www.leukaemiamedtechresearch.org.uk/about/volunteers/adam-milton-barker "Adam Milton-Barker") - [Peter Moss Leukaemia MedTech Research CIC](https://www.leukaemiamedtechresearch.org.uk "Peter Moss Leukaemia MedTech Research CIC") Founder & Managing Director.   # Versioning -We use SemVer for versioning. +We use [SemVer](https://semver.org/) for versioning.   # License -This project is licensed under the **MIT License** - see the [LICENSE](https://github.com/aiial/hias-mqtt-iot-agent/blob/main/LICENSE "LICENSE") file for details. +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE "LICENSE") file for details.   # Bugs/Issues -We use the [repo issues](https://github.com/aiial/hias-mqtt-iot-agent/issues "repo issues") to track bugs and general requests related to using this project. See [CONTRIBUTING](https://github.com/aiial/hias-mqtt-iot-agent/CONTRIBUTING.md "CONTRIBUTING") for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file +We use the [repo issues](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/issues/new/choose "repo issues") to track bugs and general requests related to using this project. See [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file diff --git a/docs/installation/ubuntu.md b/docs/installation/ubuntu.md index f6c878b..46c102d 100644 --- a/docs/installation/ubuntu.md +++ b/docs/installation/ubuntu.md @@ -12,7 +12,7 @@ You will need to ensure you have the following prerequisites installed and setup ## HIAS Core -The HIAS MQTT IoT Agent is a component of the [HIAS - Hospital Intelligent Automation Server](https://github.com/aiial/hias-core). Before beginning this tutorial you should complete the [HIAS Core](https://hias-core.readthedocs.io/en/latest/installation/ubuntu/) installation guide ensure your HIAS server is online. +The HIAS MQTT IoT Agent is a component of the [HIAS - Hospital Intelligent Automation Server](https://github.com/leukaemiamedtech/hias-core). Before beginning this tutorial you should complete the [HIAS Core](https://hias-core.readthedocs.io/en/latest/installation/ubuntu/) installation guide ensure your HIAS server is online.   @@ -21,12 +21,12 @@ You are now ready to install the HIAS MQTT IoT Agent software. ## Clone the repository -Clone the [HIAS MQTT IoT Agent](https://github.com/aiial/hias-mqtt-iot-agent " HIAS MQTT IoT Agent") repository from the [Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss](https://github.com/aiial "Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss") Github Organization to your HIAS project root. +Clone the [HIAS MQTT IoT Agent](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent " HIAS MQTT IoT Agent") repository from the [Peter Moss Leukaemia MedTech Research CIC](https://github.com/leukaemiamedtech "Peter Moss Leukaemia MedTech Research CIC") Github Organization to your HIAS project root. To clone the repository and install the project, make sure you have Git installed. Now navigate to your HIAS Core project root and then use the following command. ``` bash - git clone https://github.com/aiial/hias-mqtt-iot-agent.git + git clone https://github.com/leukaemiamedtech/hias-mqtt-iot-agent.git mv hias-mqtt-iot-agent components/agents/mqtt ``` @@ -84,6 +84,7 @@ The final configuration you have to do is in the **configuration/config.json** f ``` json { "agent": { + "secure": true, "params": [], "api": { "content": "application/json" @@ -98,7 +99,7 @@ The final configuration you have to do is in the **configuration/config.json** f You need to add the following: - **agent->proxy:** IoT Agent API Key - +- **agent->secure:** Specify true if connecting securely or false if connecting locally without encryption.   # Service @@ -116,24 +117,24 @@ Now you can continue with the HIAS [usage guide](../usage/ubuntu.md)   # Contributing -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss encourages and welcomes code contributions, bug fixes and enhancements from the Github community. +Peter Moss Leukaemia MedTech Research CIC encourages and welcomes code contributions, bug fixes and enhancements from the Github community. -Please read the [CONTRIBUTING](https://github.com/aiial/hias-mqtt-iot-agent/blob/main/CONTRIBUTING.md "CONTRIBUTING") document for a full guide to forking our repositories and submitting your pull requests. You will also find our code of conduct in the [Code of Conduct](https://github.com/aiial/hias-mqtt-iot-agent/blob/main/CODE-OF-CONDUCT.md) document. +Please read the [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for a full guide to contributing to our IoT Agent projects. You will also find our code of conduct in the [CODE OF CONDUCT](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CODE-OF-CONDUCT.md) document. ## Contributors -- [Adam Milton-Barker](https://www.leukemiaairesearch.com/association/volunteers/adam-milton-barker "Adam Milton-Barker") - [Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss](https://www.leukemiaresearchassociation.ai "Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss") President/Founder & Lead Developer, Sabadell, Spain +- [Adam Milton-Barker](https://www.leukaemiamedtechresearch.org.uk/about/volunteers/adam-milton-barker "Adam Milton-Barker") - [Peter Moss Leukaemia MedTech Research CIC](https://www.leukaemiamedtechresearch.org.uk "Peter Moss Leukaemia MedTech Research CIC") Founder & Managing Director.   # Versioning -We use SemVer for versioning. +We use [SemVer](https://semver.org/) for versioning.   # License -This project is licensed under the **MIT License** - see the [LICENSE](https://github.com/aiial/hias-mqtt-iot-agent/blob/main/LICENSE "LICENSE") file for details. +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE "LICENSE") file for details.   # Bugs/Issues -We use the [repo issues](https://github.com/aiial/hias-mqtt-iot-agent/issues "repo issues") to track bugs and general requests related to using this project. See [CONTRIBUTING](https://github.com/aiial/hias-mqtt-iot-agent/CONTRIBUTING.md "CONTRIBUTING") for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file +We use the [repo issues](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/issues/new/choose "repo issues") to track bugs and general requests related to using this project. See [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file diff --git a/docs/usage/ubuntu.md b/docs/usage/ubuntu.md index 38b6b3d..be46eb7 100644 --- a/docs/usage/ubuntu.md +++ b/docs/usage/ubuntu.md @@ -30,29 +30,30 @@ To manage the agent you can use the following commands: ``` bash sudo systemctl restart HIAS-MQTT-IoT-Agent.service sudo systemctl stop HIAS-MQTT-IoT-Agent.service +sudo systemctl status HIAS-MQTT-IoT-Agent.service ```   # Contributing -Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss encourages and welcomes code contributions, bug fixes and enhancements from the Github community. +Peter Moss Leukaemia MedTech Research CIC encourages and welcomes code contributions, bug fixes and enhancements from the Github community. -Please read the [CONTRIBUTING](https://github.com/AIIAL/HIAS-MQTT-IoT-Agent/blob/main/CONTRIBUTING.md "CONTRIBUTING") document for a full guide to forking our repositories and submitting your pull requests. You will also find our code of conduct in the [Code of Conduct](https://github.com/AIIAL/HIAS-MQTT-IoT-Agent/blob/main/CODE-OF-CONDUCT.md) document. +Please read the [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for a full guide to contributing to our IoT Agent projects. You will also find our code of conduct in the [CODE OF CONDUCT](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CODE-OF-CONDUCT.md) document. ## Contributors -- [Adam Milton-Barker](https://www.leukemiaairesearch.com/association/volunteers/adam-milton-barker "Adam Milton-Barker") - [Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss](https://www.leukemiaresearchassociation.ai "Asociación de Investigacion en Inteligencia Artificial Para la Leucemia Peter Moss") President/Founder & Lead Developer, Sabadell, Spain +- [Adam Milton-Barker](https://www.leukaemiamedtechresearch.org.uk/about/volunteers/adam-milton-barker "Adam Milton-Barker") - [Peter Moss Leukaemia MedTech Research CIC](https://www.leukaemiamedtechresearch.org.uk "Peter Moss Leukaemia MedTech Research CIC") Founder & Managing Director.   # Versioning -We use SemVer for versioning. +We use [SemVer](https://semver.org/) for versioning.   # License -This project is licensed under the **MIT License** - see the [LICENSE](https://github.com/AIIAL/HIAS-MQTT-IoT-Agent/blob/main/LICENSE "LICENSE") file for details. +This project is licensed under the **MIT License** - see the [LICENSE](LICENSE "LICENSE") file for details.   # Bugs/Issues -We use the [repo issues](https://github.com/AIIAL/HIAS-MQTT-IoT-Agent/issues "repo issues") to track bugs and general requests related to using this project. See [CONTRIBUTING](https://github.com/AIIAL/HIAS-MQTT-IoT-Agent/CONTRIBUTING.md "CONTRIBUTING") for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file +We use the [repo issues](https://github.com/leukaemiamedtech/hias-mqtt-iot-agent/issues/new/choose "repo issues") to track bugs and general requests related to using this project. See [IOT AGENT CONTRIBUTING](https://github.com/leukaemiamedtech/contributing-guides/blob/main/CONTRIBUTING-GUIDE-IOT-AGENTS.md "IOT AGENT CONTRIBUTING") guide for more info on how to submit bugs, feature requests and proposals. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 8c0c02b..f893479 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,2 +1,2 @@ site_name: HIAS MQTT IoT Agent -site_url: https://www.leukemiaairesearch.com/research/project/peter-moss-medtech-research-project/hias-mqtt-iot-agent +site_url: https://www.leukaemiamedtechresearch.org.uk/research/project/peter-moss-medtech-research-project/hias-mqtt-iot-agent diff --git a/modules/AbstractAgent.py b/modules/AbstractAgent.py index 3e30343..348a0fc 100644 --- a/modules/AbstractAgent.py +++ b/modules/AbstractAgent.py @@ -6,8 +6,7 @@ MIT License -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial -Para la Leucemia Peter Moss +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal @@ -38,7 +37,8 @@ import ssl import threading -from abc import ABC, abstractmethod +from datetime import datetime +from flask import Response from modules.helpers import helpers from modules.hiasbch import hiasbch @@ -46,6 +46,8 @@ from modules.hiashdi import hiashdi from modules.mqtt import mqtt +from abc import ABC, abstractmethod + class AbstractAgent(ABC): """ Abstract class representing a HIAS iotJumpWay IoT Agent. @@ -62,8 +64,17 @@ def __init__(self): self.hiashdi = None self.mqtt = None - self.app_types = ["Robotics", "Application", "Staff"] - self.ignore_types = ["Robotics", "HIASCDI", "HIASHDI", "HIASBCH", "Staff"] + self.app_types = [ + "Robotics", + "Application", + "Staff"] + + self.ignore_types = [ + "Robotics", + "HIASCDI", + "HIASHDI", + "HIASBCH", + "Staff"] self.helpers = helpers("Agent") self.confs = self.helpers.confs @@ -90,7 +101,8 @@ def hiashdi_connection(self): def mqtt_connection(self, credentials): """Initializes the HIAS MQTT connection. """ - self.mqtt = mqtt(self.helpers, "Agent", credentials) + self.mqtt = mqtt( + self.helpers, "Agent", credentials) self.mqtt.configure() self.mqtt.start() @@ -103,12 +115,90 @@ def hiasbch_connection(self): self.hiasbch = hiasbch(self.helpers) self.hiasbch.start() self.hiasbch.w3.geth.personal.unlockAccount( - self.hiasbch.w3.toChecksumAddress(self.credentials["hiasbch"]["un"]), - self.credentials["hiasbch"]["up"], 0) + self.hiasbch.w3.toChecksumAddress( + self.credentials["hiasbch"]["un"]), + self.credentials["hiasbch"]["up"], 0) self.helpers.logger.info( "HIAS HIASBCH Blockchain connection created.") + def check_accepts_type(self, headers): + """ Checks the request Accept types. """ + + accepted = headers.getlist('accept') + accepted = accepted[0].split(",") + + if "Accept" not in headers: + return False + + for i, ctype in enumerate(accepted): + if ctype not in self.helpers.confs["acceptTypes"]: + accepted.pop(i) + + if len(accepted): + return accepted + else: + return False + + def check_content_type(self, headers): + """ Checks the request Content-Type. """ + + content_type = headers["Content-Type"] + + if ("Content-Type" not in headers or content_type not in + self.helpers.confs["contentTypes"]): + return False + return content_type + + def check_body(self, payload, text=False): + """ Checks the request body is valid. """ + + response = False + message = "valid" + + if text is False: + try: + json_object = json.loads(json.dumps( + payload.json)) + response = json_object + except TypeError as e: + response = False + message = "invalid" + else: + if payload.data == "": + response = False + message = "invalid" + else: + response = payload.data + + self.helpers.logger.info("Request data " + message) + + return response + + def get_entity_details(self, split_topic): + """Determines entity type and entity from topic + + Args: + split_topic (list): List of topic parts + + Returns: + entity_type: The type of the entity + entity: The id of the entity + + """ + + if split_topic[1] not in self.ignore_types: + entity_type = split_topic[1][:-1] + else: + entity_type = split_topic[1] + + if entity_type in self.app_types: + entity = split_topic[2] + else: + entity = split_topic[3] + + return entity_type, entity + def get_attributes(self, entity_type, entity): """Gets entity attributes from HIASCDI. @@ -124,22 +214,53 @@ def get_attributes(self, entity_type, entity): attrs = self.hiascdi.get_attributes(entity_type, entity) rattrs = {} + rattrs["id"] = attrs["id"] + rattrs["type"] = attrs["type"] + rattrs["blockchain"] = attrs["authenticationBlockchainUser"]["value"] + rattrs["location"] = attrs["networkLocation"]["value"] - if entity_type in self.app_types: - rattrs["id"] = attrs["id"] - rattrs["type"] = attrs["type"] - rattrs["blockchain"] = attrs["authenticationBlockchainUser"]["value"] - rattrs["location"] = attrs["networkLocation"]["value"] - else: - rattrs["id"] = attrs["id"] - rattrs["type"] = attrs["type"] - rattrs["blockchain"] = attrs["authenticationBlockchainUser"]["value"] - rattrs["location"] = attrs["networkLocation"]["value"] + if entity_type not in self.app_types: rattrs["zone"] = attrs["networkZone"]["value"] return rattrs + + def parse_payload(self, payload, topic): + """Decodes the payload and splits the topic + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + + data = json.loads(payload.decode("utf-8")) + split_topic = topic.split("/") + + return data, split_topic + + def process_request(self, split_topic): + """Decodes the payload and splits the topic - def life(self): + Args: + split_topic (str): The split topic. + """ + + entity_type, entity = self.get_entity_details( + split_topic) + + self.helpers.logger.info( + "Received " + entity_type + " status data payload") + + attrs = self.get_attributes( + entity_type, entity) + + entity = attrs["id"] + location = attrs["location"] + zone = attrs["zone"] if "zone" in attrs else "NA" + bch = attrs["blockchain"] + + return entity_type, entity, location, zone, bch + + def publish_life(self): """ Publishes entity statistics to HIAS. """ cpu = psutil.cpu_percent() @@ -166,10 +287,109 @@ def life(self): }) self.helpers.logger.info("Agent life statistics published.") - threading.Timer(300.0, self.life).start() + threading.Timer(300.0, self.publish_life).start() def threading(self): """ Creates required module threads. """ # Life thread - threading.Timer(10.0, self.life).start() + threading.Timer(10.0, self.publish_life).start() + + def respond(self, responseCode, response, accepted): + """ Builds the request response """ + + headers = {} + if "application/json" in accepted: + response = Response( + response=response, status=responseCode, + mimetype="application/json") + headers['Content-Type'] = 'application/json' + elif "text/plain" in accepted: + response = self.broker.prepareResponse(response) + response = Response( + response=response, status=responseCode, + mimetype="text/plain") + headers['Content-Type'] = 'text/plain; charset=utf-8' + response.headers = headers + return response + + @abstractmethod + def status_callback(self, topic, payload): + """Called in the event of a status payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + pass + + @abstractmethod + def life_callback(self, topic, payload): + """Called in the event of a life payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + pass + + @abstractmethod + def comands_callback(self, topic, payload): + """ + iotJumpWay Device Commands Callback + + The callback function that is triggerend in the event of an device + command communication from the iotJumpWay. + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + pass + + @abstractmethod + def notifications_callback(self, topic, payload): + """Called in the event of an notifications payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + pass + + @abstractmethod + def actuators_callback(self, topic, payload): + """Called in the event of an actuator payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + pass + + @abstractmethod + def sensors_callback(self, topic, payload): + """Called in the event of a sensor payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + + @abstractmethod + def state_callback(self, topic, payload): + """Called in the event of a state payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ + + @abstractmethod + def classification_callback(self, topic, payload): + """Called in the event of a classification payload + + Args: + topic (str): The topic the payload was sent to. + payload (:obj:`str`): The payload. + """ diff --git a/modules/helpers.py b/modules/helpers.py index 4ff2e5c..e16254d 100644 --- a/modules/helpers.py +++ b/modules/helpers.py @@ -3,6 +3,10 @@ Configuration and logging functions. +MIT License + +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/modules/hiasbch.py b/modules/hiasbch.py index 41d6567..8b1c691 100644 --- a/modules/hiasbch.py +++ b/modules/hiasbch.py @@ -6,8 +6,7 @@ MIT License -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial -Para la Leucemia Peter Moss +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal @@ -32,7 +31,6 @@ """ -import bcrypt import json import sys import time @@ -60,11 +58,13 @@ def __init__(self, helpers): def start(self): """ Connects to HIASBCH. """ - self.w3 = Web3(Web3.HTTPProvider("https://" + self.credentials["server"]["host"] + self.credentials["hiasbch"]["endpoint"], request_kwargs={ + self.w3 = Web3(Web3.HTTPProvider( + "http://" + self.credentials["server"]["host"] + self.credentials["hiasbch"]["endpoint"], request_kwargs={ 'auth': HTTPBasicAuth(self.credentials["iotJumpWay"]["entity"], self.confs["agent"]["proxy"]["up"])})) - self.iotContract = self.w3.eth.contract(self.w3.toChecksumAddress(self.credentials["hiasbch"]["contracts"]["iotJumpWay"]["contract"]), - abi=json.dumps(self.credentials["hiasbch"]["contracts"]["iotJumpWay"]["abi"])) + self.iotContract = self.w3.eth.contract( + self.w3.toChecksumAddress(self.credentials["hiasbch"]["contracts"]["iotJumpWay"]["contract"]), + abi=json.dumps(self.credentials["hiasbch"]["contracts"]["iotJumpWay"]["abi"])) self.helpers.logger.info("HIASBCH connections started") def iotjumpway_access_check(self, address): diff --git a/modules/hiascdi.py b/modules/hiascdi.py index d16d2c1..0ffe3de 100644 --- a/modules/hiascdi.py +++ b/modules/hiascdi.py @@ -6,8 +6,7 @@ MIT License -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial -Para la Leucemia Peter Moss +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal @@ -35,6 +34,8 @@ import json import requests +from datetime import datetime + class hiascdi(): """ HIASCDI Helper Module @@ -66,7 +67,7 @@ def get_attributes(self, entity_type, entity): else: params = "&attrs=id,type,authenticationBlockchainUser.value,networkLocation.value,networkZone.value" - api_host = "https://" + self.helpers.credentials["server"]["host"] + \ + api_host = "http://" + self.helpers.credentials["server"]["host"] + \ self.helpers.credentials["hiascdi"]["endpoint"] api_endpoint = "/entities/" + entity + "?type=" + entity_type + params api_url = api_host + api_endpoint @@ -75,15 +76,28 @@ def get_attributes(self, entity_type, entity): return json.loads(response.text) + def get_entity(self, entity_type, entity): + """ Gets required attributes. """ + + api_host = "http://" + self.helpers.credentials["server"]["host"] + \ + self.helpers.credentials["hiascdi"]["endpoint"] + api_endpoint = "/entities/" + entity + "?type=" + entity_type + api_url = api_host + api_endpoint + + response = requests.get(api_url, headers=self.headers, auth=self.auth) + + return json.loads(response.text) + def update_entity(self, _id, typer, data): """ Updates an entity. """ - api_url = "https://" + self.helpers.credentials["server"]["host"] + "/" + \ + api_url = "http://" + self.helpers.credentials["server"]["host"] + "/" + \ self.helpers.credentials["hiascdi"]["endpoint"] + \ "/entities/" + _id + "/attrs?type=" + typer response = requests.post(api_url, data=json.dumps( data), headers=self.headers, auth=self.auth) + if response.status_code == 204: return True else: @@ -92,7 +106,7 @@ def update_entity(self, _id, typer, data): def get_sensors(self, _id, typeof): """ Gets sensor list. """ - api_url = "https://" + self.helpers.credentials["server"]["host"] + "/" + \ + api_url = "http://" + self.helpers.credentials["server"]["host"] + "/" + \ self.helpers.credentials["hiascdi"]["endpoint"] + \ "/entities/" + _id + "?type=" + typeof + "&attrs=sensors" @@ -103,7 +117,7 @@ def get_sensors(self, _id, typeof): def get_actuators(self, _id, typeof): """ Gets actuator list. """ - api_url = "https://" + self.helpers.credentials["server"]["host"] + "/" + \ + api_url = "http://" + self.helpers.credentials["server"]["host"] + "/" + \ self.helpers.credentials["hiascdi"]["endpoint"] + \ "/entities/" + _id + "?type=" + typeof + "&attrs=actuators" @@ -114,10 +128,113 @@ def get_actuators(self, _id, typeof): def get_ai_models(self, _id, typeof): """ Gets AI Agent models. """ - api_url = "https://" + self.helpers.credentials["server"]["host"] + "/" + \ + api_url = "http://" + self.helpers.credentials["server"]["host"] + "/" + \ self.helpers.credentials["hiascdi"]["endpoint"] + \ "/entities/" + _id + "?type=" + typeof + "&attrs=models" response = requests.get(api_url, headers=self.headers, auth=self.auth) return json.loads(response.text) + + def update_online_status(self, entity, entity_type, status): + """Updates the status of an entity + + Args: + entity (str): The entity ID. + entity_type (str): The entity type. + status (str): The entity status. + """ + + return self.update_entity( + entity, entity_type, { + "networkStatus": {"value": status}, + "networkStatus.metadata": {"timestamp": {"value": datetime.now().isoformat()}}, + "dateModified": {"value": datetime.now().isoformat()} + }) + + def entity_life_data(self,data): + + return { + "networkStatus": {"value": "ONLINE"}, + "networkStatus.metadata": {"timestamp": {"value": datetime.now().isoformat()}}, + "dateModified": {"value": datetime.now().isoformat()}, + "cpuUsage": { + "value": float(data["CPU"]) + }, + "memoryUsage": { + "value": float(data["Memory"]) + }, + "hddUsage": { + "value": float(data["Diskspace"]) + }, + "temperature": { + "value": float(data["Temperature"]) + }, + "location": { + "type": "geo:json", + "value": { + "type": "Point", + "coordinates": [float(data["Latitude"]), float(data["Longitude"])] + } + } + } + + def entity_actuator_data(self, entity_data, data): + + return { + "value": "Processing", + "type": entity_data[data["Property"]]["type"], + "metadata": { + "property": { + "value": entity_data[data["Property"]]["metadata"]["property"]["value"] + }, + "propertyId": { + "value": entity_data[data["Property"]]["metadata"]["propertyId"]["value"] + }, + "propertyType": { + "value": entity_data[data["Property"]]["metadata"]["propertyType"]["value"] + }, + "propertyName": { + "value": entity_data[data["Property"]]["metadata"]["propertyName"]["value"] + }, + "commands": { + "value": entity_data[data["Property"]]["metadata"]["commands"]["value"] + }, + "description": { + "value": entity_data[data["Property"]]["metadata"]["description"]["value"] + }, + "timestamp": { + "value": datetime.now().isoformat() + } + } + } + + def entity_sensor_data(self, entity_data, data): + + return { + "value": data["Value"], + "type": entity_data[data["Type"]]["type"], + "metadata": { + "property": { + "value": entity_data[data["Type"]]["metadata"]["property"]["value"] + }, + "propertyId": { + "value": entity_data[data["Type"]]["metadata"]["propertyId"]["value"] + }, + "propertyType": { + "value": entity_data[data["Type"]]["metadata"]["propertyType"]["value"] + }, + "propertyName": { + "value": entity_data[data["Type"]]["metadata"]["propertyName"]["value"] + }, + "commands": { + "value": entity_data[data["Type"]]["metadata"]["commands"]["value"] + }, + "description": { + "value": entity_data[data["Type"]]["metadata"]["description"]["value"] + }, + "timestamp": { + "value": datetime.now().isoformat() + } + } + } \ No newline at end of file diff --git a/modules/hiashdi.py b/modules/hiashdi.py index 5d76789..2f7422a 100644 --- a/modules/hiashdi.py +++ b/modules/hiashdi.py @@ -6,8 +6,7 @@ MIT License -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial -Para la Leucemia Peter Moss +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal @@ -35,6 +34,8 @@ import json import requests +from datetime import datetime + class hiashdi(): """ HIASHDI Helper Module @@ -62,7 +63,7 @@ def __init__(self, helpers): def insert_data(self, typeof, data): """ Inserts data into HIASHDI. """ - api_host = "https://" + self.helpers.credentials["server"]["host"] + "/" + \ + api_host = "http://" + self.helpers.credentials["server"]["host"] + "/" + \ self.helpers.credentials["hiashdi"]["endpoint"] api_endpoint = "/data?type=" + typeof api_url = api_host + api_endpoint @@ -74,3 +75,195 @@ def insert_data(self, typeof, data): return response.headers["Id"] else: return False + + def entity_status_data(self, entity, entity_type, location, zone, status): + + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Status": status, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_life_data(self, entity, entity_type, location, zone, data): + + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Data": data, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_actuator_data(self, entity, entity_type, location, zone, data): + + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Actuator": data["Name"], + "Type": data["Type"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_actuator_command_data(self, entity, entity_type, location, zone, data): + + return { + "Use": data["Use"], + "From": entity, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Property": data["Property"], + "Type": data["Type"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_sensor_data(self, entity, entity_type, location, zone, data): + + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Sensor": data["Sensor"], + "Type": data["Type"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_notification_data(self, entity, entity_type, location, zone, data): + + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Type": data["Type"], + "Value": data["State"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_ai_model_data(self, entity, entity_type, location, zone, data): + + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "AiAgent": entity, + "Type": data["Type"] if "Type" in data else "State", + "Value": data["Value"] if "Value" in data else data["State"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_state_data(self, entity, entity_type, location, zone, data): + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "AiAgent": entity if entity_type == "AiAgent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Type": data["Type"], + "Value": data["State"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_notification_data(self, location, data): + + return { + "Use": data["Use"], + "From": data["From"], + "To": data["To"], + "Location": location, + "Application": data["To"] if data["Use"] == "Application" else "NA", + "Staff": data["To"] if data["Use"] == "Application" else "NA", + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + def entity_bci_data(self, entity, entity_type, location, zone, data): + return { + "Use": entity_type, + "Location": location, + "Zone": zone, + "HIASBCH": entity if entity_type == "HIASBCH" else "NA", + "HIASCDI": entity if entity_type == "HIASCDI" else "NA", + "HIASHDI": entity if entity_type == "HIASHDI" else "NA", + "Agent": entity if entity_type == "Agent" else "NA", + "Application": entity if entity_type == "Application" else "NA", + "Device": entity if entity_type == "Device" else "NA", + "Staff": entity if entity_type == "Staff" else "NA", + "Robotics": entity if entity_type == "Robotics" else "NA", + "Sensor": data["Sensor"], + "Type": data["Type"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } \ No newline at end of file diff --git a/modules/mqtt.py b/modules/mqtt.py index f01b0d4..4d96faa 100644 --- a/modules/mqtt.py +++ b/modules/mqtt.py @@ -6,8 +6,7 @@ MIT License -Copyright (c) 2021 Asociación de Investigacion en Inteligencia Artificial -Para la Leucemia Peter Moss +Copyright (c) 2023 Peter Moss Leukaemia MedTech Research CIC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal @@ -70,7 +69,8 @@ def __init__(self, 'up' ] - self.helpers.logger.info(self.program + " initialization complete.") + self.helpers.logger.info( + self.program + " initialization complete.") def configure(self): """ Connection configuration. @@ -81,31 +81,37 @@ def configure(self): self.client_id = self.configs['name'] for param in self.agent: if self.configs[param] is None: - raise ConfigurationException(param + " parameter is required!") + raise ConfigurationException( + param + " parameter is required!") # Sets MQTT connection configuration - self.mqtt_config["tls"] = "/etc/ssl/certs/DST_Root_CA_X3.pem" self.mqtt_config["host"] = self.configs['host'] self.mqtt_config["port"] = self.configs['port'] + self.mqtt_config["security"] = self.configs['security'] + if self.mqtt_config["security"]: + self.mqtt_config["tls"] = "/etc/ssl/certs/ISRG_Root_X1.pem" # Sets MQTT topics self.module_topics["statusTopic"] = '%s/Agents/%s/%s/Status' % ( self.configs['location'], self.configs['zone'], self.configs['entity']) # Sets MQTT callbacks - self.actuator_callback = None + self.actuators_callback = None + self.ai_model_callback = None + self.ai_agent_callback = None self.bci_callback = None + self.classification_callback = None self.comands_callback = None self.integrityCallback = None self.life_callback = None - self.ai_model_callback = None + self.notifications_callback = None self.sensors_callback = None - self.stateCallback = None + self.state_callback = None self.status_callback = None self.zoneCallback = None self.helpers.logger.info( - "iotJumpWay " + self.client_type + " connection configured.") + "iotJumpWay " + self.client_type + " connection configured.") def start(self): """ Connection @@ -113,19 +119,32 @@ def start(self): Starts the HIAS iotJumpWay MQTT connection. """ - self.m_client = pmqtt.Client(client_id=self.client_id, clean_session=True) - self.m_client.will_set(self.module_topics["statusTopic"], "OFFLINE", 0, False) - self.m_client.tls_set(self.mqtt_config["tls"], certfile=None, keyfile=None) + self.m_client = pmqtt.Client( + client_id=self.client_id, clean_session=True) + + self.m_client.will_set( + self.module_topics["statusTopic"], "OFFLINE", 0, False) + + if self.mqtt_config["security"]: + self.m_client.tls_set( + self.mqtt_config["tls"], certfile=None, keyfile=None) + self.m_client.on_connect = self.on_connect self.m_client.on_message = self.on_message self.m_client.on_publish = self.on_publish self.m_client.on_subscribe = self.on_subscribe - self.m_client.username_pw_set(str(self.configs['un']), str(self.configs['up'])) - self.m_client.connect(self.mqtt_config["host"], self.mqtt_config["port"], 10) + self.m_client.on_disconnect = self.on_disconnect + + self.m_client.username_pw_set( + str(self.configs['un']), str(self.configs['up'])) + + self.m_client.connect( + self.mqtt_config["host"], self.mqtt_config["port"], 10) + self.m_client.loop_start() self.helpers.logger.info( - "iotJumpWay " + self.client_type + " connection started.") + "iotJumpWay " + self.client_type + " connection started.") def on_connect(self, client, obj, flags, rc): """ On connection @@ -135,21 +154,39 @@ def on_connect(self, client, obj, flags, rc): if self.is_connected != True: self.is_connected = True - - self.helpers.logger.info("iotJumpWay " + self.client_type + " connection successful.") - self.helpers.logger.info("rc: " + str(rc)) - + + self.helpers.logger.info( + "iotJumpWay " + self.client_type + " connection successful.") + + self.helpers.logger.info( + "rc: " + str(rc)) + self.status_publish("ONLINE") self.subscribe() + def on_disconnect(self, client, userdata, rc): + """ On connection + + On connection callback. + """ + + self.helpers.logger.info( + "iotJumpWay " + self.client_type + " disconnected.") + + print(userdata) + print(rc) + def status_publish(self, data): """ Status publish Publishes a status. """ - self.m_client.publish(self.module_topics["statusTopic"], data) - self.helpers.logger.info("Published to " + self.client_type + " status.") + self.m_client.publish( + self.module_topics["statusTopic"], data) + + self.helpers.logger.info( + "Published to " + self.client_type + " status.") def on_subscribe(self, client, obj, mid, granted_qos): """ On subscribe @@ -157,7 +194,8 @@ def on_subscribe(self, client, obj, mid, granted_qos): On subscription callback. """ - self.helpers.logger.info("iotJumpWay " + self.client_type + " subscription") + self.helpers.logger.info( + "iotJumpWay " + self.client_type + " subscription") def on_message(self, client, obj, msg): """ On message @@ -170,7 +208,9 @@ def on_message(self, client, obj, msg): if conn_type == "Agents": topic = split_topic[4] - elif conn_type == "AI": + elif conn_type == "AiModels": + topic = split_topic[4] + elif conn_type == "AiAgents": topic = split_topic[4] elif conn_type == "Applications": topic = split_topic[3] @@ -188,66 +228,85 @@ def on_message(self, client, obj, msg): topic = split_topic[3] self.helpers.logger.info(msg.payload) - self.helpers.logger.info("iotJumpWay " + conn_type + " " + msg.topic + " communication received.") + self.helpers.logger.info( + "iotJumpWay " + conn_type + " " + msg.topic + " communication received.") if topic == 'Actuators': - if self.actuator_callback == None: + if self.actuators_callback == None: self.helpers.logger.info( - conn_type + " actuator callback required (actuator_callback) !") + conn_type + " actuators callback required (actuators_callback)!") else: - self.actuator_callback(msg.topic, msg.payload) - elif topic == 'AI': + self.actuator_state_callback(msg.topic, msg.payload) + elif topic == 'AiAgent': + if self.ai_agent_callback == None: + self.helpers.logger.info( + conn_type + " AI Agent callback required (ai_agent_callback)!") + else: + self.ai_agent_callback(msg.topic, msg.payload) + elif topic == 'AiModel': if self.ai_model_callback == None: self.helpers.logger.info( - conn_type + " AI callback required (ai_model_callback) !") + conn_type + " AI Model callback required (ai_model_callback)!") else: self.ai_model_callback(msg.topic, msg.payload) elif topic == 'BCI': if self.bci_callback == None: self.helpers.logger.info( - conn_type + " BCI callback required (bci_callback) !") + conn_type + " BCI callback required (bci_callback)!") else: self.bci_callback(msg.topic, msg.payload) + elif topic == 'Classification': + if self.classification_callback == None: + self.helpers.logger.info( + conn_type + " classification callback required (classification_callback)!") + else: + self.classification_callback(msg.topic, msg.payload) elif topic == 'Commands': if self.comands_callback == None: self.helpers.logger.info( - conn_type + " comands callback required (comands_callback) !") + conn_type + " comands callback required (comands_callback)!") else: self.comands_callback(msg.topic, msg.payload) elif topic == 'Integrity': if self.integrityCallback == None: self.helpers.logger.info( - conn_type + " Integrity callback required (integrityCallback) !") + conn_type + " Integrity callback required (integrityCallback)!") else: self.integrityCallback(msg.topic, msg.payload) elif topic == 'Life': if self.life_callback == None: self.helpers.logger.info( - conn_type + " life callback required (life_callback) !") + conn_type + " life callback required (life_callback)!") else: self.life_callback(msg.topic, msg.payload) + elif topic == 'Notifications': + if self.notifications_callback == None: + self.helpers.logger.info( + conn_type + " notifications callback required (notifications_callback)!") + else: + self.notifications_callback(msg.topic, msg.payload) elif topic == 'Sensors': if self.sensors_callback == None: self.helpers.logger.info( - conn_type + " status callback required (sensors_callback) !") + conn_type + " status callback required (sensors_callback)!") else: self.sensors_callback(msg.topic, msg.payload) elif topic == 'State': - if self.stateCallback == None: + if self.state_callback == None: self.helpers.logger.info( - conn_type + " life callback required (stateCallback) !") + conn_type + " state callback required (state_callback)!") else: - self.stateCallback(msg.topic, msg.payload) + self.state_callback(msg.topic, msg.payload) elif topic == 'Status': if self.status_callback == None: self.helpers.logger.info( - conn_type + " status callback required (status_callback) !") + conn_type + " status callback required (status_callback)!") else: self.status_callback(msg.topic, msg.payload) elif topic == 'Zone': if self.zoneCallback == None: self.helpers.logger.info( - conn_type + " status callback required (zoneCallback) !") + conn_type + " status callback required (zoneCallback)!") else: self.zoneCallback(msg.topic, msg.payload) @@ -263,8 +322,11 @@ def publish(self, channel, data, channel_path = ""): channel = '%s/Agents/%s/%s/%s' % (self.configs['location'], self.configs['zone'], self.configs['entity'], channel) - self.m_client.publish(channel, json.dumps(data)) - self.helpers.logger.info("Published to " + channel) + self.m_client.publish( + channel, json.dumps(data)) + + self.helpers.logger.info( + "Published to " + channel) return True def subscribe(self, application = None, channelID = None, qos=0): @@ -275,7 +337,9 @@ def subscribe(self, application = None, channelID = None, qos=0): channel = '%s/#' % (self.configs['location']) self.m_client.subscribe(channel, qos=qos) - self.helpers.logger.info("-- Agent subscribed to all channels") + + self.helpers.logger.info( + "Agent subscribed to all channels") return True def on_publish(self, client, obj, mid): @@ -284,7 +348,8 @@ def on_publish(self, client, obj, mid): On publish callback. """ - self.helpers.logger.info("Published: "+str(mid)) + self.helpers.logger.info( + "Published: "+str(mid)) def on_log(self, client, obj, level, string): """ On log