openmodule 14.1.1__tar.gz → 14.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openmodule-14.1.1 → openmodule-14.2.0}/ChangeLog +16 -7
- {openmodule-14.1.1 → openmodule-14.2.0}/PKG-INFO +1 -1
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/testing.md +17 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/utils.md +22 -1
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/access_service.py +5 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/base.py +5 -19
- openmodule-14.2.0/openmodule/models/signals.py +43 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/settings.py +9 -0
- openmodule-14.2.0/openmodule/utils/signal_listener.py +174 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule.egg-info/PKG-INFO +1 -1
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule.egg-info/SOURCES.txt +4 -0
- openmodule-14.2.0/openmodule.egg-info/pbr.json +1 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule.egg-info/requires.txt +1 -1
- openmodule-14.2.0/openmodule_test/signal_simulator.py +58 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/requirements.txt +1 -1
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_model.py +1 -11
- openmodule-14.2.0/tests/test_utils_signal.py +230 -0
- openmodule-14.1.1/openmodule.egg-info/pbr.json +0 -1
- {openmodule-14.1.1 → openmodule-14.2.0}/.gitlab-ci.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/AUTHORS +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/LICENSE +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/README.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/access_service.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/anonymization.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/cleanup.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/coding_standard.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/commands.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/connection_status_listener.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/csv_export.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/database.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/README.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/access_service/openmodule/models/access_service.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/access_service/openmodule/utils/access_service.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/access_service/openmodule_test/access_service.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/access_service/tests/test_utils_access_service.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/api/openmodule/utils/api.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/api/openmodule_test/api.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/api/tests/test_utils_api.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/package_reader/openmodule/utils/package_reader.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/package_reader/openmodule_test/fake_package_creator.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/deprecated_code/package_reader/tests/test_package_reader.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/event_sending.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/getting_started.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/health.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/images/broker.drawio.png +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/known_issues.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/migrations.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/package_reader.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/rpc.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/sentry.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/settings.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/settings_provider.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/docs/translation.md +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/alert.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/config.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/connection_status.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/core.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/database/custom_types.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/database/database.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/database/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/database/migration.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/dispatcher.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/health.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/logging.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/messaging.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/alert.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/io.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/kv_store.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/presence.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/privacy.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/rpc.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/settings.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/validation.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/models/vehicle.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/rpc/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/rpc/client.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/rpc/common.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/rpc/server.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/sentry.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/threading.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/access_service.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/charset.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/cleanup.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/csv_export.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/databox.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/db_helper.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/eventlog.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/io.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/kv_store.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/matching.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/misc_functions.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/package_reader.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/presence.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/schema.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/translation.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule/utils/validation.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule.egg-info/dependency_links.txt +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule.egg-info/not-zip-safe +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule.egg-info/top_level.txt +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_commands/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_commands/setup.cfg +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_commands/setup.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_commands/translate.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/alert.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/connection_status.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/core.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/database.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/eventlistener.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/files.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/gate.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/health.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/interrupt.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/io_simulator.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/package_reader.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/presence.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/requirements.txt +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/rpc.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/sentry.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/settings.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/setup.cfg +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/setup.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/utils.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/openmodule_test/zeromq.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/setup.cfg +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/setup.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/test-requirements.txt +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/config.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/database_models_migration.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/database_models_test.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/alembic/README +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/alembic/versions/ff26e54332f9_datetime_models.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/invalid_database/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/alembic/versions/812a3e5b8517_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/alembic/versions/a7ea100a784f_key_error.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_double_column_delete_error/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/alembic/versions/812a3e5b8517_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/alembic/versions/a7ea100a784f_no_such_table_error.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_no_such_table_error/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic/versions/19789aa5361c_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic/versions/19d887929ae7_alter.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic/versions/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/alembic_migration_test_database.sqlite3 +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/migration_test_database/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/configs/config.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/configs/test_config.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/configs/test_config_1.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/standard_schemes/DEFAULT-10.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/standard_schemes/DEFAULT-20.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/standard_schemes/LEGACY-0.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/translation/locale/de/LC_MESSAGES/translation.mo +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/translation/locale/de/LC_MESSAGES/translation.po +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/translation/locale/en/LC_MESSAGES/translation.mo +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/translation/locale/en/LC_MESSAGES/translation.po +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/translation/locale/translation.pot +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/translation/translate.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/A-10.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/A-20.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/DEFAULT-10.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/DEFAULT-20.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/DEFAULT-30.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/LEGACY-0.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/TEST-10.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/TEST-20.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/TEST-30.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/resources/utils_matching/TEST-40.yml +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/sentry_main.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic/versions/7bd4fcd38fde_removed_nfc_and_pin.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic/versions/9ca98a2e5674_added_parksettings_id.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic/versions/c821971f9230_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_access_service_database/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_alembic_migrations.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_alert.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_checks.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_config.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_connection_status.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_core.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database/alembic/versions/32b8c728abbf_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_database.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_dispatcher.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_health.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_interrupt.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_io_listen.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/alembic/versions/9c5c944221f4_deprecated_kv_entry_example.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/alembic/versions/c55a69026a25_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_database/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/alembic/README +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/alembic/__init__.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/alembic/env.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/alembic/script.py.mako +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/alembic/versions/cdb3214131a9_initial.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/alembic.ini +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_kv_store_multiple_database/makemigration.sh +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_logging.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_messaging.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_mockrpcclient.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_rpc.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_schema.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_sentry.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_test_alert.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_test_gate.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_test_zeromq.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_access_service.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_charset.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_cleanup.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_csv_export.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_databox.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_eventlog.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_kv_store.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_kv_store_multiple.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_matching.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_misc_functions.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_package_reader.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_presence.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_settings.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_validation.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tests/test_utils_vehicle.py +0 -0
- {openmodule-14.1.1 → openmodule-14.2.0}/tox.ini +0 -0
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
CHANGES
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
+
v14.2.0
|
|
5
|
+
-------
|
|
6
|
+
|
|
7
|
+
* OM-1001: CostEntryData from settings models, added valid\_from and valid\_to to AccessCheckAccess and type hints for io/signals and io/inputs settings
|
|
8
|
+
* docs
|
|
9
|
+
* fixed testcase
|
|
10
|
+
* maybe fixing openmodule\_test
|
|
11
|
+
|
|
12
|
+
v14.2.0.rc0
|
|
13
|
+
-----------
|
|
14
|
+
|
|
15
|
+
* improvements for signal simulator
|
|
16
|
+
* test util for signals
|
|
17
|
+
* fixed testcase
|
|
18
|
+
* added SignalListener
|
|
19
|
+
|
|
4
20
|
v14.1.1
|
|
5
21
|
-------
|
|
6
22
|
|
|
@@ -181,8 +197,6 @@ v12.0.0.rc0
|
|
|
181
197
|
-----------
|
|
182
198
|
|
|
183
199
|
* Removed parking\_area\_id from AccessCheckAccess changed gate permission check in AccessService to new behavior
|
|
184
|
-
* removed Gate and GateType (now from settings models)
|
|
185
|
-
* fixed testcases and removed backend (deprecated in v11)
|
|
186
200
|
|
|
187
201
|
v11.1.1
|
|
188
202
|
-------
|
|
@@ -201,8 +215,3 @@ v11.0.3
|
|
|
201
215
|
* Fixed typo, added compute\_id as label to all metrics, convert all label values to string
|
|
202
216
|
* touch to get rid of skip ci on my commit
|
|
203
217
|
* docs update [skip ci]
|
|
204
|
-
* main test function deprecated and documentation for main tests added
|
|
205
|
-
|
|
206
|
-
v11.0.2
|
|
207
|
-
-------
|
|
208
|
-
|
|
@@ -267,6 +267,23 @@ presence_sim.enter(self.presence_sim.vehicle().lpr("A", "G ARIVO1"))
|
|
|
267
267
|
on_enter.wait_for_call()
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
+
### SignalSimulator
|
|
271
|
+
|
|
272
|
+
Util class for simulating signals
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
signal_simulator = SignalSimulator(lambda x: core().publish(x, "signal"))
|
|
276
|
+
# add_signal registers signals for RPC callbacks but does not emit signal message
|
|
277
|
+
signal_simulator.add_signal(False, None, SignalType.open, gate="einfahrt") # generates correct signal einfahrt-open
|
|
278
|
+
signal_simulator.add_signal(True, {"a": "b"}, SignalType.custom, signal_name="custom1")
|
|
279
|
+
# (change and) emit signals message
|
|
280
|
+
signal_simulator.set_signal("einfahrt-open", True)
|
|
281
|
+
signal_simulator.set_signal("custom1", True, {"a": "c"})
|
|
282
|
+
# register signal RPCs with callbacks from signal_simulator, which will automatically answer with correct response
|
|
283
|
+
rpc_server.register_handler("signal", "trigger_signals", TriggerSignalsRequest, TriggerSignalsResponse,
|
|
284
|
+
signal_simulator.trigger_signal_callback, register_schema=False)
|
|
285
|
+
```
|
|
286
|
+
|
|
270
287
|
### MockRPCClient
|
|
271
288
|
|
|
272
289
|
This is a fake RPCClient where you can either specify callback functions for RPCs or even the responses.
|
|
@@ -15,7 +15,28 @@ presence_listener = PresenceListener(core.messages)
|
|
|
15
15
|
presence_listener.on_enter.append(some_function)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Signals
|
|
19
|
+
|
|
20
|
+
Helper class for listening on signals.
|
|
21
|
+
```python
|
|
22
|
+
from openmodule.utils.signal_listener import SignalListener, FilterType
|
|
23
|
+
|
|
24
|
+
signal_listener = SignalListener(core.messages, core.rpc_client)
|
|
25
|
+
# register a listener for a signal with a filter_type specifying when the function should be called and optional tag
|
|
26
|
+
signal_listener.add_listener("gate1-open", FilterType.any_edge, some_function, tag="tag1")
|
|
27
|
+
signal_listener.add_listener("gate2-present_decision", FilterType.any_change, some_function2, tag="tag2")
|
|
28
|
+
# get the current value (and additional_data) of a signal, if unknown then RPC is called to get value
|
|
29
|
+
value = signal_listener.get_value("gate1-open")
|
|
30
|
+
value, additional_data = signal_listener.get_value_and_additional_data("gate1-open")
|
|
31
|
+
# trigger resending of signals via trigger_signals RPC for registered signals with specific tag
|
|
32
|
+
# or all registered signals if tag is None
|
|
33
|
+
signal_listener.trigger_signals(tag="tag1")
|
|
34
|
+
# remove listeners with specific tag or all if tag is None
|
|
35
|
+
signal_listener.remove_listener(tag="tag1")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Databox Upload
|
|
19
40
|
|
|
20
41
|
In the openmodule we have a utils function to simplify the upload with the databox service. The prerequisite is,
|
|
21
42
|
that the upload folder `/data/om_service_databox_1/upload` is mounted correctly in the compose file to the `settings.DATABOX_UPLOAD_DIR` (default: `/upload`)
|
|
@@ -65,12 +65,17 @@ class AccessCheckAccess(OpenModuleModel):
|
|
|
65
65
|
category: AccessCategory # category used for sorting and eventlog
|
|
66
66
|
used_medium: Medium # medium of the access
|
|
67
67
|
access_data: Dict # complete access data, will be used only for display and debug purposes
|
|
68
|
+
valid_from: Optional[datetime] # access is valid from this time. Only used for reservations
|
|
69
|
+
valid_to: Optional[datetime] # access is valid until this time. Only used for reservations
|
|
68
70
|
|
|
69
71
|
accepted: bool # if access service decided access can enter
|
|
70
72
|
reject_reason: Optional[AccessCheckRejectReason] # only if not accepted: reason for not accepted
|
|
71
73
|
# additional infos shown in events if reject reason is "custom". Required if reject_reason == "custom"
|
|
72
74
|
supplementary_infos: Optional[str]
|
|
73
75
|
|
|
76
|
+
_tz_valid_from = timezone_validator("valid_from")
|
|
77
|
+
_tz_valid_to = timezone_validator("valid_to")
|
|
78
|
+
|
|
74
79
|
@root_validator(skip_on_failure=True)
|
|
75
80
|
def root_validation(cls, values):
|
|
76
81
|
# if category is handauf, medium_display_name must be set
|
|
@@ -5,16 +5,17 @@ from datetime import datetime
|
|
|
5
5
|
from decimal import Decimal
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from json.encoder import ESCAPE_ASCII
|
|
8
|
-
from typing import Optional, Union
|
|
8
|
+
from typing import Optional, Union, Dict
|
|
9
9
|
|
|
10
10
|
import orjson
|
|
11
11
|
import zmq
|
|
12
12
|
from dateutil.tz import UTC
|
|
13
|
-
from pydantic import Field, BaseModel, validator
|
|
13
|
+
from pydantic import Field, BaseModel, validator
|
|
14
14
|
from pydantic.main import ROOT_KEY
|
|
15
15
|
|
|
16
16
|
from openmodule import config, sentry
|
|
17
17
|
from openmodule.config import settings
|
|
18
|
+
from settings_models.settings.common import CostEntryData as SettingsModelsCostEntryData
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _donotuse(v, *, default):
|
|
@@ -171,20 +172,5 @@ class Gateway(OpenModuleModel):
|
|
|
171
172
|
return f"{self.gate}/{self.direction}"
|
|
172
173
|
|
|
173
174
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
value: Optional[Union[int, str]]
|
|
177
|
-
group: Optional[str]
|
|
178
|
-
account_id: str
|
|
179
|
-
source: Optional[str] # if None, source will be set to service name on default: e.g. "service_iocontroller"
|
|
180
|
-
source_id: Optional[str]
|
|
181
|
-
idempotency_key: Optional[str]
|
|
182
|
-
|
|
183
|
-
@root_validator
|
|
184
|
-
def set_value_type(cls, values):
|
|
185
|
-
if values["entry_type"] in ["rate_change", "rate_validation", "partial_rate_change"]:
|
|
186
|
-
values["value"] = str(values["value"])
|
|
187
|
-
elif values["entry_type"] in ["static_cost", "pending_static_cost", "amount_validation", "time_validation"
|
|
188
|
-
"payment"]:
|
|
189
|
-
values["value"] = int(values["value"])
|
|
190
|
-
return values
|
|
175
|
+
# to be not breaking
|
|
176
|
+
CostEntryData = SettingsModelsCostEntryData
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Optional, Dict, Union, List
|
|
3
|
+
|
|
4
|
+
from openmodule.models.base import ZMQMessage, OpenModuleModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SignalType(str, Enum):
|
|
8
|
+
permanently_closed = "permanently_closed"
|
|
9
|
+
shortterm_full = "shortterm_full"
|
|
10
|
+
open = "open"
|
|
11
|
+
present_raw = "present_raw"
|
|
12
|
+
present_decision = "present_decision"
|
|
13
|
+
traffic_light_green = "traffic_light_green"
|
|
14
|
+
traffic_light_red = "traffic_light_red"
|
|
15
|
+
area_full = "area_full"
|
|
16
|
+
parkinglot_full = "parkinglot_full"
|
|
17
|
+
custom = "custom"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SignalMessage(ZMQMessage):
|
|
21
|
+
signal: str
|
|
22
|
+
type: Union[SignalType, str] # union so we can add new types without updating all services using signals
|
|
23
|
+
gate: Optional[str]
|
|
24
|
+
parking_area_id: Optional[str]
|
|
25
|
+
value: bool
|
|
26
|
+
additional_data: Optional[Dict]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GetSignalValueRequest(OpenModuleModel):
|
|
30
|
+
signal: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GetSignalValueResponse(OpenModuleModel):
|
|
34
|
+
value: bool
|
|
35
|
+
additional_data: Optional[Dict]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TriggerSignalsRequest(OpenModuleModel):
|
|
39
|
+
signals: List[str] # list of signals which current value should be sent
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TriggerSignalsResponse(OpenModuleModel):
|
|
43
|
+
success: bool
|
|
@@ -10,6 +10,7 @@ from settings_models.settings.device_keys import PairingKey, Certificate, Otp
|
|
|
10
10
|
from settings_models.settings.enforcement import EnforcementSettings
|
|
11
11
|
from settings_models.settings.gate_control import GateMode, DayMode
|
|
12
12
|
from settings_models.settings.intercom import IntercomSettings
|
|
13
|
+
from settings_models.settings.io import SignalDefinitions, InputDefinitions
|
|
13
14
|
|
|
14
15
|
from openmodule import sentry
|
|
15
16
|
from openmodule.core import core
|
|
@@ -195,6 +196,14 @@ class SettingsProvider:
|
|
|
195
196
|
def get(self, key: Literal["feature_flags"]) -> dict: # pragma: no cover
|
|
196
197
|
...
|
|
197
198
|
|
|
199
|
+
@overload
|
|
200
|
+
def get(self, key: Literal["io/signals"]) -> SignalDefinitions: # pragma: no cover
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
@overload
|
|
204
|
+
def get(self, key: Literal["io/inputs"]) -> InputDefinitions: # pragma: no cover
|
|
205
|
+
...
|
|
206
|
+
|
|
198
207
|
def get(self, key: str, scope: str = "", custom_type: Optional[Type[T]] = None) -> Optional[T]:
|
|
199
208
|
key_scope = join_key(key, scope)
|
|
200
209
|
with self._cache_lock:
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import threading
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional, Dict, Callable, List, Tuple
|
|
5
|
+
|
|
6
|
+
import zmq
|
|
7
|
+
from openmodule.dispatcher import MessageDispatcher
|
|
8
|
+
from openmodule.models.signals import SignalMessage, SignalType, GetSignalValueRequest, GetSignalValueResponse, \
|
|
9
|
+
TriggerSignalsRequest, TriggerSignalsResponse
|
|
10
|
+
from openmodule.rpc import RPCClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChangeType(str, Enum):
|
|
14
|
+
rising_edge = "rising_edge" # value changed from 0 to 1
|
|
15
|
+
falling_edge = "falling_edge" # value change from 1 to 0
|
|
16
|
+
data_change = "data_change" # value is 1, additional data is set and no rising_edge
|
|
17
|
+
none = "none" # value unchanged and not data_change
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FilterType(str, Enum):
|
|
21
|
+
rising_edge = "rising_edge" # only rising edges
|
|
22
|
+
falling_edge = "falling_edge" # only falling edges
|
|
23
|
+
any_edge = "any_edge" # rising and falling edges
|
|
24
|
+
data_change = "data_change" # all messages with value true (including rising edges) and additional_data set
|
|
25
|
+
any_change = "any_change" # rising and falling edges and additional_data changes
|
|
26
|
+
any = "any" # no filtering of changes (also when nothing changes)
|
|
27
|
+
|
|
28
|
+
def matches_change(self, change_type: ChangeType) -> bool:
|
|
29
|
+
if self.value == FilterType.rising_edge:
|
|
30
|
+
return change_type == ChangeType.rising_edge
|
|
31
|
+
elif self.value == FilterType.falling_edge:
|
|
32
|
+
return change_type == ChangeType.falling_edge
|
|
33
|
+
elif self.value == FilterType.any_edge:
|
|
34
|
+
return change_type in [ChangeType.rising_edge, ChangeType.falling_edge]
|
|
35
|
+
elif self.value == FilterType.data_change:
|
|
36
|
+
return change_type in [ChangeType.rising_edge, ChangeType.data_change]
|
|
37
|
+
elif self.value == FilterType.any_change:
|
|
38
|
+
return change_type != ChangeType.none
|
|
39
|
+
elif self.value == FilterType.any:
|
|
40
|
+
return True
|
|
41
|
+
else:
|
|
42
|
+
raise TypeError("unknown change type")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Signal:
|
|
46
|
+
def __init__(self, signal_message: SignalMessage, change_type: ChangeType):
|
|
47
|
+
self.signal: str = signal_message.signal
|
|
48
|
+
self.type: SignalType = signal_message.type
|
|
49
|
+
self.value: bool = signal_message.value
|
|
50
|
+
self.additional_data: Optional[Dict] = signal_message.additional_data
|
|
51
|
+
self.gate: Optional[str] = signal_message.gate
|
|
52
|
+
self.parking_area_id: Optional[str] = signal_message.parking_area_id
|
|
53
|
+
self.change_type: ChangeType = change_type
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Listener:
|
|
57
|
+
def __init__(self, signal_name: str, filter_type: FilterType, callback: Callable[[Signal], None],
|
|
58
|
+
tag: Optional[str]):
|
|
59
|
+
self.signal_name = signal_name
|
|
60
|
+
self.filter_type = filter_type
|
|
61
|
+
self.callback = callback
|
|
62
|
+
self.tag = tag
|
|
63
|
+
|
|
64
|
+
def matches(self, signal_name: str, change_type: ChangeType) -> bool:
|
|
65
|
+
return self.signal_name == signal_name and self.filter_type.matches_change(change_type)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SignalListener:
|
|
69
|
+
def __init__(self, dispatcher: MessageDispatcher, rpc_client: RPCClient):
|
|
70
|
+
assert not dispatcher.is_multi_threaded, (
|
|
71
|
+
"you cannot use a multithreaded message dispatcher for the SignalListener. It is highly reliant "
|
|
72
|
+
"on receiving messages in the correct order!"
|
|
73
|
+
)
|
|
74
|
+
self.current_states: Dict[str, Tuple[bool, Optional[dict]]] = dict()
|
|
75
|
+
self.listeners: List[Listener] = list()
|
|
76
|
+
self.listeners_lock = threading.Lock()
|
|
77
|
+
self.log = logging.getLogger(self.__class__.__name__)
|
|
78
|
+
self.rpc_client = rpc_client
|
|
79
|
+
dispatcher.register_handler("signal", SignalMessage, self._on_signal_message, match_type=False)
|
|
80
|
+
|
|
81
|
+
def add_listener(self, signal_name: str, filter_type: FilterType, callback: Callable[[Signal], None],
|
|
82
|
+
tag: Optional[str]):
|
|
83
|
+
"""
|
|
84
|
+
Calls the given callback method whenever a signal message is received and the circumstances fulfill the given
|
|
85
|
+
filter_type
|
|
86
|
+
:param signal_name: signal name that needs to experience a rising edge to call the callback
|
|
87
|
+
:param filter_type: One of the available FilterType
|
|
88
|
+
:param callback: the function that is called when a rising edge occurs on listen_to
|
|
89
|
+
:param tag: a tag to group listeners for later removal
|
|
90
|
+
"""
|
|
91
|
+
with self.listeners_lock:
|
|
92
|
+
self.listeners.append(Listener(signal_name, filter_type, callback, tag))
|
|
93
|
+
|
|
94
|
+
def remove_listener(self, tag: Optional[str] = None):
|
|
95
|
+
"""
|
|
96
|
+
:param tag: all listeners with this tag are removed. If no tag is given, all listeners are removed
|
|
97
|
+
"""
|
|
98
|
+
with self.listeners_lock:
|
|
99
|
+
self.listeners = [listener for listener in self.listeners if tag is not None and listener.tag != tag]
|
|
100
|
+
|
|
101
|
+
def get_value(self, signal_name: str) -> bool:
|
|
102
|
+
"""
|
|
103
|
+
returns the current value of a signal.
|
|
104
|
+
If no value is available yet, it will be requested via rpc.
|
|
105
|
+
If no answer to RPC, log exception/error and return False
|
|
106
|
+
:param signal_name: Name of signal to get value from
|
|
107
|
+
"""
|
|
108
|
+
return self.get_value_and_additional_data(signal_name)[0]
|
|
109
|
+
|
|
110
|
+
def get_value_and_additional_data(self, signal_name: str) -> Tuple[bool, Optional[dict]]:
|
|
111
|
+
"""
|
|
112
|
+
returns the current value of a signal including additional_data.
|
|
113
|
+
If no value is available yet, it will be requested via rpc.
|
|
114
|
+
If no answer to RPC, log exception/error and return (False, None)
|
|
115
|
+
:param signal_name: Name of signal to get value from
|
|
116
|
+
"""
|
|
117
|
+
if signal_name not in self.current_states:
|
|
118
|
+
try:
|
|
119
|
+
res = self.rpc_client.rpc("signal", "get_value", GetSignalValueRequest(signal=signal_name),
|
|
120
|
+
GetSignalValueResponse, timeout=1.0)
|
|
121
|
+
self.current_states[signal_name] = (res.value, res.additional_data)
|
|
122
|
+
except RPCClient.Exception:
|
|
123
|
+
self.log.exception("Error while getting signal value. Assuming False.")
|
|
124
|
+
self.current_states[signal_name] = (False, None)
|
|
125
|
+
|
|
126
|
+
return self.current_states[signal_name]
|
|
127
|
+
|
|
128
|
+
def trigger_signals(self, tag: Optional[str] = None, timeout: float = 3.0) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
:param tag: if given, only signals of listeners with given tag are triggered
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
with self.listeners_lock:
|
|
134
|
+
if tag is None:
|
|
135
|
+
signals_to_trigger = list({l.signal_name for l in self.listeners})
|
|
136
|
+
else:
|
|
137
|
+
signals_to_trigger = list({l.signal_name for l in self.listeners if l.tag == tag})
|
|
138
|
+
res = self.rpc_client.rpc("signal", "trigger_signals", TriggerSignalsRequest(signals=signals_to_trigger),
|
|
139
|
+
TriggerSignalsResponse, timeout)
|
|
140
|
+
if not res.success:
|
|
141
|
+
self.log.error("Failed to trigger all registered signals.")
|
|
142
|
+
return res.success
|
|
143
|
+
except RPCClient.Exception:
|
|
144
|
+
self.log.exception("Error while triggering signals.")
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
def _on_signal_message(self, message: SignalMessage):
|
|
148
|
+
"""
|
|
149
|
+
This handler receives all IO Messages, saves any changes and calls any listeners registered in the IoListener
|
|
150
|
+
"""
|
|
151
|
+
old_state = self.current_states.get(message.signal)
|
|
152
|
+
old_value = old_state and old_state[0] # only value without additional_data
|
|
153
|
+
self.current_states[message.signal] = (message.value, message.additional_data)
|
|
154
|
+
if old_value != message.value and message.value:
|
|
155
|
+
change_type = ChangeType.rising_edge
|
|
156
|
+
elif old_value != message.value:
|
|
157
|
+
change_type = ChangeType.falling_edge
|
|
158
|
+
elif message.value and message.additional_data:
|
|
159
|
+
change_type = ChangeType.data_change
|
|
160
|
+
else:
|
|
161
|
+
change_type = ChangeType.none
|
|
162
|
+
with self.listeners_lock:
|
|
163
|
+
for listener in self.listeners:
|
|
164
|
+
"""
|
|
165
|
+
Handles the registered listeners the same as the dispatcher.
|
|
166
|
+
Is copied here because all messages are needed to keep current_states up to date.
|
|
167
|
+
"""
|
|
168
|
+
if listener.matches(message.signal, change_type):
|
|
169
|
+
try:
|
|
170
|
+
listener.callback(Signal(message, change_type))
|
|
171
|
+
except zmq.ContextTerminated:
|
|
172
|
+
raise
|
|
173
|
+
except Exception as e:
|
|
174
|
+
self.log.exception("Error in signal callback")
|
|
@@ -74,6 +74,7 @@ openmodule/models/presence.py
|
|
|
74
74
|
openmodule/models/privacy.py
|
|
75
75
|
openmodule/models/rpc.py
|
|
76
76
|
openmodule/models/settings.py
|
|
77
|
+
openmodule/models/signals.py
|
|
77
78
|
openmodule/models/validation.py
|
|
78
79
|
openmodule/models/vehicle.py
|
|
79
80
|
openmodule/rpc/__init__.py
|
|
@@ -96,6 +97,7 @@ openmodule/utils/package_reader.py
|
|
|
96
97
|
openmodule/utils/presence.py
|
|
97
98
|
openmodule/utils/schema.py
|
|
98
99
|
openmodule/utils/settings.py
|
|
100
|
+
openmodule/utils/signal_listener.py
|
|
99
101
|
openmodule/utils/translation.py
|
|
100
102
|
openmodule/utils/validation.py
|
|
101
103
|
openmodule_commands/__init__.py
|
|
@@ -121,6 +123,7 @@ openmodule_test/sentry.py
|
|
|
121
123
|
openmodule_test/settings.py
|
|
122
124
|
openmodule_test/setup.cfg
|
|
123
125
|
openmodule_test/setup.py
|
|
126
|
+
openmodule_test/signal_simulator.py
|
|
124
127
|
openmodule_test/utils.py
|
|
125
128
|
openmodule_test/zeromq.py
|
|
126
129
|
tests/__init__.py
|
|
@@ -162,6 +165,7 @@ tests/test_utils_misc_functions.py
|
|
|
162
165
|
tests/test_utils_package_reader.py
|
|
163
166
|
tests/test_utils_presence.py
|
|
164
167
|
tests/test_utils_settings.py
|
|
168
|
+
tests/test_utils_signal.py
|
|
165
169
|
tests/test_utils_validation.py
|
|
166
170
|
tests/test_utils_vehicle.py
|
|
167
171
|
tests/invalid_database/alembic.ini
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"git_version": "3b899ae", "is_release": true}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
|
|
3
|
+
from openmodule.models.signals import SignalMessage, TriggerSignalsRequest, TriggerSignalsResponse, \
|
|
4
|
+
GetSignalValueRequest, GetSignalValueResponse, SignalType
|
|
5
|
+
from openmodule.rpc import RPCClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SignalSimulator:
|
|
9
|
+
def __init__(self, emit: Callable[[SignalMessage], None]):
|
|
10
|
+
self.emit = emit
|
|
11
|
+
self._signals = {}
|
|
12
|
+
|
|
13
|
+
def add_signal(self, value: bool, additional_data: Optional[dict], signal_type: SignalType,
|
|
14
|
+
gate: Optional[str] = None, parking_area_id: Optional[str] = None,
|
|
15
|
+
signal_name: Optional[str] = None):
|
|
16
|
+
if signal_type == SignalType.custom:
|
|
17
|
+
assert signal_name is not None
|
|
18
|
+
signal_msg = SignalMessage(signal=signal_name, type=signal_type, gate=gate, parking_area_id=parking_area_id,
|
|
19
|
+
value=value, additional_data=additional_data)
|
|
20
|
+
elif signal_type == SignalType.parkinglot_full:
|
|
21
|
+
signal_name = "parkinglot_full"
|
|
22
|
+
signal_msg = SignalMessage(signal=signal_name, type=signal_type,
|
|
23
|
+
value=value, additional_data=additional_data)
|
|
24
|
+
elif signal_type == SignalType.area_full:
|
|
25
|
+
assert parking_area_id is not None
|
|
26
|
+
signal_name = f"{parking_area_id}-area_full"
|
|
27
|
+
signal_msg = SignalMessage(signal=signal_name, type=signal_type, parking_area_id=parking_area_id,
|
|
28
|
+
value=value, additional_data=additional_data)
|
|
29
|
+
else:
|
|
30
|
+
assert gate is not None
|
|
31
|
+
signal_name = f"{gate}-{signal_type}"
|
|
32
|
+
signal_msg = SignalMessage(signal=signal_name, type=signal_type, gate=gate,
|
|
33
|
+
value=value, additional_data=additional_data)
|
|
34
|
+
self._signals[signal_msg.signal] = signal_msg
|
|
35
|
+
|
|
36
|
+
def remove_signal(self, signal: str):
|
|
37
|
+
self._signals.pop(signal, None)
|
|
38
|
+
|
|
39
|
+
def set_signal(self, signal: str, value: bool, additional_data: Optional[dict] = None):
|
|
40
|
+
self._signals[signal].value = value
|
|
41
|
+
self._signals[signal].additional_data = additional_data
|
|
42
|
+
self.emit(self._signals[signal])
|
|
43
|
+
|
|
44
|
+
def trigger_signal_callback(self, request: TriggerSignalsRequest, _):
|
|
45
|
+
res = TriggerSignalsResponse(success=True)
|
|
46
|
+
for signal in request.signals:
|
|
47
|
+
if signal in self._signals:
|
|
48
|
+
self.emit(self._signals[signal])
|
|
49
|
+
else:
|
|
50
|
+
res.success = False
|
|
51
|
+
return res
|
|
52
|
+
|
|
53
|
+
def get_value_callback(self, request: GetSignalValueRequest, _):
|
|
54
|
+
if request.signal in self._signals:
|
|
55
|
+
return GetSignalValueResponse(value=self._signals[request.signal].value,
|
|
56
|
+
additional_data=self._signals[request.signal].additional_data)
|
|
57
|
+
else:
|
|
58
|
+
raise RPCClient.TimeoutError()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
|
|
3
2
|
from datetime import datetime, timedelta
|
|
4
3
|
from typing import Optional
|
|
5
4
|
from unittest import TestCase
|
|
@@ -8,8 +7,7 @@ import freezegun
|
|
|
8
7
|
import orjson
|
|
9
8
|
from pydantic import ValidationError
|
|
10
9
|
|
|
11
|
-
from openmodule import
|
|
12
|
-
from openmodule.models.base import OpenModuleModel, ZMQMessage, Gateway, timezone_validator, CostEntryData
|
|
10
|
+
from openmodule.models.base import OpenModuleModel, ZMQMessage, Gateway, timezone_validator
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class TestModel(OpenModuleModel):
|
|
@@ -138,11 +136,3 @@ class ZMQMessageTestCase(TestCase):
|
|
|
138
136
|
|
|
139
137
|
res1.pop("timestamp")
|
|
140
138
|
self.assertDictEqual(res1, {"name": "tä\nst", "type": "tö\tst"})
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
class CostEntryDataTextCase(TestCase):
|
|
144
|
-
def test_type_parsing(self):
|
|
145
|
-
res = CostEntryData.parse_raw(b'{"entry_type": "static_cost", "value": 10, "account_id": "0"}')
|
|
146
|
-
self.assertTrue(isinstance(res.value, int))
|
|
147
|
-
res = CostEntryData.parse_raw(b'{"entry_type": "rate_change", "value": "10", "account_id": "0"}')
|
|
148
|
-
self.assertTrue(isinstance(res.value, str))
|