mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-21 22:48:16 +01:00
initial version
This commit is contained in:
parent
10a5fff66c
commit
05e9000c43
76 changed files with 2179 additions and 0 deletions
12
.idea/craftbeerpi4.iml
Normal file
12
.idea/craftbeerpi4.iml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.6.1 virtualenv at ~/aioenv" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TestRunnerService">
|
||||||
|
<option name="projectConfiguration" value="py.test" />
|
||||||
|
<option name="PROJECT_TEST_RUNNER" value="py.test" />
|
||||||
|
</component>
|
||||||
|
</module>
|
7
.idea/inspectionProfiles/profiles_settings.xml
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="useProjectProfile" value="false" />
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
4
.idea/misc.xml
Normal file
4
.idea/misc.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6.1 virtualenv at ~/aioenv" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/craftbeerpi4.iml" filepath="$PROJECT_DIR$/.idea/craftbeerpi4.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
347
.idea/workspace.xml
Normal file
347
.idea/workspace.xml
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="1a245e6a-2e43-4348-8759-dc1b63a4c9c8" name="Default" comment="" />
|
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||||
|
<option name="TRACKING_ENABLED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="CoverageDataManager">
|
||||||
|
<SUITE FILE_PATH="coverage/craftbeerpi4$run.coverage" NAME="run Coverage Results" MODIFIED="1541098167062" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
|
||||||
|
<component name="FileEditorManager">
|
||||||
|
<leaf>
|
||||||
|
<file leaf-file-name="requirements.txt" pinned="false" current-in-tab="false">
|
||||||
|
<entry file="file://$PROJECT_DIR$/requirements.txt">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="0">
|
||||||
|
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||||
|
<folding />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
<file leaf-file-name="run.py" pinned="false" current-in-tab="true">
|
||||||
|
<entry file="file://$PROJECT_DIR$/run.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="0">
|
||||||
|
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#0#16#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
</leaf>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||||
|
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||||
|
<component name="JsGulpfileManager">
|
||||||
|
<detection-done>true</detection-done>
|
||||||
|
<sorting>DEFINITION_ORDER</sorting>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectFrameBounds">
|
||||||
|
<option name="x" value="55" />
|
||||||
|
<option name="y" value="40" />
|
||||||
|
<option name="width" value="1767" />
|
||||||
|
<option name="height" value="968" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator currentView="ProjectPane" proportions="" version="1">
|
||||||
|
<flattenPackages />
|
||||||
|
<showMembers />
|
||||||
|
<showModules />
|
||||||
|
<showLibraryContents />
|
||||||
|
<hideEmptyPackages />
|
||||||
|
<abbreviatePackageNames />
|
||||||
|
<autoscrollToSource />
|
||||||
|
<autoscrollFromSource ProjectPane="true" />
|
||||||
|
<sortByType />
|
||||||
|
<manualOrder />
|
||||||
|
<foldersAlwaysOnTop value="true" />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="Scope" />
|
||||||
|
<pane id="Scratches" />
|
||||||
|
<pane id="ProjectPane">
|
||||||
|
<subPane>
|
||||||
|
<PATH>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="craftbeerpi4" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="craftbeerpi4" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
</PATH>
|
||||||
|
</subPane>
|
||||||
|
</pane>
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
|
||||||
|
<property name="nodejs_interpreter_path" value="/usr/local/bin/node" />
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="RunDashboard">
|
||||||
|
<option name="ruleStates">
|
||||||
|
<list>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="StatusDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Python.run">
|
||||||
|
<configuration default="false" name="run" type="PythonConfigurationType" factoryName="Python" temporary="true">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/run.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="DjangoTestsConfigurationType" factoryName="Django tests">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="TARGET" value="" />
|
||||||
|
<option name="SETTINGS_FILE" value="" />
|
||||||
|
<option name="CUSTOM_SETTINGS" value="false" />
|
||||||
|
<option name="USE_OPTIONS" value="false" />
|
||||||
|
<option name="OPTIONS" value="" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavaScriptTestRunnerJest" factoryName="Jest">
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<working-dir value="" />
|
||||||
|
<envs />
|
||||||
|
<scope-kind value="ALL" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavaScriptTestRunnerProtractor" factoryName="Protractor">
|
||||||
|
<config-file value="" />
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug">
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="PyBehaveRunConfigurationType" factoryName="Behave">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="ADDITIONAL_ARGS" value="" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="PyLettuceRunConfigurationType" factoryName="Lettuce">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="ADDITIONAL_ARGS" value="" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="Tox" factoryName="Tox">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js">
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="js.build_tools.npm" factoryName="npm">
|
||||||
|
<command value="run" />
|
||||||
|
<scripts />
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="tests" factoryName="Doctests">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="" />
|
||||||
|
<option name="CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="FOLDER_NAME" value="" />
|
||||||
|
<option name="TEST_TYPE" value="TEST_SCRIPT" />
|
||||||
|
<option name="PATTERN" value="" />
|
||||||
|
<option name="USE_PATTERN" value="false" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="tests" factoryName="py.test">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="craftbeerpi4" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="_new_keywords" value="""" />
|
||||||
|
<option name="_new_additionalArguments" value="""" />
|
||||||
|
<option name="_new_target" value=""."" />
|
||||||
|
<option name="_new_targetType" value=""PATH"" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="Python.run" />
|
||||||
|
</list>
|
||||||
|
<recent_temporary>
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="Python.run" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="ShelveChangesManager" show_recycled="false">
|
||||||
|
<option name="remove_strategy" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="1a245e6a-2e43-4348-8759-dc1b63a4c9c8" name="Default" comment="" />
|
||||||
|
<created>1541098050947</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1541098050947</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="55" y="40" width="1767" height="968" extended-state="0" />
|
||||||
|
<layout>
|
||||||
|
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.24957556" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
|
||||||
|
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Run" active="true" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.32925472" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32925472" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Data View" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
|
||||||
|
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="processedProjectFiles" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="VcsContentAnnotationSettings">
|
||||||
|
<option name="myLimit" value="2678400000" />
|
||||||
|
</component>
|
||||||
|
<component name="XDebuggerManager">
|
||||||
|
<breakpoint-manager />
|
||||||
|
<watches-manager />
|
||||||
|
</component>
|
||||||
|
<component name="editorHistoryManager">
|
||||||
|
<entry file="file://$PROJECT_DIR$/requirements.txt">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="0">
|
||||||
|
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||||
|
<folding />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/run.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="0">
|
||||||
|
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#0#16#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
BIN
core/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/cbpi.cpython-36.pyc
Normal file
BIN
core/__pycache__/cbpi.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/eventbus.cpython-36.pyc
Normal file
BIN
core/__pycache__/eventbus.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/plugin.cpython-36.pyc
Normal file
BIN
core/__pycache__/plugin.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/websocket.cpython-36.pyc
Normal file
BIN
core/__pycache__/websocket.cpython-36.pyc
Normal file
Binary file not shown.
1
core/api/__init__.py
Normal file
1
core/api/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__all__ = ["actor", "property", "sensor"]
|
BIN
core/api/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/api/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/api/__pycache__/actor.cpython-36.pyc
Normal file
BIN
core/api/__pycache__/actor.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/api/__pycache__/decorator.cpython-36.pyc
Normal file
BIN
core/api/__pycache__/decorator.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/api/__pycache__/property.cpython-36.pyc
Normal file
BIN
core/api/__pycache__/property.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/api/__pycache__/sensor.cpython-36.pyc
Normal file
BIN
core/api/__pycache__/sensor.cpython-36.pyc
Normal file
Binary file not shown.
16
core/api/actor.py
Normal file
16
core/api/actor.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
|
||||||
|
class Actor():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id = "";
|
||||||
|
self.name = ""
|
||||||
|
|
||||||
|
def on(self, power):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
pass
|
82
core/api/decorator.py
Normal file
82
core/api/decorator.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
from aiohttp_auth import auth
|
||||||
|
|
||||||
|
def composed(*decs):
|
||||||
|
def deco(f):
|
||||||
|
for dec in reversed(decs):
|
||||||
|
f = dec(f)
|
||||||
|
return f
|
||||||
|
return deco
|
||||||
|
|
||||||
|
def request_mapping(path, name=None, method="GET", auth_required=True):
|
||||||
|
|
||||||
|
def on_http_request(path, name=None):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.route = True
|
||||||
|
func.path = path
|
||||||
|
func.name = name
|
||||||
|
func.method = method
|
||||||
|
return func
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
if auth_required is True:
|
||||||
|
return composed(
|
||||||
|
on_http_request(path, name),
|
||||||
|
auth.auth_required
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return composed(
|
||||||
|
on_http_request(path, name)
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_websocket_message(path, name=None):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.ws = True
|
||||||
|
func.key = path
|
||||||
|
func.name = name
|
||||||
|
return func
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
def on_event(topic):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.eventbus = True
|
||||||
|
func.topic = topic
|
||||||
|
return func
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
def action(key, parameters):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.action = True
|
||||||
|
func.key = key
|
||||||
|
func.parameters = parameters
|
||||||
|
return func
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
def on_mqtt_message(topic):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.mqtt = True
|
||||||
|
func.topic = topic
|
||||||
|
return func
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
|
||||||
|
def background_task(name, interval):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.background_task = True
|
||||||
|
func.name = name
|
||||||
|
func.interval = interval
|
||||||
|
return func
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
|
||||||
|
def entry_exit(f):
|
||||||
|
def new_f():
|
||||||
|
print("Entering", f.__name__)
|
||||||
|
f()
|
||||||
|
print("Exited", f.__name__)
|
||||||
|
return new_f
|
47
core/api/property.py
Normal file
47
core/api/property.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
class PropertyType(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Property(object):
|
||||||
|
class Select(PropertyType):
|
||||||
|
def __init__(self, label, options, description=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.options = options
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
class Number(PropertyType):
|
||||||
|
def __init__(self, label, configurable=False, default_value=None, unit="", description=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = configurable
|
||||||
|
self.default_value = default_value
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
class Text(PropertyType):
|
||||||
|
def __init__(self, label, configurable=False, default_value="", description=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = configurable
|
||||||
|
self.default_value = default_value
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
class Actor(PropertyType):
|
||||||
|
def __init__(self, label, description=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = True
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
class Sensor(PropertyType):
|
||||||
|
def __init__(self, label, description=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = True
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
class Kettle(PropertyType):
|
||||||
|
def __init__(self, label, description=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = True
|
||||||
|
self.description = description
|
14
core/api/sensor.py
Normal file
14
core/api/sensor.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
class Sensor():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id = "";
|
||||||
|
self.name = ""
|
||||||
|
|
||||||
|
def on(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
pass
|
152
core/cbpi.py
Normal file
152
core/cbpi.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import asyncio
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
from os import urandom
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp_auth import auth
|
||||||
|
from aiohttp_session import session_middleware
|
||||||
|
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||||
|
from aiohttp_swagger import setup_swagger
|
||||||
|
from aiojobs.aiohttp import setup, get_scheduler_from_app
|
||||||
|
|
||||||
|
from core.controller.actor_controller import ActorController
|
||||||
|
from core.controller.system_controller import SystemController
|
||||||
|
from core.database.model import DBModel
|
||||||
|
from core.eventbus import EventBus
|
||||||
|
|
||||||
|
|
||||||
|
from core.http_endpoints.http_login import Login
|
||||||
|
from core.controller.sensor_controller import SensorController
|
||||||
|
from core.websocket import WebSocket
|
||||||
|
|
||||||
|
logger = logging.getLogger(__file__)
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
class CraftBeerPi():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
logger.info("Init CraftBeerPI")
|
||||||
|
policy = auth.SessionTktAuthentication(urandom(32), 60, include_ip=True)
|
||||||
|
middlewares = [session_middleware(EncryptedCookieStorage(urandom(32))), auth.auth_middleware(policy)]
|
||||||
|
self.app = web.Application(middlewares=middlewares)
|
||||||
|
|
||||||
|
setup(self.app)
|
||||||
|
self.bus = EventBus()
|
||||||
|
self.ws = WebSocket(self)
|
||||||
|
self.actor = ActorController(self)
|
||||||
|
self.sensor = SensorController(self)
|
||||||
|
self.system = SystemController(self)
|
||||||
|
self.login = Login(self)
|
||||||
|
|
||||||
|
def register_events(self, obj):
|
||||||
|
|
||||||
|
for method in [getattr(obj, f) for f in dir(obj) if callable(getattr(obj, f)) and hasattr(getattr(obj, f),"eventbus")]:
|
||||||
|
print(method.__getattribute__("topic"), method)
|
||||||
|
|
||||||
|
doc = None
|
||||||
|
if method.__doc__ is not None:
|
||||||
|
doc = yaml.load(method.__doc__)
|
||||||
|
doc["topic"] = method.__getattribute__("topic")
|
||||||
|
self.bus.register(method.__getattribute__("topic"), method, doc)
|
||||||
|
|
||||||
|
|
||||||
|
def register_background_task(self, obj):
|
||||||
|
for method in [getattr(obj, f) for f in dir(obj) if callable(getattr(obj, f)) and hasattr(getattr(obj, f), "background_task")]:
|
||||||
|
name = method.__getattribute__("name")
|
||||||
|
interval = method.__getattribute__("interval")
|
||||||
|
|
||||||
|
async def job_loop(app, name, interval, method):
|
||||||
|
logger.info("Start Background Task %s Interval %s Method %s" % (name,interval, method))
|
||||||
|
while True:
|
||||||
|
logger.info("Execute Task %s - interval(%s second(s)" % (name, interval))
|
||||||
|
await asyncio.sleep(interval)
|
||||||
|
await method()
|
||||||
|
|
||||||
|
async def spawn_job(app):
|
||||||
|
scheduler = get_scheduler_from_app(self.app)
|
||||||
|
await scheduler.spawn(job_loop(self.app, name, interval, method))
|
||||||
|
|
||||||
|
|
||||||
|
self.app.on_startup.append(spawn_job)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register_ws(self, obj):
|
||||||
|
if self.ws is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for method in [getattr(obj, f) for f in dir(obj) if callable(getattr(obj, f)) and hasattr(getattr(obj, f),"ws")]:
|
||||||
|
self.ws.add_callback(method, method.__getattribute__("key"))
|
||||||
|
|
||||||
|
def register(self, obj, subapp=None):
|
||||||
|
self.register_http_endpoints(obj, subapp)
|
||||||
|
self.register_events(obj)
|
||||||
|
self.register_ws(obj)
|
||||||
|
self.register_background_task(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def register_http_endpoints(self, obj, subapp=None):
|
||||||
|
routes = []
|
||||||
|
for method in [getattr(obj, f) for f in dir(obj) if callable(getattr(obj, f)) and hasattr(getattr(obj, f), "route")]:
|
||||||
|
|
||||||
|
http_method = method.__getattribute__("method")
|
||||||
|
path = method.__getattribute__("path")
|
||||||
|
|
||||||
|
def add_post():
|
||||||
|
routes.append(web.post(method.__getattribute__("path"), method))
|
||||||
|
|
||||||
|
def add_get():
|
||||||
|
routes.append(web.get(method.__getattribute__("path"), method))
|
||||||
|
|
||||||
|
def add_delete():
|
||||||
|
routes.append(web.delete(path, method))
|
||||||
|
|
||||||
|
def add_put():
|
||||||
|
routes.append(web.put(path, method))
|
||||||
|
switcher = {
|
||||||
|
"POST": add_post,
|
||||||
|
"GET": add_get,
|
||||||
|
"DELETE": add_delete,
|
||||||
|
"PUT": add_put
|
||||||
|
}
|
||||||
|
|
||||||
|
switcher[http_method]()
|
||||||
|
|
||||||
|
if subapp is not None:
|
||||||
|
sub = web.Application()
|
||||||
|
sub.add_routes(routes)
|
||||||
|
self.app.add_subapp(subapp, sub)
|
||||||
|
else:
|
||||||
|
self.app.add_routes(routes)
|
||||||
|
|
||||||
|
async def _load_extensions(self, app):
|
||||||
|
extension_list = ["core.extension.dummy"]
|
||||||
|
|
||||||
|
for extension in extension_list:
|
||||||
|
logger.info("LOADING PUGIN %s" % extension)
|
||||||
|
my_module = importlib.import_module(extension)
|
||||||
|
my_module.setup(self)
|
||||||
|
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
|
||||||
|
async def init_database(app):
|
||||||
|
await DBModel.test_connection()
|
||||||
|
|
||||||
|
async def init_controller(app):
|
||||||
|
await self.actor.init()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.app.on_startup.append(init_database)
|
||||||
|
self.app.on_startup.append(self._load_extensions)
|
||||||
|
self.app.on_startup.append(init_controller)
|
||||||
|
setup_swagger(self.app)
|
||||||
|
web.run_app(self.app)
|
||||||
|
|
||||||
|
|
0
core/controller/__init__.py
Normal file
0
core/controller/__init__.py
Normal file
BIN
core/controller/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/controller/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/controller/__pycache__/actor_controller.cpython-36.pyc
Normal file
BIN
core/controller/__pycache__/actor_controller.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/controller/__pycache__/crud_controller.cpython-36.pyc
Normal file
BIN
core/controller/__pycache__/crud_controller.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/controller/__pycache__/sensor_controller.cpython-36.pyc
Normal file
BIN
core/controller/__pycache__/sensor_controller.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/controller/__pycache__/system_controller.cpython-36.pyc
Normal file
BIN
core/controller/__pycache__/system_controller.cpython-36.pyc
Normal file
Binary file not shown.
71
core/controller/actor_controller.py
Normal file
71
core/controller/actor_controller.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp_auth.auth.decorators import auth_required
|
||||||
|
|
||||||
|
from core.api.decorator import on_event, request_mapping
|
||||||
|
from core.controller.crud_controller import CRUDController
|
||||||
|
from core.database.model import ActorModel
|
||||||
|
from core.http_endpoints.http_api import HttpAPI
|
||||||
|
from core.plugin import PluginAPI
|
||||||
|
|
||||||
|
|
||||||
|
class ActorController(HttpAPI, CRUDController, PluginAPI):
|
||||||
|
|
||||||
|
|
||||||
|
model = ActorModel
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, cbpi):
|
||||||
|
super(ActorController, self).__init__(cbpi)
|
||||||
|
self.cbpi = cbpi
|
||||||
|
self.state = False;
|
||||||
|
|
||||||
|
self.cbpi.register(self, "/actor")
|
||||||
|
self.types = {}
|
||||||
|
self.actors = {}
|
||||||
|
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
|
||||||
|
await super(ActorController, self).init()
|
||||||
|
for name, clazz in self.types.items():
|
||||||
|
print("Type", name)
|
||||||
|
for id, value in self.cache.items():
|
||||||
|
|
||||||
|
if value.type in self.types:
|
||||||
|
clazz = self.types[value.type];
|
||||||
|
self.actors[id] = clazz(self.cbpi)
|
||||||
|
print(value.type)
|
||||||
|
print("CACHE", self.cache)
|
||||||
|
print("ACTORS", self.actors)
|
||||||
|
|
||||||
|
@request_mapping(path="/{id}/on",auth_required=False)
|
||||||
|
async def http_on(self, request) -> web.Response:
|
||||||
|
self.cbpi.bus.fire(event="actor/1/on", id=1, power=99)
|
||||||
|
return web.Response(status=204)
|
||||||
|
|
||||||
|
@on_event(topic="actor/+/on")
|
||||||
|
def on(self, id, power=100) -> None:
|
||||||
|
print("ON-------------", id, power)
|
||||||
|
if id in self.actors:
|
||||||
|
i = self.actors[id]
|
||||||
|
i.on(power)
|
||||||
|
|
||||||
|
@on_event(topic="actor/+/on")
|
||||||
|
def on2(self, id, **kwargs) -> None:
|
||||||
|
print("POWERED ON", id, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register(self, name, clazz) -> None:
|
||||||
|
'''
|
||||||
|
Register a new actor type
|
||||||
|
:param name: actor name
|
||||||
|
:param clazz: actor class
|
||||||
|
:return: None
|
||||||
|
'''
|
||||||
|
self._parse_props(clazz)
|
||||||
|
self.types[name] = clazz
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
86
core/controller/crud_controller.py
Normal file
86
core/controller/crud_controller.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
class CRUDController(object):
|
||||||
|
|
||||||
|
|
||||||
|
cache = {}
|
||||||
|
caching = True
|
||||||
|
|
||||||
|
def __init__(self, core):
|
||||||
|
self.cbpi = core
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
if self.caching is True:
|
||||||
|
self.cache = await self.model.get_all()
|
||||||
|
|
||||||
|
async def get_all(self, force_db_update=False):
|
||||||
|
|
||||||
|
if self.caching is False or force_db_update:
|
||||||
|
self.cache = await self.model.get_all()
|
||||||
|
|
||||||
|
return self.cache
|
||||||
|
|
||||||
|
async def get_one(self, id):
|
||||||
|
|
||||||
|
return self.cache.get(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def _pre_add_callback(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _post_add_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def add(self, **data):
|
||||||
|
await self._pre_add_callback(data)
|
||||||
|
m = await self.model.insert(**data)
|
||||||
|
await self._post_add_callback(m)
|
||||||
|
self.cache[m.id] = m
|
||||||
|
return m
|
||||||
|
|
||||||
|
async def _pre_update_callback(self, id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _post_update_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def update(self, id, **data):
|
||||||
|
|
||||||
|
await self._pre_update_callback(id)
|
||||||
|
data["id"] = id
|
||||||
|
try:
|
||||||
|
del data["instance"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
m = await self.model.update(**data)
|
||||||
|
#self.core.push_ws("UPDATE_%s" % self.key, m)
|
||||||
|
|
||||||
|
await self._post_update_callback(m)
|
||||||
|
if self.caching is True:
|
||||||
|
self.cache[m.id] = m
|
||||||
|
return m
|
||||||
|
|
||||||
|
async def _pre_delete_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _post_delete_callback(self, id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def delete(self, id):
|
||||||
|
await self._pre_delete_callback(id)
|
||||||
|
m = await self.model.delete(id)
|
||||||
|
await self._post_delete_callback(id)
|
||||||
|
try:
|
||||||
|
if self.caching is True:
|
||||||
|
del self.cache[id]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
#self.core.push("DELETE_%s" % self.key, id)
|
||||||
|
|
||||||
|
async def delete_all(self):
|
||||||
|
self.model.delete_all()
|
||||||
|
if self.caching is True:
|
||||||
|
self.cache = {}
|
||||||
|
#self.core.push_ws("DELETE_ALL_%s" % self.key, None)
|
0
core/controller/fermentation_controller.py
Normal file
0
core/controller/fermentation_controller.py
Normal file
38
core/controller/sensor_controller.py
Normal file
38
core/controller/sensor_controller.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import logging
|
||||||
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
|
||||||
|
|
||||||
|
from core.api.decorator import background_task
|
||||||
|
from core.controller.crud_controller import CRUDController
|
||||||
|
|
||||||
|
from core.database.model import SensorModel
|
||||||
|
from core.http_endpoints.http_api import HttpAPI
|
||||||
|
|
||||||
|
|
||||||
|
class SensorController(CRUDController, HttpAPI):
|
||||||
|
|
||||||
|
model = SensorModel
|
||||||
|
|
||||||
|
def __init__(self, core):
|
||||||
|
self.core = core
|
||||||
|
self.core.register(self, "/sensor")
|
||||||
|
self.service = self
|
||||||
|
|
||||||
|
self.sensors = {"S1": "S1", "S2": "S2"}
|
||||||
|
handler = TimedRotatingFileHandler("./logs/first_logfile2.log", when="m", interval=1, backupCount=5)
|
||||||
|
#handler = RotatingFileHandler("first_logfile.log", mode='a', maxBytes=300, backupCount=2, encoding=None, delay=0)
|
||||||
|
formatter = logging.Formatter('%(asctime)s,%(sensor)s,%(message)s')
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
self.logger = logging.getLogger("SensorController")
|
||||||
|
self.logger.setLevel(logging.INFO)
|
||||||
|
self.logger.propagate = False
|
||||||
|
self.logger.addHandler(handler)
|
||||||
|
|
||||||
|
async def pre_get_one(self, id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@background_task(name="test", interval=1)
|
||||||
|
async def hallo(self):
|
||||||
|
|
||||||
|
self.logger.info("WOOHO", extra={"sensor": 1})
|
0
core/controller/step_controller.py
Normal file
0
core/controller/step_controller.py
Normal file
23
core/controller/system_controller.py
Normal file
23
core/controller/system_controller.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from aiohttp import web
|
||||||
|
from aiojobs.aiohttp import get_scheduler_from_app
|
||||||
|
|
||||||
|
from core.api.decorator import request_mapping
|
||||||
|
|
||||||
|
|
||||||
|
class SystemController():
|
||||||
|
name = "Manuel"
|
||||||
|
|
||||||
|
def __init__(self, core):
|
||||||
|
self.core = core
|
||||||
|
self.service = core.actor
|
||||||
|
self.core.register(self, "/system")
|
||||||
|
|
||||||
|
@request_mapping("/jobs", method="GET", name="get_jobs", auth_required=True)
|
||||||
|
def get_all_jobs(self, request):
|
||||||
|
scheduler = get_scheduler_from_app(self.core.app)
|
||||||
|
print(scheduler.active_count, scheduler.pending_limit)
|
||||||
|
for j in scheduler:
|
||||||
|
print(j)
|
||||||
|
|
||||||
|
# await j.close()
|
||||||
|
return web.Response(text="HALLO")
|
0
core/database/__init__.py
Normal file
0
core/database/__init__.py
Normal file
BIN
core/database/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/database/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/database/__pycache__/model.cpython-36.pyc
Normal file
BIN
core/database/__pycache__/model.cpython-36.pyc
Normal file
Binary file not shown.
156
core/database/model.py
Normal file
156
core/database/model.py
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
|
|
||||||
|
TEST_DB = "./craftbeerpi.db"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DBModel(object):
|
||||||
|
|
||||||
|
__priamry_key__ = "id"
|
||||||
|
__as_array__ = False
|
||||||
|
__order_by__ = None
|
||||||
|
__json_fields__ = []
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
|
||||||
|
self.__setattr__(self.__priamry_key__, args[self.__priamry_key__])
|
||||||
|
for f in self.__fields__:
|
||||||
|
if f in self.__json_fields__:
|
||||||
|
if args[f] is not None:
|
||||||
|
|
||||||
|
if isinstance(args[f], dict) or isinstance(args[f], list):
|
||||||
|
self.__setattr__(f, args[f])
|
||||||
|
else:
|
||||||
|
self.__setattr__(f, json.loads(args[f]))
|
||||||
|
else:
|
||||||
|
self.__setattr__(f, None)
|
||||||
|
else:
|
||||||
|
print(f,args[f])
|
||||||
|
self.__setattr__(f, args[f])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def test_connection(self):
|
||||||
|
|
||||||
|
print("CREATE DATABSE")
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
print("DB OK")
|
||||||
|
assert isinstance(db, aiosqlite.Connection)
|
||||||
|
qry = open('./core/sql/create_table_user.sql', 'r').read()
|
||||||
|
cursor = await db.executescript(qry)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_all(cls):
|
||||||
|
print("GET ALL")
|
||||||
|
if cls.__as_array__ is True:
|
||||||
|
result = []
|
||||||
|
else:
|
||||||
|
result = {}
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
|
||||||
|
if cls.__order_by__ is not None:
|
||||||
|
sql = "SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__)
|
||||||
|
else:
|
||||||
|
sql = "SELECT * FROM %s" % cls.__table_name__
|
||||||
|
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute(sql) as cursor:
|
||||||
|
async for row in cursor:
|
||||||
|
if cls.__as_array__ is True:
|
||||||
|
result.append(cls(row))
|
||||||
|
else:
|
||||||
|
result[row[0]] = cls(row)
|
||||||
|
await cursor.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_one(cls, id):
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) as cursor:
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
if row is not None:
|
||||||
|
return cls(row)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def delete(cls, id):
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
await db.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,))
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def insert(cls, **kwargs):
|
||||||
|
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
if cls.__priamry_key__ is not None and cls.__priamry_key__ in kwargs:
|
||||||
|
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
|
||||||
|
cls.__table_name__,
|
||||||
|
cls.__priamry_key__,
|
||||||
|
', '.join("'%s'" % str(x) for x in cls.__fields__),
|
||||||
|
', '.join(['?'] * len(cls.__fields__)))
|
||||||
|
data = ()
|
||||||
|
data = data + (kwargs.get(cls.__priamry_key__),)
|
||||||
|
for f in cls.__fields__:
|
||||||
|
if f in cls.__json_fields__:
|
||||||
|
data = data + (json.dumps(kwargs.get(f)),)
|
||||||
|
else:
|
||||||
|
data = data + (kwargs.get(f),)
|
||||||
|
else:
|
||||||
|
|
||||||
|
query = 'INSERT INTO %s (%s) VALUES (%s)' % (
|
||||||
|
cls.__table_name__,
|
||||||
|
', '.join("'%s'" % str(x) for x in cls.__fields__),
|
||||||
|
', '.join(['?'] * len(cls.__fields__)))
|
||||||
|
|
||||||
|
data = ()
|
||||||
|
for f in cls.__fields__:
|
||||||
|
if f in cls.__json_fields__:
|
||||||
|
data = data + (json.dumps(kwargs.get(f)),)
|
||||||
|
else:
|
||||||
|
data = data + (kwargs.get(f),)
|
||||||
|
|
||||||
|
print(query, data)
|
||||||
|
cursor = await db.execute(query, data)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
i = cursor.lastrowid
|
||||||
|
kwargs["id"] = i
|
||||||
|
|
||||||
|
return cls(kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def update(cls, **kwargs):
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
query = 'UPDATE %s SET %s WHERE %s = ?' % (cls.__table_name__, ', '.join("'%s' = ?" % str(x) for x in cls.__fields__), cls.__priamry_key__)
|
||||||
|
|
||||||
|
data = ()
|
||||||
|
for f in cls.__fields__:
|
||||||
|
if f in cls.__json_fields__:
|
||||||
|
data = data + (json.dumps(kwargs.get(f)),)
|
||||||
|
else:
|
||||||
|
data = data + (kwargs.get(f),)
|
||||||
|
|
||||||
|
data = data + (kwargs.get(cls.__priamry_key__),)
|
||||||
|
cursor = await db.execute(query, data)
|
||||||
|
await db.commit()
|
||||||
|
return cls(kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ActorModel(DBModel):
|
||||||
|
__fields__ = ["name","type","config"]
|
||||||
|
__table_name__ = "actor"
|
||||||
|
__json_fields__ = ["config"]
|
||||||
|
|
||||||
|
|
||||||
|
class SensorModel(DBModel):
|
||||||
|
__fields__ = ["name","type", "config"]
|
||||||
|
__table_name__ = "sensor"
|
||||||
|
__json_fields__ = ["config"]
|
0
core/database/orm_framework.py
Normal file
0
core/database/orm_framework.py
Normal file
92
core/eventbus.py
Normal file
92
core/eventbus.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class EventBus(object):
|
||||||
|
class Node(object):
|
||||||
|
__slots__ = '_children', '_content'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._children = {}
|
||||||
|
self._content = None
|
||||||
|
|
||||||
|
def register(self, key, value, doc=None):
|
||||||
|
|
||||||
|
if doc is not None:
|
||||||
|
self.docs[key] = doc
|
||||||
|
self.logger.info("key %s", key)
|
||||||
|
node = self._root
|
||||||
|
for sym in key.split('/'):
|
||||||
|
node = node._children.setdefault(sym, self.Node())
|
||||||
|
|
||||||
|
if not isinstance(node._content, list):
|
||||||
|
node._content = []
|
||||||
|
node._content.append(value)
|
||||||
|
|
||||||
|
def get_callbacks(self, key):
|
||||||
|
try:
|
||||||
|
node = self._root
|
||||||
|
for sym in key.split('/'):
|
||||||
|
node = node._children[sym]
|
||||||
|
if node._content is None:
|
||||||
|
raise KeyError(key)
|
||||||
|
return node._content
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def unregister(self, key, method=None):
|
||||||
|
|
||||||
|
lst = []
|
||||||
|
try:
|
||||||
|
parent, node = None, self._root
|
||||||
|
for k in key.split('/'):
|
||||||
|
parent, node = node, node._children[k]
|
||||||
|
lst.append((parent, k, node))
|
||||||
|
# TODO
|
||||||
|
print(node._content)
|
||||||
|
if method is not None:
|
||||||
|
node._content = None
|
||||||
|
else:
|
||||||
|
node._content = None
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(key)
|
||||||
|
else: # cleanup
|
||||||
|
for parent, k, node in reversed(lst):
|
||||||
|
if node._children or node._content is not None:
|
||||||
|
break
|
||||||
|
del parent._children[k]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self._root = self.Node()
|
||||||
|
self.docs = {}
|
||||||
|
|
||||||
|
def fire(self, event: str, **kwargs) -> None:
|
||||||
|
self.logger.info("EMIT EVENT %s", event)
|
||||||
|
for methods in self.iter_match(event):
|
||||||
|
for f in methods:
|
||||||
|
print("METHOD: ", f)
|
||||||
|
f(**kwargs)
|
||||||
|
|
||||||
|
def iter_match(self, topic):
|
||||||
|
|
||||||
|
lst = topic.split('/')
|
||||||
|
normal = not topic.startswith('$')
|
||||||
|
|
||||||
|
def rec(node, i=0):
|
||||||
|
if i == len(lst):
|
||||||
|
if node._content is not None:
|
||||||
|
yield node._content
|
||||||
|
else:
|
||||||
|
part = lst[i]
|
||||||
|
if part in node._children:
|
||||||
|
for content in rec(node._children[part], i + 1):
|
||||||
|
yield content
|
||||||
|
if '+' in node._children and (normal or i > 0):
|
||||||
|
for content in rec(node._children['+'], i + 1):
|
||||||
|
yield content
|
||||||
|
if '#' in node._children and (normal or i > 0):
|
||||||
|
content = node._children['#']._content
|
||||||
|
if content is not None:
|
||||||
|
yield content
|
||||||
|
|
||||||
|
return rec(self._root)
|
0
core/extension/__init__.py
Normal file
0
core/extension/__init__.py
Normal file
BIN
core/extension/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/extension/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
45
core/extension/dummy/__init__.py
Normal file
45
core/extension/dummy/__init__.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from core.database.model import ActorModel
|
||||||
|
from core.api.decorator import action, background_task
|
||||||
|
from core.api.property import Property
|
||||||
|
print("##################")
|
||||||
|
|
||||||
|
from core.api.actor import Actor
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class MyActor(Actor):
|
||||||
|
|
||||||
|
|
||||||
|
name = Property.Number(label="Test")
|
||||||
|
name1 = Property.Text(label="Test")
|
||||||
|
name2 = Property.Kettle(label="Test")
|
||||||
|
|
||||||
|
@background_task("s1", interval=2)
|
||||||
|
async def bg_job(self):
|
||||||
|
print("WOOH BG")
|
||||||
|
|
||||||
|
@action(key="name", parameters={})
|
||||||
|
def myAction(self):
|
||||||
|
print("HALLO")
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
super().state()
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
super().off()
|
||||||
|
|
||||||
|
def on(self, power=100):
|
||||||
|
super().on(power)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, core=None):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.logger.info("WOOHOO MY ACTOR")
|
||||||
|
self.core = None
|
||||||
|
|
||||||
|
|
||||||
|
def setup(cbpi):
|
||||||
|
|
||||||
|
cbpi.actor.register("MyActor", MyActor)
|
BIN
core/extension/dummy/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/extension/dummy/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
0
core/helper/__init__.py
Normal file
0
core/helper/__init__.py
Normal file
BIN
core/helper/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/helper/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/helper/__pycache__/jsondump.cpython-36.pyc
Normal file
BIN
core/helper/__pycache__/jsondump.cpython-36.pyc
Normal file
Binary file not shown.
26
core/helper/jsondump.py
Normal file
26
core/helper/jsondump.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import json
|
||||||
|
from json import JSONEncoder
|
||||||
|
|
||||||
|
from core.database.model import DBModel, ActorModel
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexEncoder(JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(obj, DBModel):
|
||||||
|
return obj.__dict__
|
||||||
|
|
||||||
|
elif isinstance(obj, ActorModel):
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif hasattr(obj, "callback"):
|
||||||
|
return obj()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except TypeError as e:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def json_dumps(obj):
|
||||||
|
return json.dumps(obj, cls=ComplexEncoder)
|
0
core/http_endpoints/__init__.py
Normal file
0
core/http_endpoints/__init__.py
Normal file
BIN
core/http_endpoints/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
core/http_endpoints/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/http_endpoints/__pycache__/http_api.cpython-36.pyc
Normal file
BIN
core/http_endpoints/__pycache__/http_api.cpython-36.pyc
Normal file
Binary file not shown.
BIN
core/http_endpoints/__pycache__/http_login.cpython-36.pyc
Normal file
BIN
core/http_endpoints/__pycache__/http_login.cpython-36.pyc
Normal file
Binary file not shown.
30
core/http_endpoints/http_api.py
Normal file
30
core/http_endpoints/http_api.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import logging
|
||||||
|
from aiohttp import web
|
||||||
|
from aiojobs.aiohttp import get_scheduler_from_app
|
||||||
|
|
||||||
|
from core.api.decorator import request_mapping
|
||||||
|
from core.helper.jsondump import json_dumps
|
||||||
|
|
||||||
|
class HttpAPI():
|
||||||
|
|
||||||
|
def __init__(self, core):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.logger.info("WOOHOO MY ACTOR")
|
||||||
|
self.cbpi = core
|
||||||
|
|
||||||
|
@request_mapping(path="/", auth_required=False)
|
||||||
|
async def http_get_all(self, request):
|
||||||
|
return web.json_response(await self.get_all(force_db_update=True), dumps=json_dumps)
|
||||||
|
|
||||||
|
@request_mapping(path="/{id}", auth_required=False)
|
||||||
|
async def http_get_one(self, request):
|
||||||
|
id = int(request.match_info['id'])
|
||||||
|
return web.json_response(await self.get_one(id), dumps=json_dumps)
|
||||||
|
|
||||||
|
|
||||||
|
@request_mapping(path="/{id}'", method="POST", auth_required=False)
|
||||||
|
async def http_add_one(self, request):
|
||||||
|
id = request.match_info['id']
|
||||||
|
await self.get_all(force_db_update=True)
|
||||||
|
return web.json_response(await self.get_one(id), dumps=json_dumps)
|
||||||
|
|
29
core/http_endpoints/http_login.py
Normal file
29
core/http_endpoints/http_login.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp_auth import auth
|
||||||
|
|
||||||
|
class Login():
|
||||||
|
|
||||||
|
def __init__(self, core):
|
||||||
|
core.app.router.add_route('POST', '/login', self.login_view)
|
||||||
|
core.app.router.add_route('GET', '/logout', self.logout_view)
|
||||||
|
self.db = {'user': 'password', 'super_user': 'super_password'}
|
||||||
|
|
||||||
|
@auth.auth_required
|
||||||
|
async def logout_view(self, request):
|
||||||
|
await auth.forget(request)
|
||||||
|
return web.Response(body='OK'.encode('utf-8'))
|
||||||
|
|
||||||
|
async def login_view(self, request):
|
||||||
|
params = await request.post()
|
||||||
|
print("HALLO LOGIN")
|
||||||
|
print(params.get('username', None), params.get('password', None))
|
||||||
|
user = params.get('username', None)
|
||||||
|
if (user in self.db and
|
||||||
|
params.get('password', None) == self.db[user]):
|
||||||
|
|
||||||
|
# User is in our database, remember their login details
|
||||||
|
await auth.remember(request, user)
|
||||||
|
return web.Response(body='OK'.encode('utf-8'))
|
||||||
|
|
||||||
|
raise web.HTTPForbidden()
|
||||||
|
|
0
core/mqtt/__init__.py
Normal file
0
core/mqtt/__init__.py
Normal file
90
core/mqtt/mqtt.py
Normal file
90
core/mqtt/mqtt.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
from aiojobs.aiohttp import get_scheduler_from_app
|
||||||
|
from hbmqtt.broker import Broker
|
||||||
|
from hbmqtt.client import MQTTClient
|
||||||
|
from hbmqtt.mqtt.constants import QOS_1, QOS_0
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from core.mqtt_matcher import MQTTMatcher
|
||||||
|
|
||||||
|
|
||||||
|
class MQTT():
|
||||||
|
def __init__(self, core):
|
||||||
|
|
||||||
|
self.config = {
|
||||||
|
'listeners': {
|
||||||
|
'default': {
|
||||||
|
'type': 'tcp',
|
||||||
|
'bind': '0.0.0.0:1885',
|
||||||
|
},
|
||||||
|
'ws': {
|
||||||
|
'bind': '0.0.0.0:8081',
|
||||||
|
'type': 'ws'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'sys_interval': 10,
|
||||||
|
'topic-check': {
|
||||||
|
'enabled': True,
|
||||||
|
'plugins': [
|
||||||
|
'topic_taboo'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'auth': {
|
||||||
|
'allow-anonymous': True,
|
||||||
|
'password-file': '/Users/manuelfritsch/github/aio_sample/core/user.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.core = core
|
||||||
|
self.broker = Broker(self.config, plugin_namespace="hbmqtt.broker.plugins")
|
||||||
|
self.client = MQTTClient()
|
||||||
|
self.matcher = MQTTMatcher()
|
||||||
|
self.mqtt_methods = {"test": self.ok_msg, "$SYS/broker/#": self.sysmsg}
|
||||||
|
self.core.app.on_startup.append(self.start_broker)
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def sysmsg(self, msg):
|
||||||
|
|
||||||
|
print("SYS", msg)
|
||||||
|
|
||||||
|
def ok_msg(self, msg):
|
||||||
|
self.count = self.count + 1
|
||||||
|
print("MSFG", msg, self.count)
|
||||||
|
|
||||||
|
def publish(self, topic, message):
|
||||||
|
print("PUSH NOW", topic)
|
||||||
|
self.core.app.loop.create_task(self.client.publish(topic, str.encode(message), QOS_0))
|
||||||
|
|
||||||
|
def register_callback(self, func: Callable, topic) -> None:
|
||||||
|
|
||||||
|
self.mqtt_methods[topic] = func
|
||||||
|
|
||||||
|
async def on_message(self):
|
||||||
|
while True:
|
||||||
|
|
||||||
|
message = await self.client.deliver_message()
|
||||||
|
matched = False
|
||||||
|
packet = message.publish_packet
|
||||||
|
print(message.topic)
|
||||||
|
#print(message.topic.split('/'))
|
||||||
|
data = packet.payload.data.decode("utf-8")
|
||||||
|
|
||||||
|
for callback in self.matcher.iter_match(message.topic):
|
||||||
|
print("MATCH")
|
||||||
|
callback(data)
|
||||||
|
matched = True
|
||||||
|
|
||||||
|
if matched == False:
|
||||||
|
print("NO HANDLER", data)
|
||||||
|
|
||||||
|
async def start_broker(self, app):
|
||||||
|
|
||||||
|
await self.broker.start()
|
||||||
|
#
|
||||||
|
await self.client.connect('mqtt://username:manuel@localhost:1885')
|
||||||
|
# await self.client.connect('mqtt://broker.hivemq.com:1883')
|
||||||
|
|
||||||
|
for k, v in self.mqtt_methods.items():
|
||||||
|
print("############MQTT Subscribe:", k, v)
|
||||||
|
await self.client.subscribe([(k, QOS_1)])
|
||||||
|
self.matcher[k] = v
|
||||||
|
await get_scheduler_from_app(app).spawn(self.on_message())
|
167
core/mqtt/mqtt_matcher.py
Normal file
167
core/mqtt/mqtt_matcher.py
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
class MQTTMatcher(object):
|
||||||
|
|
||||||
|
|
||||||
|
class Node(object):
|
||||||
|
__slots__ = '_children', '_content'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._children = {}
|
||||||
|
self._content = None
|
||||||
|
|
||||||
|
|
||||||
|
def register(self, key, value):
|
||||||
|
node = self._root
|
||||||
|
for sym in key.split('/'):
|
||||||
|
node = node._children.setdefault(sym, self.Node())
|
||||||
|
|
||||||
|
if not isinstance(node._content, list):
|
||||||
|
node._content = []
|
||||||
|
node._content.append(value)
|
||||||
|
|
||||||
|
def get_callbacks(self, key):
|
||||||
|
try:
|
||||||
|
node = self._root
|
||||||
|
for sym in key.split('/'):
|
||||||
|
node = node._children[sym]
|
||||||
|
if node._content is None:
|
||||||
|
raise KeyError(key)
|
||||||
|
return node._content
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def unregister(self, key, method=None):
|
||||||
|
|
||||||
|
lst = []
|
||||||
|
try:
|
||||||
|
parent, node = None, self._root
|
||||||
|
for k in key.split('/'):
|
||||||
|
parent, node = node, node._children[k]
|
||||||
|
lst.append((parent, k, node))
|
||||||
|
# TODO
|
||||||
|
print(node._content)
|
||||||
|
if method is not None:
|
||||||
|
node._content = None
|
||||||
|
else:
|
||||||
|
node._content = None
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(key)
|
||||||
|
else: # cleanup
|
||||||
|
for parent, k, node in reversed(lst):
|
||||||
|
if node._children or node._content is not None:
|
||||||
|
break
|
||||||
|
del parent._children[k]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._root = self.Node()
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
print("...",key, value)
|
||||||
|
node = self._root
|
||||||
|
for sym in key.split('/'):
|
||||||
|
print(sym)
|
||||||
|
node = node._children.setdefault(sym, self.Node())
|
||||||
|
print(node)
|
||||||
|
if not isinstance(node._content, list):
|
||||||
|
#print("new array")
|
||||||
|
node._content = []
|
||||||
|
node._content.append(value)
|
||||||
|
#node._content = value
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
node = self._root
|
||||||
|
for sym in key.split('/'):
|
||||||
|
node = node._children[sym]
|
||||||
|
if node._content is None:
|
||||||
|
raise KeyError(key)
|
||||||
|
return node._content
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(key)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __delitem__(self, thekey):
|
||||||
|
print("DELETE")
|
||||||
|
|
||||||
|
if isinstance(thekey, tuple):
|
||||||
|
key = thekey[1]
|
||||||
|
methods = thekey[0]
|
||||||
|
print(methods.__module__, methods.__name__)
|
||||||
|
else:
|
||||||
|
methods = None
|
||||||
|
key = thekey
|
||||||
|
|
||||||
|
lst = []
|
||||||
|
try:
|
||||||
|
parent, node = None, self._root
|
||||||
|
for k in key.split('/'):
|
||||||
|
parent, node = node, node._children[k]
|
||||||
|
lst.append((parent, k, node))
|
||||||
|
# TODO
|
||||||
|
print(node._content)
|
||||||
|
if methods is not None:
|
||||||
|
|
||||||
|
node._content = None
|
||||||
|
else:
|
||||||
|
node._content = None
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(key)
|
||||||
|
else: # cleanup
|
||||||
|
for parent, k, node in reversed(lst):
|
||||||
|
if node._children or node._content is not None:
|
||||||
|
break
|
||||||
|
del parent._children[k]
|
||||||
|
'''
|
||||||
|
def iter_match(self, topic):
|
||||||
|
|
||||||
|
lst = topic.split('/')
|
||||||
|
normal = not topic.startswith('$')
|
||||||
|
def rec(node, i=0):
|
||||||
|
if i == len(lst):
|
||||||
|
if node._content is not None:
|
||||||
|
yield node._content
|
||||||
|
else:
|
||||||
|
part = lst[i]
|
||||||
|
if part in node._children:
|
||||||
|
for content in rec(node._children[part], i + 1):
|
||||||
|
yield content
|
||||||
|
if '+' in node._children and (normal or i > 0):
|
||||||
|
for content in rec(node._children['+'], i + 1):
|
||||||
|
yield content
|
||||||
|
if '#' in node._children and (normal or i > 0):
|
||||||
|
content = node._children['#']._content
|
||||||
|
if content is not None:
|
||||||
|
yield content
|
||||||
|
return rec(self._root)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
m = MQTTMatcher()
|
||||||
|
|
||||||
|
def test_name():
|
||||||
|
print("actor/1/on")
|
||||||
|
|
||||||
|
def test_name2():
|
||||||
|
print("actor/2/on")
|
||||||
|
|
||||||
|
def test_name3():
|
||||||
|
print("actor/#")
|
||||||
|
|
||||||
|
def test_name4():
|
||||||
|
print("actor/+/on")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
m.register("actor/1/on", test_name)
|
||||||
|
m.register("actor/1/on", test_name)
|
||||||
|
m.register("actor/1/on", test_name)
|
||||||
|
|
||||||
|
print(m.get_callbacks("actor/1/on"))
|
||||||
|
|
||||||
|
|
||||||
|
m.unregister("actor/1/on")
|
||||||
|
|
||||||
|
for methods in m.iter_match("actor/1/on"):
|
||||||
|
|
||||||
|
for f in methods:
|
||||||
|
f()
|
||||||
|
|
46
core/plugin.py
Normal file
46
core/plugin.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from core.api.property import Property
|
||||||
|
|
||||||
|
|
||||||
|
class PluginAPI():
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_props(self, cls):
|
||||||
|
|
||||||
|
name = cls.__name__
|
||||||
|
|
||||||
|
result = {"name": name, "class": cls, "properties": [], "actions": []}
|
||||||
|
|
||||||
|
|
||||||
|
tmpObj = cls()
|
||||||
|
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
|
||||||
|
for m in members:
|
||||||
|
if isinstance(tmpObj.__getattribute__(m), Property.Number):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
result["properties"].append(
|
||||||
|
{"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Text):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
result["properties"].append(
|
||||||
|
{"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Select):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
result["properties"].append(
|
||||||
|
{"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
result["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
result["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
result["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
|
||||||
|
|
||||||
|
for method_name, method in cls.__dict__.items():
|
||||||
|
if hasattr(method, "action"):
|
||||||
|
key = method.__getattribute__("key")
|
||||||
|
parameters = method.__getattribute__("parameters")
|
||||||
|
result["actions"].append({"method": method_name, "label": key, "parameters": parameters})
|
||||||
|
pprint(result, width=200)
|
104
core/sql/create_table_user.sql
Normal file
104
core/sql/create_table_user.sql
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS dashboard
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS dashboard_content
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
dbid INTEGER(80),
|
||||||
|
element_id INTEGER,
|
||||||
|
type VARCHAR(80),
|
||||||
|
x INTEGER(5),
|
||||||
|
y INTEGER(5),
|
||||||
|
config VARCHAR(3000)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS actor
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
type VARCHAR(80),
|
||||||
|
config VARCHAR(3000)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sensor
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
type VARCHAR(80),
|
||||||
|
config VARCHAR(3000)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kettle
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
sensor VARCHAR(80),
|
||||||
|
heater VARCHAR(10),
|
||||||
|
automatic VARCHAR(255),
|
||||||
|
logic VARCHAR(50),
|
||||||
|
config VARCHAR(1000),
|
||||||
|
agitator VARCHAR(10),
|
||||||
|
target_temp INTEGER,
|
||||||
|
height INTEGER,
|
||||||
|
diameter INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS config
|
||||||
|
(
|
||||||
|
name VARCHAR(50) PRIMARY KEY NOT NULL,
|
||||||
|
value VARCHAR(255),
|
||||||
|
type VARCHAR(50),
|
||||||
|
description VARCHAR(255),
|
||||||
|
options VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sensor
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
type VARCHAR(100),
|
||||||
|
name VARCHAR(80),
|
||||||
|
config VARCHAR(3000)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS step
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
"order" INTEGER,
|
||||||
|
name VARCHAR(80),
|
||||||
|
type VARCHAR(100),
|
||||||
|
stepstate VARCHAR(255),
|
||||||
|
state VARCHAR(1),
|
||||||
|
start INTEGER,
|
||||||
|
end INTEGER,
|
||||||
|
config VARCHAR(255),
|
||||||
|
kettleid INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tank
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
brewname VARCHAR(80),
|
||||||
|
sensor VARCHAR(80),
|
||||||
|
sensor2 VARCHAR(80),
|
||||||
|
sensor3 VARCHAR(80),
|
||||||
|
heater VARCHAR(10),
|
||||||
|
logic VARCHAR(50),
|
||||||
|
config VARCHAR(1000),
|
||||||
|
cooler VARCHAR(10),
|
||||||
|
target_temp INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS translation
|
||||||
|
(
|
||||||
|
language_code VARCHAR(3) NOT NULL,
|
||||||
|
key VARCHAR(80) NOT NULL,
|
||||||
|
text VARCHAR(100) NOT NULL,
|
||||||
|
PRIMARY KEY (language_code, key)
|
||||||
|
);
|
BIN
core/test.db
Normal file
BIN
core/test.db
Normal file
Binary file not shown.
92
core/websocket.py
Normal file
92
core/websocket.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import logging
|
||||||
|
import weakref
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp import web
|
||||||
|
from typing import Iterable, Callable
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocket:
|
||||||
|
|
||||||
|
def __init__(self, core) -> None:
|
||||||
|
self.core = core
|
||||||
|
self._callbacks = defaultdict(set)
|
||||||
|
self._clients = weakref.WeakSet()
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.core.app.add_routes([web.get('/ws', self.websocket_handler)])
|
||||||
|
|
||||||
|
def add_callback(self, func: Callable, event: str) -> None:
|
||||||
|
self._callbacks[event].add(func)
|
||||||
|
|
||||||
|
async def emit(self, event: str, *args, **kwargs) -> None:
|
||||||
|
for func in self._event_funcs(event):
|
||||||
|
await func(*args, **kwargs)
|
||||||
|
|
||||||
|
def _event_funcs(self, event: str) -> Iterable[Callable]:
|
||||||
|
for func in self._callbacks[event]:
|
||||||
|
yield func
|
||||||
|
|
||||||
|
async def websocket_handler(self, request):
|
||||||
|
ws = web.WebSocketResponse()
|
||||||
|
await ws.prepare(request)
|
||||||
|
|
||||||
|
self._clients.add(ws)
|
||||||
|
|
||||||
|
c = len(self._clients) - 1
|
||||||
|
|
||||||
|
self.logger.info(ws)
|
||||||
|
self.logger.info(c)
|
||||||
|
try:
|
||||||
|
async for msg in ws:
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
if msg.data == 'close':
|
||||||
|
|
||||||
|
await ws.close()
|
||||||
|
self.logger.info("WS Close")
|
||||||
|
else:
|
||||||
|
msg_obj = msg.json()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.core.bus.fire(msg_obj["topic"], id=1, power=22)
|
||||||
|
#await self.fire(msg_obj["key"], ws, msg)
|
||||||
|
|
||||||
|
#await ws.send_str(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
|
self.logger.error('ws connection closed with exception %s' % ws.exception())
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self._clients.discard(ws)
|
||||||
|
|
||||||
|
self.logger.info("Web Socket Close")
|
||||||
|
|
||||||
|
return ws
|
||||||
|
|
||||||
|
|
||||||
|
async def websocket_handler(request):
|
||||||
|
ws = web.WebSocketResponse()
|
||||||
|
await ws.prepare(request)
|
||||||
|
|
||||||
|
_ws.append(ws)
|
||||||
|
|
||||||
|
c = len(_ws) - 1
|
||||||
|
|
||||||
|
async for msg in ws:
|
||||||
|
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
if msg.data == 'close':
|
||||||
|
await ws.close()
|
||||||
|
else:
|
||||||
|
|
||||||
|
await ws.send_str(msg.data)
|
||||||
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
|
print('ws connection closed with exception %s' %
|
||||||
|
ws.exception())
|
||||||
|
|
||||||
|
del _ws[c]
|
||||||
|
print('websocket connection closed')
|
||||||
|
|
||||||
|
return ws
|
BIN
craftbeerpi.db
Normal file
BIN
craftbeerpi.db
Normal file
Binary file not shown.
6
create_password.py
Normal file
6
create_password.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import sys
|
||||||
|
from getpass import getpass
|
||||||
|
from passlib.hash import sha512_crypt
|
||||||
|
|
||||||
|
passwd = input() if not sys.stdin.isatty() else getpass()
|
||||||
|
print(sha512_crypt.encrypt(passwd))
|
1
logs/first_logfile2.log
Normal file
1
logs/first_logfile2.log
Normal file
|
@ -0,0 +1 @@
|
||||||
|
2018-11-01 19:49:28,690,1,WOOHO
|
4
logs/first_logfile2.log.2018-11-01_17-21
Normal file
4
logs/first_logfile2.log.2018-11-01_17-21
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
2018-11-01 17:20:45,660,1,WOOHO
|
||||||
|
2018-11-01 17:20:46,666,1,WOOHO
|
||||||
|
2018-11-01 17:21:25,509,1,WOOHO
|
||||||
|
2018-11-01 17:21:26,512,1,WOOHO
|
2
logs/first_logfile2.log.2018-11-01_17-23
Normal file
2
logs/first_logfile2.log.2018-11-01_17-23
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
2018-11-01 17:23:12,685,1,WOOHO
|
||||||
|
2018-11-01 17:23:13,691,1,WOOHO
|
1
logs/first_logfile2.log.2018-11-01_17-24
Normal file
1
logs/first_logfile2.log.2018-11-01_17-24
Normal file
|
@ -0,0 +1 @@
|
||||||
|
2018-11-01 17:24:28,706,1,WOOHO
|
2
logs/first_logfile2.log.2018-11-01_17-27
Normal file
2
logs/first_logfile2.log.2018-11-01_17-27
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
2018-11-01 17:27:21,293,1,WOOHO
|
||||||
|
2018-11-01 17:27:22,302,1,WOOHO
|
61
logs/first_logfile2.log.2018-11-01_17-32
Normal file
61
logs/first_logfile2.log.2018-11-01_17-32
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
2018-11-01 17:31:36,985,1,WOOHO
|
||||||
|
2018-11-01 17:31:37,991,1,WOOHO
|
||||||
|
2018-11-01 17:31:38,993,1,WOOHO
|
||||||
|
2018-11-01 17:31:39,994,1,WOOHO
|
||||||
|
2018-11-01 17:31:40,996,1,WOOHO
|
||||||
|
2018-11-01 17:31:42,000,1,WOOHO
|
||||||
|
2018-11-01 17:31:43,001,1,WOOHO
|
||||||
|
2018-11-01 17:31:44,005,1,WOOHO
|
||||||
|
2018-11-01 17:31:45,007,1,WOOHO
|
||||||
|
2018-11-01 17:31:46,013,1,WOOHO
|
||||||
|
2018-11-01 17:31:47,014,1,WOOHO
|
||||||
|
2018-11-01 17:31:48,020,1,WOOHO
|
||||||
|
2018-11-01 17:31:49,024,1,WOOHO
|
||||||
|
2018-11-01 17:31:50,028,1,WOOHO
|
||||||
|
2018-11-01 17:31:51,032,1,WOOHO
|
||||||
|
2018-11-01 17:31:52,040,1,WOOHO
|
||||||
|
2018-11-01 17:31:53,045,1,WOOHO
|
||||||
|
2018-11-01 17:31:54,050,1,WOOHO
|
||||||
|
2018-11-01 17:31:55,054,1,WOOHO
|
||||||
|
2018-11-01 17:31:56,057,1,WOOHO
|
||||||
|
2018-11-01 17:31:57,060,1,WOOHO
|
||||||
|
2018-11-01 17:31:58,061,1,WOOHO
|
||||||
|
2018-11-01 17:31:59,066,1,WOOHO
|
||||||
|
2018-11-01 17:32:00,070,1,WOOHO
|
||||||
|
2018-11-01 17:32:01,075,1,WOOHO
|
||||||
|
2018-11-01 17:32:02,079,1,WOOHO
|
||||||
|
2018-11-01 17:32:03,083,1,WOOHO
|
||||||
|
2018-11-01 17:32:04,087,1,WOOHO
|
||||||
|
2018-11-01 17:32:05,089,1,WOOHO
|
||||||
|
2018-11-01 17:32:06,094,1,WOOHO
|
||||||
|
2018-11-01 17:32:07,098,1,WOOHO
|
||||||
|
2018-11-01 17:32:08,101,1,WOOHO
|
||||||
|
2018-11-01 17:32:09,104,1,WOOHO
|
||||||
|
2018-11-01 17:32:10,106,1,WOOHO
|
||||||
|
2018-11-01 17:32:11,108,1,WOOHO
|
||||||
|
2018-11-01 17:32:12,112,1,WOOHO
|
||||||
|
2018-11-01 17:32:13,115,1,WOOHO
|
||||||
|
2018-11-01 17:32:14,116,1,WOOHO
|
||||||
|
2018-11-01 17:32:15,119,1,WOOHO
|
||||||
|
2018-11-01 17:32:16,121,1,WOOHO
|
||||||
|
2018-11-01 17:32:17,122,1,WOOHO
|
||||||
|
2018-11-01 17:32:18,127,1,WOOHO
|
||||||
|
2018-11-01 17:32:19,130,1,WOOHO
|
||||||
|
2018-11-01 17:32:20,135,1,WOOHO
|
||||||
|
2018-11-01 17:32:21,136,1,WOOHO
|
||||||
|
2018-11-01 17:32:22,142,1,WOOHO
|
||||||
|
2018-11-01 17:32:23,145,1,WOOHO
|
||||||
|
2018-11-01 17:32:24,148,1,WOOHO
|
||||||
|
2018-11-01 17:32:25,153,1,WOOHO
|
||||||
|
2018-11-01 17:32:26,159,1,WOOHO
|
||||||
|
2018-11-01 17:32:27,162,1,WOOHO
|
||||||
|
2018-11-01 17:32:28,166,1,WOOHO
|
||||||
|
2018-11-01 17:32:29,169,1,WOOHO
|
||||||
|
2018-11-01 17:32:32,214,1,WOOHO
|
||||||
|
2018-11-01 17:32:33,215,1,WOOHO
|
||||||
|
2018-11-01 17:32:34,220,1,WOOHO
|
||||||
|
2018-11-01 17:32:35,224,1,WOOHO
|
||||||
|
2018-11-01 17:32:36,229,1,WOOHO
|
||||||
|
2018-11-01 17:32:37,235,1,WOOHO
|
||||||
|
2018-11-01 17:32:38,238,1,WOOHO
|
||||||
|
2018-11-01 17:32:39,242,1,WOOHO
|
192
main.py
Normal file
192
main.py
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
import aiohttp
|
||||||
|
import aiosqlite
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp_swagger import *
|
||||||
|
from aiojobs.aiohttp import setup, spawn, get_scheduler_from_app
|
||||||
|
from hbmqtt.broker import Broker
|
||||||
|
from hbmqtt.client import MQTTClient
|
||||||
|
from hbmqtt.mqtt.constants import QOS_1
|
||||||
|
|
||||||
|
from core.matcher import MQTTMatcher
|
||||||
|
from core.websocket import websocket_handler
|
||||||
|
|
||||||
|
TEST_DB = "test.db"
|
||||||
|
c = MQTTClient()
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
matcher = MQTTMatcher()
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'listeners': {
|
||||||
|
'default': {
|
||||||
|
'type': 'tcp',
|
||||||
|
'bind': '0.0.0.0:1885',
|
||||||
|
},
|
||||||
|
'my-ws-1': {
|
||||||
|
'bind': '0.0.0.0:8888',
|
||||||
|
'type': 'ws'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'sys_interval': 10,
|
||||||
|
'auth': {
|
||||||
|
'allow-anonymous': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broker = Broker(config, plugin_namespace="hbmqtt.test.plugins")
|
||||||
|
|
||||||
|
|
||||||
|
async def test2(name):
|
||||||
|
while True:
|
||||||
|
print(name)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def handle(request):
|
||||||
|
name = request.match_info.get('name', "Anonymous")
|
||||||
|
text = "Hello, " + name
|
||||||
|
|
||||||
|
return web.Response(text=text)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connection():
|
||||||
|
async with aiosqlite.connect(TEST_DB) as db:
|
||||||
|
print("DB OK")
|
||||||
|
assert isinstance(db, aiosqlite.Connection)
|
||||||
|
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
|
||||||
|
|
||||||
|
async def listen_to_redis(app):
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
#for w in _ws:
|
||||||
|
# pass
|
||||||
|
# await w.send_str("HALLO")
|
||||||
|
|
||||||
|
# print(w)
|
||||||
|
|
||||||
|
|
||||||
|
async def myjob(app):
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
print("JOB")
|
||||||
|
|
||||||
|
|
||||||
|
def ok_msg(msg):
|
||||||
|
print("OK", msg)
|
||||||
|
|
||||||
|
|
||||||
|
def ok_msg1(msg):
|
||||||
|
print("OK1", msg)
|
||||||
|
|
||||||
|
|
||||||
|
def ok_msg2(msg):
|
||||||
|
print("OK2", msg)
|
||||||
|
|
||||||
|
|
||||||
|
mqtt_methods = {"test": ok_msg, "test/+/ab": ok_msg1, "test/+": ok_msg2}
|
||||||
|
|
||||||
|
|
||||||
|
async def on_message():
|
||||||
|
while True:
|
||||||
|
message = await c.deliver_message()
|
||||||
|
matched = False
|
||||||
|
packet = message.publish_packet
|
||||||
|
print(message.topic)
|
||||||
|
print(message.topic.split('/'))
|
||||||
|
data = packet.payload.data.decode("utf-8")
|
||||||
|
|
||||||
|
for callback in matcher.iter_match(message.topic):
|
||||||
|
print("MATCH")
|
||||||
|
callback(data)
|
||||||
|
matched = True
|
||||||
|
|
||||||
|
if matched == False:
|
||||||
|
print("NO HANDLER", data)
|
||||||
|
|
||||||
|
#for w in _ws:
|
||||||
|
# await w.send_str(data)
|
||||||
|
|
||||||
|
|
||||||
|
async def start_background_tasks(app):
|
||||||
|
app['redis_listener'] = app.loop.create_task(listen_to_redis(app))
|
||||||
|
|
||||||
|
|
||||||
|
async def start_broker(app):
|
||||||
|
print(app)
|
||||||
|
await broker.start()
|
||||||
|
|
||||||
|
await c.connect('mqtt://localhost:1885')
|
||||||
|
|
||||||
|
for k, v in mqtt_methods.items():
|
||||||
|
print(k, v)
|
||||||
|
await c.subscribe([(k, QOS_1)])
|
||||||
|
|
||||||
|
matcher[k] = v
|
||||||
|
# await c.subscribe([('/test', QOS_1),('/hallo', QOS_1)])
|
||||||
|
|
||||||
|
|
||||||
|
await get_scheduler_from_app(app).spawn(on_message())
|
||||||
|
|
||||||
|
|
||||||
|
job = None
|
||||||
|
|
||||||
|
|
||||||
|
async def start_task(request):
|
||||||
|
global job
|
||||||
|
job = await spawn(request, myjob(app))
|
||||||
|
await test_connection()
|
||||||
|
return web.Response(text="OK")
|
||||||
|
|
||||||
|
|
||||||
|
async def stop_task(request):
|
||||||
|
await job.close()
|
||||||
|
return web.Response(text="OK")
|
||||||
|
|
||||||
|
|
||||||
|
async def stats(request):
|
||||||
|
s = get_scheduler_from_app(app)
|
||||||
|
|
||||||
|
return web.Response(text="%s" % s.active_count)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setup(app)
|
||||||
|
|
||||||
|
|
||||||
|
def start_bg(app, name, method):
|
||||||
|
print("HALLO111")
|
||||||
|
|
||||||
|
async def start(app):
|
||||||
|
app[name] = app.loop.create_task(method(name))
|
||||||
|
|
||||||
|
app.on_startup.append(start)
|
||||||
|
|
||||||
|
|
||||||
|
# start_bg(app, "test", test2)
|
||||||
|
# start_bg(app, "test2", test2)
|
||||||
|
|
||||||
|
#app.on_startup.append(start_background_tasks)
|
||||||
|
app.on_startup.append(start_broker)
|
||||||
|
|
||||||
|
app.add_routes([web.get('/', handle),
|
||||||
|
web.get('/stop', stop_task),
|
||||||
|
web.get('/start', start_task),
|
||||||
|
web.get('/stats', stats),
|
||||||
|
web.get('/ws', websocket_handler),
|
||||||
|
web.get('/{name}', handle)
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
setup_swagger(app)
|
||||||
|
|
||||||
|
web.run_app(app)
|
37
requirements.txt
Normal file
37
requirements.txt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
aiohttp==3.4.4
|
||||||
|
aiohttp-auth==0.1.1
|
||||||
|
aiohttp-route-decorator==0.1.4
|
||||||
|
aiohttp-security==0.4.0
|
||||||
|
aiohttp-session==2.7.0
|
||||||
|
aiohttp-swagger==1.0.5
|
||||||
|
aiojobs==0.2.2
|
||||||
|
aiosqlite==0.7.0
|
||||||
|
asn1crypto==0.24.0
|
||||||
|
async-timeout==3.0.1
|
||||||
|
atomicwrites==1.2.1
|
||||||
|
attrs==18.2.0
|
||||||
|
cffi==1.11.5
|
||||||
|
chardet==3.0.4
|
||||||
|
cryptography==2.3.1
|
||||||
|
docopt==0.6.2
|
||||||
|
hbmqtt==0.9.4
|
||||||
|
idna==2.7
|
||||||
|
idna-ssl==1.1.0
|
||||||
|
Jinja2==2.10
|
||||||
|
MarkupSafe==1.0
|
||||||
|
more-itertools==4.3.0
|
||||||
|
multidict==4.4.2
|
||||||
|
passlib==1.7.1
|
||||||
|
pluggy==0.7.1
|
||||||
|
py==1.7.0
|
||||||
|
pycparser==2.19
|
||||||
|
pync==2.0.3
|
||||||
|
pytest==3.8.2
|
||||||
|
pytest-aiohttp==0.3.0
|
||||||
|
python-dateutil==2.7.5
|
||||||
|
PyYAML==3.13
|
||||||
|
six==1.11.0
|
||||||
|
ticket-auth==0.1.4
|
||||||
|
transitions==0.6.8
|
||||||
|
websockets==6.0
|
||||||
|
yarl==1.2.6
|
12
run.py
Normal file
12
run.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp_auth import auth
|
||||||
|
from core.cbpi import CraftBeerPi
|
||||||
|
|
||||||
|
cbpi = CraftBeerPi()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cbpi.start()
|
0
test.py
Normal file
0
test.py
Normal file
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
70
tests/test_app.py
Normal file
70
tests/test_app.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||||
|
from aiohttp import web
|
||||||
|
import json
|
||||||
|
|
||||||
|
from hbmqtt.broker import Broker
|
||||||
|
|
||||||
|
from core.cbpi import CraftBeerPi
|
||||||
|
from core.database.model import ActorModel
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppTestCase(AioHTTPTestCase):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def get_application(self):
|
||||||
|
self.cbpi = CraftBeerPi()
|
||||||
|
return self.cbpi.app
|
||||||
|
|
||||||
|
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_example(self):
|
||||||
|
|
||||||
|
resp = await self.client.request("GET", "/actor/1/on")
|
||||||
|
print(resp.status)
|
||||||
|
assert resp.status == 204
|
||||||
|
|
||||||
|
resp = await self.client.request("GET", "/actor/")
|
||||||
|
print(resp.status)
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
text = await resp.json()
|
||||||
|
pprint(text)
|
||||||
|
'''
|
||||||
|
resp = await self.client.request("GET", "/actor/2")
|
||||||
|
print(resp.status)
|
||||||
|
assert resp.status == 200
|
||||||
|
text = await resp.json()
|
||||||
|
pprint(text)
|
||||||
|
'''
|
||||||
|
#ws = await self.client.ws_connect("/ws");
|
||||||
|
#await ws.send_str(json.dumps({"key": "test"}))
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_example2(self):
|
||||||
|
print("TEST2222")
|
||||||
|
|
||||||
|
print("CLIENT ###### ", self.client)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ws = await self.client.ws_connect("/ws");
|
||||||
|
await ws.send_str(json.dumps({"topic": "test"}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#resp = await ws.receive()
|
||||||
|
|
||||||
|
#print("##### REPSONE", resp)
|
||||||
|
assert "Manuel" in await self.cbpi.actor.get_name(), "OH NOW"
|
||||||
|
|
||||||
|
await self.client.close()
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
Loading…
Reference in a new issue