Showing preview only (254K chars total). Download the full file or copy to clipboard to get everything.
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 <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 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
[](https://github.com/CounterFit-IoT/CounterFit/blob/master/LICENSE)
[](https://GitHub.com/CounterFit-IoT/CounterFit/graphs/contributors/)
[](https://GitHub.com/CounterFit-IoT/CounterFit/issues/)
[](https://GitHub.com/CounterFit-IoT/CounterFit/pull/)
[](http://makeapullrequest.com)
[](https://GitHub.com/CounterFit-IoT/CounterFit/watchers/)
[](https://GitHub.com/CounterFit-IoT/CounterFit/network/)
[](https://GitHub.com/CounterFit-IoT/CounterFit/stargazers/)

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.

## 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
*  [](./shims/SeeedStudios/grove/README.md) [](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).
*  [](./shims/SeeedStudios/grove/README.md) [](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
================================================
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_sensor_port">Name:</label>
</div>
<div class="col">
<input class="form-control" type="text" id="new_sensor_name" value="sensor_1" style="width: 100%;"/>
</div>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/boolean_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">Pin {{sensor.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
<label for="value{{sensor.id}}">Value:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="value{{sensor.id}}" value="{{sensor.value}}"
{% if sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="random{{sensor.id}}">Random:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="random{{sensor.id}}"
{% if sensor.random: %}
checked
{% endif %}
/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
var is_random_control = document.getElementById("random{{sensor.id}}")
var value_control = document.getElementById("value{{sensor.id}}")
is_random_control.addEventListener("change", function() {
value_control.disabled = is_random_control.checked
})
set_button.addEventListener("click", function() {
// get the values
var is_random = is_random_control.checked
var value = value_control.checked
var payload = {
"port": "{{sensor.port}}",
'value': value,
'is_random': is_random
}
var xhr = new XMLHttpRequest();
var url = "./boolean_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/camera_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">{{sensor.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="source{{sensor.id}}">Source:</label>
</div>
<div class="col">
<select name="source{{sensor.id}}" id="source{{sensor.id}}" class="form-control">
<option value="File"
{% if sensor.image_source.name == "FILE": %}
selected
{% endif %}
>File</option>
<option value="WebCam"
{% if sensor.image_source.name == "WEBCAM": %}
selected
{% endif %}
>WebCam</option>
</select>
</div>
</div>
<div id="camera_file_settings{{sensor.id}}"
{% if sensor.image_source.name == "FILE": %}
style="display:block"
{% else %}
style="display:none"
{% endif %}
>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="image_file_name{{sensor.id}}">Image File:</label>
</div>
<div class="col">
{{sensor.image_file_name}}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<input class="form-control" type="file" id="image_file_name{{sensor.id}}" value="" style="width: 100%;"/>
</div>
</div>
<div id="web_cam_settings{{sensor.id}}"
{% if sensor.image_source.name == "FILE": %}
style="display:none"
{% else %}
style="display:block"
{% endif %}
>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="web_cam_source{{sensor.id}}">Camera:</label>
</div>
<div class="col">
<select name="web_cam_source{{sensor.id}}" id="web_cam_source{{sensor.id}}" class="form-control">
</select>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<video id="web_cam{{sensor.id}}" autoplay style="width: 100%;"/></video>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var web_cam = document.getElementById("web_cam{{sensor.id}}");
var web_cam_source = document.getElementById("web_cam_source{{sensor.id}}");
var running_video = false;
var source_control = document.getElementById("source{{sensor.id}}")
var camera_file_settings_control = document.getElementById("camera_file_settings{{sensor.id}}")
var web_cam_settings_control = document.getElementById("web_cam_settings{{sensor.id}}")
var image_file_name_control = document.getElementById("image_file_name{{sensor.id}}")
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
var get_devices = function (mediaDevices) {
web_cam_source.innerHTML = '';
let count = 1;
mediaDevices.forEach(mediaDevice => {
if (mediaDevice.kind === 'videoinput') {
const option = document.createElement('option');
option.value = mediaDevice.deviceId;
const label = mediaDevice.label || `Camera ${count++}`;
const textNode = document.createTextNode(label);
option.appendChild(textNode);
web_cam_source.appendChild(option);
}
});
}
var stop_media_tracks = function (stream) {
stream.getTracks().forEach(track => {
track.stop();
});
}
var select_web_cam = async function (device_id) {
if (web_cam.srcObject) {
stop_media_tracks(web_cam.srcObject);
}
var selected_device_Id = {
deviceId : { exact: device_id },
width: { ideal: 4096 },
height: { ideal: 2160 }
}
const constraints = {
video: selected_device_Id,
audio: false
};
web_cam.srcObject = await navigator.mediaDevices.getUserMedia(constraints)
web_cam.play();
}
var set_web_cam_settings = async function () {
var selected_source = source_control.value
if (selected_source == "File") {
camera_file_settings_control.style.display = "block"
web_cam_settings_control.style.display = "none"
}
else {
camera_file_settings_control.style.display = "none"
web_cam_settings_control.style.display = "block"
if (!running_video) {
running_video = true;
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
web_cam.srcObject = await navigator.mediaDevices.getUserMedia({ video: true })
navigator.mediaDevices.enumerateDevices().then(get_devices);
web_cam_source.addEventListener("change", async function() {
var selected_web_cam_source = web_cam_source.value
await select_web_cam(selected_web_cam_source)
})
web_cam.play();
}
}
}
}
if (source_control.value == 'WebCam') {
const select_web_cam_for_device = async () => {
await set_web_cam_settings()
device_id = '{{sensor.web_cam_device_id}}'
if (device_id) {
await select_web_cam(device_id)
web_cam_source.value = device_id
}
}
select_web_cam_for_device()
}
source_control.addEventListener("change", set_web_cam_settings)
var array_buffer_to_base64 = function(buffer) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
var post_camera_sensor_settings = function (payload) {
var xhr = new XMLHttpRequest();
var url = "./camera_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
}
set_button.addEventListener("click", function() {
var selected_source = source_control.value
var payload = {
"port": "{{sensor.port}}",
"source": selected_source
}
if (selected_source == "File") {
const file = image_file_name_control.files[0];
payload["image_file_name"] = file.name
const reader = new FileReader();
reader.onload = function(event)
{
var contents = event.target.result;
payload["file_contents"] = array_buffer_to_base64(contents);
post_camera_sensor_settings(payload)
};
reader.readAsArrayBuffer(file);
}
else {
var selected_web_cam_source = web_cam_source.value
payload["web_cam_device_id"] = selected_web_cam_source
post_camera_sensor_settings(payload)
}
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
var socket = io();
socket.on('capture_camera_from_webcam{{sensor.id}}', function(data, ack) {
console.log('Camera capture received');
var canvas = document.createElement('canvas');
canvas.width = web_cam.videoWidth;
canvas.height = web_cam.videoHeight;
var context = canvas.getContext('2d');
context.drawImage(web_cam, 0, 0, canvas.width, canvas.height);
canvas.toBlob
data['image_base64'] = canvas.toDataURL("image/jpeg")
ack(data);
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/float_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">Pin {{sensor.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
Units:
</div>
<div class="col">
{{ sensor.unit }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
Value range:
</div>
<div class="col">
{{ "{:,.2f}".format(sensor.valid_min) }} to {{ "{:,.2f}".format(sensor.valid_max) }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="value{{sensor.id}}">Value:</label>
</div>
<div class="col">
<input class="form-control" type="number" id="value{{sensor.id}}" value="{{sensor.value}}" style="width: 100%;"
{% if sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="random{{sensor.id}}">Random:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="random{{sensor.id}}"
{% if sensor.random: %}
checked
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col" style="text-align:center">
<label for="random_min{{sensor.id}}">Min</label>
</div>
<div class="col" style="text-align:center">
<label for="random_max{{sensor.id}}">Max</label>
</div>
</div>
<div class="row">
<div class="col">
<input class="form-control" type="number" id="random_min{{sensor.id}}" value="{{sensor.random_min}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
<div class="col">
<input class="form-control" type="number" id="random_max{{sensor.id}}" value="{{sensor.random_max}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
var is_random_control = document.getElementById("random{{sensor.id}}")
var random_min_control = document.getElementById("random_min{{sensor.id}}")
var random_max_control = document.getElementById("random_max{{sensor.id}}")
var value_control = document.getElementById("value{{sensor.id}}")
is_random_control.addEventListener("change", function() {
value_control.disabled = is_random_control.checked
random_min_control.disabled = !is_random_control.checked
random_max_control.disabled = !is_random_control.checked
})
set_button.addEventListener("click", function() {
// get the values
var is_random = is_random_control.checked
var random_min = parseFloat(random_min_control.value)
var random_max = parseFloat(random_max_control.value)
var value = parseFloat(value_control.value)
var payload = {
"port": "{{sensor.port}}",
'value': value,
'is_random': is_random,
'random_min': random_min,
'random_max': random_max
}
var xhr = new XMLHttpRequest();
var url = "./float_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/gps_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">Port {{sensor.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
<label for="source{{sensor.id}}">Source:</label>
</div>
<div class="col">
<select name="source{{sensor.id}}" id="source{{sensor.id}}" class="form-control">
<option value="latlon"
{% if sensor.value_type.name == "LATLON": %}
selected
{% endif %}
>Lat/Lon</option>
<option value="nmeasentences"
{% if sensor.value_type.name == "NMEA": %}
selected
{% endif %}
>NMEA</option>
<option value="gpxfile"
{% if sensor.value_type.name == "GPX": %}
selected
{% endif %}
>GPX file</option>
</select>
</div>
</div>
<div id="latlonsettings{{sensor.id}}"
{% if sensor.value_type.name == "LATLON": %}
style="display:block"
{% else %}
style="display:none"
{% endif %}
>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="lat{{sensor.id}}">Lat:</label>
</div>
<div class="col">
<input class="form-control" type="number" id="lat{{sensor.id}}" value="{{sensor.lat}}" style="width: 100%;"/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="lon{{sensor.id}}">Lat:</label>
</div>
<div class="col">
<input class="form-control" type="number" id="lon{{sensor.id}}" value="{{sensor.lon}}" style="width: 100%;"/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="number_of_satellites{{sensor.id}}"># Satellites:</label>
</div>
<div class="col">
<input class="form-control" type="number" id="number_of_satellites{{sensor.id}}" value="{{sensor.number_of_satellites}}" style="width: 100%;"/>
</div>
</div>
</div>
<div id="nmeasettings{{sensor.id}}"
{% if sensor.value_type.name == "NMEA": %}
style="display:block"
{% else %}
style="display:none"
{% endif %}
>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="nmea{{sensor.id}}">NMEA:</label>
</div>
<div class="col">
<textarea id="nmea{{sensor.id}}" name="nmea{{sensor.id}}" rows="8" cols="100" style="width: 100%;white-space: nowrap;">{{sensor.raw_nmea}}</textarea>
</div>
</div>
</div>
<div id="gpxsettings{{sensor.id}}"
{% if sensor.value_type.name == "GPX": %}
style="display:block"
{% else %}
style="display:none"
{% endif %}
>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="gpx_file_name{{sensor.id}}">GPX File:</label>
</div>
<div class="col">
{{sensor.gpx_file_name}}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<input class="form-control" type="file" id="gpx_file_name{{sensor.id}}" value="" style="width: 100%;"/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="repeat{{sensor.id}}">Repeat:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="repeat{{sensor.id}}"
{% if sensor.repeat: %}
checked
{% endif %}
/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var source_control = document.getElementById("source{{sensor.id}}")
var lat_lon_settings_control = document.getElementById("latlonsettings{{sensor.id}}")
var nmea_settings_control = document.getElementById("nmeasettings{{sensor.id}}")
var gpx_file_settings_control = document.getElementById("gpxsettings{{sensor.id}}")
var lat_control = document.getElementById("lat{{sensor.id}}")
var lon_control = document.getElementById("lon{{sensor.id}}")
var number_of_satellites_control = document.getElementById("number_of_satellites{{sensor.id}}")
var nmea_control = document.getElementById("nmea{{sensor.id}}")
var gpx_file_control = document.getElementById("gpx_file_name{{sensor.id}}")
var repeat_control = document.getElementById("repeat{{sensor.id}}")
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
source_control.addEventListener("change", function () {
var selected_source = source_control.value
if (selected_source == "latlon") {
lat_lon_settings_control.style.display = "block"
nmea_settings_control.style.display = "none"
gpx_file_settings_control.style.display = "none"
}
else if (selected_source == "nmeasentences") {
lat_lon_settings_control.style.display = "none"
nmea_settings_control.style.display = "block"
gpx_file_settings_control.style.display = "none"
}
else if (selected_source == "gpxfile") {
lat_lon_settings_control.style.display = "none"
nmea_settings_control.style.display = "none"
gpx_file_settings_control.style.display = "block"
}
})
var post_gps_sensor_settings = function (payload) {
var xhr = new XMLHttpRequest();
var url = "./gps_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
}
set_button.addEventListener("click", function() {
var selected_source = source_control.value
var payload = {
"port": "{{sensor.port}}",
"repeat": repeat_control.checked,
"source": selected_source
}
if (selected_source == "latlon") {
payload["lat"] = parseFloat(lat_control.value)
payload["lon"] = parseFloat(lon_control.value)
payload["number_of_satellites"] = parseInt(number_of_satellites_control.value)
post_gps_sensor_settings(payload)
}
else if (selected_source == "nmeasentences") {
payload["nmea"] = nmea_control.value
post_gps_sensor_settings(payload)
}
else if (selected_source == "gpxfile") {
const file = gpx_file_control.files[0];
payload["gpx_file_name"] = file.name
const reader = new FileReader();
reader.onload = function(event)
{
// NOTE: event.target point to FileReader
var contents = event.target.result;
payload["gpx"] = contents;
post_gps_sensor_settings(payload)
};
reader.readAsText(file);
}
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/home.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>CounterFit</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='images/favicon.png') }}">
<script src="{{ url_for('static', filename='socket.io.min.js') }}"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function () {
socket.emit('my event', { data: 'I\'m connected!' });
});
socket.onmessage = (data) => {
console.log(data);
};
socket.on('device_connect', function(data) {
var connection_led_title = document.getElementById("connectionLedTitleId")
var connection_led = document.getElementById("connectionLed")
var connection_label = document.getElementById("connectionLabel")
if (data.connected) {
connection_led_title.innerHTML = "Connected"
connection_label.innerHTML = "Connected"
connection_label.style.color = "white"
connection_led.style.visibility = "visible"
} else {
connection_led_title.innerHTML = "Disconnected"
connection_label.innerHTML = "Disconnected"
connection_label.style.color = "grey"
connection_led.style.visibility = "hidden"
}
});
</script>
</head>
<body class="bg-light" style="background-color: grey!important;">
<header class="bd-header bg-dark py-3 d-flex align-items-stretch border-bottom border-dark fixed-top">
<div class="container-fluid d-flex align-items-center">
<h1 class="d-flex align-items-center fs-4 text-white mb-0">
<img src="{{ url_for('static', filename='images/CounterFitLogo.png') }}" width="58" height="58"
class="me-3" alt="CounterFit logo">
CounterFit - Fake IoT Hardware
</h1>
<div class="ms-auto">
<span id="connectionLabel"
{% if is_connected: %}
style="color:white;">Connected
{% else %}
style="color:grey;">Disconnected
{% endif %}
</span>
<svg width="24px" height="40px" viewBox="0 0 48 80" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
role="img" aria-labelledby="connectionLedTitleId" style="margin-left: 20px;">
<title id="connectionLedTitleId">Disconnected</title>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(0.000000, 2.000000)" fill-rule="nonzero">
<g id="LED" transform="translate(0.353000, -0.353000)">
<rect id="Base" x="0" y="43.445" width="46.86" height="8.059" fill="#FFFFFF" stroke="#000000" stroke-width="1"></rect>
<g id="Pins" transform="translate(6.209000, 47.009000)" fill="#FFFFFF" stroke="#000000" stroke-width="1">
<rect id="Rectangle2" x="0" y="8.8817842e-15" width="6" height="30.557"></rect>
<rect id="Rectangle1" x="28.442" y="8.8817842e-15" width="6" height="30.557"></rect>
</g>
<path d="M23.43,0 C11.844,0 2.452,9.392 2.452,20.978 L2.452,42.941 L44.407,42.941 L44.407,20.978 C44.408,9.392 35.016,0 23.43,0 Z M40.155,20.791 C40.126,21.09 40.098,21.383 40.071,21.668 C40.011,22.237 39.934,22.776 39.861,23.272 C39.723,24.266 39.553,25.094 39.423,25.673 C39.291,26.253 39.19,26.584 39.19,26.584 C39.19,26.584 39.087,26.253 38.957,25.673 C38.827,25.093 38.655,24.265 38.518,23.272 C38.445,22.775 38.368,22.237 38.308,21.668 C38.281,21.384 38.252,21.092 38.222,20.793 C38.174,20.511 38.141,20.225 38.1,19.934 C38.065,19.645 38.03,19.35 37.994,19.052 C37.952,18.756 37.864,18.462 37.811,18.163 C37.749,17.864 37.682,17.565 37.63,17.262 C37.57,16.96 37.455,16.671 37.372,16.373 L37.123,15.482 C37.025,15.191 36.906,14.908 36.8,14.623 C36.689,14.339 36.586,14.054 36.487,13.769 C36.369,13.495 36.228,13.234 36.11,12.966 C35.989,12.698 35.87,12.435 35.753,12.176 C35.635,11.918 35.477,11.69 35.353,11.448 C35.081,10.982 34.872,10.486 34.597,10.11 C34.332,9.719 34.107,9.335 33.899,8.989 C33.658,8.656 33.447,8.365 33.274,8.127 C32.93,7.642 32.753,7.345 32.753,7.345 C32.753,7.345 33.067,7.491 33.585,7.783 C33.84,7.933 34.163,8.097 34.501,8.331 C34.831,8.587 35.211,8.882 35.608,9.234 C36.037,9.566 36.391,9.996 36.795,10.444 C36.991,10.672 37.203,10.901 37.39,11.156 C37.565,11.42 37.743,11.689 37.924,11.963 C38.104,12.24 38.291,12.521 38.456,12.818 C38.604,13.123 38.754,13.434 38.896,13.751 C39.033,14.07 39.201,14.382 39.314,14.714 L39.625,15.727 C39.715,16.069 39.834,16.404 39.896,16.75 C39.958,17.097 40.002,17.445 40.053,17.79 C40.098,18.134 40.163,18.474 40.172,18.814 C40.173,19.153 40.174,19.49 40.175,19.82 C40.166,20.154 40.168,20.476 40.155,20.791 Z"
stroke="#000000" stroke-width="2" fill="#FFFFFF">
</path>
<path id="connectionLed" d="M23.43,0 C11.844,0 2.452,9.392 2.452,20.978 L2.452,42.941 L44.407,42.941 L44.407,20.978 C44.408,9.392 35.016,0 23.43,0 Z M40.155,20.791 C40.126,21.09 40.098,21.383 40.071,21.668 C40.011,22.237 39.934,22.776 39.861,23.272 C39.723,24.266 39.553,25.094 39.423,25.673 C39.291,26.253 39.19,26.584 39.19,26.584 C39.19,26.584 39.087,26.253 38.957,25.673 C38.827,25.093 38.655,24.265 38.518,23.272 C38.445,22.775 38.368,22.237 38.308,21.668 C38.281,21.384 38.252,21.092 38.222,20.793 C38.174,20.511 38.141,20.225 38.1,19.934 C38.065,19.645 38.03,19.35 37.994,19.052 C37.952,18.756 37.864,18.462 37.811,18.163 C37.749,17.864 37.682,17.565 37.63,17.262 C37.57,16.96 37.455,16.671 37.372,16.373 L37.123,15.482 C37.025,15.191 36.906,14.908 36.8,14.623 C36.689,14.339 36.586,14.054 36.487,13.769 C36.369,13.495 36.228,13.234 36.11,12.966 C35.989,12.698 35.87,12.435 35.753,12.176 C35.635,11.918 35.477,11.69 35.353,11.448 C35.081,10.982 34.872,10.486 34.597,10.11 C34.332,9.719 34.107,9.335 33.899,8.989 C33.658,8.656 33.447,8.365 33.274,8.127 C32.93,7.642 32.753,7.345 32.753,7.345 C32.753,7.345 33.067,7.491 33.585,7.783 C33.84,7.933 34.163,8.097 34.501,8.331 C34.831,8.587 35.211,8.882 35.608,9.234 C36.037,9.566 36.391,9.996 36.795,10.444 C36.991,10.672 37.203,10.901 37.39,11.156 C37.565,11.42 37.743,11.689 37.924,11.963 C38.104,12.24 38.291,12.521 38.456,12.818 C38.604,13.123 38.754,13.434 38.896,13.751 C39.033,14.07 39.201,14.382 39.314,14.714 L39.625,15.727 C39.715,16.069 39.834,16.404 39.896,16.75 C39.958,17.097 40.002,17.445 40.053,17.79 C40.098,18.134 40.163,18.474 40.172,18.814 C40.173,19.153 40.174,19.49 40.175,19.82 C40.166,20.154 40.168,20.476 40.155,20.791 Z"
stroke="#000000" stroke-width="2" fill="#F98500"
{% if is_connected: %}
visibility="visible"
{% else %}
visibility="hidden"
{% endif %}
>
</path>
</g>
</g>
</g>
</svg>
</div>
</div>
</header>
<div class="card shadow-sm border-dark" style="margin-left: 10px;margin-right: 10px; margin-bottom: 10px; margin-top: 100px;">
<div class="card-header py-3">
<h2 style="text-align:center">Sensors</h2>
</div>
<div class="card-body">
<div class="container-fluid">
<div class="row">
<div class="col-3">
<div class="card shadow-sm border-primary" style="margin-top: 11px;">
<div class="card-header py-3 text-white bg-primary">
<h4 class="my-0 fw-normal" style="text-align:center">Create sensor</h4>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
<label for="new_sensor_type">Sensor Type:</label>
</div>
<div class="col">
<select name="new_sensor_type" id="new_sensor_type" class="form-control">
{% for sensor_type in all_sensors %}
<option value="{{ sensor_type.sensor_name() }}" data-sensortype="{{ sensor_type.sensor_type() }}">{{ sensor_type.sensor_name() }}</option>
{% endfor %}
</select>
</div>
</div>
<div id="pin_sensor_create_settings" style="display:block">
{% include "pin_sensor_create_settings.html" %}
</div>
<div id="serial_sensor_create_settings" style="display:none">
{% include "serial_sensor_create_settings.html" %}
</div>
<div id="binary_sensor_create_settings" style="display:none">
{% include "binary_sensor_create_settings.html" %}
</div>
<div id="i2c_sensor_create_settings" style="display:none">
{% include "i2c_sensor_create_settings.html" %}
</div>
</div>
</div>
<div class="card-footer">
<button type="button" id="add_new_sensor_button" class="w-100 btn btn-lg btn-primary">Add</button>
</div>
</div>
</div>
<div class="col-9">
<div class="container">
<div class="row row-cols-1 row-cols-sm-2 row-cols-sm-3 g-3">
{% for sensor in sensors %}
<div class="col">
{% 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 %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-dark" style="margin: 10px;">
<div class="card-header py-3">
<h2 style="text-align:center">Actuators</h2>
</div>
<div class="card-body">
<div class="container-fluid">
<div class="row">
<div class="col-3">
<div class="card shadow-sm border-primary" style="margin-top: 11px;">
<div class="card-header py-3 text-white bg-primary">
<h4 class="my-0 fw-normal" style="text-align:center">Create actuator</h4>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
<label for="new_actuator_type">Actuator Type:</label>
</div>
<div class="col">
<select name="new_actuator_type" id="new_actuator_type" class="form-control">
{% for actuator_type in all_actuators %}
<option value="{{ actuator_type.actuator_name() }}">{{ actuator_type.actuator_name() }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_actuator_port">Pin:</label>
</div>
<div class="col">
<select name="new_actuator_port" id="new_actuator_port" class="form-control">
{% for port in ports %}
<option value="{{ port }}">{{ port }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<div class="card-footer">
<button type="button" id="add_new_actuator_button" class="w-100 btn btn-lg btn-primary" >Add</button>
</div>
</div>
</div>
<div class="col-9">
<div class="container">
<div class="row row-cols-1 row-cols-sm-2 row-cols-sm-3 g-3">
{% for actuator in actuators %}
<div class="col">
{% 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 %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var add_new_sensor_button = document.getElementById("add_new_sensor_button")
var add_new_actuator_button = document.getElementById("add_new_actuator_button")
var new_sensor_type_control = document.getElementById("new_sensor_type")
var new_actuator_type_control = document.getElementById("new_actuator_type")
var new_sensor_pin_control = document.getElementById("new_sensor_pin")
var new_i2c_sensor_pin_control = document.getElementById("new_i2c_sensor_pin")
var new_sensor_port_control = document.getElementById("new_sensor_port")
var new_sensor_name_control = document.getElementById("new_sensor_name")
var new_actuator_port_control = document.getElementById("new_actuator_port")
var new_sensor_units_control = document.getElementById("new_sensor_units")
var new_i2c_sensor_units_control = document.getElementById("new_i2c_sensor_units")
var pin_sensor_create_settings = document.getElementById("pin_sensor_create_settings")
var serial_sensor_create_settings = document.getElementById("serial_sensor_create_settings")
var binary_sensor_create_settings = document.getElementById("binary_sensor_create_settings")
var i2c_sensor_create_settings = document.getElementById("i2c_sensor_create_settings")
new_sensor_type_control.addEventListener("change", function () {
var option = new_sensor_type_control.options[new_sensor_type_control.selectedIndex]
sensor_type = option.getAttribute('data-sensortype')
if (sensor_type == "SensorType.SERIAL"){
pin_sensor_create_settings.style.display = "none"
serial_sensor_create_settings.style.display = "block"
binary_sensor_create_settings.style.display = "none"
i2c_sensor_create_settings.style.display = "none"
}
else if (sensor_type == "SensorType.BINARY"){
pin_sensor_create_settings.style.display = "none"
serial_sensor_create_settings.style.display = "none"
binary_sensor_create_settings.style.display = "block"
i2c_sensor_create_settings.style.display = "none"
}
else if (sensor_type == "SensorType.I2C"){
pin_sensor_create_settings.style.display = "none"
serial_sensor_create_settings.style.display = "none"
binary_sensor_create_settings.style.display = "none"
i2c_sensor_create_settings.style.display = "block"
}
else {
pin_sensor_create_settings.style.display = "block"
serial_sensor_create_settings.style.display = "none"
binary_sensor_create_settings.style.display = "none"
i2c_sensor_create_settings.style.display = "none"
}
})
add_new_sensor_button.addEventListener("click", function () {
var new_sensor_type = new_sensor_type_control.value
var new_sensor_unit = new_sensor_units_control.value
var new_i2c_sensor_unit = new_i2c_sensor_units_control.value
var new_sensor_pin = parseInt(new_sensor_pin_control.value)
var new_i2c_sensor_pin = parseInt(new_i2c_sensor_pin_control.value)
var new_sensor_port = new_sensor_port_control.value
var new_sensor_name = new_sensor_name_control.value
var payload = {
'type': new_sensor_type,
'pin': new_sensor_pin,
'i2c_pin': new_i2c_sensor_pin,
'port': new_sensor_port,
'name': new_sensor_name,
'unit': new_sensor_unit,
'i2c_unit': new_i2c_sensor_unit
}
var xhr = new XMLHttpRequest();
var url = "./create_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
add_new_actuator_button.addEventListener("click", function () {
var new_actuator_type = new_actuator_type_control.value
var new_actuator_port = parseInt(new_actuator_port_control.value)
var payload = {
'type': new_actuator_type,
'port': new_actuator_port
}
var xhr = new XMLHttpRequest();
var url = "./create_actuator";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
function populate_units() {
var new_sensor_type = new_sensor_type_control.value
var payload = {
'type': new_sensor_type,
}
var xhr = new XMLHttpRequest();
var url = "./sensor_units";
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
response = JSON.parse(xhr.response)
var inner = ""
response.units.forEach(function (item, index) {
inner += "<option value=\"" + item + "\">" + item + "</option>"
});
new_sensor_units_control.innerHTML = inner
new_i2c_sensor_units_control.innerHTML = inner
}
}
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
}
new_sensor_type_control.addEventListener('change', (event) => {
populate_units()
});
populate_units()
})
</script>
</body>
</html>
================================================
FILE: counterfit-app/src/CounterFit/templates/i2c_float_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">I<sup>2</sup>C: {{sensor.address}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
Units:
</div>
<div class="col">
{{ sensor.unit }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
Value range:
</div>
<div class="col">
{{ "{:,.2f}".format(sensor.valid_min) }} to {{ "{:,.2f}".format(sensor.valid_max) }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="value{{sensor.id}}">Value:</label>
</div>
<div class="col">
<input class="form-control" type="number" id="value{{sensor.id}}" value="{{sensor.value}}" style="width: 100%;"
{% if sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="random{{sensor.id}}">Random:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="random{{sensor.id}}"
{% if sensor.random: %}
checked
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col" style="text-align:center">
<label for="random_min{{sensor.id}}">Min</label>
</div>
<div class="col" style="text-align:center">
<label for="random_max{{sensor.id}}">Max</label>
</div>
</div>
<div class="row">
<div class="col">
<input class="form-control" type="number" id="random_min{{sensor.id}}" value="{{sensor.random_min}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
<div class="col">
<input class="form-control" type="number" id="random_max{{sensor.id}}" value="{{sensor.random_max}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
var is_random_control = document.getElementById("random{{sensor.id}}")
var random_min_control = document.getElementById("random_min{{sensor.id}}")
var random_max_control = document.getElementById("random_max{{sensor.id}}")
var value_control = document.getElementById("value{{sensor.id}}")
is_random_control.addEventListener("change", function() {
value_control.disabled = is_random_control.checked
random_min_control.disabled = !is_random_control.checked
random_max_control.disabled = !is_random_control.checked
})
set_button.addEventListener("click", function() {
// get the values
var is_random = is_random_control.checked
var random_min = parseFloat(random_min_control.value)
var random_max = parseFloat(random_max_control.value)
var value = parseFloat(value_control.value)
var payload = {
"port": "{{sensor.port}}",
'value': value,
'is_random': is_random,
'random_min': random_min,
'random_max': random_max
}
var xhr = new XMLHttpRequest();
var url = "./float_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/i2c_integer_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">I<sup>2</sup>C: {{sensor.address}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
Units:
</div>
<div class="col">
{{ sensor.unit }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
Value range:
</div>
<div class="col">
{{ "{:,d}".format(sensor.valid_min) }} to {{ "{:,d}".format(sensor.valid_max) }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="value{{sensor.id}}">Value:</label>
</div>
<div class="col">
<input class="form-control" type="number" step="1" pattern="\d+" id="value{{sensor.id}}" value="{{sensor.value}}" style="width: 100%;"
{% if sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="random{{sensor.id}}">Random:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="random{{sensor.id}}"
{% if sensor.random: %}
checked
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col" style="text-align:center">
<label for="random_min{{sensor.id}}">Min</label>
</div>
<div class="col" style="text-align:center">
<label for="random_max{{sensor.id}}">Max</label>
</div>
</div>
<div class="row">
<div class="col">
<input class="form-control" type="number" step="1" pattern="\d+" id="random_min{{sensor.id}}" value="{{sensor.random_min}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
<div class="col">
<input class="form-control" type="number" step="1" pattern="\d+" id="random_max{{sensor.id}}" value="{{sensor.random_max}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
var is_random_control = document.getElementById("random{{sensor.id}}")
var random_min_control = document.getElementById("random_min{{sensor.id}}")
var random_max_control = document.getElementById("random_max{{sensor.id}}")
var value_control = document.getElementById("value{{sensor.id}}")
is_random_control.addEventListener("change", function() {
value_control.disabled = is_random_control.checked
random_min_control.disabled = !is_random_control.checked
random_max_control.disabled = !is_random_control.checked
})
set_button.addEventListener("click", function() {
// get the values
var is_random = is_random_control.checked
var random_min = parseInt(random_min_control.value)
var random_max = parseInt(random_max_control.value)
var value = parseInt(value_control.value)
var payload = {
"port": "{{sensor.port}}",
'value': value,
'is_random': is_random,
'random_min': random_min,
'random_max': random_max
}
var xhr = new XMLHttpRequest();
var url = "./integer_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/i2c_sensor_create_settings.html
================================================
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_i2c_sensor_units">Units:</label>
</div>
<div class="col">
<select name="new_i2c_sensor_units" id="new_i2c_sensor_units" class="form-control">
</select>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_i2c_sensor_pin">I<sup>2</sup>C address:</label>
</div>
<div class="col">
<select name="new_i2c_sensor_pin" id="new_i2c_sensor_pin" class="form-control">
{% for port in ports_and__hex %}
<option value="{{ port[0] }}">{{ port[1] }}</option>
{% endfor %}
</select>
</div>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/integer_sensor.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ sensor.sensor_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">Pin {{sensor.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
Units:
</div>
<div class="col">
{{ sensor.unit }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
Value range:
</div>
<div class="col">
{{ "{:,d}".format(sensor.valid_min) }} to {{ "{:,d}".format(sensor.valid_max) }}
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="value{{sensor.id}}">Value:</label>
</div>
<div class="col">
<input class="form-control" type="number" step="1" pattern="\d+" id="value{{sensor.id}}" value="{{sensor.value}}" style="width: 100%;"
{% if sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="random{{sensor.id}}">Random:</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="random{{sensor.id}}"
{% if sensor.random: %}
checked
{% endif %}
/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col" style="text-align:center">
<label for="random_min{{sensor.id}}">Min</label>
</div>
<div class="col" style="text-align:center">
<label for="random_max{{sensor.id}}">Max</label>
</div>
</div>
<div class="row">
<div class="col">
<input class="form-control" type="number" step="1" pattern="\d+" id="random_min{{sensor.id}}" value="{{sensor.random_min}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
<div class="col">
<input class="form-control" type="number" step="1" pattern="\d+" id="random_max{{sensor.id}}" value="{{sensor.random_max}}" style="width: 100%;"
{% if not sensor.random: %}
disabled
{% endif %}
/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="sensor_set_button{{sensor.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="sensor_delete_button{{sensor.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var set_button = document.getElementById("sensor_set_button{{sensor.id}}")
var delete_button = document.getElementById("sensor_delete_button{{sensor.id}}")
var is_random_control = document.getElementById("random{{sensor.id}}")
var random_min_control = document.getElementById("random_min{{sensor.id}}")
var random_max_control = document.getElementById("random_max{{sensor.id}}")
var value_control = document.getElementById("value{{sensor.id}}")
is_random_control.addEventListener("change", function() {
value_control.disabled = is_random_control.checked
random_min_control.disabled = !is_random_control.checked
random_max_control.disabled = !is_random_control.checked
})
set_button.addEventListener("click", function() {
// get the values
var is_random = is_random_control.checked
var random_min = parseInt(random_min_control.value)
var random_max = parseInt(random_max_control.value)
var value = parseInt(value_control.value)
var payload = {
"port": "{{sensor.port}}",
'value': value,
'is_random': is_random,
'random_min': random_min,
'random_max': random_max
}
var xhr = new XMLHttpRequest();
var url = "./integer_sensor_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{sensor.port}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_sensor";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/led_actuator.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ actuator.actuator_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">Pin {{actuator.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col">
<label for="color{{actuator.id}}">Color:</label>
</div>
<div class="col">
<input class="form-control" type="color" id="color{{actuator.id}}" value="{{actuator.color}}" style="width: 100%;"/>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
</div>
<div class="col">
<svg class="mx-auto d-block" width="48px" height="80px" viewBox="0 0 48 80" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
role="img" aria-labelledby="ledTitleId{{actuator.id}}">
<title id="ledTitleId{{actuator.id}}">{% if actuator.value: %}LED on{% else %}LED off{% endif %}</title>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(0.000000, 2.000000)" fill-rule="nonzero">
<g id="LED" transform="translate(0.353000, -0.353000)">
<rect id="Base" fill="#000000" x="0" y="43.445" width="46.86" height="8.059"></rect>
<g id="Pins" transform="translate(6.209000, 47.009000)" fill="#000000">
<rect id="Rectangle2" x="0" y="8.8817842e-15" width="6" height="30.557"></rect>
<rect id="Rectangle1" x="28.442" y="8.8817842e-15" width="6" height="30.557"></rect>
</g>
<path d="M23.43,0 C11.844,0 2.452,9.392 2.452,20.978 L2.452,42.941 L44.407,42.941 L44.407,20.978 C44.408,9.392 35.016,0 23.43,0 Z M40.155,20.791 C40.126,21.09 40.098,21.383 40.071,21.668 C40.011,22.237 39.934,22.776 39.861,23.272 C39.723,24.266 39.553,25.094 39.423,25.673 C39.291,26.253 39.19,26.584 39.19,26.584 C39.19,26.584 39.087,26.253 38.957,25.673 C38.827,25.093 38.655,24.265 38.518,23.272 C38.445,22.775 38.368,22.237 38.308,21.668 C38.281,21.384 38.252,21.092 38.222,20.793 C38.174,20.511 38.141,20.225 38.1,19.934 C38.065,19.645 38.03,19.35 37.994,19.052 C37.952,18.756 37.864,18.462 37.811,18.163 C37.749,17.864 37.682,17.565 37.63,17.262 C37.57,16.96 37.455,16.671 37.372,16.373 L37.123,15.482 C37.025,15.191 36.906,14.908 36.8,14.623 C36.689,14.339 36.586,14.054 36.487,13.769 C36.369,13.495 36.228,13.234 36.11,12.966 C35.989,12.698 35.87,12.435 35.753,12.176 C35.635,11.918 35.477,11.69 35.353,11.448 C35.081,10.982 34.872,10.486 34.597,10.11 C34.332,9.719 34.107,9.335 33.899,8.989 C33.658,8.656 33.447,8.365 33.274,8.127 C32.93,7.642 32.753,7.345 32.753,7.345 C32.753,7.345 33.067,7.491 33.585,7.783 C33.84,7.933 34.163,8.097 34.501,8.331 C34.831,8.587 35.211,8.882 35.608,9.234 C36.037,9.566 36.391,9.996 36.795,10.444 C36.991,10.672 37.203,10.901 37.39,11.156 C37.565,11.42 37.743,11.689 37.924,11.963 C38.104,12.24 38.291,12.521 38.456,12.818 C38.604,13.123 38.754,13.434 38.896,13.751 C39.033,14.07 39.201,14.382 39.314,14.714 L39.625,15.727 C39.715,16.069 39.834,16.404 39.896,16.75 C39.958,17.097 40.002,17.445 40.053,17.79 C40.098,18.134 40.163,18.474 40.172,18.814 C40.173,19.153 40.174,19.49 40.175,19.82 C40.166,20.154 40.168,20.476 40.155,20.791 Z"
stroke="#000000" stroke-width="2" fill="#FFFFFF">
</path>
<path id="led{{actuator.id}}" d="M23.43,0 C11.844,0 2.452,9.392 2.452,20.978 L2.452,42.941 L44.407,42.941 L44.407,20.978 C44.408,9.392 35.016,0 23.43,0 Z M40.155,20.791 C40.126,21.09 40.098,21.383 40.071,21.668 C40.011,22.237 39.934,22.776 39.861,23.272 C39.723,24.266 39.553,25.094 39.423,25.673 C39.291,26.253 39.19,26.584 39.19,26.584 C39.19,26.584 39.087,26.253 38.957,25.673 C38.827,25.093 38.655,24.265 38.518,23.272 C38.445,22.775 38.368,22.237 38.308,21.668 C38.281,21.384 38.252,21.092 38.222,20.793 C38.174,20.511 38.141,20.225 38.1,19.934 C38.065,19.645 38.03,19.35 37.994,19.052 C37.952,18.756 37.864,18.462 37.811,18.163 C37.749,17.864 37.682,17.565 37.63,17.262 C37.57,16.96 37.455,16.671 37.372,16.373 L37.123,15.482 C37.025,15.191 36.906,14.908 36.8,14.623 C36.689,14.339 36.586,14.054 36.487,13.769 C36.369,13.495 36.228,13.234 36.11,12.966 C35.989,12.698 35.87,12.435 35.753,12.176 C35.635,11.918 35.477,11.69 35.353,11.448 C35.081,10.982 34.872,10.486 34.597,10.11 C34.332,9.719 34.107,9.335 33.899,8.989 C33.658,8.656 33.447,8.365 33.274,8.127 C32.93,7.642 32.753,7.345 32.753,7.345 C32.753,7.345 33.067,7.491 33.585,7.783 C33.84,7.933 34.163,8.097 34.501,8.331 C34.831,8.587 35.211,8.882 35.608,9.234 C36.037,9.566 36.391,9.996 36.795,10.444 C36.991,10.672 37.203,10.901 37.39,11.156 C37.565,11.42 37.743,11.689 37.924,11.963 C38.104,12.24 38.291,12.521 38.456,12.818 C38.604,13.123 38.754,13.434 38.896,13.751 C39.033,14.07 39.201,14.382 39.314,14.714 L39.625,15.727 C39.715,16.069 39.834,16.404 39.896,16.75 C39.958,17.097 40.002,17.445 40.053,17.79 C40.098,18.134 40.163,18.474 40.172,18.814 C40.173,19.153 40.174,19.49 40.175,19.82 C40.166,20.154 40.168,20.476 40.155,20.791 Z"
stroke="#000000" stroke-width="2" fill="{{actuator.color}}"
{% if actuator.value: %}
visibility="visible"
{% else %}
visibility="hidden"
{% endif %}
>
</path>
</g>
</g>
</g>
</svg>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="actuator_set_button{{actuator.id}}" class="w-100 btn btn-lg btn-primary">Set</button>
</div>
<div class="col">
<button id="actuator_delete_button{{actuator.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var set_button = document.getElementById("actuator_set_button{{actuator.id}}")
var delete_button = document.getElementById("actuator_delete_button{{actuator.id}}")
var color_control = document.getElementById("color{{actuator.id}}")
var led_control = document.getElementById("led{{actuator.id}}")
var led_title = document.getElementById("ledTitleId{{actuator.id}}")
set_button.addEventListener("click", function() {
// get the values
var color = color_control.value
var payload = {
"port": "{{actuator.id}}",
'color': color
}
var xhr = new XMLHttpRequest();
var url = "./led_actuator_settings";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
led_control.style.fill=color
});
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{actuator.id}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_actuator";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
var socket = io();
socket.on('actuator_change{{actuator.id}}', function(data) {
if (data.value){
led_control.style.visibility = "visible"
led_title.innerHTML = "LED on"
} else {
led_control.style.visibility = "hidden"
led_title.innerHTML = "LED off"
}
console.log('actuator change received');
console.log(data);
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/pin_sensor_create_settings.html
================================================
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_sensor_units">Units:</label>
</div>
<div class="col">
<select name="new_sensor_units" id="new_sensor_units" class="form-control">
</select>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_sensor_pin">Pin:</label>
</div>
<div class="col">
<select name="new_sensor_pin" id="new_sensor_pin" class="form-control">
{% for port in ports %}
<option value="{{ port }}">{{ port }}</option>
{% endfor %}
</select>
</div>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/relay_actuator.html
================================================
<div class="card shadow-sm border-secondary" style="margin: 10px;">
<div class="card-header py-3 text-white bg-secondary">
<div class="container-fluid d-flex align-items-center">
<h4 class="my-0 fw-normal">{{ actuator.actuator_name() }}</h4>
<h4 class="my-0 fw-normal ms-auto">Pin {{actuator.port}}</h4>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row" style="margin-top: 20px;">
<div class="col">
</div>
<div class="col">
<svg width="100px" height="70px" viewBox="0 0 100 70" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title id="relayTitleId{{actuator.id}}">Relay off</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(-38.000000, -48.000000)">
<g id="relay" transform="translate(38.000000, 48.000000)">
<g id="Group" transform="translate(0.000000, 8.048780)" fill="#000000">
<g id="Coil" transform="translate(1.800000, 0.000000)" stroke="#000000">
<path d="M34.6652288,0 C30.2799974,0.000716979501 26.7251751,7.25114198 26.7249115,16.1951014 C26.7256647,16.5616279 26.732518,16.9279965 26.7454597,17.2935745 L29.6325367,17.2935745 C29.603894,17.0663725 29.5787654,16.8373106 29.5571897,16.606744 C29.527386,16.2468027 29.5062389,15.8839824 29.4938277,15.5196416 C29.4854396,15.2615087 29.4814421,15.0028379 29.4818427,14.7441155 C29.4816482,14.3788099 29.4902217,14.0137125 29.5075312,13.6501907 C29.5251117,13.2868306 29.5513999,12.9255284 29.5862975,12.5676366 C29.6211462,12.2093075 29.6645807,11.854836 29.7164381,11.5055519 C29.7681183,11.156872 29.8281264,10.8137873 29.8962381,10.4775803 C29.9647693,10.1407973 30.0413516,9.81137364 30.1256976,9.49054651 C30.2094893,9.1696531 30.3009306,8.85768848 30.3996787,8.55582108 C30.4988128,8.25459717 30.6051131,7.96388558 30.7181816,7.68477469 C30.8310061,7.405681 30.9504511,7.13850026 31.0760703,6.88423089 C31.2020488,6.62955955 31.3340675,6.38818459 31.4716307,6.16101205 C31.6091423,5.93400684 31.7520193,5.72147579 31.8997261,5.52421584 C32.0470977,5.32657983 32.1991184,5.14441084 32.3552195,4.97839052 C32.5119183,4.81239073 32.672513,4.66285908 32.8363988,4.53035847 C32.9996336,4.39791614 33.1659421,4.28254298 33.3347026,4.18467026 C33.5039207,4.08726214 33.6753733,4.00756254 33.8484169,3.94587066 C34.0207777,3.88389961 34.194515,3.83987576 34.3689811,3.81396317 C34.4932198,3.79566163 34.6177313,3.78655783 34.7422802,3.78666899 C34.9172702,3.78669367 35.0921505,3.80490855 35.266269,3.8412457 C35.4409935,3.87769374 35.6147325,3.93234469 35.7868332,4.0049933 C35.9589921,4.07740267 36.1293023,4.16772825 36.2971238,4.27563067 C36.4644083,4.38353688 36.6290019,4.50877794 36.7902899,4.65088601 C36.9515484,4.79358936 37.109284,4.95298699 37.262907,5.12848288 C37.4169285,5.30306792 37.5666635,5.49358677 37.7115514,5.69932592 C37.8566331,5.90594971 37.9966534,6.12758997 38.1310854,6.36341281 C38.2648204,6.59861858 38.3928375,6.847582 38.514659,7.10937408 C38.6370516,7.37154796 38.7530679,7.64632092 38.8622723,7.93266148 C38.9715218,8.21899024 39.0738219,8.51653383 39.1687884,8.82417501 C39.2630949,9.13182869 39.3499639,9.44910644 39.4290705,9.77482165 C39.5089312,10.0999261 39.5809412,10.4331438 39.6448309,10.7732273 C39.70848,11.1136897 39.7639163,11.4605703 39.8109318,11.8125695 C39.8577037,12.1646656 39.8959949,12.5214138 39.925662,12.8814777 C39.9554657,13.2414186 39.9766127,13.6042385 39.989024,13.968579 C39.9979854,14.2266699 40.0025561,14.4853411 40.0027274,14.7441062 C40.0023539,15.1094713 39.9932097,15.4745688 39.9753293,15.838031 C39.9577849,16.2021533 39.9314965,16.5642164 39.896563,16.9228592 C39.8821906,17.0469795 39.8667781,17.170567 39.8503325,17.2935745 L42.5798697,17.2935745 C42.5950975,16.9281457 42.6042354,16.5617769 42.6072678,16.1950921 C42.6072678,11.8994145 41.7703038,7.77979717 40.2808671,4.74259276 C38.7914304,1.70538835 36.771414,-0.000585681312 34.6652394,0 L34.6652288,0 Z" id="Path" stroke-width="1.00027776" fill-rule="nonzero" stroke-linejoin="round"></path>
<path d="M48.2653954,9.31541879e-06 C43.8801641,0.000717275729 40.3253419,7.25114219 40.3250783,16.1951014 C40.3258323,16.5616279 40.3326865,16.9279966 40.3456291,17.2935745 L43.2327059,17.2935745 C43.2040632,17.0663725 43.1789345,16.8373106 43.1573589,16.606744 C43.1275552,16.2468027 43.1064081,15.8839824 43.0939969,15.5196416 C43.0856088,15.2615087 43.0816113,15.0028379 43.0820119,14.7441155 C43.0818173,14.3788099 43.0903909,14.0137125 43.1077003,13.6501907 C43.1252808,13.2868306 43.1515691,12.9255284 43.1864666,12.5676366 C43.2213154,12.2093075 43.2647499,11.854836 43.3166072,11.5055519 C43.3682875,11.156872 43.4282956,10.8137873 43.4964073,10.4775803 C43.5649385,10.1407973 43.6415208,9.81137364 43.7258667,9.49054651 C43.8096585,9.1696531 43.9010997,8.85768848 43.9998479,8.55582108 C44.0989819,8.25459717 44.2052822,7.96388558 44.3183508,7.68477469 C44.4311753,7.405681 44.5506203,7.13850026 44.6762394,6.88423089 C44.802218,6.62955955 44.9342366,6.38818459 45.0717999,6.16101205 C45.2093115,5.93400684 45.3521885,5.72147579 45.4998952,5.52421584 C45.6472668,5.32657983 45.7992876,5.14441084 45.9553887,4.97839052 C46.1120875,4.81239073 46.2726821,4.66285908 46.436568,4.53035847 C46.5998027,4.39791614 46.7661112,4.28254298 46.9348718,4.18467026 C47.1040898,4.08726214 47.2755425,4.00756254 47.4485861,3.94587066 C47.6209469,3.88389961 47.7946841,3.83987576 47.9691503,3.81396317 C48.093389,3.79566163 48.2179005,3.78655783 48.3424494,3.78666899 C48.5174393,3.78669367 48.6923197,3.80490855 48.8664382,3.8412457 C49.0411627,3.87769374 49.2149016,3.93234469 49.3870024,4.0049933 C49.5591613,4.07740267 49.7294715,4.16772825 49.897293,4.27563067 C50.0645774,4.38353688 50.2291711,4.50877794 50.3904591,4.65088601 C50.5517176,4.79358936 50.7094532,4.95298699 50.8630761,5.12848288 C51.0170976,5.30306792 51.1668327,5.49358677 51.3117205,5.69932592 C51.4568023,5.90594971 51.5968225,6.12758997 51.7312545,6.36341281 C51.8649895,6.59861858 51.9930067,6.847582 52.1148282,7.10937408 C52.2372208,7.37154796 52.353237,7.64632092 52.4624414,7.93266148 C52.571691,8.21899024 52.6739911,8.51653383 52.7689575,8.82417501 C52.8632641,9.13182869 52.9501331,9.44910644 53.0292396,9.77482165 C53.1091003,10.0999261 53.1811103,10.4331438 53.245,10.7732273 C53.3086492,11.1136897 53.3640854,11.4605703 53.411101,11.8125695 C53.4578729,12.1646656 53.4961641,12.5214138 53.5258312,12.8814777 C53.5556349,13.2414186 53.5767819,13.6042385 53.5891931,13.968579 C53.5981546,14.2266699 53.6027253,14.4853411 53.6028966,14.7441062 C53.6025232,15.1094744 53.593379,15.4745751 53.5754985,15.8380403 C53.5579541,16.2021626 53.5316657,16.5642257 53.4967322,16.9228685 C53.4823598,17.0469888 53.4669473,17.1705763 53.4505017,17.2935745 L56.1800389,17.2935745 C56.1952637,16.9281549 56.2043988,16.561786 56.2074282,16.1951014 C56.2074282,11.8994238 55.3704642,7.77980649 53.8810275,4.74260207 C52.3915908,1.70539766 50.3715744,-0.000576365893 48.2653998,9.31541879e-06 L48.2653954,9.31541879e-06 Z" id="Path" stroke-width="1.00027776" fill-rule="nonzero" stroke-linejoin="round"></path>
<path d="M61.8783956,9.31541879e-06 C57.4931643,0.000717275729 53.9383421,7.25114219 53.9380786,16.1951014 C53.9388326,16.5616279 53.9456868,16.9279966 53.9586294,17.2935745 L56.8457061,17.2935745 C56.8170634,17.0663725 56.7919348,16.8373106 56.7703591,16.606744 C56.7405554,16.2468027 56.7194084,15.8839824 56.7069971,15.5196416 C56.698609,15.2615087 56.6946115,15.0028379 56.6950121,14.7441155 C56.6948176,14.3788099 56.7033911,14.0137125 56.7207006,13.6501907 C56.7382811,13.2868306 56.7645693,12.9255284 56.7994669,12.5676366 C56.8343156,12.2093075 56.8777501,11.854836 56.9296075,11.5055519 C56.9812877,11.156872 57.0412958,10.8137873 57.1094075,10.4775803 C57.1779387,10.1407973 57.2545211,9.81137364 57.338867,9.49054651 C57.4226588,9.1696531 57.5141,8.85768848 57.6128481,8.55582108 C57.7119822,8.25459717 57.8182825,7.96388558 57.931351,7.68477469 C58.0441755,7.405681 58.1636205,7.13850026 58.2892397,6.88423089 C58.4152182,6.62955955 58.5472369,6.38818459 58.6848001,6.16101205 C58.8223117,5.93400684 58.9651888,5.72147579 59.1128955,5.52421584 C59.2602671,5.32657983 59.4122879,5.14441084 59.5683889,4.97839052 C59.7250878,4.81239073 59.8856824,4.66285908 60.0495683,4.53035847 C60.212803,4.39791614 60.3791115,4.28254298 60.5478721,4.18467026 C60.7170901,4.08726214 60.8885427,4.00756254 61.0615863,3.94587066 C61.2339472,3.88389961 61.4076844,3.83987576 61.5821505,3.81396317 C61.7063892,3.79566163 61.8309007,3.78655783 61.9554497,3.78666899 C62.1304396,3.78669367 62.30532,3.80490855 62.4794384,3.8412457 C62.654163,3.87769374 62.8279019,3.93234469 63.0000026,4.0049933 C63.1721615,4.07740267 63.3424717,4.16772825 63.5102932,4.27563067 C63.6775777,4.38353688 63.8421713,4.50877794 64.0034593,4.65088601 C64.1647178,4.79358936 64.3224535,4.95298699 64.4760764,5.12848288 C64.6300979,5.30306792 64.7798329,5.49358677 64.9247208,5.69932592 C65.0698025,5.90594971 65.2098228,6.12758997 65.3442548,6.36341281 C65.4779898,6.59861858 65.6060069,6.847582 65.7278284,7.10937408 C65.850221,7.37154796 65.9662373,7.64632092 66.0754417,7.93266148 C66.1846912,8.21899024 66.2869913,8.51653383 66.3819578,8.82417501 C66.4762644,9.13182869 66.5631334,9.44910644 66.6422399,9.77482165 C66.7221006,10.0999261 66.7941106,10.4331438 66.8580003,10.7732273 C66.9216495,11.1136897 66.9770857,11.4605703 67.0241013,11.8125695 C67.0708731,12.1646656 67.1091643,12.5214138 67.1388314,12.8814777 C67.1686351,13.2414186 67.1897822,13.6042385 67.2021934,13.968579 C67.2111549,14.2266699 67.2157255,14.4853411 67.2158968,14.7441062 C67.2155234,15.1094744 67.2063793,15.4745751 67.1884987,15.8380403 C67.1709543,16.2021626 67.144666,16.5642257 67.1097324,16.9228685 C67.0953601,17.0469888 67.0799475,17.1705763 67.0635019,17.2935745 L69.7930391,17.2935745 C69.808264,16.9281549 69.817399,16.561786 69.8204285,16.1951014 C69.8204285,11.8994238 68.9834644,7.77980649 67.4940278,4.74260207 C66.0045911,1.70539766 63.9845746,-0.000576365893 61.8784,9.31541879e-06 L61.8783956,9.31541879e-06 Z" id="Path" stroke-width="1.00027776" fill-rule="nonzero" stroke-linejoin="round"></path>
<polyline id="Path" stroke-width="3.449638" stroke-linecap="square" points="0 16.0743902 27 16.0743902 27 16.0743902 27 16.0743902 27 16.0743902"></polyline>
<polyline id="Path-Copy" stroke-width="3.45" stroke-linecap="square" points="69.3412154 16.0743902 96.4412154 16.0743902 96.4412154 16.0743902 96.4412154 16.0743902 96.4412154 16.0743902"></polyline>
</g>
<g id="relay_open{{actuator.id}}" transform="translate(0.000000, 33.170732)" fill-rule="nonzero">
<path d="M39.250709,0.036993145 C36.5409268,-0.279344691 34.0160538,1.46318254 33.3453935,4.11251418 L0,4.14634146 L0,6.82926829 L33.4385858,6.91350179 C34.1289886,9.22493605 36.2491483,10.8104375 38.6573559,10.8162112 C39.9623614,10.8088356 41.2204787,10.3278685 42.1986641,9.46240051 L58.1531896,20.7317073 L59.7560976,18.4442341 L43.7736143,7.14691742 C43.9695968,6.5730598 44.0672734,5.97012212 44.0625105,5.36362198 C44.017822,2.63078109 41.9604912,0.353330981 39.250709,0.036993145 Z M38.6200789,8.01522358 C37.1583679,8.01522358 35.973417,6.82806111 35.973417,5.36362198 C35.973417,3.89918286 37.1583679,2.71202038 38.6200789,2.71202038 C40.08179,2.71202038 41.2667409,3.89918286 41.2667409,5.36362198 C41.2667409,6.82079964 40.0931444,8.00496177 38.6387174,8.01522358 L38.6200789,8.01522358 Z" id="Shape"></path>
<path d="M100,4.14634146 L69.6976868,4.12347239 C69.0687317,1.5026095 66.5776027,-0.249323806 63.8756306,0.0290019564 C61.1736586,0.307327719 59.0987544,2.52960071 59.0263399,5.22271055 C58.9539255,7.91582039 60.9064572,10.2446045 63.5896322,10.6653479 C66.2728071,11.0860913 68.8545435,9.46831689 69.6234482,6.88442233 L100,6.82926829 L100,4.14634146 Z M64.4081865,7.97039598 C62.9526565,7.97039598 61.7727162,6.80020295 61.7727162,5.3566967 C61.7727162,3.91319045 62.9526565,2.74299742 64.4081865,2.74299742 C65.8637166,2.74299742 67.0436569,3.91319045 67.0436569,5.3566967 C67.0386624,6.79098414 65.8729092,7.95530668 64.4267462,7.97039598 L64.4081865,7.97039598 Z" id="Shape"></path>
</g>
<g id="relay_closed{{actuator.id}}" transform="translate(0.000000, 33.170732)" fill-rule="nonzero"
visibility="hidden">
<path d="M39.250709,0.036993145 C36.5409268,-0.279344691 34.0160538,1.46318254 33.3453935,4.11251418 L0,4.14634146 L0,6.82926829 L33.4385858,6.91350179 C34.1289886,9.22493605 36.2491483,10.8104375 38.6573559,10.8162112 C39.9623614,10.8088356 42.9268293,9.75609756 43.6585366,6.82926829 L59.7560976,6.82926829 L59.7560976,4.14634146 L43.6585366,4.14634146 C43.6585366,2.43902439 41.9604912,0.353330981 39.250709,0.036993145 Z M38.6200789,8.01522358 C37.1583679,8.01522358 35.973417,6.82806111 35.973417,5.36362198 C35.973417,3.89918286 37.1583679,2.71202038 38.6200789,2.71202038 C40.08179,2.71202038 41.2667409,3.89918286 41.2667409,5.36362198 C41.2667409,6.82079964 40.0931444,8.00496177 38.6387174,8.01522358 L38.6200789,8.01522358 Z" id="Shape"></path>
<path d="M100,4.14634146 L69.6976868,4.12347239 C69.0687317,1.5026095 66.5776027,-0.249323806 63.8756306,0.0290019564 C61.1736586,0.307327719 59.0987544,2.52960071 59.0263399,5.22271055 C58.9539255,7.91582039 60.9064572,10.2446045 63.5896322,10.6653479 C66.2728071,11.0860913 68.8545435,9.46831689 69.6234482,6.88442233 L100,6.82926829 L100,4.14634146 Z M64.4081865,7.97039598 C62.9526565,7.97039598 61.7727162,6.80020295 61.7727162,5.3566967 C61.7727162,3.91319045 62.9526565,2.74299742 64.4081865,2.74299742 C65.8637166,2.74299742 67.0436569,3.91319045 67.0436569,5.3566967 C67.0386624,6.79098414 65.8729092,7.95530668 64.4267462,7.97039598 L64.4081865,7.97039598 Z" id="Shape"></path>
</g>
</g>
<rect id="Rectangle" stroke="#000000" stroke-width="3" x="11.5" y="1.5" width="77" height="67"></rect>
</g>
</g>
</g>
</svg>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="container">
<div class="row">
<div class="col">
<button id="actuator_delete_button{{actuator.id}}" class="w-100 btn btn-lg btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
var delete_button = document.getElementById("actuator_delete_button{{actuator.id}}")
var relay_closed = document.getElementById("relay_closed{{actuator.id}}")
var relay_open = document.getElementById("relay_open{{actuator.id}}")
var relay_title = document.getElementById("relayTitleId{{actuator.id}}")
delete_button.addEventListener("click", function(){
var payload = {
"port": "{{actuator.id}}"
}
var xhr = new XMLHttpRequest();
var url = "./delete_actuator";
xhr.open("POST", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
var data = JSON.stringify(payload);
xhr.send(data);
location.reload();
});
var socket = io();
socket.on('actuator_change{{actuator.id}}', function(data) {
if (data.value){
relay_open.style.visibility = "hidden"
relay_closed.style.visibility = "visible"
relay_title.innerHTML = "Relay on"
} else {
relay_open.style.visibility = "visible"
relay_closed.style.visibility = "hidden"
relay_title.innerHTML = "Relay off"
}
console.log('actuator change received');
console.log(data);
});
})
</script>
</div>
================================================
FILE: counterfit-app/src/CounterFit/templates/serial_sensor_create_settings.html
================================================
<div class="row" style="margin-top: 20px;">
<div class="col">
<label for="new_sensor_port">Port:</label>
</div>
<div class="col">
<input class="form-control" type="text" id="new_sensor_port" value="/dev/ttyAMA0" style="width: 100%;"/>
</div>
</div>
================================================
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
================================================
<?xml version="1.0"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1" creator="AllTrails.com" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<metadata>
<name><![CDATA[Trail Planner Map]]></name>
<desc><![CDATA[]]></desc>
<link href="http://www.alltrails.com">
<text>AllTrails, LLC</text>
</link>
<bounds minlat="47.72935" minlon="-122.26477" maxlat="47.73552" maxlon="-122.25581"/>
</metadata>
<trk>
<name><![CDATA[Trail Planner Map]]></name>
<desc><![CDATA[]]></desc>
<src>AllTrails</src>
<trkseg>
<trkpt lat="47.73481" lon="-122.257">
<ele>107.05</ele>
</trkpt>
<trkpt lat="47.73481" lon="-122.25701">
<ele>107.05</ele>
</trkpt>
<trkpt lat="47.73476" lon="-122.25682">
<ele>108.0</ele>
</trkpt>
</trkseg>
</trk>
</gpx>
================================================
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**

## 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', <port>)
> ```
>
> Replace `<port>` 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', <port>)
> ```
>
> Replace `<port>` 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
 [](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
 [](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 <https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html>`_
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 / packag
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
SYMBOL INDEX (326 symbols across 26 files)
FILE: counterfit-app/src/CounterFit/actuators.py
class ActuatorType (line 5) | class ActuatorType(Enum):
class ActuatorBase (line 10) | class ActuatorBase(ABC):
method __init__ (line 11) | def __init__(self, port: str):
method actuator_name (line 16) | def actuator_name() -> str:
method actuator_type (line 21) | def actuator_type() -> ActuatorType:
method port (line 25) | def port(self) -> str:
method id (line 30) | def id(self) -> str:
class FloatActuatorBase (line 34) | class FloatActuatorBase(ActuatorBase):
method __init__ (line 35) | def __init__(self, port: str):
method actuator_name (line 41) | def actuator_name() -> str:
method actuator_type (line 45) | def actuator_type() -> ActuatorType:
method value (line 49) | def value(self) -> float:
method value (line 53) | def value(self, val: float):
class BooleanActuatorBase (line 57) | class BooleanActuatorBase(ActuatorBase):
method __init__ (line 58) | def __init__(self, port: str):
method actuator_name (line 65) | def actuator_name() -> str:
method actuator_type (line 69) | def actuator_type() -> ActuatorType:
method value (line 73) | def value(self) -> bool:
method value (line 77) | def value(self, val: bool):
class RelayActuator (line 81) | class RelayActuator(BooleanActuatorBase):
method actuator_name (line 83) | def actuator_name() -> str:
class LedActuator (line 87) | class LedActuator(BooleanActuatorBase):
method __init__ (line 88) | def __init__(self, port: str):
method actuator_name (line 93) | def actuator_name() -> str:
method color (line 97) | def color(self) -> str:
method color (line 101) | def color(self, val: str):
FILE: counterfit-app/src/CounterFit/binary_sensors.py
class BinarySensorBase (line 8) | class BinarySensorBase(SensorBase):
method __init__ (line 9) | def __init__(self, name: str):
method sensor_type (line 16) | def sensor_type() -> SensorType:
method sensor_name (line 21) | def sensor_name() -> str:
method id (line 25) | def id(self) -> str:
method value (line 29) | def value(self) -> io.BytesIO:
method value (line 33) | def value(self, val: io.BytesIO):
class CameraImageSource (line 37) | class CameraImageSource(Enum):
class CameraSensor (line 42) | class CameraSensor(BinarySensorBase):
method __init__ (line 43) | def __init__(self, name: str):
method sensor_name (line 50) | def sensor_name() -> str:
method image_source (line 54) | def image_source(self) -> CameraImageSource:
method image_source (line 58) | def image_source(self, val: CameraImageSource):
method image_file_name (line 62) | def image_file_name(self) -> str:
method image_file_name (line 66) | def image_file_name(self, val: str):
method web_cam_device_id (line 70) | def web_cam_device_id(self) -> str:
method web_cam_device_id (line 74) | def web_cam_device_id(self, val: str):
FILE: counterfit-app/src/CounterFit/counterfit.py
function get_all_subclasses (line 35) | def get_all_subclasses(cls, class_list):
function home (line 51) | def home():
function set_and_send_connected (line 72) | def set_and_send_connected(connected: bool = True) -> None:
function device_connect (line 79) | def device_connect():
function device_disconnect (line 86) | def device_disconnect():
function create_pin_sensor (line 92) | def create_pin_sensor(sensor, body):
function create_serial_sensor (line 106) | def create_serial_sensor(sensor, body):
function create_binary_sensor (line 112) | def create_binary_sensor(sensor, body):
function create_i2c_sensor (line 118) | def create_i2c_sensor(sensor, body):
function create_sensor (line 126) | def create_sensor():
function create_actuator (line 148) | def create_actuator():
function get_sensor_value (line 166) | def get_sensor_value():
function get_serial_sensor_character (line 181) | def get_serial_sensor_character():
function get_serial_sensor_line (line 196) | def get_serial_sensor_line():
function capture_camera_image_response (line 213) | def capture_camera_image_response(data):
function capture_camera_image (line 230) | def capture_camera_image(sensor: CameraSensor, port) -> bool:
function get_binary_sensor_data (line 256) | def get_binary_sensor_data():
function delete_sensor (line 283) | def delete_sensor():
function delete_actuator (line 297) | def delete_actuator():
function set_float_sensor_settings (line 311) | def set_float_sensor_settings():
function set_integer_sensor_settings (line 333) | def set_integer_sensor_settings():
function set_led_actuator_settings (line 355) | def set_led_actuator_settings():
function set_boolean_sensor_settings (line 371) | def set_boolean_sensor_settings():
function set_gps_sensor_settings (line 389) | def set_gps_sensor_settings():
function set_camera_sensor_settings (line 418) | def set_camera_sensor_settings():
function get_sensor_units (line 444) | def get_sensor_units():
function set_actuator_value (line 467) | def set_actuator_value():
function open_browser (line 485) | def open_browser(port):
function main (line 489) | def main():
FILE: counterfit-app/src/CounterFit/i2c_sensors.py
class I2CSensorBase (line 9) | class I2CSensorBase(SensorBase):
method sensor_type (line 11) | def sensor_type() -> SensorType:
method sensor_unit_type (line 16) | def sensor_unit_type() -> SensorType:
method sensor_name (line 21) | def sensor_name() -> str:
method id (line 25) | def id(self) -> str:
method address (line 29) | def address(self) -> str:
class FloatI2CSensorBase (line 33) | class FloatI2CSensorBase(I2CSensorBase):
method __init__ (line 34) | def __init__(self, port: str, valid_min: float, valid_max: float):
method sensor_name (line 45) | def sensor_name() -> str:
method sensor_units (line 50) | def sensor_units() -> List[str]:
method sensor_unit_type (line 54) | def sensor_unit_type() -> SensorType:
method unit (line 59) | def unit(self) -> str:
method value (line 63) | def value(self) -> float:
method value (line 70) | def value(self, val: float):
method random_min (line 76) | def random_min(self) -> float:
method random_min (line 80) | def random_min(self, val: float):
method random_max (line 86) | def random_max(self) -> float:
method random_max (line 90) | def random_max(self, val: float):
method valid_min (line 96) | def valid_min(self) -> float:
method valid_max (line 100) | def valid_max(self) -> float:
class IntegerI2CSensorBase (line 104) | class IntegerI2CSensorBase(I2CSensorBase):
method __init__ (line 105) | def __init__(self, port: str, valid_min: int, valid_max: int):
method sensor_name (line 116) | def sensor_name() -> str:
method sensor_units (line 121) | def sensor_units() -> List[str]:
method sensor_unit_type (line 125) | def sensor_unit_type() -> SensorType:
method unit (line 130) | def unit(self) -> str:
method value (line 134) | def value(self) -> int:
method value (line 141) | def value(self, val: int):
method random_min (line 147) | def random_min(self) -> int:
method random_min (line 151) | def random_min(self, val: int):
method random_max (line 157) | def random_max(self) -> int:
method random_max (line 161) | def random_max(self, val: int):
method valid_min (line 167) | def valid_min(self) -> int:
method valid_max (line 171) | def valid_max(self) -> int:
class DistanceUnit (line 176) | class DistanceUnit(Enum):
class DistanceSensor (line 180) | class DistanceSensor(IntegerI2CSensorBase):
method __init__ (line 181) | def __init__(self, port: str, unit):
method sensor_name (line 190) | def sensor_name() -> str:
method unit (line 194) | def unit(self) -> str:
method sensor_units (line 198) | def sensor_units() -> List[str]:
FILE: counterfit-app/src/CounterFit/sensors.py
class SensorType (line 7) | class SensorType(Enum):
class SensorBase (line 16) | class SensorBase(ABC):
method __init__ (line 17) | def __init__(self, port: str):
method sensor_name (line 23) | def sensor_name() -> str:
method sensor_type (line 28) | def sensor_type() -> SensorType:
method id (line 33) | def id(self) -> str:
method port (line 37) | def port(self) -> str:
method random (line 41) | def random(self) -> bool:
method random (line 45) | def random(self, val: bool):
class DefaultUnit (line 50) | class DefaultUnit(Enum):
class PercentUnit (line 55) | class PercentUnit(Enum):
class FloatSensorBase (line 59) | class FloatSensorBase(SensorBase):
method __init__ (line 60) | def __init__(self, port: str, valid_min: float, valid_max: float):
method sensor_name (line 71) | def sensor_name() -> str:
method sensor_units (line 76) | def sensor_units() -> List[str]:
method unit (line 81) | def unit(self) -> str:
method sensor_type (line 85) | def sensor_type() -> SensorType:
method value (line 89) | def value(self) -> float:
method value (line 96) | def value(self, val: float):
method random_min (line 102) | def random_min(self) -> float:
method random_min (line 106) | def random_min(self, val: float):
method random_max (line 112) | def random_max(self) -> float:
method random_max (line 116) | def random_max(self, val: float):
method valid_min (line 122) | def valid_min(self) -> float:
method valid_max (line 126) | def valid_max(self) -> float:
class IntegerSensorBase (line 130) | class IntegerSensorBase(SensorBase):
method __init__ (line 131) | def __init__(self, port: str, valid_min: int, valid_max: int):
method sensor_name (line 142) | def sensor_name() -> str:
method sensor_units (line 147) | def sensor_units() -> List[str]:
method unit (line 152) | def unit(self) -> str:
method sensor_type (line 156) | def sensor_type() -> SensorType:
method value (line 160) | def value(self) -> int:
method value (line 167) | def value(self, val: int):
method random_min (line 173) | def random_min(self) -> int:
method random_min (line 177) | def random_min(self, val: int):
method random_max (line 183) | def random_max(self) -> int:
method random_max (line 187) | def random_max(self, val: int):
method valid_min (line 193) | def valid_min(self) -> int:
method valid_max (line 197) | def valid_max(self) -> int:
class BooleanSensorBase (line 201) | class BooleanSensorBase(SensorBase):
method __init__ (line 202) | def __init__(self, port: str):
method sensor_name (line 209) | def sensor_name() -> str:
method sensor_type (line 213) | def sensor_type() -> SensorType:
method value (line 217) | def value(self) -> bool:
method value (line 224) | def value(self, val: bool):
class TemperatureUnit (line 229) | class TemperatureUnit(Enum):
class TemperatureSensor (line 235) | class TemperatureSensor(FloatSensorBase):
method __init__ (line 236) | def __init__(self, port: str, unit):
method sensor_name (line 252) | def sensor_name() -> str:
method unit (line 256) | def unit(self) -> str:
method sensor_units (line 260) | def sensor_units() -> List[str]:
class HumiditySensor (line 268) | class HumiditySensor(FloatSensorBase):
method __init__ (line 269) | def __init__(self, port: str, unit):
method sensor_name (line 278) | def sensor_name() -> str:
method unit (line 282) | def unit(self) -> str:
method sensor_units (line 286) | def sensor_units() -> List[str]:
class PressureUnit (line 291) | class PressureUnit(Enum):
class PressureSensor (line 298) | class PressureSensor(FloatSensorBase):
method __init__ (line 299) | def __init__(self, port: str, unit):
method sensor_name (line 308) | def sensor_name() -> str:
method unit (line 312) | def unit(self) -> str:
method sensor_units (line 316) | def sensor_units() -> List[str]:
class AnalogSensor (line 325) | class AnalogSensor(IntegerSensorBase):
method __init__ (line 327) | def __init__(self, port: str, unit):
method sensor_name (line 332) | def sensor_name() -> str:
method unit (line 336) | def unit(self) -> str:
method sensor_units (line 340) | def sensor_units() -> List[str]:
class LightSensor (line 344) | class LightSensor(AnalogSensor):
method sensor_name (line 346) | def sensor_name() -> str:
class UVSensor (line 350) | class UVSensor(AnalogSensor):
method sensor_name (line 352) | def sensor_name() -> str:
class IRSensor (line 356) | class IRSensor(AnalogSensor):
method sensor_name (line 358) | def sensor_name() -> str:
class SoilMoistureSensor (line 362) | class SoilMoistureSensor(AnalogSensor):
method sensor_name (line 364) | def sensor_name() -> str:
class ButtonSensor (line 368) | class ButtonSensor(BooleanSensorBase):
method sensor_name (line 370) | def sensor_name() -> str:
method sensor_units (line 374) | def sensor_units() -> List[str]:
FILE: counterfit-app/src/CounterFit/serial_sensors.py
class SerialSensorBase (line 10) | class SerialSensorBase(SensorBase):
method __init__ (line 11) | def __init__(self, port: str):
method sensor_type (line 20) | def sensor_type() -> SensorType:
method sensor_name (line 25) | def sensor_name() -> str:
method id (line 29) | def id(self) -> str:
method value (line 33) | def value(self) -> str:
method value (line 37) | def value(self, val: str):
method repeat (line 42) | def repeat(self) -> bool:
method repeat (line 46) | def repeat(self, val: bool):
method read (line 49) | def read(self):
method read_line (line 66) | def read_line(self):
class GPSValueType (line 77) | class GPSValueType(Enum):
class GPSSensor (line 84) | class GPSSensor(SerialSensorBase):
method __init__ (line 85) | def __init__(self, port: str):
method sensor_name (line 100) | def sensor_name() -> str:
method _decimal_decrees_to_ddmmm (line 104) | def _decimal_decrees_to_ddmmm(decimal_degrees: float) -> str:
method _build_checksum (line 116) | def _build_checksum(sentence: str) -> str:
method _build_sentence_from_lat_lon_num_satellites (line 129) | def _build_sentence_from_lat_lon_num_satellites(
method _build_value (line 141) | def _build_value(self) -> None:
method read (line 164) | def read(self):
method value_type (line 236) | def value_type(self) -> GPSValueType:
method value_type (line 240) | def value_type(self, val: GPSValueType):
method lat (line 245) | def lat(self) -> float:
method lat (line 249) | def lat(self, val: float):
method lon (line 254) | def lon(self) -> float:
method lon (line 258) | def lon(self, val: float):
method number_of_satellites (line 263) | def number_of_satellites(self) -> int:
method number_of_satellites (line 267) | def number_of_satellites(self, val: int):
method raw_nmea (line 272) | def raw_nmea(self) -> str:
method raw_nmea (line 276) | def raw_nmea(self, val: str):
method gpx_file_name (line 281) | def gpx_file_name(self) -> str:
method gpx_file_name (line 285) | def gpx_file_name(self, val: str):
method gpx_file_contents (line 290) | def gpx_file_contents(self) -> str:
method gpx_file_contents (line 294) | def gpx_file_contents(self, val: str):
FILE: counterfit-app/src/tests/test_serial_sensors.py
function test_decimal_degree_conversion (line 8) | def test_decimal_degree_conversion():
function test_checksum (line 14) | def test_checksum():
function test_gps_empty_read_line (line 65) | def test_gps_empty_read_line():
function test_gps_empty_read (line 71) | def test_gps_empty_read():
function add_checksum_to_expected (line 77) | def add_checksum_to_expected(expected: str) -> str:
function test_gps_time_setting_when_using_lat_lon_n_e (line 83) | def test_gps_time_setting_when_using_lat_lon_n_e():
function test_gps_time_setting_when_using_lat_lon_s_w (line 103) | def test_gps_time_setting_when_using_lat_lon_s_w():
function test_gps_nmea (line 121) | def test_gps_nmea():
function test_gps_nmea_repeat (line 136) | def test_gps_nmea_repeat():
function test_gps_nmea_repeat_has_delay (line 157) | def test_gps_nmea_repeat_has_delay():
function test_gps_nmea_multiple_sentences_has_delay (line 190) | def test_gps_nmea_multiple_sentences_has_delay():
function test_gps_nmea_multiple_sentences_has_delay_only_for_gga (line 224) | def test_gps_nmea_multiple_sentences_has_delay_only_for_gga():
function test_gps_gpx (line 271) | def test_gps_gpx():
function test_gps_gpx_repeat (line 300) | def test_gps_gpx_repeat():
function test_setting_lat_lon_clears_value (line 341) | def test_setting_lat_lon_clears_value():
function test_setting_gpx_clears_value (line 365) | def test_setting_gpx_clears_value():
function test_setting_nmea_clears_value (line 400) | def test_setting_nmea_clears_value():
FILE: shims/CounterFitConnection/counterfit_connection.py
class CounterFitConnection (line 29) | class CounterFitConnection:
method init (line 37) | def init(hostname: str = 'localhost', port: int = 5000) -> None:
method get_sensor_float_value (line 45) | def get_sensor_float_value(port: int) -> float:
method get_sensor_int_value (line 53) | def get_sensor_int_value(port: int) -> int:
method get_sensor_boolean_value (line 61) | def get_sensor_boolean_value(port: int) -> bool:
method read_serial_sensor_char (line 69) | def read_serial_sensor_char(port: str) -> str:
method read_serial_sensor_line (line 77) | def read_serial_sensor_line(port: str) -> str:
method read_binary_sensor (line 85) | def read_binary_sensor(port: str) -> io.BytesIO:
method set_actuator_float_value (line 94) | def set_actuator_float_value(port: int, value: float) -> None:
method set_actuator_boolean_value (line 101) | def set_actuator_boolean_value(port: int, value: bool) -> None:
method is_connected (line 108) | def is_connected() -> bool:
FILE: shims/CounterFitConnection/tests/test_counterfit_connection.py
function test_init_counterfit_device (line 14) | def test_init_counterfit_device():
function test_camera_image (line 46) | def test_camera_image():
function test_is_connected (line 56) | def test_is_connected():
function test_is_connected_is_false (line 63) | def test_is_connected_is_false():
FILE: shims/SeeedStudios/Seeed_Python_DHT/counterfit_shims_seeed_python_dht.py
class DHT (line 27) | class DHT():
method __init__ (line 33) | def __init__(self, dht_type, pin = 12,bus_num = 1):
method read (line 37) | def read(self, retries = 15):
FILE: shims/SeeedStudios/Seeed_Python_DHT/tests/test_counterfit_shims_seeed_python_dht.py
function init_counterfit_device (line 18) | def init_counterfit_device():
function test_humidity_and_temperature (line 24) | def test_humidity_and_temperature(init_counterfit_device):
FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/adc.py
class ADC (line 35) | class ADC():
method __init__ (line 42) | def __init__(self, address = 0x04):
method read_raw (line 45) | def read_raw(self, channel):
method read_voltage (line 58) | def read_voltage(self, channel):
method read (line 71) | def read(self, channel):
method name (line 84) | def name(self):
method version (line 94) | def version(self):
method read_register (line 104) | def read_register(self, n):
FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/grove_led.py
class GroveLed (line 33) | class GroveLed():
method __init__ (line 39) | def __init__(self, pin):
method on (line 43) | def on(self) -> None:
method off (line 49) | def off(self) -> None:
FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/grove_light_sensor_v1_2.py
class GroveLightSensor (line 33) | class GroveLightSensor:
method __init__ (line 40) | def __init__(self, pin: int):
method light (line 44) | def light(self) -> int:
FILE: shims/SeeedStudios/grove_py/counterfit_shims_grove/grove_relay.py
class GroveRelay (line 32) | class GroveRelay():
method __init__ (line 39) | def __init__(self, pin):
method on (line 43) | def on(self) -> None:
method off (line 49) | def off(self) -> None:
FILE: shims/SeeedStudios/grove_py/tests/test_grove_led.py
function init_counterfit_device (line 18) | def init_counterfit_device():
function test_turn_led_on (line 24) | def test_turn_led_on(init_counterfit_device):
function test_turn_led_off (line 31) | def test_turn_led_off(init_counterfit_device):
FILE: shims/SeeedStudios/grove_py/tests/test_grove_light_sensor_v1_2.py
function init_counterfit_device (line 16) | def init_counterfit_device():
function test_light_sensor_light_is_50 (line 22) | def test_light_sensor_light_is_50(init_counterfit_device):
FILE: shims/SeeedStudios/grove_py/tests/test_grove_relay.py
function init_counterfit_device (line 18) | def init_counterfit_device():
function test_turn_relay_off (line 31) | def test_turn_relay_off(init_counterfit_device):
FILE: shims/SeeedStudios/seeed-python-si114x/counterfit_shims_seeed_si114x.py
class grove_si114x (line 25) | class grove_si114x():
method __init__ (line 33) | def __init__(self, light_pin = 0, ir_pin = 1, uv_pin = 2):
method ReadVisible (line 39) | def ReadVisible(self):
method ReadIR (line 46) | def ReadIR(self):
method ReadUV (line 53) | def ReadUV(self):
FILE: shims/SeeedStudios/seeed-python-si114x/tests/test_counterfit_shims_seeed_si114x.py
function init_counterfit_device (line 18) | def init_counterfit_device():
function test_humidity_and_temperature (line 24) | def test_humidity_and_temperature(init_counterfit_device):
FILE: shims/picamera/counterfit_shims_picamera/camera.py
class PiCamera (line 9) | class PiCamera():
method __init__ (line 14) | def __init__(self, resolution = None, **kwargs):
method resolution (line 25) | def resolution(self):
method resolution (line 33) | def resolution(self, val):
method rotation (line 42) | def rotation(self):
method rotation (line 49) | def rotation(self, val: int):
method __resize_and_crop (line 55) | def __resize_and_crop(self, image: Image) -> Image:
method capture (line 78) | def capture(self, output, image_format=None):
FILE: shims/picamera/tests/test_counterfit_shims_picamera.py
function init_counterfit_device (line 16) | def init_counterfit_device():
function test_capture (line 22) | def test_capture(init_counterfit_device):
function test_rotate (line 30) | def test_rotate(init_counterfit_device):
function test_resolution_init (line 44) | def test_resolution_init(init_counterfit_device):
function test_resolution (line 51) | def test_resolution(init_counterfit_device):
function test_resolution_and_rotate (line 59) | def test_resolution_and_rotate(init_counterfit_device):
FILE: shims/pyserial/counterfit_shims_serial.py
class Serial (line 9) | class Serial():
method __init__ (line 12) | def __init__(self, port: str, baud: int = 9600, **kwargs):
method read (line 15) | def read(self, **kwargs) -> bytes:
method readline (line 20) | def readline(self, **kwargs) -> bytes:
method reset_input_buffer (line 25) | def reset_input_buffer(self):
method flush (line 29) | def flush(self):
FILE: shims/pyserial/tests/test_counterfit_shims_serial.py
function init_counterfit_device (line 16) | def init_counterfit_device():
function test_read (line 22) | def test_read(init_counterfit_device):
function test_read_line (line 39) | def test_read_line(init_counterfit_device):
FILE: shims/rpi_vl53l0x/counterfit_shims_rpi_vl53l0x/vl53l0x.py
class VL53L0X (line 9) | class VL53L0X(object):
method __init__ (line 10) | def __init__(self, address:int = 0x29):
method get_libver (line 13) | def get_libver(self):
method get_devver (line 16) | def get_devver(self):
method begin (line 19) | def begin(self):
method wait_ready (line 22) | def wait_ready(self):
method get_distance (line 30) | def get_distance(self):
FILE: shims/rpi_vl53l0x/tests/test_counterfit_shims_rpi_vl53l0x.py
function init_counterfit_device (line 15) | def init_counterfit_device():
function test_get_distance (line 21) | def test_get_distance(init_counterfit_device):
Condensed preview — 118 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (249K chars).
[
{
"path": ".devcontainer/Dockerfile",
"chars": 1371,
"preview": "# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.236.0/containers/python-3/.dev"
},
{
"path": ".devcontainer/devcontainer.json",
"chars": 2134,
"preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:\n// https://github.co"
},
{
"path": ".gitignore",
"chars": 1819,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2021 Jim Bennett\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 4285,
"preview": "# CounterFit\n\n[](https://github.co"
},
{
"path": "counterfit-app/.pylintrc",
"chars": 235,
"preview": "[FORMAT]\n\n# Maximum number of characters on a single line.\nmax-line-length=140\n\n[DESIGN]\n\n# Maximum number of attributes"
},
{
"path": "counterfit-app/LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2021 Jim Bennett\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "counterfit-app/MANIFEST.in",
"chars": 111,
"preview": "include src/CounterFit/static/*.*\ninclude src/CounterFit/static/images/*.*\ninclude src/CounterFit/templates/*.*"
},
{
"path": "counterfit-app/README.md",
"chars": 1590,
"preview": "# CounterFit\n\nIoT is great fun, but has a downside - hardware. You need access to a range of devices such as sensors and"
},
{
"path": "counterfit-app/build.sh",
"chars": 40,
"preview": "rm ./dist/*\npython3 setup.py bdist_wheel"
},
{
"path": "counterfit-app/requirements.txt",
"chars": 124,
"preview": "flask=2.1.2\nflask-socketio=5.2.0\neventlet=0.33.3\nsetuptools=65.5.0\ntwine=4.0.2\nwheel=0.38.4\npytest=7.2.2\npytest-runner=6"
},
{
"path": "counterfit-app/setup.cfg",
"chars": 1160,
"preview": "[metadata]\nname = CounterFit\nversion = attr: CounterFit.__version__\nurl = https://github.com/CounterFit-IoT/CounterFit\np"
},
{
"path": "counterfit-app/setup.py",
"chars": 237,
"preview": "from setuptools import setup\n\nsetup(\n name='CounterFit',\n install_requires=[\n \"Flask==2.1.2\",\n \"Flas"
},
{
"path": "counterfit-app/src/CounterFit/__init__.py",
"chars": 239,
"preview": "# pylint: disable=C0103\n\nfrom CounterFit.sensors import *\nfrom CounterFit.serial_sensors import *\nfrom CounterFit.binary"
},
{
"path": "counterfit-app/src/CounterFit/actuators.py",
"chars": 1962,
"preview": "from abc import ABC, abstractmethod\nfrom enum import Enum\n\n\nclass ActuatorType(Enum):\n FLOAT = 1\n BOOLEAN = 2\n\n\ncl"
},
{
"path": "counterfit-app/src/CounterFit/binary_sensors.py",
"chars": 1745,
"preview": "from abc import abstractmethod\nfrom enum import Enum\nimport io\n\nfrom CounterFit.sensors import SensorBase, SensorType\n\n\n"
},
{
"path": "counterfit-app/src/CounterFit/counterfit.py",
"chars": 13705,
"preview": "# pylint: disable=C0103,E0401,W0603\n\nimport argparse\nimport io\nimport json\nimport uuid\nimport webbrowser\nfrom base64 imp"
},
{
"path": "counterfit-app/src/CounterFit/i2c_sensors.py",
"chars": 4571,
"preview": "from abc import abstractmethod\nfrom enum import Enum\nfrom typing import List\nimport random\n\nfrom CounterFit.sensors impo"
},
{
"path": "counterfit-app/src/CounterFit/sensors.py",
"chars": 8044,
"preview": "from abc import ABC, abstractmethod\nfrom enum import Enum\nfrom typing import List\nimport random\n\n\nclass SensorType(Enum)"
},
{
"path": "counterfit-app/src/CounterFit/serial_sensors.py",
"chars": 9123,
"preview": "from abc import abstractmethod\nfrom enum import Enum\nimport datetime\nimport re\nfrom xml.dom.minidom import Element, pars"
},
{
"path": "counterfit-app/src/CounterFit/static/style.css",
"chars": 205,
"preview": ".sensor_container {\n}\n\n.actuator_container {\n}\n\n#circle {\n width: 50px;\n height: 50px;\n -webkit-border-radius: "
},
{
"path": "counterfit-app/src/CounterFit/templates/binary_sensor_create_settings.html",
"chars": 276,
"preview": "<div class=\"row\" style=\"margin-top: 20px;\">\n <div class=\"col\">\n <label for=\"new_sensor_port\">Name:</label>\n "
},
{
"path": "counterfit-app/src/CounterFit/templates/boolean_sensor.html",
"chars": 3737,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/camera_sensor.html",
"chars": 10928,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/float_sensor.html",
"chars": 6018,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/gps_sensor.html",
"chars": 10249,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/home.html",
"chars": 24906,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <title>CounterFit</title>\n <link rel=\"stylesheet\" href=\"{{ url_for('stat"
},
{
"path": "counterfit-app/src/CounterFit/templates/i2c_float_sensor.html",
"chars": 6021,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/i2c_integer_sensor.html",
"chars": 6094,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/i2c_sensor_create_settings.html",
"chars": 713,
"preview": "<div class=\"row\" style=\"margin-top: 20px;\">\n <div class=\"col\">\n <label for=\"new_i2c_sensor_units\">Units:</labe"
},
{
"path": "counterfit-app/src/CounterFit/templates/integer_sensor.html",
"chars": 6079,
"preview": "<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-secon"
},
{
"path": "counterfit-app/src/CounterFit/templates/led_actuator.html",
"chars": 9172,
"preview": "\n<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-seco"
},
{
"path": "counterfit-app/src/CounterFit/templates/pin_sensor_create_settings.html",
"chars": 655,
"preview": "<div class=\"row\" style=\"margin-top: 20px;\">\n <div class=\"col\">\n <label for=\"new_sensor_units\">Units:</label>\n "
},
{
"path": "counterfit-app/src/CounterFit/templates/relay_actuator.html",
"chars": 17032,
"preview": "\n<div class=\"card shadow-sm border-secondary\" style=\"margin: 10px;\">\n <div class=\"card-header py-3 text-white bg-seco"
},
{
"path": "counterfit-app/src/CounterFit/templates/serial_sensor_create_settings.html",
"chars": 280,
"preview": "<div class=\"row\" style=\"margin-top: 20px;\">\n <div class=\"col\">\n <label for=\"new_sensor_port\">Port:</label>\n "
},
{
"path": "counterfit-app/src/__main__.py",
"chars": 47,
"preview": "from CounterFit.counterfit import main\n\nmain()\n"
},
{
"path": "counterfit-app/src/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "counterfit-app/src/tests/route.gpx",
"chars": 953,
"preview": "<?xml version=\"1.0\"?>\n<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instanc"
},
{
"path": "counterfit-app/src/tests/test_serial_sensors.py",
"chars": 11593,
"preview": "# pylint: disable=protected-access,line-too-long\nimport datetime\nimport time\n\nfrom CounterFit.serial_sensors import GPSS"
},
{
"path": "counterfit-app/upload.sh",
"chars": 48,
"preview": "python3 -m twine upload --repository pypi dist/*"
},
{
"path": "requirements.txt",
"chars": 24,
"preview": "twine=4.0.2\nblack=23.1.0"
},
{
"path": "samples/grove/button-temperature-sensor-controlled-relay/README.md",
"chars": 1966,
"preview": "# Relay controlled by temperature sensor and button sample\n\nThis sample shows how to use the CounterFit Grove shims to s"
},
{
"path": "samples/grove/button-temperature-sensor-controlled-relay/main.py",
"chars": 1068,
"preview": "\n\n# In this example:\n# We will create a circuit where, when a button is pressed, we will check the current temperature\n#"
},
{
"path": "samples/grove/button-temperature-sensor-controlled-relay/requirements.txt",
"chars": 33,
"preview": "counterfit\ncounterfit_shims_grove"
},
{
"path": "samples/grove/light-sensor-controlled-led/README.md",
"chars": 1574,
"preview": "# Light sensor controlled LED sample\n\nThis sample shows how to use the CounterFit Grove shims to simulate an LED and a l"
},
{
"path": "samples/grove/light-sensor-controlled-led/app.py",
"chars": 556,
"preview": "import time\n\nfrom counterfit_connection import CounterFitConnection\nfrom counterfit_shims_grove.grove_light_sensor_v1_2 "
},
{
"path": "samples/grove/light-sensor-controlled-led/requirements.txt",
"chars": 33,
"preview": "counterfit\ncounterfit_shims_grove"
},
{
"path": "shims/CounterFitConnection/.gitignore",
"chars": 14,
"preview": "test_image.png"
},
{
"path": "shims/CounterFitConnection/.pylintrc",
"chars": 126,
"preview": "[FORMAT]\n\n# Maximum number of characters on a single line.\nmax-line-length=140\n\n[MESSAGES CONTROL]\ndisable=trailing-whit"
},
{
"path": "shims/CounterFitConnection/README.md",
"chars": 397,
"preview": "# CounterFit Shims - CounterFit connection\n\n"
},
{
"path": "shims/CounterFitConnection/build.sh",
"chars": 40,
"preview": "rm ./dist/*\npython3 setup.py bdist_wheel"
},
{
"path": "shims/CounterFitConnection/counterfit_connection.py",
"chars": 3984,
"preview": "'''\nProvides a connection to the CounterFit Virtual IoT Device app. This connection is re-used by all the virtual sensor"
},
{
"path": "shims/CounterFitConnection/requirements.txt",
"chars": 106,
"preview": "wheel=0.38.4\nsetuptools=65.5.0\ntwine=4.0.2\npytest=7.2.2\npytest-runner=6.0.0\nrequests=2.28.2\npylint=2.16.2\n"
},
{
"path": "shims/CounterFitConnection/setup.py",
"chars": 1504,
"preview": "'''\nConnection library for use with the CounterFit Virtual IoT Device app\n'''\n\n# pylint: disable=redefined-builtin\n\nfrom"
},
{
"path": "shims/CounterFitConnection/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "shims/CounterFitConnection/tests/test_counterfit_connection.py",
"chars": 2536,
"preview": "'''\nTests the CounterFit app connection\n\nTo run this test, ensure you have the CounterFit Virtual IoT Device app running"
},
{
"path": "shims/CounterFitConnection/upload.sh",
"chars": 48,
"preview": "python3 -m twine upload --repository pypi dist/*"
},
{
"path": "shims/SeeedStudios/Seeed_Python_DHT/.pylintrc",
"chars": 126,
"preview": "[FORMAT]\n\n# Maximum number of characters on a single line.\nmax-line-length=140\n\n[MESSAGES CONTROL]\ndisable=trailing-whit"
},
{
"path": "shims/SeeedStudios/Seeed_Python_DHT/README.md",
"chars": 1872,
"preview": "# CounterFit Shims - Grove\n\n [ [ [ [ [ [!"
},
{
"path": "shims/rpi_vl53l0x/build.sh",
"chars": 40,
"preview": "rm ./dist/*\npython3 setup.py bdist_wheel"
},
{
"path": "shims/rpi_vl53l0x/counterfit_shims_rpi_vl53l0x/__init__.py",
"chars": 190,
"preview": "'''\nThe picamera package consists of several modules which provide a pure Python\ninterface to the Raspberry Pi's camera "
},
{
"path": "shims/rpi_vl53l0x/counterfit_shims_rpi_vl53l0x/vl53l0x.py",
"chars": 728,
"preview": "# pylint: disable=unused-argument,import-error\n'''Shims for PySerial\n'''\n\nfrom counterfit_connection import CounterFitCo"
},
{
"path": "shims/rpi_vl53l0x/requirements.txt",
"chars": 128,
"preview": "wheel=0.38.4\nsetuptools=65.5.0\ntwine=4.0.2\npytest=7.2.2\npytest-runner=6.0.0\nrequests=2.28.2\npylint=2.16.2\ncounterfit-con"
},
{
"path": "shims/rpi_vl53l0x/setup.py",
"chars": 1606,
"preview": "'''\nShims for the rpi_vl53l0x library for use with the CounterFit Virtual IoT Device app\n'''\n# pylint: disable=redefined"
},
{
"path": "shims/rpi_vl53l0x/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "shims/rpi_vl53l0x/tests/test_counterfit_shims_rpi_vl53l0x.py",
"chars": 793,
"preview": "'''\nTests the RPI VL53L0X connection\n\nCreate a distance sensor at the address 0x29 and set the distance to 100mm\n\n'''\n# "
},
{
"path": "shims/rpi_vl53l0x/upload.sh",
"chars": 48,
"preview": "python3 -m twine upload --repository pypi dist/*"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the CounterFit-IoT/CounterFit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 118 files (225.0 KB), approximately 63.6k tokens, and a symbol index with 326 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.