Repository: CounterFit-IoT/CounterFit Branch: main Commit: c2d275986907 Files: 118 Total size: 225.0 KB Directory structure: gitextract_1o2wq7mb/ ├── .devcontainer/ │ ├── Dockerfile │ └── devcontainer.json ├── .gitignore ├── LICENSE ├── README.md ├── counterfit-app/ │ ├── .pylintrc │ ├── LICENSE │ ├── MANIFEST.in │ ├── README.md │ ├── build.sh │ ├── requirements.txt │ ├── setup.cfg │ ├── setup.py │ ├── src/ │ │ ├── CounterFit/ │ │ │ ├── __init__.py │ │ │ ├── actuators.py │ │ │ ├── binary_sensors.py │ │ │ ├── counterfit.py │ │ │ ├── i2c_sensors.py │ │ │ ├── sensors.py │ │ │ ├── serial_sensors.py │ │ │ ├── static/ │ │ │ │ └── style.css │ │ │ └── templates/ │ │ │ ├── binary_sensor_create_settings.html │ │ │ ├── boolean_sensor.html │ │ │ ├── camera_sensor.html │ │ │ ├── float_sensor.html │ │ │ ├── gps_sensor.html │ │ │ ├── home.html │ │ │ ├── i2c_float_sensor.html │ │ │ ├── i2c_integer_sensor.html │ │ │ ├── i2c_sensor_create_settings.html │ │ │ ├── integer_sensor.html │ │ │ ├── led_actuator.html │ │ │ ├── pin_sensor_create_settings.html │ │ │ ├── relay_actuator.html │ │ │ └── serial_sensor_create_settings.html │ │ ├── __main__.py │ │ └── tests/ │ │ ├── __init__.py │ │ ├── route.gpx │ │ └── test_serial_sensors.py │ └── upload.sh ├── images/ │ └── Artwork.sketch ├── requirements.txt ├── samples/ │ └── grove/ │ ├── button-temperature-sensor-controlled-relay/ │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ └── light-sensor-controlled-led/ │ ├── README.md │ ├── app.py │ └── requirements.txt └── shims/ ├── CounterFitConnection/ │ ├── .gitignore │ ├── .pylintrc │ ├── README.md │ ├── build.sh │ ├── counterfit_connection.py │ ├── requirements.txt │ ├── setup.py │ ├── tests/ │ │ ├── __init__.py │ │ └── test_counterfit_connection.py │ └── upload.sh ├── SeeedStudios/ │ ├── Seeed_Python_DHT/ │ │ ├── .pylintrc │ │ ├── README.md │ │ ├── build.sh │ │ ├── counterfit_shims_seeed_python_dht.py │ │ ├── requirements.txt │ │ ├── setup.py │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ └── test_counterfit_shims_seeed_python_dht.py │ │ └── upload.sh │ ├── grove_py/ │ │ ├── .gitignore │ │ ├── .pylintrc │ │ ├── README.md │ │ ├── build.sh │ │ ├── counterfit_shims_grove/ │ │ │ ├── __init__.py │ │ │ ├── adc.py │ │ │ ├── grove_led.py │ │ │ ├── grove_light_sensor_v1_2.py │ │ │ └── grove_relay.py │ │ ├── requirements.txt │ │ ├── setup.py │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ ├── test_grove_led.py │ │ │ ├── test_grove_light_sensor_v1_2.py │ │ │ └── test_grove_relay.py │ │ └── upload.sh │ └── seeed-python-si114x/ │ ├── README.md │ ├── build.sh │ ├── counterfit_shims_seeed_si114x.py │ ├── requirements.txt │ ├── setup.py │ ├── tests/ │ │ ├── __init__.py │ │ └── test_counterfit_shims_seeed_si114x.py │ └── upload.sh ├── picamera/ │ ├── .gitignore │ ├── README.md │ ├── build.sh │ ├── counterfit_shims_picamera/ │ │ ├── __init__.py │ │ └── camera.py │ ├── requirements.txt │ ├── setup.py │ ├── tests/ │ │ ├── __init__.py │ │ └── test_counterfit_shims_picamera.py │ └── upload.sh ├── pyserial/ │ ├── README.md │ ├── build.sh │ ├── counterfit_shims_serial.py │ ├── requirements.txt │ ├── setup.py │ ├── tests/ │ │ ├── __init__.py │ │ └── test_counterfit_shims_serial.py │ └── upload.sh └── rpi_vl53l0x/ ├── README.md ├── build.sh ├── counterfit_shims_rpi_vl53l0x/ │ ├── __init__.py │ └── vl53l0x.py ├── requirements.txt ├── setup.py ├── tests/ │ ├── __init__.py │ └── test_counterfit_shims_rpi_vl53l0x.py └── upload.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainer/Dockerfile ================================================ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.236.0/containers/python-3/.devcontainer/base.Dockerfile # [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster ARG VARIANT="3.10-bullseye" FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 ARG NODE_VERSION="none" RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. # COPY requirements.txt /tmp/pip-tmp/ # RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ # && rm -rf /tmp/pip-tmp # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends # [Optional] Uncomment this line to install global node packages. # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 ================================================ FILE: .devcontainer/devcontainer.json ================================================ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.236.0/containers/python-3 { "name": "Python 3", "build": { "dockerfile": "Dockerfile", "context": "..", "args": { // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local on arm64/Apple Silicon. "VARIANT": "3.10-bullseye", // Options "NODE_VERSION": "none" } }, // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. "vscode": { // Set *default* container specific settings.json values on container create. "settings": { "python.defaultInterpreterPath": "/usr/local/bin/python", "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-python.python", "ms-python.vscode-pylance", "github.copilot" ] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "pip3 install --user -r requirements.txt", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" } ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ .vscode .DS_Store ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Jim Bennett 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. ================================================ FILE: README.md ================================================ # CounterFit [![GitHub license](https://img.shields.io/github/license/CounterFit-IoT/CounterFit.svg)](https://github.com/CounterFit-IoT/CounterFit/blob/master/LICENSE) [![GitHub contributors](https://img.shields.io/github/contributors/CounterFit-IoT/CounterFit.svg)](https://GitHub.com/CounterFit-IoT/CounterFit/graphs/contributors/) [![GitHub issues](https://img.shields.io/github/issues/CounterFit-IoT/CounterFit.svg)](https://GitHub.com/CounterFit-IoT/CounterFit/issues/) [![GitHub pull-requests](https://img.shields.io/github/issues-pr/CounterFit-IoT/CounterFit.svg)](https://GitHub.com/CounterFit-IoT/CounterFit/pull/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub watchers](https://img.shields.io/github/watchers/CounterFit-IoT/CounterFit.svg?style=social&label=Watch&maxAge=2592000)](https://GitHub.com/CounterFit-IoT/CounterFit/watchers/) [![GitHub forks](https://img.shields.io/github/forks/CounterFit-IoT/CounterFit.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/CounterFit-IoT/CounterFit/network/) [![GitHub stars](https://img.shields.io/github/stars/CounterFit-IoT/CounterFit.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/CounterFit-IoT/CounterFit/stargazers/) ![The CounterFit logo](./images/CounterFitLogo.png) IoT is great fun, but has a downside - hardware. You need access to a range of devices such as sensors and actuators to build your IoT projects. Sometimes you might have these devices, other times you may not - maybe you are waiting for a delivery, or parts are out of stock, or they are too expensive. That's where this tool comes in. ## What is CounterFit CounterFit is a tool that is designed to fake various IoT hardware components, such as LEDs, buttons, temperature sensors and the like, that you can then access from IoT device code running on your computer rather than on an IoT device. It is made of two parts: * The CounterFit app - this is a web app run locally where you can connect fake sensors and actuators to your virtual hardware * Shims - these are libraries that fake popular hardware APIs so you can take code that runs against well known hardware and run it against the CounterFit app. **This project is under construction** This project is seriously under construction! Please let me know if you want to help. ![Under development animated GIF](https://media.giphy.com/media/3o7qE1YN7aBOFPRw8E/giphy.gif) ## Installing and running the app * Install the CounterFit app: ```sh pip install CounterFit ``` * Run the app: ```sh counterfit ``` * The app will launch, listening for web requests on port 5000, and open a web browser for you to start adding virtual sensors and actuators to your project ### Running on a different port To use a different port than the default 5000, set the `--port` option when you run the app: ```sh counterfit --port 5050 ``` ## Shims The shims are designed to mimic the APIs for popular hardware components. The idea being you should be able to take code built against the shim and eventually run it on real hardware by changing the name of the package that is imported. ### Available shims * ![Seeed Grove Py Shim](https://img.shields.io/badge/Platform-Python-green) [![Seeed Grove Py Shim](https://img.shields.io/badge/Shim-Grove.py-yellow)](./shims/SeeedStudios/grove/README.md) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-grove)](https://pypi.org/project/counterfit-shims-grove) [Grove.Py](https://github.com/Seeed-Studio/grove.py) shims that work with the [Seeed Grove ecosystem](https://www.seeedstudio.com/category/Grove-c-1003.html). * ![Seeed Grove DHT Shim](https://img.shields.io/badge/Platform-Python-green) [![Seeed DHT Shim](https://img.shields.io/badge/Shim-Seeed_DHT-yellow)](./shims/SeeedStudios/grove/README.md) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-seeed-python-dht)](https://pypi.org/project/counterfit-shims-seeed-python-dht) [Seeed DHT](https://github.com/Seeed-Studio/Seeed_Python_DHT) shims that work with the [Seeed DHT sensors](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html). ## Samples Check out the [samples](./samples) directory for a range of samples. ================================================ FILE: counterfit-app/.pylintrc ================================================ [FORMAT] # Maximum number of characters on a single line. max-line-length=140 [DESIGN] # Maximum number of attributes for a class (see R0902). max-attributes=13 [MESSAGES CONTROL] disable=trailing-whitespace,R0801,C0114,C0115,C0116 ================================================ FILE: counterfit-app/LICENSE ================================================ MIT License Copyright (c) 2021 Jim Bennett 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. ================================================ FILE: counterfit-app/MANIFEST.in ================================================ include src/CounterFit/static/*.* include src/CounterFit/static/images/*.* include src/CounterFit/templates/*.* ================================================ FILE: counterfit-app/README.md ================================================ # CounterFit IoT is great fun, but has a downside - hardware. You need access to a range of devices such as sensors and actuators to build your IoT projects. Sometimes you might have these devices, other times you may not - maybe you are waiting for a delivery, or parts are out of stock, or they are too expensive. That's where this tool comes in. ## What is CounterFit CounterFit is a tool that is designed to fake various IoT hardware components, such as LEDs, buttons, temperature sensors and the like, that you can then access from IoT device code running on your computer rather than on an IoT device. It is made of two parts: * The CounterFit app - this is a web app run locally where you can connect fake sensors and actuators to your virtual hardware * Shims - these are libraries that mimic popular hardware APIs so you can take code that runs against well known hardware and run it against the CounterFit app. ## Getting started * Install the CounterFit app: ```sh pip install CounterFit ``` * Run the app: ```sh CounterFit ``` * The app will launch, listening for web requests on port 5000, and open a web browser for you to start adding virtual sensors and actuators to your project ### Running on a different port To use a different port than the default 5000, set the `--port` option when you run the app: ```sh CounterFit --port 5050 ``` ## Connecting your code You can connect your device code to CounterFit, using one of the available shims. See the [shim list for more details](https://github.com/CounterFit-IoT/CounterFit#shims). ================================================ FILE: counterfit-app/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: counterfit-app/requirements.txt ================================================ flask=2.1.2 flask-socketio=5.2.0 eventlet=0.33.3 setuptools=65.5.0 twine=4.0.2 wheel=0.38.4 pytest=7.2.2 pytest-runner=6.0.0 ================================================ FILE: counterfit-app/setup.cfg ================================================ [metadata] name = CounterFit version = attr: CounterFit.__version__ url = https://github.com/CounterFit-IoT/CounterFit project_urls = Documentation = https://github.com/CounterFit-IoT/CounterFit Source Code = https://github.com/CounterFit-IoT/CounterFit Issue Tracker = https://github.com/CounterFit-IoT/CounterFit/issues Twitter = https://twitter.com/JimBobBennett license = MIT author = Jim Bennett maintainer = Jim Bennett description = A virtual IoT hardware simulator. long_description = file: README.md long_description_content_type = text/markdown classifiers = Development Status :: 2 - Pre-Alpha Intended Audience :: Developers Topic :: System :: Hardware License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python [options] packages = find: package_dir = = src include_package_data = true python_requires = >= 3.8 # Dependencies are in setup.py for GitHub's dependency graph. [options.packages.find] where = src [options.entry_points] console_scripts = counterfit = CounterFit.counterfit:main [tool:pytest] testpaths = src/tests ================================================ FILE: counterfit-app/setup.py ================================================ from setuptools import setup setup( name='CounterFit', install_requires=[ "Flask==2.1.2", "Flask-SocketIO==5.2.0", "eventlet==0.33.3" ], tests_require=['pytest==7.2.2'], test_suite='tests', ) ================================================ FILE: counterfit-app/src/CounterFit/__init__.py ================================================ # pylint: disable=C0103 from CounterFit.sensors import * from CounterFit.serial_sensors import * from CounterFit.binary_sensors import * from CounterFit.i2c_sensors import * from CounterFit.actuators import * __version__ = "0.1.4.dev09" ================================================ FILE: counterfit-app/src/CounterFit/actuators.py ================================================ from abc import ABC, abstractmethod from enum import Enum class ActuatorType(Enum): FLOAT = 1 BOOLEAN = 2 class ActuatorBase(ABC): def __init__(self, port: str): self.__port = port @staticmethod @abstractmethod def actuator_name() -> str: pass @staticmethod @abstractmethod def actuator_type() -> ActuatorType: pass @property def port(self) -> str: return self.__port @property # pylint: disable=invalid-name def id(self) -> str: return self.__port class FloatActuatorBase(ActuatorBase): def __init__(self, port: str): super().__init__(port) self.__value = 0 @staticmethod @abstractmethod def actuator_name() -> str: pass @staticmethod def actuator_type() -> ActuatorType: return ActuatorType.FLOAT @property def value(self) -> float: return self.__value @value.setter def value(self, val: float): self.__value = val class BooleanActuatorBase(ActuatorBase): def __init__(self, port: str): super().__init__(port) self.__value = False @staticmethod @abstractmethod def actuator_name() -> str: pass @staticmethod def actuator_type() -> ActuatorType: return ActuatorType.BOOLEAN @property def value(self) -> bool: return self.__value @value.setter def value(self, val: bool): self.__value = val class RelayActuator(BooleanActuatorBase): @staticmethod def actuator_name() -> str: return "Relay" class LedActuator(BooleanActuatorBase): def __init__(self, port: str): super().__init__(port) self.__color = "#FF0000" @staticmethod def actuator_name() -> str: return "LED" @property def color(self) -> str: return self.__color @color.setter def color(self, val: str): self.__color = val ================================================ FILE: counterfit-app/src/CounterFit/binary_sensors.py ================================================ from abc import abstractmethod from enum import Enum import io from CounterFit.sensors import SensorBase, SensorType class BinarySensorBase(SensorBase): def __init__(self, name: str): super().__init__(name) self.__value = io.BytesIO() self._next_repeat_time = None self._value_position = 0 @staticmethod def sensor_type() -> SensorType: return SensorType.BINARY @staticmethod @abstractmethod def sensor_name() -> str: pass @property def id(self) -> str: return self.port.replace("/", "").replace(" ", "") @property def value(self) -> io.BytesIO: return self.__value @value.setter def value(self, val: io.BytesIO): self.__value = val class CameraImageSource(Enum): FILE = 1 WEBCAM = 2 class CameraSensor(BinarySensorBase): def __init__(self, name: str): super().__init__(name) self.__image_source = CameraImageSource.FILE self.__image_file_name = "" self.__web_cam_device_id = "" @staticmethod def sensor_name() -> str: return "Camera" @property def image_source(self) -> CameraImageSource: return self.__image_source @image_source.setter def image_source(self, val: CameraImageSource): self.__image_source = val @property def image_file_name(self) -> str: return self.__image_file_name @image_file_name.setter def image_file_name(self, val: str): self.__image_file_name = val @property def web_cam_device_id(self) -> str: return self.__web_cam_device_id @web_cam_device_id.setter def web_cam_device_id(self, val: str): self.__web_cam_device_id = val ================================================ FILE: counterfit-app/src/CounterFit/counterfit.py ================================================ # pylint: disable=C0103,E0401,W0603 import argparse import io import json import uuid import webbrowser from base64 import b64decode, b64encode from threading import Timer from eventlet import event from eventlet.timeout import Timeout from flask import Flask, request, render_template from flask_socketio import SocketIO from CounterFit.sensors import SensorBase, SensorType from CounterFit.serial_sensors import GPSSensor, GPSValueType, SerialSensorBase from CounterFit.binary_sensors import BinarySensorBase, CameraImageSource, CameraSensor from CounterFit.actuators import ActuatorBase app = Flask(__name__) app.config["SECRET_KEY"] = "247783f3-bdda-4536-bffc-109e2464f10b" socketio = SocketIO(app) sensor_cache = {} actuator_cache = {} all_sensors = [] all_actuators = [] is_connected = False def get_all_subclasses(cls, class_list): for sub_class in cls.__subclasses__(): if len(sub_class.__abstractmethods__) == 0: class_list.append(sub_class) get_all_subclasses(sub_class, class_list) get_all_subclasses(SensorBase, all_sensors) get_all_subclasses(ActuatorBase, all_actuators) all_sensors = sorted(all_sensors, key=lambda s: s.sensor_name()) all_actuators = sorted(all_actuators, key=lambda a: a.actuator_name()) @app.route("/", methods=["GET"]) def home(): ports = [] ports_and__hex = [] for port in range(0, 127): str_port = str(port) if str_port not in sensor_cache and str_port not in actuator_cache: ports.append(str_port) ports_and__hex.append((str_port, f"0x{port:02x}")) return render_template( "home.html", sensors=sensor_cache.values(), actuators=actuator_cache.values(), all_sensors=all_sensors, all_actuators=all_actuators, is_connected=is_connected, ports=ports, ports_and__hex=ports_and__hex, ) def set_and_send_connected(connected: bool = True) -> None: global is_connected is_connected = connected socketio.emit("device_connect", {"connected": is_connected}) @app.route("/connect", methods=["POST"]) def device_connect(): set_and_send_connected() return "OK", 200 @app.route("/disconnect", methods=["POST"]) def device_disconnect(): set_and_send_connected(False) return "OK", 200 def create_pin_sensor(sensor, body): port = str(body["pin"]) unit = body["unit"] if sensor.sensor_type() == SensorType.FLOAT: new_sensor = sensor(port, unit) elif sensor.sensor_type() == SensorType.INTEGER: new_sensor = sensor(port, unit) else: new_sensor = sensor(port) sensor_cache[port.lower()] = new_sensor def create_serial_sensor(sensor, body): port = body["port"] new_sensor = sensor(port) sensor_cache[port.lower()] = new_sensor def create_binary_sensor(sensor, body): name = body["name"] new_sensor = sensor(name) sensor_cache[name.lower()] = new_sensor def create_i2c_sensor(sensor, body): port = str(body["i2c_pin"]) unit = body["i2c_unit"] new_sensor = sensor(port, unit) sensor_cache[port.lower()] = new_sensor @app.route("/create_sensor", methods=["POST"]) def create_sensor(): body = request.get_json() print("Create sensor called:", body) sensor_type = body["type"] for sensor in all_sensors: if sensor.sensor_name() == sensor_type: if sensor.sensor_type() == SensorType.SERIAL: create_serial_sensor(sensor, body) elif sensor.sensor_type() == SensorType.BINARY: create_binary_sensor(sensor, body) elif sensor.sensor_type() == SensorType.I2C: create_i2c_sensor(sensor, body) else: create_pin_sensor(sensor, body) return "OK", 200 @app.route("/create_actuator", methods=["POST"]) def create_actuator(): body = request.get_json() print("Create actuator called:", body) actuator_type = body["type"] port = str(body["port"]) for actuator in all_actuators: if actuator.actuator_name() == actuator_type: new_actuator = actuator(port) actuator_cache[port.lower()] = new_actuator return "OK", 200 @app.route("/sensor_value", methods=["GET"]) def get_sensor_value(): set_and_send_connected() port = str(request.args.get("port", "")) if port.lower() in sensor_cache: sensor = sensor_cache[port.lower()] response = {"value": sensor.value} print("Returning sensor value", response, "for port", port) return json.dumps(response) return "Sensor with port " + str(port) + " not found", 404 @app.route("/serial_sensor_character", methods=["GET"]) def get_serial_sensor_character(): set_and_send_connected() port = str(request.args.get("port", "")) if port.lower() in sensor_cache: sensor: SerialSensorBase = sensor_cache[port.lower()] response = {"value": sensor.read()} print("Returning sensor value", response, "for port", port) return json.dumps(response) return "Sensor with port " + str(port) + " not found", 404 @app.route("/serial_sensor_line", methods=["GET"]) def get_serial_sensor_line(): set_and_send_connected() port = str(request.args.get("port", "")) if port.lower() in sensor_cache: sensor: SerialSensorBase = sensor_cache[port.lower()] response = {"value": sensor.read_line()} print("Returning sensor value", response, "for port", port) return json.dumps(response) return "Sensor with port " + str(port) + " not found", 404 events = {} def capture_camera_image_response(data): try: e = events[data["uuid"]] port = data["port"] image: str = data["image_base64"] image = image.replace("data:image/jpeg;base64,", "").strip() camera_sensor: CameraSensor = sensor_cache[port.lower()] msg = b64decode(image) camera_sensor.value = io.BytesIO(msg) e.send(data) del events[data["uuid"]] except KeyError: pass def capture_camera_image(sensor: CameraSensor, port) -> bool: if sensor.image_source == CameraImageSource.WEBCAM: u = str(uuid.uuid4()) req = {"uuid": u, "port": port} socketio.emit( "capture_camera_from_webcam" + str(port).replace("/", "").replace(" ", ""), req, callback=capture_camera_image_response, ) timeout = Timeout(10) try: e = events[u] = event.Event() e.wait(10) except Timeout: return False finally: events.pop(u, None) timeout.cancel() return True @app.route("/binary_sensor_data", methods=["GET"]) def get_binary_sensor_data(): set_and_send_connected() port = str(request.args.get("port", "")) if port.lower() in sensor_cache: if isinstance(sensor_cache[port.lower()], CameraSensor): camera_sensor: CameraSensor = sensor_cache[port.lower()] if not capture_camera_image(camera_sensor, port): return ( "Timeout capturing camera image from webcam for camera " + str(port), 504, ) sensor: BinarySensorBase = sensor_cache[port.lower()] sensor.value.seek(0) img_byte = sensor.value.getvalue() response = {"value": b64encode(img_byte).decode()} print("Returning sensor value", str(response)[0:500], "for port", port) return json.dumps(response) return "Sensor with port " + str(port) + " not found", 404 @app.route("/delete_sensor", methods=["POST"]) def delete_sensor(): body = request.get_json() print("Delete sensor called:", body) port = body["port"] if port.lower() in sensor_cache: del sensor_cache[port.lower()] return "OK", 200 @app.route("/delete_actuator", methods=["POST"]) def delete_actuator(): body = request.get_json() print("Delete actuator called:", body) port = body["port"] if port.lower() in actuator_cache: del actuator_cache[port.lower()] return "OK", 200 @app.route("/float_sensor_settings", methods=["POST"]) def set_float_sensor_settings(): body = request.get_json() print("Float sensor settings called:", body) port = body["port"] value = body["value"] is_random = body["is_random"] random_min = body["random_min"] random_max = body["random_max"] if port.lower() in sensor_cache: sensor = sensor_cache[port.lower()] sensor.value = value sensor.random = is_random sensor.random_min = random_min sensor.random_max = random_max return "OK", 200 @app.route("/integer_sensor_settings", methods=["POST"]) def set_integer_sensor_settings(): body = request.get_json() print("Integer sensor settings called:", body) port = body["port"] value = body["value"] is_random = body["is_random"] random_min = body["random_min"] random_max = body["random_max"] if port.lower() in sensor_cache: sensor = sensor_cache[port.lower()] sensor.value = value sensor.random = is_random sensor.random_min = random_min sensor.random_max = random_max return "OK", 200 @app.route("/led_actuator_settings", methods=["POST"]) def set_led_actuator_settings(): body = request.get_json() print("LED actuator settings called:", body) port = body["port"] color = body["color"] if port.lower() in actuator_cache: actuator = actuator_cache[port.lower()] actuator.color = color return "OK", 200 @app.route("/boolean_sensor_settings", methods=["POST"]) def set_boolean_sensor_settings(): body = request.get_json() print("Boolean sensor settings called:", body) port = body["port"] value = body["value"] is_random = body["is_random"] if port.lower() in sensor_cache: sensor = sensor_cache[port.lower()] sensor.value = value sensor.random = is_random return "OK", 200 @app.route("/gps_sensor_settings", methods=["POST"]) def set_gps_sensor_settings(): body = request.get_json() print("GPS sensor settings called:", body) port = body["port"] if port.lower() in sensor_cache: sensor: GPSSensor = sensor_cache[port.lower()] sensor.repeat = body["repeat"] source = body["source"] if source == "latlon": sensor.value_type = GPSValueType.LATLON sensor.lat = body["lat"] sensor.lon = body["lon"] sensor.number_of_satellites = body["number_of_satellites"] elif source == "nmeasentences": sensor.value_type = GPSValueType.NMEA sensor.raw_nmea = body["nmea"] else: sensor.value_type = GPSValueType.GPX sensor.gpx_file_contents = body["gpx"] sensor.gpx_file_name = body["gpx_file_name"] return "OK", 200 @app.route("/camera_sensor_settings", methods=["POST"]) def set_camera_sensor_settings(): body = request.get_json() print("Camera sensor settings called:", str(body)[0:500]) port = body["port"] if port.lower() in sensor_cache: sensor: CameraSensor = sensor_cache[port.lower()] sensor.image_source = ( CameraImageSource.FILE if body["source"] == "File" else CameraImageSource.WEBCAM ) if sensor.image_source == CameraImageSource.FILE: sensor.image_file_name = body["image_file_name"] msg = b64decode(body["file_contents"]) sensor.value = io.BytesIO(msg) else: sensor.web_cam_device_id = body["web_cam_device_id"] return "OK", 200 @app.route("/sensor_units", methods=["POST"]) def get_sensor_units(): body = request.get_json() print("Sensor units called:", body) sensor_type = body["type"] # pylint: disable=R1705 for sensor in all_sensors: if sensor.sensor_name() == sensor_type: if ( sensor.sensor_type() == SensorType.FLOAT or sensor.sensor_type() == SensorType.INTEGER or sensor.sensor_type() == SensorType.I2C ): return {"units": sensor.sensor_units()} else: return {"units": []} return "Not found", 404 @app.route("/actuator_value", methods=["POST"]) def set_actuator_value(): set_and_send_connected() port = str(request.args.get("port", "")) body = request.get_json() print("Actuator value called:", body, "for port", port) value = body["value"] if port.lower() in actuator_cache: actuator = actuator_cache[port.lower()] actuator.value = value socketio.emit("actuator_change" + str(port), {"port": port, "value": value}) return "OK", 200 def open_browser(port): webbrowser.open_new(f"http://127.0.0.1:{port}/") def main(): parser = argparse.ArgumentParser() parser.add_argument( "--port", metavar="port", type=int, default=5000, help="the port to run on" ) parser.add_argument( "--dontopen", action="store_true", help="If this is present, CounterFit is not automatically opened in a browser", ) args = parser.parse_args() print(f"CounterFit - virtual IoT hardware running on port {args.port}") if args.dontopen is None: print("Loading browser...") Timer(3, open_browser, [args.port]).start() socketio.run(app, host="0.0.0.0", port=args.port) if __name__ == "__main__": main() ================================================ FILE: counterfit-app/src/CounterFit/i2c_sensors.py ================================================ from abc import abstractmethod from enum import Enum from typing import List import random from CounterFit.sensors import SensorBase, SensorType class I2CSensorBase(SensorBase): @staticmethod def sensor_type() -> SensorType: return SensorType.I2C @staticmethod @abstractmethod def sensor_unit_type() -> SensorType: pass @staticmethod @abstractmethod def sensor_name() -> str: pass @property def id(self) -> str: return self.port @property def address(self) -> str: return f"0x{int(self.port):02x}" class FloatI2CSensorBase(I2CSensorBase): def __init__(self, port: str, valid_min: float, valid_max: float): super().__init__(port) self.__valid_min = valid_min self.__valid_max = valid_max self.value = valid_min self.random_min = float(valid_min) self.random_max = float(valid_max) @staticmethod @abstractmethod def sensor_name() -> str: pass @staticmethod @abstractmethod def sensor_units() -> List[str]: pass @staticmethod def sensor_unit_type() -> SensorType: return SensorType.FLOAT @property @abstractmethod def unit(self) -> str: pass @property def value(self) -> float: if self._random: return round(random.uniform(self.__random_min, self.__random_max), 2) return self.__value @value.setter def value(self, val: float): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__value = val @property def random_min(self) -> float: return self.__random_min @random_min.setter def random_min(self, val: float): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_min = val @property def random_max(self) -> float: return self.__random_max @random_max.setter def random_max(self, val: float): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_max = val @property def valid_min(self) -> float: return self.__valid_min @property def valid_max(self) -> float: return self.__valid_max class IntegerI2CSensorBase(I2CSensorBase): def __init__(self, port: str, valid_min: int, valid_max: int): super().__init__(port) self.__valid_min = valid_min self.__valid_max = valid_max self.value = valid_min self.random_min = int(valid_min) self.random_max = int(valid_max) @staticmethod @abstractmethod def sensor_name() -> str: pass @staticmethod @abstractmethod def sensor_units() -> List[str]: pass @staticmethod def sensor_unit_type() -> SensorType: return SensorType.INTEGER @property @abstractmethod def unit(self) -> str: pass @property def value(self) -> int: if self._random: return random.randint(self.__random_min, self.__random_max) return self.__value @value.setter def value(self, val: int): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__value = val @property def random_min(self) -> int: return self.__random_min @random_min.setter def random_min(self, val: int): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_min = val @property def random_max(self) -> int: return self.__random_max @random_max.setter def random_max(self, val: int): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_max = val @property def valid_min(self) -> int: return self.__valid_min @property def valid_max(self) -> int: return self.__valid_max # pylint: disable=C0103 class DistanceUnit(Enum): Millimeter = 1 class DistanceSensor(IntegerI2CSensorBase): def __init__(self, port: str, unit): if isinstance(unit, str): unit = DistanceUnit[unit] self.__unit = unit super().__init__(port, 0, 999999) @staticmethod def sensor_name() -> str: return "Distance" @property def unit(self) -> str: return self.__unit.name @staticmethod def sensor_units() -> List[str]: return [DistanceUnit.Millimeter.name] ================================================ FILE: counterfit-app/src/CounterFit/sensors.py ================================================ from abc import ABC, abstractmethod from enum import Enum from typing import List import random class SensorType(Enum): FLOAT = 1 INTEGER = 2 BOOLEAN = 3 SERIAL = 4 BINARY = 5 I2C = 6 class SensorBase(ABC): def __init__(self, port: str): self.__port = port self._random = False @staticmethod @abstractmethod def sensor_name() -> str: pass @staticmethod @abstractmethod def sensor_type() -> SensorType: pass @property # pylint: disable=invalid-name def id(self) -> str: return self.__port @property def port(self) -> str: return self.__port @property def random(self) -> bool: return self._random @random.setter def random(self, val: bool): self._random = val # pylint: disable=C0103 class DefaultUnit(Enum): NoUnits = 1 # pylint: disable=C0103 class PercentUnit(Enum): Percent = 1 class FloatSensorBase(SensorBase): def __init__(self, port: str, valid_min: float, valid_max: float): super().__init__(port) self.__valid_min = valid_min self.__valid_max = valid_max self.value = valid_min self.random_min = float(valid_min) self.random_max = float(valid_max) @staticmethod @abstractmethod def sensor_name() -> str: pass @staticmethod @abstractmethod def sensor_units() -> List[str]: pass @property @abstractmethod def unit(self) -> str: pass @staticmethod def sensor_type() -> SensorType: return SensorType.FLOAT @property def value(self) -> float: if self._random: return round(random.uniform(self.__random_min, self.__random_max), 2) return self.__value @value.setter def value(self, val: float): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__value = val @property def random_min(self) -> float: return self.__random_min @random_min.setter def random_min(self, val: float): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_min = val @property def random_max(self) -> float: return self.__random_max @random_max.setter def random_max(self, val: float): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_max = val @property def valid_min(self) -> float: return self.__valid_min @property def valid_max(self) -> float: return self.__valid_max class IntegerSensorBase(SensorBase): def __init__(self, port: str, valid_min: int, valid_max: int): super().__init__(port) self.__valid_min = valid_min self.__valid_max = valid_max self.value = valid_min self.random_min = int(valid_min) self.random_max = int(valid_max) @staticmethod @abstractmethod def sensor_name() -> str: pass @staticmethod @abstractmethod def sensor_units() -> List[str]: pass @property @abstractmethod def unit(self) -> str: pass @staticmethod def sensor_type() -> SensorType: return SensorType.INTEGER @property def value(self) -> int: if self._random: return random.randint(self.__random_min, self.__random_max) return self.__value @value.setter def value(self, val: int): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__value = val @property def random_min(self) -> int: return self.__random_min @random_min.setter def random_min(self, val: int): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_min = val @property def random_max(self) -> int: return self.__random_max @random_max.setter def random_max(self, val: int): if val < self.__valid_min or val > self.__valid_max: raise ValueError() self.__random_max = val @property def valid_min(self) -> int: return self.__valid_min @property def valid_max(self) -> int: return self.__valid_max class BooleanSensorBase(SensorBase): def __init__(self, port: str): super().__init__(port) self.value = False @staticmethod @abstractmethod def sensor_name() -> str: pass @staticmethod def sensor_type() -> SensorType: return SensorType.BOOLEAN @property def value(self) -> bool: if self._random: return random.choice([True, False]) return self.__value @value.setter def value(self, val: bool): self.__value = val # pylint: disable=C0103 class TemperatureUnit(Enum): Celsius = 1 Fahrenheit = 2 Kelvin = 3 class TemperatureSensor(FloatSensorBase): def __init__(self, port: str, unit): if isinstance(unit, str): unit = TemperatureUnit[unit] self.__unit = unit if self.__unit == TemperatureUnit.Celsius: valid_min = -273.15 elif self.__unit == TemperatureUnit.Fahrenheit: valid_min = -459.67 else: valid_min = 0 super().__init__(port, valid_min, 999999999.0) @staticmethod def sensor_name() -> str: return "Temperature" @property def unit(self) -> str: return self.__unit.name @staticmethod def sensor_units() -> List[str]: return [ TemperatureUnit.Celsius.name, TemperatureUnit.Fahrenheit.name, TemperatureUnit.Kelvin.name, ] class HumiditySensor(FloatSensorBase): def __init__(self, port: str, unit): if isinstance(unit, str): unit = PercentUnit[unit] self.__unit = unit super().__init__(port, 0.0, 100.0) @staticmethod def sensor_name() -> str: return "Humidity" @property def unit(self) -> str: return self.__unit.name @staticmethod def sensor_units() -> List[str]: return [PercentUnit.Percent.name] # pylint: disable=C0103,C0102 class PressureUnit(Enum): kPa = 1 torr = 2 atm = 3 bar = 4 class PressureSensor(FloatSensorBase): def __init__(self, port: str, unit): if isinstance(unit, str): unit = PressureUnit[unit] self.__unit = unit super().__init__(port, 0, 999999999.0) @staticmethod def sensor_name() -> str: return "Pressure" @property def unit(self) -> str: return self.__unit.name @staticmethod def sensor_units() -> List[str]: return [ PressureUnit.kPa.name, PressureUnit.torr.name, PressureUnit.atm.name, PressureUnit.bar.name, ] class AnalogSensor(IntegerSensorBase): # pylint: disable=W0613 def __init__(self, port: str, unit): super().__init__(port, 0, 1023) @staticmethod @abstractmethod def sensor_name() -> str: pass @property def unit(self) -> str: return DefaultUnit.NoUnits.name @staticmethod def sensor_units() -> List[str]: return [DefaultUnit.NoUnits.name] class LightSensor(AnalogSensor): @staticmethod def sensor_name() -> str: return "Light" class UVSensor(AnalogSensor): @staticmethod def sensor_name() -> str: return "UV" class IRSensor(AnalogSensor): @staticmethod def sensor_name() -> str: return "IR" class SoilMoistureSensor(AnalogSensor): @staticmethod def sensor_name() -> str: return "Soil Moisture" class ButtonSensor(BooleanSensorBase): @staticmethod def sensor_name() -> str: return "Button" @staticmethod def sensor_units() -> List[str]: return [DefaultUnit.NoUnits.name] ================================================ FILE: counterfit-app/src/CounterFit/serial_sensors.py ================================================ from abc import abstractmethod from enum import Enum import datetime import re from xml.dom.minidom import Element, parseString from CounterFit.sensors import SensorBase, SensorType class SerialSensorBase(SensorBase): def __init__(self, port: str): super().__init__(port) self.__value = "" self.__repeat = False self._next_repeat_time = None self._value_position = 0 @staticmethod def sensor_type() -> SensorType: return SensorType.SERIAL @staticmethod @abstractmethod def sensor_name() -> str: pass @property def id(self) -> str: return self.port.replace("/", "").replace(" ", "") @property def value(self) -> str: return self.__value @value.setter def value(self, val: str): self.__value = val self._value_position = 0 @property def repeat(self) -> bool: return self.__repeat @repeat.setter def repeat(self, val: bool): self.__repeat = val def read(self): current_utc = datetime.datetime.utcnow() if self._next_repeat_time is None: self._next_repeat_time = current_utc + datetime.timedelta(0, 1) if self._value_position >= len(self.value): if self.repeat and current_utc > self._next_repeat_time: self._next_repeat_time = current_utc self._value_position = 0 if self._value_position >= len(self.value): return "" char = self.value[self._value_position] self._value_position += 1 return char def read_line(self): line = "" char = self.read() while char not in ("\n", ""): line = line + char char = self.read() return line class GPSValueType(Enum): LATLON = 1 NMEA = 2 GPX = 3 # pylint: disable=too-many-instance-attributes class GPSSensor(SerialSensorBase): def __init__(self, port: str): super().__init__(port) self.__value_type = GPSValueType.LATLON self.__lat = 0.0 self.__lon = 0.0 self.__number_of_satellites = 0 self.__raw_nmea = "" self.__gpx_file_name = "" self.__gpx_file_contents = "" self.__substitute_value = "" self.__substitute_start_position = 0 self.__substitute_end_position = 0 self.__next_gpgga_line_time = None @staticmethod def sensor_name() -> str: return "UART GPS" @staticmethod def _decimal_decrees_to_ddmmm(decimal_degrees: float) -> str: decimal_degrees = abs(decimal_degrees) degrees = int(decimal_degrees) minutes = (float(decimal_degrees) - float(degrees)) * 60 minutes_string = f"{minutes:.8f}".zfill(11).rstrip("0") if minutes_string.endswith("."): minutes_string += "0" return f"{degrees}{minutes_string}" @staticmethod def _build_checksum(sentence: str) -> str: checksum_data = re.sub( "(\n|\r\n)", "", sentence[sentence.find("$") + 1 : sentence.find("*")] ) checksum = 0 for character in checksum_data: checksum ^= ord(character) return hex(checksum).replace("0x", "").upper().zfill(2) @staticmethod def _build_sentence_from_lat_lon_num_satellites( lat: float, lon: float, num_satellites: int ) -> str: converted_lat = GPSSensor._decimal_decrees_to_ddmmm(lat) converted_lon = GPSSensor._decimal_decrees_to_ddmmm(lon) lat_dir = "N" if lat > 0 else "S" lon_dir = "E" if lon > 0 else "W" # use a timestamp of xxxxxx.xx, and this will be replaced with the current time when the value is requested # Use a checksum of zz, and again it will be replaces return f"$GPGGA,xxxxxx.xx,{converted_lat},{lat_dir},{converted_lon},{lon_dir},1,{num_satellites},,0,M,0,M,,*zz\n" def _build_value(self) -> None: if self.value_type == GPSValueType.LATLON: self.value = GPSSensor._build_sentence_from_lat_lon_num_satellites( self.lat, self.lon, self.number_of_satellites ) if self.value_type == GPSValueType.NMEA: self.value = self.raw_nmea if not self.value.endswith("\n"): self.value += "\n" if self.value_type == GPSValueType.GPX: self.value = "" if self.gpx_file_contents: document = parseString(self.gpx_file_contents) track_parts = document.getElementsByTagName("trkpt") track_part: Element for track_part in track_parts: self.value += GPSSensor._build_sentence_from_lat_lon_num_satellites( float(track_part.getAttribute("lat")), float(track_part.getAttribute("lon")), 3, ) def read(self): current_utc = datetime.datetime.utcnow() if self._next_repeat_time is None: self._next_repeat_time = current_utc + datetime.timedelta(0, 1) if self.__next_gpgga_line_time is None: self.__next_gpgga_line_time = current_utc + datetime.timedelta(0, 1) # only repeat after a delay if self._value_position >= len(self.value): if self.repeat and current_utc > self._next_repeat_time: self._next_repeat_time = current_utc + datetime.timedelta(0, 1) self._value_position = 0 if self._value_position >= len(self.value): return "" chars_from_position = self.value[self._value_position :] # if we are a newline right before a $GPGGA then wait one second before sending it to space out GPX files or long NMEA data files if ( self._value_position > 0 and chars_from_position.startswith("$") and chars_from_position[3:].startswith("GGA") ): if current_utc > self.__next_gpgga_line_time: self.__next_gpgga_line_time = current_utc + datetime.timedelta(0, 1) else: return "" if chars_from_position.startswith("$GPGGA,xxxxxx.xx"): self.__substitute_start_position = self._value_position # Find the end checksum self.__substitute_end_position = self.value.find( "*zz", self._value_position ) + len("*zz") # find the block the right length self.__substitute_value = self.value[ self.__substitute_start_position : self.__substitute_end_position ] current_utc = datetime.datetime.utcnow() time_stamp = f"{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00" self.__substitute_value = self.__substitute_value.replace( "xxxxxx.xx", time_stamp ) checksum = GPSSensor._build_checksum(self.__substitute_value) self.__substitute_value = self.__substitute_value.replace( "*zz", "*" + checksum ) if ( self.__substitute_start_position <= self._value_position and self.__substitute_end_position > self._value_position ): next_char = self.__substitute_value[ self._value_position - self.__substitute_start_position ] self._value_position += 1 return next_char self.__substitute_start_position = 0 self.__substitute_end_position = 0 self.__substitute_value = "" return super().read() @property def value_type(self) -> GPSValueType: return self.__value_type @value_type.setter def value_type(self, val: GPSValueType): self.__value_type = val self._build_value() @property def lat(self) -> float: return self.__lat @lat.setter def lat(self, val: float): self.__lat = val self._build_value() @property def lon(self) -> float: return self.__lon @lon.setter def lon(self, val: float): self.__lon = val self._build_value() @property def number_of_satellites(self) -> int: return self.__number_of_satellites @number_of_satellites.setter def number_of_satellites(self, val: int): self.__number_of_satellites = val self._build_value() @property def raw_nmea(self) -> str: return self.__raw_nmea @raw_nmea.setter def raw_nmea(self, val: str): self.__raw_nmea = val self._build_value() @property def gpx_file_name(self) -> str: return self.__gpx_file_name @gpx_file_name.setter def gpx_file_name(self, val: str): self.__gpx_file_name = val self._build_value() @property def gpx_file_contents(self) -> str: return self.__gpx_file_contents @gpx_file_contents.setter def gpx_file_contents(self, val: str): self.__gpx_file_contents = val self._build_value() ================================================ FILE: counterfit-app/src/CounterFit/static/style.css ================================================ .sensor_container { } .actuator_container { } #circle { width: 50px; height: 50px; -webkit-border-radius: 25px; -moz-border-radius: 25px; border-radius: 25px; background: red; } ================================================ FILE: counterfit-app/src/CounterFit/templates/binary_sensor_create_settings.html ================================================
================================================ FILE: counterfit-app/src/CounterFit/templates/boolean_sensor.html ================================================

{{ sensor.sensor_name() }}

Pin {{sensor.port}}

================================================ FILE: counterfit-app/src/CounterFit/templates/camera_sensor.html ================================================

{{ sensor.sensor_name() }}

{{sensor.port}}

{{sensor.image_file_name}}
================================================ FILE: counterfit-app/src/CounterFit/templates/float_sensor.html ================================================

{{ sensor.sensor_name() }}

Pin {{sensor.port}}

Units:
{{ sensor.unit }}
Value range:
{{ "{:,.2f}".format(sensor.valid_min) }} to {{ "{:,.2f}".format(sensor.valid_max) }}
================================================ FILE: counterfit-app/src/CounterFit/templates/gps_sensor.html ================================================

{{ sensor.sensor_name() }}

Port {{sensor.port}}

{{sensor.gpx_file_name}}
================================================ FILE: counterfit-app/src/CounterFit/templates/home.html ================================================ CounterFit

CounterFit logo CounterFit - Fake IoT Hardware

Connected {% else %} style="color:grey;">Disconnected {% endif %} Disconnected

Sensors

Create sensor

{% include "pin_sensor_create_settings.html" %}
{% for sensor in sensors %}
{% if sensor.sensor_type().name == "FLOAT": %} {% with sensor=sensor %} {% include "float_sensor.html" %} {% endwith %} {% endif %} {% if sensor.sensor_type().name == "INTEGER": %} {% with sensor=sensor %} {% include "integer_sensor.html" %} {% endwith %} {% endif %} {% if sensor.sensor_type().name == "BOOLEAN": %} {% with sensor=sensor %} {% include "boolean_sensor.html" %} {% endwith %} {% endif %} {% if sensor.sensor_type().name == "I2C": %} {% if sensor.sensor_unit_type().name == "FLOAT" %} {% with sensor=sensor %} {% include "i2c_float_sensor.html" %} {% endwith %} {% endif %} {% if sensor.sensor_unit_type().name == "INTEGER" %} {% with sensor=sensor %} {% include "i2c_integer_sensor.html" %} {% endwith %} {% endif %} {% endif %} {% if sensor.sensor_name() == "UART GPS": %} {% with sensor=sensor %} {% include "gps_sensor.html" %} {% endwith %} {% endif %} {% if sensor.sensor_name() == "Camera": %} {% with sensor=sensor %} {% include "camera_sensor.html" %} {% endwith %} {% endif %}
{% endfor %}

Actuators

Create actuator

{% for actuator in actuators %}
{% if actuator.actuator_name() == "LED": %} {% with actuator=actuator %} {% include "led_actuator.html" %} {% endwith %} {% elif actuator.actuator_name() == "Relay": %} {% with actuator=actuator %} {% include "relay_actuator.html" %} {% endwith %} {% endif %}
{% endfor %}
================================================ FILE: counterfit-app/src/CounterFit/templates/i2c_float_sensor.html ================================================

{{ sensor.sensor_name() }}

I2C: {{sensor.address}}

Units:
{{ sensor.unit }}
Value range:
{{ "{:,.2f}".format(sensor.valid_min) }} to {{ "{:,.2f}".format(sensor.valid_max) }}
================================================ FILE: counterfit-app/src/CounterFit/templates/i2c_integer_sensor.html ================================================

{{ sensor.sensor_name() }}

I2C: {{sensor.address}}

Units:
{{ sensor.unit }}
Value range:
{{ "{:,d}".format(sensor.valid_min) }} to {{ "{:,d}".format(sensor.valid_max) }}
================================================ FILE: counterfit-app/src/CounterFit/templates/i2c_sensor_create_settings.html ================================================
================================================ FILE: counterfit-app/src/CounterFit/templates/integer_sensor.html ================================================

{{ sensor.sensor_name() }}

Pin {{sensor.port}}

Units:
{{ sensor.unit }}
Value range:
{{ "{:,d}".format(sensor.valid_min) }} to {{ "{:,d}".format(sensor.valid_max) }}
================================================ FILE: counterfit-app/src/CounterFit/templates/led_actuator.html ================================================

{{ actuator.actuator_name() }}

Pin {{actuator.port}}

{% if actuator.value: %}LED on{% else %}LED off{% endif %}
================================================ FILE: counterfit-app/src/CounterFit/templates/pin_sensor_create_settings.html ================================================
================================================ FILE: counterfit-app/src/CounterFit/templates/relay_actuator.html ================================================

{{ actuator.actuator_name() }}

Pin {{actuator.port}}

Relay off
================================================ FILE: counterfit-app/src/CounterFit/templates/serial_sensor_create_settings.html ================================================
================================================ FILE: counterfit-app/src/__main__.py ================================================ from CounterFit.counterfit import main main() ================================================ FILE: counterfit-app/src/tests/__init__.py ================================================ ================================================ FILE: counterfit-app/src/tests/route.gpx ================================================ AllTrails, LLC AllTrails 107.05 107.05 108.0 ================================================ FILE: counterfit-app/src/tests/test_serial_sensors.py ================================================ # pylint: disable=protected-access,line-too-long import datetime import time from CounterFit.serial_sensors import GPSSensor, GPSValueType def test_decimal_degree_conversion(): assert GPSSensor._decimal_decrees_to_ddmmm(47.653814) == "4739.22884" assert GPSSensor._decimal_decrees_to_ddmmm(-47.09565) == "4705.739" assert GPSSensor._decimal_decrees_to_ddmmm(-47) == "4700.0" def test_checksum(): assert ( GPSSensor._build_checksum( "$GPGGA,005206.768,5227.561,N,01230.806,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "68" ) assert ( GPSSensor._build_checksum( "GPGGA,005207.768,5249.958,N,01308.049,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "6E" ) assert ( GPSSensor._build_checksum( "$GPGGA,005208.768,5310.190,N,01409.023,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "6A" ) assert ( GPSSensor._build_checksum( "$GPGGA,005209.768,5300.094,N,01450.881,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "63" ) assert ( GPSSensor._build_checksum( "$GPGGA,005210.768,5148.313,N,01608.005,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "62" ) assert ( GPSSensor._build_checksum( "# $GPGGA,005211.768,5142.192,N,01454.836,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "61" ) assert ( GPSSensor._build_checksum( "$GPGGA,005212.768,5135.443,N,01332.439,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "6F" ) assert ( GPSSensor._build_checksum( "$GPGGA,005213.768,5153.810,N,01259.150,E,1,12,1.0,0.0,M,0.0,M,,*00" ) == "62" ) def test_gps_empty_read_line(): gps = GPSSensor("/dev/ttyAMA0") line = gps.read_line() assert line == "" def test_gps_empty_read(): gps = GPSSensor("/dev/ttyAMA0") char = gps.read() assert char == "" def add_checksum_to_expected(expected: str) -> str: checksum = GPSSensor._build_checksum(expected) expected += checksum return expected def test_gps_time_setting_when_using_lat_lon_n_e(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.LATLON gps.lat = 47 gps.lon = 122 gps.number_of_satellites = 3 current_utc = datetime.datetime.utcnow() line = gps.read_line() expected = add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4700.0,N,12200.0,E,1,3,,0,M,0,M,,*" ) assert line == expected assert gps.read_line() == "" def test_gps_time_setting_when_using_lat_lon_s_w(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.LATLON gps.lat = -47 gps.lon = -122 gps.number_of_satellites = 3 current_utc = datetime.datetime.utcnow() expected = add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4700.0,S,12200.0,W,1,3,,0,M,0,M,,*" ) assert gps.read_line() == expected assert gps.read_line() == "" def test_gps_nmea(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) assert ( gps.read_line() == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) assert gps.read_line() == "" def test_gps_nmea_repeat(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) gps.repeat = True for _ in range(0, 20): line = gps.read_line() if line == "": time.sleep(1) line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) def test_gps_nmea_repeat_has_delay(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) gps.repeat = True line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) line = gps.read_line() assert line == "" line = gps.read_line() assert line == "" line = gps.read_line() assert line == "" time.sleep(1) line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) def test_gps_nmea_multiple_sentences_has_delay(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" + "\n" + "$GNGGA,020604.001,4739.228833333,S,12207.031866667,E,1,3,,164.7,M,-17.1,M,,*67" ) line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) line = gps.read_line() assert line == "" line = gps.read_line() assert line == "" line = gps.read_line() assert line == "" time.sleep(1.5) line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,S,12207.031866667,E,1,3,,164.7,M,-17.1,M,,*67" ) def test_gps_nmea_multiple_sentences_has_delay_only_for_gga(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" + "\n" + "$GPGSA,A,3,01,02,03,04,05,06,07,08,09,10,11,12,1.0,1.0,1.0*30" + "\n" + "$GNGGA,020604.001,4739.228833333,S,12207.031866667,E,1,3,,164.7,M,-17.1,M,,*67" + "\n" + "$GPGSA,A,3,01,02,03,04,05,06,07,08,09,10,11,12,1.0,1.0,1.0*30" ) line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) line = gps.read_line() assert line == "$GPGSA,A,3,01,02,03,04,05,06,07,08,09,10,11,12,1.0,1.0,1.0*30" line = gps.read_line() assert line == "" line = gps.read_line() assert line == "" time.sleep(1) line = gps.read_line() assert ( line == "$GNGGA,020604.001,4739.228833333,S,12207.031866667,E,1,3,,164.7,M,-17.1,M,,*67" ) line = gps.read_line() assert line == "$GPGSA,A,3,01,02,03,04,05,06,07,08,09,10,11,12,1.0,1.0,1.0*30" line = gps.read_line() assert line == "" line = gps.read_line() assert line == "" def test_gps_gpx(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.GPX with open("./src/tests/route.gpx", "r", encoding="UTF-8") as gpx_file: gps.gpx_file_contents = gpx_file.read() current_utc = datetime.datetime.utcnow() assert gps.read_line() == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0886,N,12215.42,W,1,3,,0,M,0,M,,*" ) time.sleep(2) current_utc = datetime.datetime.utcnow() assert gps.read_line() == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0886,N,12215.4206,W,1,3,,0,M,0,M,,*" ) time.sleep(2) current_utc = datetime.datetime.utcnow() assert gps.read_line() == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0856,N,12215.4092,W,1,3,,0,M,0,M,,*" ) assert gps.read_line() == "" def test_gps_gpx_repeat(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.GPX with open("./src/tests/route.gpx", "r", encoding="UTF-8") as gpx_file: gps.gpx_file_contents = gpx_file.read() gps.repeat = True for _ in range(0, 20): line = gps.read_line() if line == "": time.sleep(1) line = gps.read_line() current_utc = datetime.datetime.utcnow() assert line == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0886,N,12215.42,W,1,3,,0,M,0,M,,*" ) line = gps.read_line() if line == "": time.sleep(1) line = gps.read_line() current_utc = datetime.datetime.utcnow() assert line == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0886,N,12215.4206,W,1,3,,0,M,0,M,,*" ) line = gps.read_line() if line == "": time.sleep(1) line = gps.read_line() current_utc = datetime.datetime.utcnow() assert line == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0856,N,12215.4092,W,1,3,,0,M,0,M,,*" ) def test_setting_lat_lon_clears_value(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) gps.value_type = GPSValueType.LATLON gps.lat = -47 gps.lon = -122 gps.number_of_satellites = 3 current_utc = datetime.datetime.utcnow() expected = add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4700.0,S,12200.0,W,1,3,,0,M,0,M,,*" ) assert gps.read_line() == expected assert gps.read_line() == "" def test_setting_gpx_clears_value(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) gps.value_type = GPSValueType.GPX with open("./src/tests/route.gpx", "r", encoding="UTF-8") as gpx_file: gps.gpx_file_contents = gpx_file.read() current_utc = datetime.datetime.utcnow() assert gps.read_line() == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0886,N,12215.42,W,1,3,,0,M,0,M,,*" ) time.sleep(2) current_utc = datetime.datetime.utcnow() assert gps.read_line() == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0886,N,12215.4206,W,1,3,,0,M,0,M,,*" ) time.sleep(2) current_utc = datetime.datetime.utcnow() assert gps.read_line() == add_checksum_to_expected( f"$GPGGA,{current_utc.hour:02d}{current_utc.minute:02d}{current_utc.second:02}.00,4744.0856,N,12215.4092,W,1,3,,0,M,0,M,,*" ) assert gps.read_line() == "" def test_setting_nmea_clears_value(): gps = GPSSensor("/dev/ttyAMA0") gps.value_type = GPSValueType.GPX with open("./src/tests/route.gpx", "r", encoding="UTF-8") as gpx_file: gps.gpx_file_contents = gpx_file.read() gps.value_type = GPSValueType.NMEA gps.raw_nmea = ( "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) assert ( gps.read_line() == "$GNGGA,020604.001,4739.228833333,N,12207.031866667,W,1,3,,164.7,M,-17.1,M,,*67" ) assert gps.read_line() == "" ================================================ FILE: counterfit-app/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: requirements.txt ================================================ twine=4.0.2 black=23.1.0 ================================================ FILE: samples/grove/button-temperature-sensor-controlled-relay/README.md ================================================ # Relay controlled by temperature sensor and button sample This sample shows how to use the CounterFit Grove shims to simulate a relay controlled by a temperature sensor and button setup. The temperature value is read whenever the button is pressed, and the relay is activated if the temperature is under 38 degrees celsius. ## Setup * Create a Python virtual environment: ```sh python3 -m venv .venv ``` * Activate the environment: macOS/Linux: ```sh source ./.venv/bin/activate ``` Windows: ```cmd .venv\Scripts\activate.bat ``` * Install the required pip packages: ```sh pip install -r requirements.txt ``` This will install the [CounterFit](https://github.com/CounterFit-IoT/CounterFit/tree/main/counterfit-app) app and the [CounterFit Grove Shims](https://github.com/CounterFit-IoT/CounterFit/tree/main/shims). ## Configure the hardware * Launch the CounterFit app: ```sh CounterFit ``` This will launch the app in a web browser, running on port 5000. > If you want to change the port, pass it is as the `--port` parameter when launching `CounterFit` * Create a **button** on **pin 1**, and a **temperature sensor** on **pin 2**, and a **relay** on **pin 3** ![Screenshot of the used setup](https://github.com/AChillFeeder/CounterFit/blob/temperature_button_relay_example/samples/grove/button-temperature-sensor-controlled-relay/assets/HardwareSetup.png) ## Run the code * Run the `app.py` file in the same virtual environment (you will need a new terminal or CMD) > If you used a different port, then you will need to edit the port use to initialize the CounterFit connection: > > ```python > CounterFitConnection.init('127.0.0.1', ) > ``` > > Replace `` with the port you used. * Adjust the values in CounterFit for the temperature level. When the value is less than 38 and the button is pressed, the relay will activate. ================================================ FILE: samples/grove/button-temperature-sensor-controlled-relay/main.py ================================================ # In this example: # We will create a circuit where, when a button is pressed, we will check the current temperature # If the recorded temperature is under 38 celsius, the relay will be activated # PIN 1: Button # PIN 2: Temperature sensor # PIN 3: Relay import time from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_relay import GroveRelay CounterFitConnection.init('127.0.0.1', 5000) relay = GroveRelay(3) while True: button = CounterFitConnection.get_sensor_boolean_value(1) # The button returns boolean values, True for pressed and False for released if button: print("The button has been pressed") temperature = CounterFitConnection.get_sensor_float_value(2) # Get temperature value print(f"Recorded temperature is {temperature}") if temperature < 38: print("Activate relay") relay.on() time.sleep(5) relay.off() time.sleep(0.5) # Used a short sleep timer as not to miss a button press when the script is on break ================================================ FILE: samples/grove/button-temperature-sensor-controlled-relay/requirements.txt ================================================ counterfit counterfit_shims_grove ================================================ FILE: samples/grove/light-sensor-controlled-led/README.md ================================================ # Light sensor controlled LED sample This sample shows how to use the CounterFit Grove shims to simulate an LED and a light sensor, controlling the LED based on the light levels read from the sensor. ## Setup * Create a Python virtual environment: ```sh python3 -m venv .venv ``` * Activate the environment: macOS/Linux: ```sh source ./.venv/bin/activate ``` Windows: ```cmd .venv\Scripts\activate.bat ``` * Install the required pip packages: ```sh pip install -r requirements.txt ``` This will install the [CounterFit](https://github.com/CounterFit-IoT/CounterFit/tree/main/counterfit-app) app and the [CounterFit Grove Shims](https://github.com/CounterFit-IoT/CounterFit/tree/main/shims/grove). ## Configure the hardware * Launch the CounterFit app: ```sh CounterFit ``` This will launch the app in a web browser, running on port 5000. > If you want to change the port, pass it is as the `--port` parameter when launching `CounterFit` * Create a light sensor on pin 1, and an LED actuator on pin 2 ## Run the code * Run the `app.py` file in the same virtual environment (you will need a new terminal or CMD) > If you used a different port, then you will need to edit the port use to initialize the CounterFit connection: > > ```python > CounterFitConnection.init('127.0.0.1', ) > ``` > > Replace `` with the port you used. * Adjust the values in CounterFit for the light level. When the value is less than 200, the LED will light up. ================================================ FILE: samples/grove/light-sensor-controlled-led/app.py ================================================ import time from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor from counterfit_shims_grove.grove_led import GroveLed CounterFitConnection.init('127.0.0.1', 5000) light_sensor = GroveLightSensor(1) led = GroveLed(2) while True: sensor_value = light_sensor.light print(f'Light sensor reading: {sensor_value}') if sensor_value > 200: print('Turning LED off') led.off() else: print('Turning LED on') led.on() time.sleep(2) ================================================ FILE: samples/grove/light-sensor-controlled-led/requirements.txt ================================================ counterfit counterfit_shims_grove ================================================ FILE: shims/CounterFitConnection/.gitignore ================================================ test_image.png ================================================ FILE: shims/CounterFitConnection/.pylintrc ================================================ [FORMAT] # Maximum number of characters on a single line. max-line-length=140 [MESSAGES CONTROL] disable=trailing-whitespace ================================================ FILE: shims/CounterFitConnection/README.md ================================================ # CounterFit Shims - CounterFit connection ![CounterFit Connection](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-connection)](https://pypi.org/project/counterfit-connection) A core connection library to use with the [CounterFit virtual IoT device app](https://github.com/CounterFit-IoT/CounterFit). Use this when writing a shim library. ================================================ FILE: shims/CounterFitConnection/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/CounterFitConnection/counterfit_connection.py ================================================ ''' Provides a connection to the CounterFit Virtual IoT Device app. This connection is re-used by all the virtual sensors. Examples: When connecting to localhost on the default port: .. code-block:: python from counterfit_shims_grove.counterfit_connection import CounterFitConnection CounterFitConnection.init() When connection to another computer on a different port: .. code-block:: python from counterfit_shims_grove.counterfit_connection import CounterFitConnection CounterFitConnection.init('192.168.197.1', 5050) ''' # pylint: disable=duplicate-code,bare-except from base64 import b64decode import io import requests class CounterFitConnection: ''' Connects to the CounterFit Virtual IoT device on a give host and port, and allows the value of sensors to be read, as well as setting the value of actuators. ''' base_url = '' @staticmethod def init(hostname: str = 'localhost', port: int = 5000) -> None: ''' Initializes the connection to the Virtual IoT Device running on the given url and port ''' CounterFitConnection.base_url = f'http://{hostname}:{str(port)}/' requests.post(CounterFitConnection.base_url + 'connect') @staticmethod def get_sensor_float_value(port: int) -> float: ''' Reads a float value from the sensor on the given port ''' response = requests.get(CounterFitConnection.base_url + 'sensor_value?port=' + str(port)) return float(response.json()['value']) @staticmethod def get_sensor_int_value(port: int) -> int: ''' Reads an integer value from the sensor on the given port ''' response = requests.get(CounterFitConnection.base_url + 'sensor_value?port=' + str(port)) return int(response.json()['value']) @staticmethod def get_sensor_boolean_value(port: int) -> bool: ''' Reads a bool value from the sensor on the given port ''' response = requests.get(CounterFitConnection.base_url + 'sensor_value?port=' + str(port)) return bool(response.json()['value']) @staticmethod def read_serial_sensor_char(port: str) -> str: ''' Reads a character from the serial sensor on the given port ''' response = requests.get(CounterFitConnection.base_url + 'serial_sensor_character?port=' + port) return str(response.json()['value']) @staticmethod def read_serial_sensor_line(port: str) -> str: ''' Reads a line from the serial sensor on the given port ''' response = requests.get(CounterFitConnection.base_url + 'serial_sensor_line?port=' + port) return str(response.json()['value']) @staticmethod def read_binary_sensor(port: str) -> io.BytesIO: ''' Reads a character from the serial sensor on the given port ''' response = requests.get(CounterFitConnection.base_url + 'binary_sensor_data?port=' + port) msg = b64decode(response.json()['value']) return io.BytesIO(msg) @staticmethod def set_actuator_float_value(port: int, value: float) -> None: ''' Sends a float value to the actuator on the given port ''' requests.post(CounterFitConnection.base_url + 'actuator_value?port=' + str(port), json= {'value':value}) @staticmethod def set_actuator_boolean_value(port: int, value: bool) -> None: ''' Sends a bool value to the actuator on the given port ''' requests.post(CounterFitConnection.base_url + 'actuator_value?port=' + str(port), json= {'value':value}) @staticmethod def is_connected() -> bool: ''' Determines if CounterFit is running ''' try: requests.post(CounterFitConnection.base_url + 'connect') return True except: return False ================================================ FILE: shims/CounterFitConnection/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 ================================================ FILE: shims/CounterFitConnection/setup.py ================================================ ''' Connection library for use with the CounterFit Virtual IoT Device app ''' # pylint: disable=redefined-builtin from codecs import open from os import path from setuptools import setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-connection', py_modules=['counterfit_connection'], version='0.1.0.dev5', description='Connection library for use with the CounterFit Virtual IoT Device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot grove seeed virtual hardware", install_requires=['requests'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/CounterFitConnection/tests/__init__.py ================================================ ================================================ FILE: shims/CounterFitConnection/tests/test_counterfit_connection.py ================================================ ''' Tests the CounterFit app connection To run this test, ensure you have the CounterFit Virtual IoT Device app running ''' # pylint: disable=redefined-outer-name,unused-argument,duplicate-code import pytest import time from counterfit_connection import CounterFitConnection def test_init_counterfit_device(): ''' Tests the connection. You should see the CounterFit app status change to connected ''' CounterFitConnection.init('127.0.0.1', 5000) # def test_get_sensor_bool_value(): # ''' # Tests reading a True boolean value from a boolean sensor on port 0 # ''' # CounterFitConnection.init('127.0.0.1', 5000) # assert CounterFitConnection.get_sensor_boolean_value(0) # def test_read_serial_char(): # ''' # Tests reading a character from a serial sensor on port /dev/ttyAMA0 containing the text 'hello' # ''' # CounterFitConnection.init('127.0.0.1', 5000) # assert CounterFitConnection.read_serial_sensor_char('/dev/ttyAMA0') == 'h' # assert CounterFitConnection.read_serial_sensor_char('/dev/ttyAMA0') == 'e' # assert CounterFitConnection.read_serial_sensor_char('/dev/ttyAMA0') == 'l' # assert CounterFitConnection.read_serial_sensor_char('/dev/ttyAMA0') == 'l' # assert CounterFitConnection.read_serial_sensor_char('/dev/ttyAMA0') == 'o' # def test_read_serial_line(): # ''' # Tests reading a line from a serial sensor on port /dev/ttyAMA0 containing the text 'hello\nworld' # ''' # CounterFitConnection.init('127.0.0.1', 5000) # assert CounterFitConnection.read_serial_sensor_line('/dev/ttyAMA0') == 'hello' # assert CounterFitConnection.read_serial_sensor_line('/dev/ttyAMA0') == 'world' def test_camera_image(): ''' Tests reading an image from a camera sensor. The image is saved locally ''' CounterFitConnection.init('127.0.0.1', 5000) image_data = CounterFitConnection.read_binary_sensor('Picamera') with open('test_image.png', 'wb') as image_file: image_file.write(image_data.read()) def test_is_connected(): ''' Tests is connected. Make sure counterfit is running ''' CounterFitConnection.init('127.0.0.1', 5000) assert CounterFitConnection.is_connected() def test_is_connected_is_false(): ''' Tests is connected. Make sure counterfit is running until you see a message telling you to close it ''' CounterFitConnection.init('127.0.0.1', 5000) print("Please close counterfit") time.sleep(10) assert not CounterFitConnection.is_connected() ================================================ FILE: shims/CounterFitConnection/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/.pylintrc ================================================ [FORMAT] # Maximum number of characters on a single line. max-line-length=140 [MESSAGES CONTROL] disable=trailing-whitespace ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/README.md ================================================ # CounterFit Shims - Grove ![Grove Shim](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-seeed-python-dht)](https://pypi.org/project/counterfit-shims-seeed-python-dht) Shims for the Seeed Grove DHT digital temperature and humidity sensor to use with the [CounterFit virtual IoT device app](https://github.com/CounterFit-IoT/CounterFit). See the [Seeed Python DHT Docs](https://github.com/Seeed-Studio/Seeed_Python_DHT) for the API documentation. ## Getting started To use these shims, you will need to install [CounterFit](https://github.com/CounterFit-IoT/CounterFit) and have it running, with the appropriate hardware created. * Install this package from pip: ```sh pip install counterfit-shims-seeed-python-dht ``` * Import the Grove modules as normal, but using the `counterfit_shims_seeed_python_dht` package instead of the `seeed-python-dht` package, as well as importing the `CounterFitConnection` from the `counterfit_shims_grove.counterfit_connection` module: ```python from counterfit_connection import CounterFitConnection from counterfit_shims_seeed_python_dht import DHT ``` * Configure the connection to the CounterFit app. Change the hostname and port to where you are running it: ```python CounterFitConnection.init('127.0.0.1', 5000) ``` * Write your Grove code as usual, setting the pins to match the ones you set in the CounterFit app. To keep to the interface that the `DHT` specifies, only one pin can be passed to the constructor. The sensor assumes the pin given is for the humidity sensor, and the temperature is on the next pin. For example, create a humidity sensor on pin 1, and a temperature sensor on pin 2. Then pass 1 to the `DHT` constructor. ```python sensor =.DHT("11", 1) humi, temp = sensor.read() ``` ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/counterfit_shims_seeed_python_dht.py ================================================ ''' This is the code for - `Grove - DHT11 digital temperature and humidity sensor `_ Examples: .. code-block:: python import time from counterfit_shims_seeed_python_dht.counterfit_connection import CounterFitConnection import counterfit_shims_seeed_python_dht.seeed_dht # Init the connection to the CounterFit Virtual IoT Device app CounterFitConnection.init('127.0.0.1', 5000) # connect to a humidity sensor on pin 12 and a temperature sensor on pin 13 PIN = 12 sensor = seeed_dht.DHT("11", 12) humi, temp = sensor.read() ''' # pylint: disable=too-few-public-methods,unused-argument from counterfit_connection import CounterFitConnection __all__ = ['DHT'] class DHT(): ''' Class for Grove DHT Args: pin(int): The pin for the humidity sensor. The temperature sensor needs to be on the next pin. ''' def __init__(self, dht_type, pin = 12,bus_num = 1): self.__humidity_pin = pin self.__temperature_pin = pin + 1 def read(self, retries = 15): ''' Read the humidity and temperature from the sensor ''' humidity = CounterFitConnection.get_sensor_float_value(self.__humidity_pin) temperature = CounterFitConnection.get_sensor_float_value(self.__temperature_pin) return humidity, temperature ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 counterfit-connection ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/setup.py ================================================ ''' Shims for the Seeed Python DHT library for use with the CounterFit Virtual IoT Device app ''' from codecs import open from os import path from setuptools import setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-shims-seeed-python-dht', py_modules=['counterfit_shims_seeed_python_dht'], version='0.1.0.dev1', description='Shims for the Seeed Grove DHT for the CounterFit virtual IoT device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot grove seeed virtual hardware", install_requires=['requests','counterfit-connection'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/tests/__init__.py ================================================ ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/tests/test_counterfit_shims_seeed_python_dht.py ================================================ ''' Tests the Grove DHT. To run this test, ensure you have the CounterFit Virtual IoT Device app running, with a humidity sensor on pin 0 and a temperature sensor on pin 1. The humidity sensor should be set to 50%, and the temperature sensor to 25°C ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection from counterfit_shims_seeed_python_dht import DHT @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_humidity_and_temperature(init_counterfit_device): ''' Tests values returned from the DHT sensor ''' sensor = DHT("11", 12) humi, temp = sensor.read() assert humi == 50.0 assert temp == 25.0 ================================================ FILE: shims/SeeedStudios/Seeed_Python_DHT/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: shims/SeeedStudios/grove_py/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ .vscode ================================================ FILE: shims/SeeedStudios/grove_py/.pylintrc ================================================ [FORMAT] # Maximum number of characters on a single line. max-line-length=140 [MESSAGES CONTROL] disable=trailing-whitespace [SIMILARITIES] disable=duplicate-code ================================================ FILE: shims/SeeedStudios/grove_py/README.md ================================================ # CounterFit Shims - Grove ![Grove Shim](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-grove)](https://pypi.org/project/counterfit-shims-grove) Shims for the Seeed Grove sensors and actuators to use with the [CounterFit virtual IoT device app](https://github.com/CounterFit-IoT/CounterFit). See the [Grove Py Docs](https://github.com/Seeed-Studio/grove.py) for the API documentation. ## Getting started To use these shims, you will need to install [CounterFit](https://github.com/CounterFit-IoT/CounterFit) and have it running, with the appropriate hardware created. * Install this package from pip: ```sh pip install counterfit-shims-grove ``` * Import the Grove modules as normal, but using the `counterfit_shims_grove` package instead of the `grove` package, as well as importing the `CounterFitConnection` from the `counterfit_shims_grove.counterfit_connection` module: ```python from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor from counterfit_shims_grove.grove_led import GroveLed ``` * Configure the connection to the CounterFit app. Change the hostname and port to where you are running it: ```python CounterFitConnection.init('127.0.0.1', 5000) ``` * Write your Grove code as usual, setting the pins to match the ones you set in the CounterFit app: ```python light_sensor = GroveLightSensor(2) sensor_value = light_sensor.light ``` ## Implemented shims Not all the Grove sensor and actuators are currently implemented: | Sensor/Actuator | | ------ | | grove_led | | grove_light_sensor_v1_2 | ================================================ FILE: shims/SeeedStudios/grove_py/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/__init__.py ================================================ ''' CounterFit Virtual IoT Device shims for Grove sensors. This library provides shims that mimic the Grove Python libraries from: https://github.com/Seeed-Studio/grove.py These shims don't communicate with real hardware, instead they communicate with a running instance of the CounterFit Virtual IoT Device app ''' from .grove_light_sensor_v1_2 import GroveLightSensor from .grove_led import GroveLed ================================================ FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/adc.py ================================================ ''' This is the code for - `Grove Base Hat for RPi `_ - `Grove Base Hat for RPi Zero `_ Grove Base Hat incorporates a micro controller STM32F030F4. Raspberry Pi does not have ADC unit, so we use an external chip to transmit analog data to raspberry pi. Examples: .. code-block:: python import time from counterfit_connection import CounterFitConnection from counterfit_shims_grove.adc import ADC # Init the connection to the CounterFit Virtual IoT Device app CounterFitConnection.init('127.0.0.1', 5000) adc = ADC() while True: # Read channel 0(Slot A0) print(adc.read(0)) time.sleep(1) ''' # pylint: disable=no-self-use # pylint: disable=import-error from counterfit_connection import CounterFitConnection __all__ = ["ADC"] class ADC(): ''' Class ADC for the ADC unit on Grove Base Hat for RPi. Args: address(int): optional, i2c address of the ADC unit, default 0x04 ''' def __init__(self, address = 0x04): pass def read_raw(self, channel): ''' Read the raw data of ADC unit, with 12 bits resolution. Args: channel (int): 0 - 7, specify the channel to read Returns: (int): the adc result, in [0 - 4095] ''' raise NotImplementedError() # read input voltage (mV) def read_voltage(self, channel): ''' Read the voltage data of ADC unit. Args: channel (int): 0 - 7, specify the channel to read Returns: (int): the voltage result, in mV ''' raise NotImplementedError() # input voltage / output voltage (%) def read(self, channel): ''' Read the ratio between channel input voltage and power voltage (most time it's 3.3V). Args: channel (int): 0 - 7, specify the channel to read Returns: (int): the ratio, in 0.1% ''' return CounterFitConnection.get_sensor_int_value(channel) @property def name(self): ''' Get the Hat name. Returns: (string): could be :class:`RPI_HAT_NAME` or :class:`RPI_ZERO_HAT_NAME` ''' return 'Virtual Grove Hat' @property def version(self): ''' Get the Hat firmware version. Returns: (int): firmware version ''' return 1 # pylint: disable=invalid-name def read_register(self, n): ''' Read the ADC Core (through I2C) registers Grove Base Hat for RPI I2C Registers - 0x00 ~ 0x01: - 0x10 ~ 0x17: ADC raw data - 0x20 ~ 0x27: input voltage - 0x29: output voltage (Grove power supply voltage) - 0x30 ~ 0x37: input voltage / output voltage Args: n(int): register address. Returns: (int) : 16-bit register value. ''' raise NotImplementedError() ================================================ FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/grove_led.py ================================================ ''' This is the code for - `Grove - Red LED `_ - `Grove - Green LED `_ - `Grove - Purple LED `_ - `Grove - White LED `_ Examples: .. code-block:: python import time from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor # Init the connection to the CounterFit Virtual IoT Device app CounterFitConnection.init('127.0.0.1', 5000) # connect to pin 5(slot D5) PIN = 5 led = GroveLed(PIN) while True: led.on() time.sleep(1) led.off() time.sleep(1) ''' # pylint: disable=import-error from counterfit_connection import CounterFitConnection __all__ = ['GroveLed'] class GroveLed(): ''' Class for Grove - XXXX Led Args: pin(int): number of digital pin the led connected. ''' def __init__(self, pin): self.__pin = pin # pylint: disable=invalid-name def on(self) -> None: ''' light on the led ''' CounterFitConnection.set_actuator_boolean_value(self.__pin, True) def off(self) -> None: ''' light off the led ''' CounterFitConnection.set_actuator_boolean_value(self.__pin, False) ================================================ FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/grove_light_sensor_v1_2.py ================================================ ''' This is the shim code for - `Grove - Light Sensor `_ Examples: .. code-block:: python import time from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor # Init the connection to the CounterFit Virtual IoT Device app CounterFitConnection.init('127.0.0.1', 5000) # connect to alalog pin 2(slot A2) PIN = 2 sensor = GroveLightSensor(pin) print('Detecting light...') while True: print('Light value: {0}'.format(sensor.light)) time.sleep(1) ''' # pylint: disable=too-few-public-methods # pylint: disable=import-error from counterfit_connection import CounterFitConnection __all__ = ['GroveLightSensor'] class GroveLightSensor: ''' Grove Light Sensor class Args: pin(int): number of analog pin/channel the sensor connected. ''' def __init__(self, pin: int): self.__pin = pin @property def light(self) -> int: ''' Get the light strength value, maximum value is 1023 ''' return CounterFitConnection.get_sensor_int_value(self.__pin) ================================================ FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/grove_relay.py ================================================ ''' This is the code for - `Grove - Relay `_ Examples: .. code-block:: python import time from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_relay import GroveRelay # Init the connection to the CounterFit Virtual IoT Device app CounterFitConnection.init('127.0.0.1', 5000) # connect to pin 5(slot D5) PIN = 5 relay = GroveRelay(PIN) while True: relay.on() time.sleep(1) relay.off() time.sleep(1) ''' # pylint: disable=import-error from counterfit_connection import CounterFitConnection __all__ = ["GroveRelay"] class GroveRelay(): ''' Class for Grove - Relay Args: pin(int): number of digital pin the relay connected. ''' def __init__(self, pin): self.__pin = pin # pylint: disable=invalid-name def on(self) -> None: ''' light on the led ''' CounterFitConnection.set_actuator_boolean_value(self.__pin, True) def off(self) -> None: ''' light off the led ''' CounterFitConnection.set_actuator_boolean_value(self.__pin, False) ================================================ FILE: shims/SeeedStudios/grove_py/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 counterfit-connection ================================================ FILE: shims/SeeedStudios/grove_py/setup.py ================================================ ''' Shims for the Seeed Grove Py library for use with the CounterFit Virtual IoT Device app ''' from codecs import open from os import path from setuptools import find_packages, setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-shims-grove', packages=find_packages(include=['counterfit_shims_grove']), version='0.1.4.dev5', description='Shims for the Seeed Grove sensors for the CounterFit virtual IoT device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot grove seeed virtual hardware", install_requires=['requests','counterfit-connection'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/SeeedStudios/grove_py/tests/__init__.py ================================================ ================================================ FILE: shims/SeeedStudios/grove_py/tests/test_grove_led.py ================================================ ''' Tests the Grove LED shim. To run this test, ensure you have the CounterFit Virtual IoT Device app running, with an LED actuator on pin 1. Uncomment the relevant test below and run it to see the LED change state ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_led import GroveLed @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_turn_led_on(init_counterfit_device): ''' Tests the on method of the Grove LED shim ''' sensor = GroveLed(1) sensor.on() def test_turn_led_off(init_counterfit_device): ''' Tests the off method of the Grove LED shim ''' sensor = GroveLed(1) sensor.off() ================================================ FILE: shims/SeeedStudios/grove_py/tests/test_grove_light_sensor_v1_2.py ================================================ ''' Tests the Grove light sensor shim. To run this test, ensure you have the CounterFit Virtual IoT Device app running, with a light sensor on pin 1 set to a value of 50 ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_light_sensor_light_is_50(init_counterfit_device): ''' Tests the light property of the Grove Light Sensor shim ''' sensor = GroveLightSensor(0) assert sensor.light == 50 ================================================ FILE: shims/SeeedStudios/grove_py/tests/test_grove_relay.py ================================================ ''' Tests the Grove Relay shim. To run this test, ensure you have the CounterFit Virtual IoT Device app running, with a relay actuator on pin 1. Uncomment the relevant test below and run it to see the relay change state ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection from counterfit_shims_grove.grove_relay import GroveRelay @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) # def test_turn_relay_on(init_counterfit_device): # ''' # Tests the on method of the Grove relay shim # ''' # sensor = GroveRelay(1) # sensor.on() def test_turn_relay_off(init_counterfit_device): ''' Tests the off method of the Grove relay shim ''' sensor = GroveRelay(1) sensor.off() ================================================ FILE: shims/SeeedStudios/grove_py/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/README.md ================================================ # CounterFit Shims - Grove ![Grove Shim](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-seeed-python-si114x)](https://pypi.org/project/counterfit-shims-seeed-python-si114x) Shims for the Seeed Grove SI114X sunlight sensor to use with the [CounterFit virtual IoT device app](https://github.com/CounterFit-IoT/CounterFit). See the [Seeed Python SI114X Docs](https://github.com/Seeed-Studio/Seeed_Python_SI114X) for the API documentation. ## Getting started To use these shims, you will need to install [CounterFit](https://github.com/CounterFit-IoT/CounterFit) and have it running, with the appropriate hardware created. * Install this package from pip: ```sh pip install counterfit-shims-seeed-python-si114x ``` * Import the Grove modules as normal, but using the `counterfit_shims_seeed_python_si114x` package instead of the `seeed-python-si114x` package, as well as importing the `CounterFitConnection` from the `counterfit_shims_grove.counterfit_connection` module: ```python from counterfit_connection import CounterFitConnection from counterfit_shims_seeed_python_si114x import si114x ``` * Configure the connection to the CounterFit app. Change the hostname and port to where you are running it: ```python CounterFitConnection.init('127.0.0.1', 5000) ``` * Write your Grove code as usual. The default assumes you have a light sensor on pin 0, IR on pin 1 and UV on pin 2. You can change these passing additional arguments to the init ```python sensor =.si114x() light = sensor.ReadVisible() ir = sensor.ReadIR() uv = sensor.ReadUV() ``` ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/counterfit_shims_seeed_si114x.py ================================================ ''' This is the code for - `Grove - Sunlight sensor `_ Examples: .. code-block:: python import time from counterfit_connection import CounterFitConnection import counterfit_shims_seeed_si114x # Init the connection to the CounterFit Virtual IoT Device app CounterFitConnection.init('127.0.0.1', 5000) light_sensor = seeed_si114x.grove_si114x() light = light_sensor.ReadVisible ''' # pylint: disable=too-few-public-methods,unused-argument from counterfit_connection import CounterFitConnection __all__ = ['grove_si114x'] class grove_si114x(): ''' Class for Grove SI114X Args: light_pin(int): The pin for the light sensor ir_pin(int): The pin for the IR sensor uv_pin(int): The pin for the UV sensor ''' def __init__(self, light_pin = 0, ir_pin = 1, uv_pin = 2): self.__light_pin = light_pin self.__ir_pin = ir_pin self.__uv_pin = uv_pin @property def ReadVisible(self): ''' Read the visible light from the sensor ''' return CounterFitConnection.get_sensor_int_value(self.__light_pin) @property def ReadIR(self): ''' Read the IR light from the sensor ''' return CounterFitConnection.get_sensor_int_value(self.__ir_pin) @property def ReadUV(self): ''' Read the IR light from the sensor ''' return CounterFitConnection.get_sensor_int_value(self.__uv_pin) ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 counterfit-connection ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/setup.py ================================================ ''' Shims for the Seeed Python SI144X library for use with the CounterFit Virtual IoT Device app ''' from codecs import open from os import path from setuptools import setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-shims-seeed-python-si114x', py_modules=['counterfit_shims_seeed_python_si114x'], version='0.1.0.dev1', description='Shims for the Seeed Grove SI144X Sunlight sensor for the CounterFit virtual IoT device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot grove seeed virtual hardware", install_requires=['requests','counterfit-connection'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/tests/__init__.py ================================================ ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/tests/test_counterfit_shims_seeed_si114x.py ================================================ ''' Tests the Grove SI114X. To run this test, ensure you have the CounterFit Virtual IoT Device app running, with a light sensor on pin 0, a IR sensor on pin 1, and a UV sensor on pin 2. The light sensor should be set to 500, the IR sensor to 750, and the UV sensor to 1000 ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection from counterfit_shims_seeed_si114x import grove_si114x @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_humidity_and_temperature(init_counterfit_device): ''' Tests values returned from the SI114X sensor ''' sensor = grove_si114x() assert sensor.ReadVisible == 500 assert sensor.ReadIR == 750 assert sensor.ReadUV == 1000 ================================================ FILE: shims/SeeedStudios/seeed-python-si114x/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: shims/picamera/.gitignore ================================================ test_image*.* ================================================ FILE: shims/picamera/README.md ================================================ # CounterFit Shims - Picamera ![Picamera Shim](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-picamera)](https://pypi.org/project/counterfit-shims-picamera) Shims for the Picamera to read from a virtual camera See the [Picamera Docs](https://picamera.readthedocs.io/) for the API documentation. ## Getting started To use these shims, you will need to install [CounterFit](https://github.com/CounterFit-IoT/CounterFit) and have it running, with the appropriate camera hardware created. Create the camera with a name of `Picamera`. * Install this package from pip: ```sh pip install counterfit-shims-picamera ``` * Import Picamera using the `counterfit_shims_picamera` package instead of the `picamera` package, as well as importing the `CounterFitConnection` from the `counterfit_connection` module: ```python from counterfit_connection import CounterFitConnection import counterfit_shims_picamera ``` * Configure the connection to the CounterFit app. Change the hostname and port to where you are running it: ```python CounterFitConnection.init('127.0.0.1', 5000) ``` * Write your Picamera code as usual. For example, to capture an image as a JPEG: ```python camera = PiCamera() image = io.BytesIO() camera.capture(image, 'jpeg') ``` ================================================ FILE: shims/picamera/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/picamera/counterfit_shims_picamera/__init__.py ================================================ ''' The picamera package consists of several modules which provide a pure Python interface to the Raspberry Pi's camera module. ''' from counterfit_shims_picamera.camera import PiCamera ================================================ FILE: shims/picamera/counterfit_shims_picamera/camera.py ================================================ ''' The picamera package consists of several modules which provide a pure Python interface to the Raspberry Pi's camera module. ''' # pylint: disable=import-error from counterfit_connection import CounterFitConnection from PIL import Image class PiCamera(): ''' Provides a pure Python interface to a virtual Raspberry Pi camera module. ''' # pylint: disable=unused-argument def __init__(self, resolution = None, **kwargs): if resolution is not None: self.__width: int = resolution[0] self.__height: int = resolution[1] else: self.__width = -1 self.__height = -1 self.__rotation = 0 @property def resolution(self): ''' Retrieves or sets the resolution at which image captures, video recordings, and previews will be captured. ''' return (self.__width, self.__height) @resolution.setter def resolution(self, val): ''' Retrieves or sets the resolution at which image captures, video recordings, and previews will be captured. ''' self.__width = int(val[0]) self.__height = int(val[1]) @property def rotation(self): ''' Retrieves or sets the current rotation of the camera's image. ''' return self.__rotation @rotation.setter def rotation(self, val: int): ''' Retrieves or sets the current rotation of the camera's image. ''' self.__rotation = val def __resize_and_crop(self, image: Image) -> Image: width_adjust = self.__width / image.size[0] height_adjust = self.__height / image.size[1] max_adjust = max([width_adjust, height_adjust]) new_width = int(image.size[0] * max_adjust) new_height = int(image.size[1] * max_adjust) image = image.resize((new_width, new_height)) left = 0 bottom = 0 if image.size[0] > self.__width: left = int((image.size[0] - self.__width) / 2) if image.size[1] > self.__height: bottom = int((image.size[1] - self.__height) / 2) image = image.crop((left, bottom, self.__width + left, self.__height + bottom)) return image def capture(self, output, image_format=None): ''' Capture an image from the camera, storing it in *output*. ''' # read the image from Counterfit raw_image = CounterFitConnection.read_binary_sensor('Picamera') raw_image.seek(0) image = Image.open(raw_image) if self.__rotation > 0: image = image.rotate(self.__rotation, expand=True) if self.__width > 0 and self.__height > 0: image = self.__resize_and_crop(image) image.save(output, format=image_format) ================================================ FILE: shims/picamera/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 counterfit-connection pillow=9.4.0 ================================================ FILE: shims/picamera/setup.py ================================================ ''' Shims for the Picamera library for use with the CounterFit Virtual IoT Device app ''' # pylint: disable=redefined-builtin from codecs import open from os import path from setuptools import find_packages, setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-shims-picamera', packages=find_packages(include=['counterfit_shims_picamera']), version='0.1.0.dev5', description='Shims for the PiCamera library for the CounterFit virtual IoT device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot picamera camera virtual hardware", install_requires=['requests','counterfit-connection','pillow'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/picamera/tests/__init__.py ================================================ ================================================ FILE: shims/picamera/tests/test_counterfit_shims_picamera.py ================================================ ''' Tests the PySerial connection. To test this, launch CounterFit, and add 2 GPS sensors, one on /dev/tty0 and one on /dev/tty1. Set them both to NMEA data, and the data as hello\nworld ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection import counterfit_shims_picamera @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_capture(init_counterfit_device): ''' Tests capturing an image ''' camera = counterfit_shims_picamera.PiCamera() camera.capture('test_image.png') camera.capture('test_image.jpeg') def test_rotate(init_counterfit_device): ''' Tests capturing an image ''' camera = counterfit_shims_picamera.PiCamera() camera.rotation = 90 camera.capture('test_image_rotate_90.jpeg') camera.rotation = 180 camera.capture('test_image_rotate_180.jpeg') camera.rotation = 270 camera.capture('test_image_rotate_270.jpeg') def test_resolution_init(init_counterfit_device): ''' Tests capturing an image ''' camera = counterfit_shims_picamera.PiCamera((1024,768)) camera.capture('test_image_resolution_1024_768_init.jpeg') def test_resolution(init_counterfit_device): ''' Tests capturing an image ''' camera = counterfit_shims_picamera.PiCamera() camera.resolution = (1024,768) camera.capture('test_image_resolution_1024_768.jpeg') def test_resolution_and_rotate(init_counterfit_device): ''' Tests capturing an image ''' camera = counterfit_shims_picamera.PiCamera() camera.rotation = 90 camera.resolution = (1024,768) camera.capture('test_image_rotate_resolution_1024_768.jpeg') ================================================ FILE: shims/picamera/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: shims/pyserial/README.md ================================================ # CounterFit Shims - PySerial ![PySerial Shim](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-serial)](https://pypi.org/project/counterfit-shims-serial) Shims for the PySerial to read sensors that use a virtual serial port See the [PySerial Docs](https://pyserial.readthedocs.io/en/latest/pyserial.html) for the API documentation. ## Getting started To use these shims, you will need to install [CounterFit](https://github.com/CounterFit-IoT/CounterFit) and have it running, with the appropriate hardware created. * Install this package from pip: ```sh pip install counterfit-shims-serial ``` * Import PySerial using the `counterfit_shims_serial` package instead of the `serial` package, as well as importing the `CounterFitConnection` from the `counterfit_shims_grove.counterfit_connection` module: ```python from counterfit_connection import CounterFitConnection import counterfit_shims_serial ``` * Configure the connection to the CounterFit app. Change the hostname and port to where you are running it: ```python CounterFitConnection.init('127.0.0.1', 5000) ``` * Write your PySerial code as usual, setting the port to match the one you set in the CounterFit app. For example, create a UART sensor on port `/dev/ttyAMA0`: ```python serial = counterfit_shims_serial.Serial('/dev/ttyAMA0', 9600, timeout=1) ``` The baud and timeout settings are ignored. ================================================ FILE: shims/pyserial/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/pyserial/counterfit_shims_serial.py ================================================ # pylint: disable=unused-argument,import-error '''Shims for PySerial ''' from counterfit_connection import CounterFitConnection __all__ = ['Serial'] class Serial(): '''Shims for the PySerial Serial class ''' def __init__(self, port: str, baud: int = 9600, **kwargs): self.__port = port def read(self, **kwargs) -> bytes: '''Reads a character from the serial port ''' return CounterFitConnection.read_serial_sensor_char(self.__port).encode('utf-8') def readline(self, **kwargs) -> bytes: '''Reads a line from the serial port ''' return CounterFitConnection.read_serial_sensor_line(self.__port).encode('utf-8') def reset_input_buffer(self): '''Does nothing - here for PySerial compatability ''' def flush(self): '''Does nothing - here for PySerial compatability ''' ================================================ FILE: shims/pyserial/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 counterfit-connection ================================================ FILE: shims/pyserial/setup.py ================================================ ''' Shims for the PySerial library for use with the CounterFit Virtual IoT Device app ''' # pylint: disable=redefined-builtin from codecs import open from os import path from setuptools import setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-shims-serial', py_modules=['counterfit_shims_serial'], version='0.1.0.dev3', description='Shims for the PySerial library for the CounterFit virtual IoT device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot pyserial serial virtual hardware", install_requires=['requests','counterfit-connection'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/pyserial/tests/__init__.py ================================================ ================================================ FILE: shims/pyserial/tests/test_counterfit_shims_serial.py ================================================ ''' Tests the PySerial connection. To test this, launch CounterFit, and add 2 GPS sensors, one on /dev/tty0 and one on /dev/tty1. Set them both to NMEA data, and the data as hello\nworld ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection import counterfit_shims_serial @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_read(init_counterfit_device): ''' Tests values returned from the Serial port ''' serial = counterfit_shims_serial.Serial('/dev/tty0') assert serial.read().decode('utf-8') == 'h' assert serial.read().decode('utf-8') == 'e' assert serial.read().decode('utf-8') == 'l' assert serial.read().decode('utf-8') == 'l' assert serial.read().decode('utf-8') == 'o' assert serial.read().decode('utf-8') == '\n' assert serial.read().decode('utf-8') == 'w' assert serial.read().decode('utf-8') == 'o' assert serial.read().decode('utf-8') == 'r' assert serial.read().decode('utf-8') == 'l' assert serial.read().decode('utf-8') == 'd' def test_read_line(init_counterfit_device): ''' Tests values returned from the Serial port ''' serial = counterfit_shims_serial.Serial('/dev/tty1', 9600, timeout=1) assert serial.readline().decode('utf-8') == 'hello' assert serial.readline().decode('utf-8') == 'world' ================================================ FILE: shims/pyserial/upload.sh ================================================ python3 -m twine upload --repository pypi dist/* ================================================ FILE: shims/rpi_vl53l0x/README.md ================================================ # CounterFit Shims - VL53L0X distance sensor ![RPI_VL53L0X Shim](https://img.shields.io/badge/Platform-Python-green) [![PyPI](https://img.shields.io/pypi/v/counterfit-shims-rpi-vl53l0x)](https://pypi.org/project/counterfit-shims-rpi-vl53l0x) Shims for the Rpi-VL53L0X distance sensor. See the [Rpi-VL53L0X Docs](https://github.com/turmary/VL53L0X_rasp) for the API documentation. ## Getting started To use these shims, you will need to install [CounterFit](https://github.com/CounterFit-IoT/CounterFit) and have it running, with the appropriate hardware created. * Install this package from pip: ```sh pip install counterfit-shims-rpi-vl53l0x ``` * Import VL53L0X using the `counterfit_shims_rpi-vl53l0x` package instead of the `rpi-vl53l0x` package, as well as importing the `CounterFitConnection` from the `counterfit_connection` module: ```python from counterfit_connection import CounterFitConnection from counterfit_shims_rpi_vl53l0x.vl53l0x import VL53L0X ``` * Configure the connection to the CounterFit app. Change the hostname and port to where you are running it: ```python CounterFitConnection.init('127.0.0.1', 5000) ``` * Write your VL53L0X code as usual, setting the I2C address to match the one you set in the CounterFit app. For example, create a distance sensor on port `0x29`: ```python distance_sensor = VL53L0X(0x29) ``` ================================================ FILE: shims/rpi_vl53l0x/build.sh ================================================ rm ./dist/* python3 setup.py bdist_wheel ================================================ FILE: shims/rpi_vl53l0x/counterfit_shims_rpi_vl53l0x/__init__.py ================================================ ''' The picamera package consists of several modules which provide a pure Python interface to the Raspberry Pi's camera module. ''' from counterfit_shims_rpi_vl53l0x.vl53l0x import VL53L0X ================================================ FILE: shims/rpi_vl53l0x/counterfit_shims_rpi_vl53l0x/vl53l0x.py ================================================ # pylint: disable=unused-argument,import-error '''Shims for PySerial ''' from counterfit_connection import CounterFitConnection __all__ = ['VL53L0X'] class VL53L0X(object): def __init__(self, address:int = 0x29): self.__address = address def get_libver(self): return 'VL53L0X for CounterFit' def get_devver(self): return 'VL53L0X for CounterFit' def begin(self): return 0 def wait_ready(self): ''' return None -- Error status False -- Timeout True -- Ready ''' return CounterFitConnection.is_connected() def get_distance(self): return CounterFitConnection.get_sensor_int_value(self.__address) ================================================ FILE: shims/rpi_vl53l0x/requirements.txt ================================================ wheel=0.38.4 setuptools=65.5.0 twine=4.0.2 pytest=7.2.2 pytest-runner=6.0.0 requests=2.28.2 pylint=2.16.2 counterfit-connection ================================================ FILE: shims/rpi_vl53l0x/setup.py ================================================ ''' Shims for the rpi_vl53l0x library for use with the CounterFit Virtual IoT Device app ''' # pylint: disable=redefined-builtin from codecs import open from os import path from setuptools import find_packages, setup here = path.abspath(path.dirname(__file__)) with open(path.join(here, "README.md"), encoding="utf-8") as f: long_description = f.read() setup( name='counterfit-shims-rpi-vl53l0x', packages=find_packages(include=['counterfit_shims_rpi_vl53l0x']), version='0.1.0.dev3', description='Shims for the rpi_vl53l0x library for the CounterFit virtual IoT device app', long_description=long_description, long_description_content_type='text/markdown', author='Jim Bennett', url="https://github.com/CounterFit-IoT/CounterFit", license='MIT', classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: System :: Hardware", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], keywords="iot rpi_vl53l0x vl53l0x virtual hardware", install_requires=['requests','counterfit-connection'], setup_requires=['pytest-runner'], tests_require=['pytest==4.4.1'], test_suite='tests', ) ================================================ FILE: shims/rpi_vl53l0x/tests/__init__.py ================================================ ================================================ FILE: shims/rpi_vl53l0x/tests/test_counterfit_shims_rpi_vl53l0x.py ================================================ ''' Tests the RPI VL53L0X connection Create a distance sensor at the address 0x29 and set the distance to 100mm ''' # pylint: disable=redefined-outer-name,unused-argument import pytest from counterfit_connection import CounterFitConnection from counterfit_shims_rpi_vl53l0x.vl53l0x import VL53L0X @pytest.fixture def init_counterfit_device(): ''' Test fixture to initialise the connection to the CounterFit Virtual IoT device running on localhost on port 5000 ''' CounterFitConnection.init('127.0.0.1', 5000) def test_get_distance(init_counterfit_device): ''' Tests values returned from the Serial port ''' distance_sensor = VL53L0X() distance_sensor.begin() if distance_sensor.wait_ready(): assert distance_sensor.get_distance() == 100 ================================================ FILE: shims/rpi_vl53l0x/upload.sh ================================================ python3 -m twine upload --repository pypi dist/*