├── .env.sample ├── .gitignore ├── .gitmodules ├── .markdownlint.json ├── .pylintrc ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE.txt ├── Makefile ├── README.md ├── README_en.md ├── docs ├── .gitignore ├── README.md ├── SUMMARY.md ├── adapter │ ├── README.md │ ├── auth.md │ ├── file.md │ ├── graphql.md │ ├── mqtt.md │ ├── suggestion.md │ └── visual.md ├── api │ ├── README.md │ ├── auth.md │ ├── data.md │ ├── datatype.md │ ├── device.md │ └── extend.md ├── config.md ├── example.md ├── faq.md ├── howtouse │ ├── README.md │ └── ex1.md └── images │ └── howtouse │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── 6.png ├── icons ├── LICENSE │ ├── LICENSE.CC_BY_4_0.txt │ └── LICENSE.txt ├── code.png ├── icon.svg ├── mipmap-hdpi │ └── ic_launcher.png ├── mipmap-mdpi │ └── ic_launcher.png ├── mipmap-xhdpi │ └── ic_launcher.png ├── mipmap-xxhdpi │ └── ic_launcher.png ├── mipmap-xxxhdpi │ └── ic_launcher.png └── playstore │ └── icon.png ├── libfreeiot ├── .DS_Store ├── __init__.py ├── adapters │ ├── .DS_Store │ ├── __init__.py │ ├── base.py │ ├── file │ │ ├── __init__.py │ │ └── main.py │ └── mqtt │ │ ├── .DS_Store │ │ ├── Constant │ │ ├── Status.py │ │ └── __init__.py │ │ ├── Parse │ │ ├── __init__.py │ │ ├── main.py │ │ └── sys.py │ │ ├── __init__.py │ │ └── main.py ├── app.py ├── config.py ├── core │ ├── __init__.py │ ├── resources │ │ ├── __init__.py │ │ ├── data.py │ │ └── device.py │ └── routes.py └── version.py ├── manage.sample.py ├── requirements.txt ├── setup.cfg ├── setup.py └── supervisor.conf /.env.sample: -------------------------------------------------------------------------------- 1 | # 全局配置 2 | APP_HOST = 127.0.0.1 3 | APP_PORT = 3000 4 | APP_DEBUG = true 5 | FLASK_CONFIG = development 6 | MONGO_HOST = localhost 7 | MONGO_PORT = 27017 8 | MONGO_DBNAME = freeiot 9 | 10 | # MQTT Adapter 配置 11 | MQTT_HOST = localhost 12 | MQTT_PORT = 1883 13 | MQTT_CLIENTID = "mqtt-adapter" 14 | MQTT_PARSE_DRIVER = json 15 | TOPICS_NEED = ["battery", "position"] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | .static_storage/ 57 | .media/ 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | # MacOS File 108 | .DS_Store 109 | 110 | # Configuration file 111 | .env 112 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "api"] 2 | path = api 3 | url = git@github.com:noahziheng/freeiot-api.git 4 | [submodule "web"] 5 | path = web 6 | url = git@github.com:noahziheng/freeiot-web.git 7 | [submodule "android"] 8 | path = android 9 | url = git@github.com:noahziheng/freeiot-android.git 10 | [submodule "firmware/esp8266"] 11 | path = firmware/esp8266 12 | url = git@github.com:noahziheng/freeiot-firmware-esp8266.git 13 | [submodule "firmware/nodejs"] 14 | path = firmware/nodejs 15 | url = git@github.com:noahziheng/freeiot-firmware-nodejs.git 16 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD013": false, 4 | "MD033": false, 5 | "MD041": false, 6 | "MD002": false 7 | } -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. 21 | jobs=1 22 | 23 | # List of plugins (as comma separated values of python modules names) to load, 24 | # usually to register additional checkers. 25 | load-plugins= 26 | 27 | # Pickle collected data for later comparisons. 28 | persistent=yes 29 | 30 | # Specify a configuration file. 31 | #rcfile= 32 | 33 | # When enabled, pylint would attempt to guess common misconfiguration and emit 34 | # user-friendly hints instead of false-positive error messages 35 | suggestion-mode=yes 36 | 37 | # Allow loading of arbitrary C extensions. Extensions are imported into the 38 | # active Python interpreter and may run arbitrary code. 39 | unsafe-load-any-extension=no 40 | 41 | 42 | [MESSAGES CONTROL] 43 | 44 | # Only show warnings with the listed confidence levels. Leave empty to show 45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 46 | confidence= 47 | 48 | # Disable the message, report, category or checker with the given id(s). You 49 | # can either give multiple identifiers separated by comma (,) or put this 50 | # option multiple times (only on the command line, not in the configuration 51 | # file where it should appear only once).You can also use "--disable=all" to 52 | # disable everything first and then reenable specific checks. For example, if 53 | # you want to run only the similarities checker, you can use "--disable=all 54 | # --enable=similarities". If you want to run only the classes checker, but have 55 | # no Warning level messages displayed, use"--disable=all --enable=classes 56 | # --disable=W" 57 | disable=print-statement, 58 | parameter-unpacking, 59 | unpacking-in-except, 60 | old-raise-syntax, 61 | backtick, 62 | long-suffix, 63 | old-ne-operator, 64 | old-octal-literal, 65 | import-star-module-level, 66 | non-ascii-bytes-literal, 67 | raw-checker-failed, 68 | bad-inline-option, 69 | locally-disabled, 70 | locally-enabled, 71 | file-ignored, 72 | suppressed-message, 73 | useless-suppression, 74 | deprecated-pragma, 75 | apply-builtin, 76 | basestring-builtin, 77 | buffer-builtin, 78 | cmp-builtin, 79 | coerce-builtin, 80 | execfile-builtin, 81 | file-builtin, 82 | long-builtin, 83 | raw_input-builtin, 84 | reduce-builtin, 85 | standarderror-builtin, 86 | unicode-builtin, 87 | xrange-builtin, 88 | coerce-method, 89 | delslice-method, 90 | getslice-method, 91 | setslice-method, 92 | no-absolute-import, 93 | old-division, 94 | dict-iter-method, 95 | dict-view-method, 96 | next-method-called, 97 | metaclass-assignment, 98 | indexing-exception, 99 | raising-string, 100 | reload-builtin, 101 | oct-method, 102 | hex-method, 103 | nonzero-method, 104 | cmp-method, 105 | input-builtin, 106 | round-builtin, 107 | intern-builtin, 108 | unichr-builtin, 109 | map-builtin-not-iterating, 110 | zip-builtin-not-iterating, 111 | range-builtin-not-iterating, 112 | filter-builtin-not-iterating, 113 | using-cmp-argument, 114 | eq-without-hash, 115 | div-method, 116 | idiv-method, 117 | rdiv-method, 118 | exception-message-attribute, 119 | invalid-str-codec, 120 | sys-max-int, 121 | bad-python3-import, 122 | deprecated-string-function, 123 | deprecated-str-translate-call, 124 | deprecated-itertools-function, 125 | deprecated-types-field, 126 | next-method-defined, 127 | dict-items-not-iterating, 128 | dict-keys-not-iterating, 129 | dict-values-not-iterating, 130 | W0612, 131 | W0613, 132 | C0301, 133 | C0330, 134 | C0326, 135 | R0903, 136 | C0103, 137 | W0603, 138 | R0201 139 | 140 | # Enable the message, report, category or checker with the given id(s). You can 141 | # either give multiple identifier separated by comma (,) or put this option 142 | # multiple time (only on the command line, not in the configuration file where 143 | # it should appear only once). See also the "--disable" option for examples. 144 | enable=c-extension-no-member 145 | 146 | 147 | [REPORTS] 148 | 149 | # Python expression which should return a note less than 10 (10 is the highest 150 | # note). You have access to the variables errors warning, statement which 151 | # respectively contain the number of errors / warnings messages and the total 152 | # number of statements analyzed. This is used by the global evaluation report 153 | # (RP0004). 154 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 155 | 156 | # Template used to display messages. This is a python new-style format string 157 | # used to format the message information. See doc for all details 158 | #msg-template= 159 | 160 | # Set the output format. Available formats are text, parseable, colorized, json 161 | # and msvs (visual studio).You can also give a reporter class, eg 162 | # mypackage.mymodule.MyReporterClass. 163 | output-format=text 164 | 165 | # Tells whether to display a full report or only the messages 166 | reports=no 167 | 168 | # Activate the evaluation score. 169 | score=yes 170 | 171 | 172 | [REFACTORING] 173 | 174 | # Maximum number of nested blocks for function / method body 175 | max-nested-blocks=5 176 | 177 | # Complete name of functions that never returns. When checking for 178 | # inconsistent-return-statements if a never returning function is called then 179 | # it will be considered as an explicit return statement and no message will be 180 | # printed. 181 | never-returning-functions=optparse.Values,sys.exit 182 | 183 | 184 | [LOGGING] 185 | 186 | # Logging modules to check that the string format arguments are in logging 187 | # function parameter format 188 | logging-modules=logging 189 | 190 | 191 | [SPELLING] 192 | 193 | # Limits count of emitted suggestions for spelling mistakes 194 | max-spelling-suggestions=4 195 | 196 | # Spelling dictionary name. Available dictionaries: none. To make it working 197 | # install python-enchant package. 198 | spelling-dict= 199 | 200 | # List of comma separated words that should not be checked. 201 | spelling-ignore-words= 202 | 203 | # A path to a file that contains private dictionary; one word per line. 204 | spelling-private-dict-file= 205 | 206 | # Tells whether to store unknown words to indicated private dictionary in 207 | # --spelling-private-dict-file option instead of raising a message. 208 | spelling-store-unknown-words=no 209 | 210 | 211 | [MISCELLANEOUS] 212 | 213 | # List of note tags to take in consideration, separated by a comma. 214 | notes=FIXME, 215 | XXX, 216 | TODO 217 | 218 | 219 | [TYPECHECK] 220 | 221 | # List of decorators that produce context managers, such as 222 | # contextlib.contextmanager. Add to this list to register other decorators that 223 | # produce valid context managers. 224 | contextmanager-decorators=contextlib.contextmanager 225 | 226 | # List of members which are set dynamically and missed by pylint inference 227 | # system, and so shouldn't trigger E1101 when accessed. Python regular 228 | # expressions are accepted. 229 | generated-members= 230 | 231 | # Tells whether missing members accessed in mixin class should be ignored. A 232 | # mixin class is detected if its name ends with "mixin" (case insensitive). 233 | ignore-mixin-members=yes 234 | 235 | # This flag controls whether pylint should warn about no-member and similar 236 | # checks whenever an opaque object is returned when inferring. The inference 237 | # can return multiple potential results while evaluating a Python object, but 238 | # some branches might not be evaluated, which results in partial inference. In 239 | # that case, it might be useful to still emit no-member and other checks for 240 | # the rest of the inferred objects. 241 | ignore-on-opaque-inference=yes 242 | 243 | # List of class names for which member attributes should not be checked (useful 244 | # for classes with dynamically set attributes). This supports the use of 245 | # qualified names. 246 | ignored-classes=optparse.Values,thread._local,_thread._local 247 | 248 | # List of module names for which member attributes should not be checked 249 | # (useful for modules/projects where namespaces are manipulated during runtime 250 | # and thus existing member attributes cannot be deduced by static analysis. It 251 | # supports qualified module names, as well as Unix pattern matching. 252 | ignored-modules= 253 | 254 | # Show a hint with possible names when a member name was not found. The aspect 255 | # of finding the hint is based on edit distance. 256 | missing-member-hint=yes 257 | 258 | # The minimum edit distance a name should have in order to be considered a 259 | # similar match for a missing member name. 260 | missing-member-hint-distance=1 261 | 262 | # The total number of similar names that should be taken in consideration when 263 | # showing a hint for a missing member. 264 | missing-member-max-choices=1 265 | 266 | 267 | [VARIABLES] 268 | 269 | # List of additional names supposed to be defined in builtins. Remember that 270 | # you should avoid to define new builtins when possible. 271 | additional-builtins= 272 | 273 | # Tells whether unused global variables should be treated as a violation. 274 | allow-global-unused-variables=yes 275 | 276 | # List of strings which can identify a callback function by name. A callback 277 | # name must start or end with one of those strings. 278 | callbacks=cb_, 279 | _cb 280 | 281 | # A regular expression matching the name of dummy variables (i.e. expectedly 282 | # not used). 283 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 284 | 285 | # Argument names that match this expression will be ignored. Default to name 286 | # with leading underscore 287 | ignored-argument-names=_.*|^ignored_|^unused_ 288 | 289 | # Tells whether we should check for unused import in __init__ files. 290 | init-import=no 291 | 292 | # List of qualified module names which can have objects that can redefine 293 | # builtins. 294 | redefining-builtins-modules=six.moves,past.builtins,future.builtins 295 | 296 | 297 | [FORMAT] 298 | 299 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 300 | expected-line-ending-format= 301 | 302 | # Regexp for a line that is allowed to be longer than the limit. 303 | ignore-long-lines=^\s*(# )??$ 304 | 305 | # Number of spaces of indent required inside a hanging or continued line. 306 | indent-after-paren=4 307 | 308 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 309 | # tab). 310 | indent-string=' ' 311 | 312 | # Maximum number of characters on a single line. 313 | max-line-length=100 314 | 315 | # Maximum number of lines in a module 316 | max-module-lines=1000 317 | 318 | # List of optional constructs for which whitespace checking is disabled. `dict- 319 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 320 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 321 | # `empty-line` allows space-only lines. 322 | no-space-check=trailing-comma, 323 | dict-separator 324 | 325 | # Allow the body of a class to be on the same line as the declaration if body 326 | # contains single statement. 327 | single-line-class-stmt=no 328 | 329 | # Allow the body of an if to be on the same line as the test if there is no 330 | # else. 331 | single-line-if-stmt=no 332 | 333 | 334 | [SIMILARITIES] 335 | 336 | # Ignore comments when computing similarities. 337 | ignore-comments=yes 338 | 339 | # Ignore docstrings when computing similarities. 340 | ignore-docstrings=yes 341 | 342 | # Ignore imports when computing similarities. 343 | ignore-imports=no 344 | 345 | # Minimum lines number of a similarity. 346 | min-similarity-lines=4 347 | 348 | 349 | [BASIC] 350 | 351 | # Naming style matching correct argument names 352 | argument-naming-style=snake_case 353 | 354 | # Regular expression matching correct argument names. Overrides argument- 355 | # naming-style 356 | #argument-rgx= 357 | 358 | # Naming style matching correct attribute names 359 | attr-naming-style=snake_case 360 | 361 | # Regular expression matching correct attribute names. Overrides attr-naming- 362 | # style 363 | #attr-rgx= 364 | 365 | # Bad variable names which should always be refused, separated by a comma 366 | bad-names=foo, 367 | bar, 368 | baz, 369 | toto, 370 | tutu, 371 | tata 372 | 373 | # Naming style matching correct class attribute names 374 | class-attribute-naming-style=any 375 | 376 | # Regular expression matching correct class attribute names. Overrides class- 377 | # attribute-naming-style 378 | #class-attribute-rgx= 379 | 380 | # Naming style matching correct class names 381 | class-naming-style=PascalCase 382 | 383 | # Regular expression matching correct class names. Overrides class-naming-style 384 | #class-rgx= 385 | 386 | # Naming style matching correct constant names 387 | const-naming-style=UPPER_CASE 388 | 389 | # Regular expression matching correct constant names. Overrides const-naming- 390 | # style 391 | #const-rgx= 392 | 393 | # Minimum line length for functions/classes that require docstrings, shorter 394 | # ones are exempt. 395 | docstring-min-length=-1 396 | 397 | # Naming style matching correct function names 398 | function-naming-style=snake_case 399 | 400 | # Regular expression matching correct function names. Overrides function- 401 | # naming-style 402 | #function-rgx= 403 | 404 | # Good variable names which should always be accepted, separated by a comma 405 | good-names=i, 406 | j, 407 | k, 408 | ex, 409 | Run, 410 | _ 411 | 412 | # Include a hint for the correct naming format with invalid-name 413 | include-naming-hint=no 414 | 415 | # Naming style matching correct inline iteration names 416 | inlinevar-naming-style=any 417 | 418 | # Regular expression matching correct inline iteration names. Overrides 419 | # inlinevar-naming-style 420 | #inlinevar-rgx= 421 | 422 | # Naming style matching correct method names 423 | method-naming-style=snake_case 424 | 425 | # Regular expression matching correct method names. Overrides method-naming- 426 | # style 427 | #method-rgx= 428 | 429 | # Naming style matching correct module names 430 | module-naming-style=snake_case 431 | 432 | # Regular expression matching correct module names. Overrides module-naming- 433 | # style 434 | #module-rgx= 435 | 436 | # Colon-delimited sets of names that determine each other's naming style when 437 | # the name regexes allow several styles. 438 | name-group= 439 | 440 | # Regular expression which should only match function or class names that do 441 | # not require a docstring. 442 | no-docstring-rgx=^_ 443 | 444 | # List of decorators that produce properties, such as abc.abstractproperty. Add 445 | # to this list to register other decorators that produce valid properties. 446 | property-classes=abc.abstractproperty 447 | 448 | # Naming style matching correct variable names 449 | variable-naming-style=snake_case 450 | 451 | # Regular expression matching correct variable names. Overrides variable- 452 | # naming-style 453 | #variable-rgx= 454 | 455 | 456 | [IMPORTS] 457 | 458 | # Allow wildcard imports from modules that define __all__. 459 | allow-wildcard-with-all=no 460 | 461 | # Analyse import fallback blocks. This can be used to support both Python 2 and 462 | # 3 compatible code, which means that the block might have code that exists 463 | # only in one or another interpreter, leading to false positives when analysed. 464 | analyse-fallback-blocks=no 465 | 466 | # Deprecated modules which should not be used, separated by a comma 467 | deprecated-modules=optparse,tkinter.tix 468 | 469 | # Create a graph of external dependencies in the given file (report RP0402 must 470 | # not be disabled) 471 | ext-import-graph= 472 | 473 | # Create a graph of every (i.e. internal and external) dependencies in the 474 | # given file (report RP0402 must not be disabled) 475 | import-graph= 476 | 477 | # Create a graph of internal dependencies in the given file (report RP0402 must 478 | # not be disabled) 479 | int-import-graph= 480 | 481 | # Force import order to recognize a module as part of the standard 482 | # compatibility libraries. 483 | known-standard-library= 484 | 485 | # Force import order to recognize a module as part of a third party library. 486 | known-third-party=enchant 487 | 488 | 489 | [CLASSES] 490 | 491 | # List of method names used to declare (i.e. assign) instance attributes. 492 | defining-attr-methods=__init__, 493 | __new__, 494 | setUp 495 | 496 | # List of member names, which should be excluded from the protected access 497 | # warning. 498 | exclude-protected=_asdict, 499 | _fields, 500 | _replace, 501 | _source, 502 | _make 503 | 504 | # List of valid names for the first argument in a class method. 505 | valid-classmethod-first-arg=cls 506 | 507 | # List of valid names for the first argument in a metaclass class method. 508 | valid-metaclass-classmethod-first-arg=mcs 509 | 510 | 511 | [DESIGN] 512 | 513 | # Maximum number of arguments for function / method 514 | max-args=5 515 | 516 | # Maximum number of attributes for a class (see R0902). 517 | max-attributes=7 518 | 519 | # Maximum number of boolean expressions in a if statement 520 | max-bool-expr=5 521 | 522 | # Maximum number of branch for function / method body 523 | max-branches=12 524 | 525 | # Maximum number of locals for function / method body 526 | max-locals=15 527 | 528 | # Maximum number of parents for a class (see R0901). 529 | max-parents=7 530 | 531 | # Maximum number of public methods for a class (see R0904). 532 | max-public-methods=20 533 | 534 | # Maximum number of return / yield for function / method body 535 | max-returns=6 536 | 537 | # Maximum number of statements in function / method body 538 | max-statements=50 539 | 540 | # Minimum number of public methods for a class (see R0903). 541 | min-public-methods=2 542 | 543 | 544 | [EXCEPTIONS] 545 | 546 | # Exceptions that will emit a warning when being caught. Defaults to 547 | # "Exception" 548 | overgeneral-exceptions=Exception 549 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | 6 | # 缓存依赖 7 | cache: 8 | directories: 9 | - $HOME/.npm 10 | 11 | before_install: 12 | - export TZ='Asia/Shanghai' # 更改时区 13 | 14 | # 依赖安装 15 | install: 16 | - npm install gitbook-cli -g 17 | - gitbook install 18 | 19 | # 构建脚本 20 | script: 21 | # 自定义输出目录 gitbook build src dest 22 | - gitbook build ./docs ./build 23 | - touch ./build/.nojekyll 24 | 25 | # 分支白名单 26 | branches: 27 | only: 28 | - master # 只对 master 分支进行构建 29 | 30 | # GitHub Pages 部署 31 | deploy: 32 | provider: pages 33 | skip_cleanup: true 34 | # 在项目仪表盘的 Settings -> Environment Variables 中配置 35 | github_token: $GITHUB_TOKEN 36 | # 将 build 目录下的内容推送到默认的 gh-pages 分支上 37 | local_dir: build 38 | name: $GIT_NAME 39 | email: $GIT_EMAIL 40 | on: 41 | branch: master -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/usr/local/bin/python3", 3 | "python.linting.pylintEnabled": true, 4 | "python.linting.enabled": true, 5 | "python.linting.pylintArgs": ["--rcfile=${workspaceFolder}/.pylintrc"] 6 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Noah Gao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SETUP = python3 setup.py 2 | 3 | default: i 4 | 5 | i: 6 | @(python3 -c 'from libfreeiot import version; print("FreeIOT %s" % version.__version__)') 7 | 8 | test: 9 | $(SETUP) test 10 | 11 | clean: 12 | rm -fr build/ dist/ *.egg-info/ 13 | find . | grep __pycache__ | xargs rm -fr 14 | find . | grep .pyc | xargs rm -f 15 | 16 | all: build sdist bdist bdist_egg bdist_wheel 17 | 18 | build: 19 | $(SETUP) build 20 | 21 | sdist: 22 | $(SETUP) sdist 23 | 24 | bdist: 25 | $(SETUP) bdist 26 | 27 | bdist_egg: 28 | $(SETUP) bdist_egg 29 | 30 | bdist_wheel: 31 | $(SETUP) bdist_wheel 32 | 33 | install: 34 | $(SETUP) install --user --prefix= 35 | 36 | release: 37 | $(SETUP) sdist bdist_wheel upload -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Banner](./icons/code.png) 2 | 3 | # FreeIOT 4 | 5 | > 一个轻巧灵活的开源物联网中间件框架 6 | 7 | ![Documentation Build](https://api.travis-ci.org/noahziheng/freeiot.svg) 8 | [![pypi](https://img.shields.io/pypi/v/libfreeiot.svg)](https://pypi.org/project/libfreeiot/) 9 | [![license](https://img.shields.io/github/license/noahziheng/freeiot.svg?style=flat-square)](./LICENSE.txt) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 11 | 12 | [English Introduction](./README_en.md) 13 | 14 | ## 特点 15 | 16 | - :loop: 结构轻巧可靠,开发定制轻松 17 | - :bulb: Adapter 设计,可扩展性强 18 | - :unlock: 开源实现,安全可控(欢迎加入开发) 19 | - :pencil: 文档齐全,简明易懂(WIP) 20 | 21 | ## 安装 22 | 23 | > FreeIOT 使用 Python3 开发,您应在安装 FreeIOT 之前先行安装 Python3.5 以上版本。 24 | 25 | ### 通过包管理器安装 26 | 27 | ```shell 28 | pip3 install libfreeiot # *nix 类系统使用 29 | pip install libfreeiot # Windows 系统使用 30 | ``` 31 | 32 | ### 构建安装 33 | 34 | ```shell 35 | git clone https://github.com/noahziheng/freeiot.git 36 | cd freeiot 37 | python3 setup.py install --user # *nix 类系统使用 38 | python setup.py install --user # Windows 系统使用 39 | ``` 40 | 41 | ## 文档 42 | 43 | [点击进入文档中心](https://noahziheng.github.io/freeiot) 44 | 45 | ## 与 FreeIOT v1 关系 46 | 47 | FreeIOT v1 是全端支持的物联网开放平台,本人于 2016 年进行开发运营,基于 Node 生态构建服务端,并有设备侧支持方案及服务侧客户端。 48 | 49 | 特点是以模块为描述设备的基本原子,现已停止支持和营运。 50 | 51 | 库中 `backup/v1` 分支存放有 FreeIOT v1 的全部源代码及 Markdown 文档。 52 | 53 | 现 FreeIOT 项目参照了 v1 分支的部分思想,但已改采用 Python 实现,实为 Adapter 形式的中间件框架,与 v1 分支并无实质关联。 54 | 55 | ## 相关项目 56 | 57 | - [Flask](https://github.com/pallets/flask) 58 | - [PyMongo](https://github.com/mongodb/mongo-python-driver) 59 | - [Eclipse Paho](https://www.eclipse.org/paho/) 60 | 61 | ## 开源授权 62 | 63 | Copyright (C) 2018 Noah Gao 64 | 65 | Licensed under the MIT License. 66 | For more information see LICENSE.txt. 67 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | ![Banner](./icons/code.png) 2 | 3 | # FreeIOT 4 | 5 | > A lightweight, flexible and open-source Middleware Internet-Of-Thing Framework. 6 | 7 | ![Documentation Build](https://api.travis-ci.org/noahziheng/freeiot.svg) 8 | [![pypi](https://img.shields.io/pypi/v/libfreeiot.svg)](https://pypi.org/project/libfreeiot/) 9 | [![license](https://img.shields.io/github/license/noahziheng/freeiot.svg?style=flat-square)](./LICENSE.txt) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 11 | 12 | [English Introduction](./README_en.md) 13 | 14 | ## Features 15 | 16 | - :loop: A lightweight and reliable structure, suitable for the development of custom. 17 | - :bulb: Based on Adapter design, strong scalability here. 18 | - :unlock: Open-source implementation,safe and controllable. Welcome join us. 19 | - :pencil: Document is complete and easy to understand.(WIP) 20 | 21 | ## Installation 22 | 23 | > FreeIOT require Python3.5+ 24 | 25 | ### Via Package Manager 26 | 27 | ```shell 28 | 29 | pip install libfreeiot 30 | ``` 31 | 32 | ### Build Setup 33 | 34 | ```shell 35 | git clone https://github.com/noahziheng/freeiot.git 36 | cd freeiot 37 | python3 setup.py install --user # On *nix-like system 38 | python setup.py install --user # On Windows system 39 | ``` 40 | 41 | ## Documentation 42 | 43 | [Documentation Here](https://noahziheng.github.io/freeiot) 44 | 45 | ## Related Project 46 | 47 | - [Flask](https://github.com/pallets/flask) 48 | - [PyMongo](https://github.com/mongodb/mongo-python-driver) 49 | - [Eclipse Paho](https://www.eclipse.org/paho/) 50 | 51 | ## License 52 | 53 | Copyright (C) 2018 Noah Gao 54 | 55 | Licensed under the MIT License. 56 | For more information see LICENSE.txt. 57 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _book/ 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | FreeIOT 5 |

6 |

7 | 轻巧灵活的开源物联网中间件框架
8 | 更好的开发体验,更低的开发门槛 9 |

10 | 11 | # 简介 12 | 13 | FreeIOT是一个开源的物联网中间件框架,致力于提高物联网开发者的开发体验,降低物联网设备的开发门槛,将在保证性能的前提下提供最优的体验。 14 | 15 | 项目完全基于 MIT 开源,您可在任一项目内使用来自 FreeIOT 的代码或开放资源。 16 | 17 | > 本项目正在寻找合作开发者、文档编写志愿者、社区运营志愿者,如您有能力和时间帮助我,请邮件noahgaocn@gmail.com 与我联系 :) 18 | 19 | ## 主要特性 20 | 21 | 易用:基于 Adapter 驱动框架,开发轻快易懂; 22 | 23 | 灵活:除核心库外,各外部组件均可自主选用; 24 | 25 | 接口友好:采用行业通用的 MQTT 协议和 RESTFul API 作为接口,文档及代码全部开放,兼容性优秀; 26 | 27 | 开放:开源项目,不受任何商业协议掣肘,亦不用担心安全性问题。 28 | 29 | ## 版权申明 30 | 31 | [![license](https://img.shields.io/github/license/noahziheng/freeiot.svg?style=flat-square)](https://github.com/noahziheng/freeiot/blob/master/LICENSE.txt) 32 | [![知识共享许可协议](https://i.creativecommons.org/l/by-nc/4.0/88x31.png)](http://creativecommons.org/licenses/by-nc/4.0/) 33 | 34 | FreeIOT 项目使用 MIT 开源许可证,有关协议信息参照 [协议正文](https://github.com/noahziheng/freeiot/blob/master/LICENSE.txt) 35 | 36 | 本文档内容采用 [知识共享署名-非商业性使用 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc/4.0/) 进行许可。 37 | 38 | 对 FreeIOT 项目及本文档有任何疑问或者建议,请访问项目 [Github 仓库](https://github.com/noahziheng/freeiot) 发布 Issue 询问。 39 | 40 | 有关 FreeIOT 项目及本文档的最新进展,请及时访问项目 [Github 仓库](https://github.com/noahziheng/freeiot) 获取。 41 | 42 | > 本文档的版权归 FreeIOT 文档小组所有,本文档及其描述的内容受有关法律的版权保护,对本文档内容的任何形式的非法复制,泄露或散布,将导致相应的法律责任。 43 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [首页](README.md) 4 | - [快速指南](howtouse/README.md) 5 | - [开发者快速指南](howtouse/README.md) 6 | - [安装](howtouse/README.md#installation) 7 | - [基本使用](howtouse/README.md#basic_usage) 8 | - [数据](howtouse/README.md#data) 9 | - [设备](howtouse/README.md#device) 10 | - [RESTFul API](howtouse/README.md#api) 11 | - [Adapter](howtouse/README.md#adapter) 12 | - [写在最后](howtouse/README.md#end) 13 | - [实例1:MQTT 方案保存温度数据](howtouse/ex1.md) 14 | - [配置文件详解](config.md) 15 | - [RESTFul API](api/README.md) 16 | - [基本规则](api/README.md) 17 | - [权限管理](api/auth.md) 18 | - [设备相关](api/device.md) 19 | - [数据相关](api/data.md) 20 | - [API 扩展](api/extend.md) 21 | - [附录:数据类型](api/datatype.md) 22 | - [Adapter 开发](adapter/README.md) 23 | - [基本规则](adapter/README.md) 24 | - [开发建议](adapter/suggestion.md) 25 | - [MQTTAdapter 说明](adapter/mqtt.md) 26 | - [FileAdapter 说明](adapter/file.md) 27 | - [GraphQLAdapter 说明](adapter/graphql.md) 28 | - [VisualAdapter 说明](adapter/visual.md) 29 | - [AuthAdapter 说明](adapter/auth.md) 30 | - [常见问题解答(FAQ)](faq.md) 31 | - [实例](example.md) 32 | -------------------------------------------------------------------------------- /docs/adapter/README.md: -------------------------------------------------------------------------------- 1 | # Adapter 开发文档 2 | 3 | Adapter 是 FreeIOT 的核心,设计上就是通过 Adapter 为基于 FreeIOT 开发的系统提供各项外围功能。我们为它提供了一些内部接口,这些接口还会进一步扩展功能,欢迎大家一起出谋划策。 4 | 5 | ## 顺序调用 6 | 7 | 首先我们来看一下 Adapter 的传入过程,借用快速指南中的一个启动脚本: 8 | 9 | ```python 10 | from libfreeiot import app 11 | from libfreeiot.adapters.mqtt import MQTTAdapter 12 | 13 | app.run(adapters = [ MQTTAdapter() ]) 14 | ``` 15 | 16 | 显然,我们在核心实例的 `run` 方法中将一个存有 Adapter 实例的数组传给了 `adapters` 参数,通过这种方式启用了 MQTTAdapter。 17 | 18 | 事实上,`app.run()` 方法将按顺序调用传入的 Adapter 实例的 `init` 和 `run` 方法,所以我们可以利用 Adapter 机制将功能拼组起来。大多数场景下,我们可以这样使用: 19 | 20 | ```python 21 | from libfreeiot import app 22 | from libfreeiot.adapters.mqtt import MQTTAdapter 23 | from libfreeiot.adapters.file import FileAdapter 24 | from libfreeiot.adapters.graphql import GraphQLAdapter 25 | from libfreeiot.adapters.visual import VisualAdapter 26 | from libfreeiot.adapters.auth import AuthAdapter 27 | from custom.adapters.example import CustomAdapter 28 | 29 | app.run(adapters = [ 30 | MQTTAdapter(), # MQTT 数据监听 31 | FileAdatper(), # 文件存储 32 | GraphQLAdapter(), # GraphQL 查询接口 33 | VisualAdapter(), # 数据可视化界面 34 | AuthAdapter(), # 用户认证 35 | CustomAdapter() # 自行实现,实现新接口用于下发 MQTT 消息,必须在 MQTTAdapter 后 36 | ]) 37 | ``` 38 | 39 | ## 顺序作用域 40 | 41 | 上节中最后的例子中提到,自行实现的用于下发 MQTT 消息的 CustomAdapter,必须在 MQTTAdapter 后,这是因为 Adapter 一项全局机制 -- 顺序作用域。 42 | 43 | 它通过 `init(app, scope)` 方法被注入到 Adapter 中,其中的 app 为执行到此时的 Flask App 实例,scope 为执行到此时的作用域变量(本质是字典)。 44 | 45 | scope 中可能有如下值(官方提供的): 46 | 47 | | 字段名 | 描述 | 48 | | ---------- | -------------------------------------------------------- | 49 | | mongo | MongoDB 操作指针,具体使用可参考 Flask-PyMongo 文档 | 50 | | mqttClient | 由 MQTTAdapter 注入,当前连接到 MQTT Broker 的客户端实例 | 51 | 52 | 下面我们来手动实现一个检测 MongoDB 指针注入情况的 Adapter 例子,体会一下这一机制(变量)的用法: 53 | 54 | ```python 55 | class ExampleAdapter(): 56 | """ Example Adapter Class """ 57 | 58 | def init(self, app, scope): 59 | """ Initial Function for Adapter """ 60 | self.app = app 61 | self.scope = scope 62 | self.mongo = scope["mongo"] 63 | 64 | def run(self): 65 | """ Main Entry for Adapter """ 66 | print("Hello from ExampleAdapter") 67 | print(self.scope["mongo"] == self.mongo) # True 68 | print(self.mongo.db.devices.find()) # All devices 69 | ``` 70 | 71 | ## BaseAdapter 72 | 73 | 我们建议所有的 Adapter 都继承于 BaseAdapter,包括官方的。 74 | 75 | BaseAdapter 只有两个方法,`init(app, scope)` 和 `run()`。 76 | 77 | `init(app, scope)` 方法用于供核心将 Flask App 实例和 Adapter 顺序作用域注入各 Adapter,覆盖该方法时需要调用 BaseAdapter 父类中的方法 78 | 79 | `run()` 方法作为整个 Adapter 的入口,核心将按 Adapter 的传入顺序进行调用。 80 | 81 | 例如,我们修改上面的例子如下: 82 | 83 | ```python 84 | from libfreeiot.adapters.base import BaseAdapter 85 | 86 | class ExampleAdapter(BaseAdapter): 87 | """ Example Adapter Class """ 88 | 89 | def init(self, app, scope): 90 | """ Initial Function for Adapter,not required by default """ 91 | super(ExampleAdapter, self).init(app, scope) 92 | 93 | def run(self): 94 | """ Main Entry for Adapter """ 95 | print("Hello from ExampleAdapter") 96 | mongo = self.scope["mongo"] 97 | print(self.scope["mongo"] == mongo) # True 98 | print(mongo.db.devices.find()) # All devices 99 | ``` 100 | 101 | ## 增加新的 API 访问接口(Flask Endpoint) 102 | 103 | 前面已经提到,Flask App 实例也按顺序注入了 Adapter,那么我们在 Adapter 中即可增加新的接口。 104 | 105 | 例如,我们增加 `GET /api/example` 接口,返回 `{"msg": "Hello World"}`,则可以写出这样一个 Adapter(同目录 `example.py`): 106 | 107 | ```python 108 | from flask import jsonify 109 | from libfreeiot.adapters.base import BaseAdapter 110 | 111 | class ExampleAdapter(BaseAdapter): 112 | """ Example Adapter Class """ 113 | 114 | def run(self): 115 | """ Main Entry for Adapter """ 116 | print("Hello from ExampleAdapter") 117 | app = self.app 118 | 119 | @app.route('/api/example') 120 | def example(): 121 | """ Example GET Route """ 122 | return jsonify({"msg": "Hello World"}) 123 | ``` 124 | 125 | 将其挂入运行序列(同目录 `manage.py`): 126 | 127 | ```python 128 | from libfreeiot import app 129 | from example import ExampleAdapter 130 | 131 | app.run(adapters = [ ExampleAdapter() ]) 132 | ``` 133 | 134 | 运行后,可观察 命令行中的 `Hello from ExampleAdapter`,随后运行下面的指令可验证其的工作: 135 | 136 | ```shell 137 | curl http://127.0.0.1:3000/api/example\ 138 | -H "Accept: application/json"\ 139 | -H "Content-type: application/json"\ 140 | ``` 141 | 142 | 可得到 143 | 144 | ```json 145 | { 146 | "msg": "Hello World" 147 | } 148 | ``` 149 | 150 | 至此,已经可以在 Adapter 中使用 Flask 的方式开发接口了~ 151 | 152 | > 注意:目前在 Adapter 中新增的端口默认均不配有 JWT 认证,需使用 Flask-JWT-Simple 提供的 jwt_required 函数,实现方式请参看该项目文档 153 | -------------------------------------------------------------------------------- /docs/adapter/auth.md: -------------------------------------------------------------------------------- 1 | # AuthAdapter 说明文档 2 | 3 | AuthAdapter 是官方支持用于使用数据库等手段进行鉴权的 Adapter。 4 | 5 | 目前仍在开发中,敬请期待~ 6 | 7 | 欢迎加入开发,Github 见~ -------------------------------------------------------------------------------- /docs/adapter/file.md: -------------------------------------------------------------------------------- 1 | # FileAdapter 说明文档 2 | 3 | FileAdapter 是官方支持用于提供二进制文件上传能力的 Adapter。 4 | 5 | ```python 6 | from libfreeiot.adapters.file import FileAdapter 7 | 8 | app.run(adapters=[ 9 | ... 10 | FileAdapter() 11 | ]) 12 | ``` 13 | 14 | ## 配置 15 | 16 | 当前版本无需配置即可使用 17 | 18 | ## (增加 API)上传文件:POST /api/upload 19 | 20 | > 该接口不受核心库 JWT 保护,无需使用认证头 21 | > 该接口不是标准 JSON 接口,需设置头 Content-Type: multipart/form-data 22 | 23 | 该接口接受以下字段: 24 | 25 | | 字段名 | 类型 | 描述 | 26 | | ------ | ---- | ---------- | 27 | | file | File | 待上传文件 | 28 | 29 | 该接口返回以下字段: 30 | 31 | **注意** 该接口返回 JSON 编码的字符串,内容为上传后的存放文件名,可用其他手段(如 MQTT)上传到核心系统中。 32 | 33 | ## (增加 API) 获取文件:GET /api/upload/ 34 | 35 | 该接口将直接返回请求的文件,filename query 代表存放文件名。如文件不存在,返回 HTTP 404。 36 | -------------------------------------------------------------------------------- /docs/adapter/graphql.md: -------------------------------------------------------------------------------- 1 | # GraphQLAdapter 说明文档 2 | 3 | GraphQLAdapter 是官方支持用于提供 GraphQL 接口进行数据查询的 Adapter。 4 | 5 | 目前仍在开发中,敬请期待~ 6 | 7 | 欢迎加入开发,Github 见~ -------------------------------------------------------------------------------- /docs/adapter/mqtt.md: -------------------------------------------------------------------------------- 1 | # MQTTAdapter 说明文档 2 | 3 | MQTTAdapter 是官方支持用于提供 MQTT 服务监听能力的 Adapter。 4 | 5 | ```python 6 | from libfreeiot.adapters.mqtt import MQTTAdapter 7 | 8 | app.run(adapters=[ 9 | ... 10 | MQTTAdapter() 11 | ]) 12 | ``` 13 | 14 | ## 配置 15 | 16 | 在 `.env` 中新增部分配置如下: 17 | 18 | | 配置项 | 默认值 | 描述 | 19 | | ----------------- | -------------- | --------------------------------------------------------- | 20 | | MQTT_HOST | localhost | MQTT Broker 地址 | 21 | | MQTT_PORT | 1883 | MQTT Broker 地址 | 22 | | MQTT_CLIENTID | "mqtt-adapter" | 连接到 MQTT Broker 时使用的 clientID | 23 | | TOPICS_NEED | [] | MQTT 服务需关注的 topic,使用 JSON 数组表示 | 24 | | MQTT_PARSE_DRIVER | json | `WIP` MQTT 服务消息解析驱动,可选 json, msgpack, protobuf | 25 | 26 | ## 依赖 27 | 28 | MQTTAdapter 需要一个支持 MQTT3 协议的 MQTT Broker,建议使用 [Eclipse Mosquitto](https://mosquitto.org/)。 29 | 30 | ## 设备上离线 31 | 32 | ### 设备上线识别 33 | 34 | MQTTAdapter 将默认订阅 `SYS/online` Topic,向其中 Publish 包括以下字段的消息即可触发设备上线: 35 | 36 | | 字段名 | 类型 | 描述 | 37 | | ------- | ------ | ------------ | 38 | | id | String | 设备识别 ID | 39 | | version | String | 设备版本信息 | 40 | 41 | 设备上线动作触发后,MQTTAdapter 将进行如下操作: 42 | 43 | - 将设备状态置为 `STATUS_WAIT` 44 | - 订阅配置中 `TOPICS_NEED` 声明的对应数据主题 45 | 46 | ### 设备离线识别 47 | 48 | MQTTAdapter 将默认订阅 `SYS/will` Topic,向其中 Publish 包括以下字段的消息即可触发设备离线: 49 | 50 | | 字段名 | 类型 | 描述 | 51 | | ------- | ------ | ------------ | 52 | | id | String | 设备识别 ID | 53 | | version | String | 设备版本信息 | 54 | 55 | 设备离线动作触发后,MQTTAdapter 将进行如下操作: 56 | 57 | - 将设备状态置为 `STATUS_WAIT` 58 | - 订阅配置中 `TOPIC_NEEDS` 声明的对应数据主题 59 | 60 | 建议使用 MQTT 中的遗嘱(will)机制处理离线消息的发送,并使用 Qos `2`,以保证设备异常失去响应时系统也能触发离线动作。 61 | 62 | ## 数据发送 63 | 64 | MQTTAdapter 对数据相关 topic(MQTT) 中的信息做出如下规定: 65 | 66 | - 每条消息均需有“主题”(topic(FreeIOT))用于描述消息类别,如电池电量的 topic(FreeIOT) 定为 `battery` 67 | - 每条消息必须有来源/目标设备,以设备识别 ID 描述,如上述电量消息来自设备 `5a9a8e31baf04c21194069d3` 68 | - 每条消息必须有一标志字段(flag)标明传输类型(现支持 `u` 代表设备到系统,`d` 代表系统到设备),如上述电量消息由设备上传,flag 为 `u` 69 | - 每条消息不论封装格式,发到 Topic 中就是内容(`content`)如电池电量当前为 20%,经 JSON 编码后就是 `20` 70 | - 则上述的电量消息应 publish 到 `5a9a8e31baf04c21194069d3/battery/u` 这一 topic(MQTT),消息内容为 `20` 71 | 72 | MQTTAdapter 将默认在设备上线后订阅所有 `TOPICS_NEED` 中声明的 `//u` 和 `//d` Topic,向其中 Publish 任意消息即可触发数据存储: 73 | 74 | 数据存储动作触发后,MQTTAdapter 将进行如下操作: 75 | 76 | - 新建数据记录,分段记录 `topic`、`content`、`flag` 77 | - 更新设备 `lastdata` 字段记载的本 topic 最新数据 78 | 79 | ## `WIP` 使用 MQTT 更新设备状态 80 | 81 | > 本功能在当前版本中暂未支持 82 | 83 | MQTTAdapter 将默认订阅 `SYS/status` Topic,向其中 Publish 包括以下字段的消息即可触发设备状态更新: 84 | 85 | | 字段名 | 类型 | 描述 | 86 | | ------- | ------ | ------------ | 87 | | id | String | 设备识别 ID | 88 | | status | Int | 设备状态 | 89 | 90 | 设备上线动作触发后,MQTTAdapter 将进行如下操作: 91 | 92 | - 新建数据记录,记录 `topic` 为 status,`content` 为 status 段内容,`flag` 为 `sys` 93 | - 检查配置中 `TOPICS_NEED` 声明的对应数据主题的订阅状态,及时补订 94 | - 将设备状态置为 status 段内容 95 | -------------------------------------------------------------------------------- /docs/adapter/suggestion.md: -------------------------------------------------------------------------------- 1 | # Adapter 开发建议 2 | 3 | ## 尽量使用 BaseAdapter 4 | 5 | 由于 FreeIOT 仍处在积极开发,仍将加入、调整内部接口,所以我们建议使用注入部分已封装的 BaseAdapter 作为新 Adapter 的基类,这样也能获取到最新版本 API 对 BaseAdapter 进行完善后的便利。 6 | 7 | ## 将接口控制在 `/api` 和 `/ui` 内 8 | 9 | 为了避免全局污染,建议在开发新 Adapter 时,新增的访问点均带有 `/api`(接口)和 `/ui`(界面)前缀 10 | 11 | 文档仍在编写,此文优先级较低,如有开发问题请提出 Issue。 12 | -------------------------------------------------------------------------------- /docs/adapter/visual.md: -------------------------------------------------------------------------------- 1 | # VisualAdapter 说明文档 2 | 3 | VisualAdapter 是官方支持用于提供系统状态监控和数据可视化界面的 Adapter。 4 | 5 | 目前仍在开发中,敬请期待~ 6 | 7 | 欢迎加入开发,Github 见~ -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # RESTFul API 2 | 3 | FreeIOT 使用 RESTFul API 作为“核心接口”,总体比较简单,只包括核心功能和常用接口,有几条通用的原则列在下面: 4 | 5 | - 各接口均包含 JWT 验证头,请在了解接口前阅读 [权限管理](./auth.md) 6 | - 所有接口提交返回的数据格式均为 [JSON](https://www.json.org) 7 | - 本文档中的全功能代指具备以下几种能力: 8 | - 全局查询(GET .../XXX/) 9 | - 独立查询(GET .../XXX/``) 10 | - 新增记录(POST .../XXX/) 11 | - 修改记录(POST .../XXX/``) 12 | - 删除记录(DELETE .../XXX/``) 13 | - 注:与原版 REST 不同的是,FreeIOT 基于兼容考虑并未实现 PUT 方法修改 14 | - 各接口均具有参数解析,ObjectId、DBRef 等 Mongo 类型无需提交完整对象,只需在对应字段提交 id 字符串即可 15 | - 数据返回均返回原始数据结构,如访问 ObjectId 类型的字符串形式,需使用 _id.$oid 16 | -------------------------------------------------------------------------------- /docs/api/auth.md: -------------------------------------------------------------------------------- 1 | # 权限管理 2 | 3 | FreeIOT 默认采用 [JWT](https://jwt.io) 作为权限管理手段。 4 | 5 | ## 鉴权请求头 `Authorization:Bearer ` 6 | 7 | 每个 FreeIOT 核心接口都需要在有认证情况下访问,均需带有这一请求头,其中的 JWT Token 可使用下文的 `/api/auth` 接口认证得到。 8 | 9 | ## 主认证:POST `/api/auth` 10 | 11 | 该接口接受以下字段: 12 | 13 | | 字段名 | 类型 | 描述 | 14 | | -------- | ------ | ------ | 15 | | username | String | 用户名 | 16 | | password | String | 密码 | 17 | 18 | 该接口返回以下字段: 19 | 20 | | 字段名 | 类型 | 描述 | 21 | | ------ | ------ | ---------------- | 22 | | jwt | String | JWT Token 字符串 | 23 | 24 | 例: 25 | 26 | ```shell 27 | curl http://127.0.0.1:3000/api/auth\ 28 | -H "Accept: application/json"\ 29 | -H "Content-type: application/json"\ 30 | -X POST\ 31 | -d '{"username":"admin", "password": "admin"}' 32 | ``` 33 | 34 | 可以得到 35 | 36 | ```json 37 | { 38 | "jwt": "" 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/data.md: -------------------------------------------------------------------------------- 1 | # 数据接口 2 | 3 | 有关数据描述格式,参见 [FreeIOT 快速指南](../howtouse/README.md#data) 4 | 5 | - 接口地址: `/api/data` 6 | - 接口类型: 全功能(限制全局查询及记录修改) 7 | 8 | ## 独立查询:GET `/api/data/` 9 | 10 | 该接口返回以下字段: 11 | 12 | | 字段名 | 类型 | 描述 | 13 | | ------- | -------- | ----------------------------------------------------- | 14 | | _id | ObjectId | 数据标识 ID | 15 | | topic | String | 数据来源主题 | 16 | | flag | String | 数据传输类型标志,默认支持 `u` 代表上传,`d` 代表下放 | 17 | | device | DBRef | 数据来源设备指针引用 | 18 | | content | Any | 数据内容 | 19 | 20 | 例: 21 | 22 | ```shell 23 | curl http://127.0.0.1:3000/api/data/5a9b71a4baf04c3898fc9a2c\ 24 | -H "Accept: application/json"\ 25 | -H "Content-type: application/json"\ 26 | -H "Authorization:Bearer " 27 | ``` 28 | 29 | 可以得到 30 | 31 | ```json 32 | { 33 | "_id": { 34 | "$oid": "5a9b71a4baf04c3898fc9a2c" 35 | }, 36 | "device": { 37 | "$ref": "devices", 38 | "$id": "5a9a8e31baf04c21194069d3" 39 | }, 40 | "topic": "tempature", 41 | "flag": "u", 42 | "content": 20 43 | } 44 | ``` 45 | 46 | ## 新增记录:POST `/api/data` 47 | 48 | 该接口接受以下字段: 49 | 50 | | 字段名 | 类型 | 描述 | 51 | | ------- | ------ | ----------------------------------------------------- | 52 | | topic | String | 数据来源主题 | 53 | | flag | String | 数据传输类型标志,默认支持 `u` 代表上传,`d` 代表下放 | 54 | | device | String | 数据来源设备标识 ID | 55 | | content | Any | 数据内容(JSON 编码字符串) | 56 | 57 | 该接口返回以下字段: 58 | 59 | | 字段名 | 类型 | 描述 | 60 | | ------ | -------- | ----------- | 61 | | _id | ObjectId | 数据标识 ID | 62 | 63 | 例: 64 | 65 | ```shell 66 | curl http://127.0.0.1:3000/api/data\ 67 | -H "Accept: application/json"\ 68 | -H "Content-type: application/json"\ 69 | -H "Authorization:Bearer "\ 70 | -X POST\ 71 | -d '{"device": "5a9a8e31baf04c21194069d3", "topic": "tempature", "flag": "u", "content": "20"}' 72 | ``` 73 | 74 | 可以得到 75 | 76 | ```json 77 | { 78 | "$oid": "5a9b7d14baf04c3c51ad851c" 79 | } 80 | ``` 81 | 82 | ## 删除记录: DELETE `/api/data/` 83 | 84 | 该接口返回以下字段: 85 | 86 | | 字段名 | 类型 | 描述 | 87 | | ------ | ----- | ------------------------ | 88 | | n | Int | 受影响数据个数,通常为 1 | 89 | | ok | Float | 执行结果 1.0 代表成功 | 90 | 91 | 例: 92 | 93 | ```shell 94 | curl http://127.0.0.1:3000/api/data/5a9b7d14baf04c3c51ad851c\ 95 | -H "Accept: application/json"\ 96 | -H "Content-type: application/json"\ 97 | -H "Authorization:Bearer "\ 98 | -X DELETE 99 | ``` 100 | 101 | 可以得到 102 | 103 | ```json 104 | { 105 | "n": 1, 106 | "ok": 1.0 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /docs/api/datatype.md: -------------------------------------------------------------------------------- 1 | # 附录: 数据类型 2 | 3 | 设备的 Status 字段可能为: 4 | 5 | | 实际内容 | 代指 | 描述 | 6 | | -------- | ------------------ | -------------------------- | 7 | | -1 | STATUS_BROKEN | 设备损坏 | 8 | | 0 | STATUS_UNKNOWN | 状态未知 | 9 | | 1 | STATUS_WAIT | 等待指示(连接后默认状态) | 10 | | 2 | STATUS_RUNNING | 正在运行 | 11 | | 3 | STATUS_CHARGING | 正在充电 | 12 | | 4 | STATUS_AUTOPROTECT | 设备自我保护 | 13 | 14 | ObjectId 类型(MongoDB)用于描述设备或数据的识别 ID,其格式为: 15 | 16 | | 字段名 | 类型 | 描述 | 17 | | ------ | ------ | ------------- | 18 | | $oid | String | ID 字符串形式 | 19 | 20 | DBRef 类型(MongoDB)用于描述关联的其他设备或数据,其格式为: 21 | 22 | | 字段名 | 类型 | 描述 | 23 | | ------ | ------ | ----------------------------------------------- | 24 | | $ref | String | 可能为 `devices` 或 `datas`,分别代指设备或数据 | 25 | | $id | String | 关联到的设备/数据 ID | 26 | 27 | DataRef 类型(FreeIOT)用于记录某个数据主题的状态,详细参考 [数据](../howtouse/README.md#data) 一节的介绍,其格式为: 28 | 29 | | 字段名 | 类型 | 描述 | 30 | | -------- | ------ | -------------------- | 31 | | content | Any | 数据的实际内容 | 32 | | flag | String | 数据的上传、下载标识 | 33 | | original | DBRef | 对原始数据存档的引用 | -------------------------------------------------------------------------------- /docs/api/device.md: -------------------------------------------------------------------------------- 1 | # 设备接口 2 | 3 | 有关设备描述格式,参见 [FreeIOT 快速指南](../howtouse/README.md#device) 4 | 5 | - 接口地址: `/api/device` 6 | - 接口类型: 全功能 7 | 8 | ## 独立查询:GET `/api/device/` 9 | 10 | 该接口返回以下字段: 11 | 12 | | 字段名 | 类型 | 描述 | 13 | | -------- | --------------- | ---------------- | 14 | | _id | ObjectId | 设备标识 ID | 15 | | remark | String | 设备备注名 | 16 | | status | Int | 设备状态 | 17 | | version | String | 版本信息 | 18 | | lastdata | Object(DataRef) | 最新数据指针引用 | 19 | 20 | 例: 21 | 22 | ```shell 23 | curl http://127.0.0.1:3000/api/device/5a9a8e31baf04c21194069d3\ 24 | -H "Accept: application/json"\ 25 | -H "Content-type: application/json"\ 26 | -H "Authorization:Bearer " 27 | ``` 28 | 29 | 可以得到 30 | 31 | ```json 32 | { 33 | "_id": { 34 | "$oid": "5a9a8e31baf04c21194069d3" 35 | }, 36 | "remark": "\u6d4b\u8bd5\u6837\u673a", 37 | "status": 1, 38 | "version": "test version", 39 | "lastdata": { 40 | "tempature": { 41 | "flag": "u", 42 | "content": 20, 43 | "original": { 44 | "$ref": "datas", 45 | "$id": { 46 | "$oid": "5a9b71a4baf04c3898fc9a2c" 47 | } 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ## 全局查询:GET `/api/device` 55 | 56 | 使用方式同独立查询,返回包含全部系统中设备的数组,略 57 | 58 | ## 新增记录:POST `/api/device` 59 | 60 | 该接口接受以下字段: 61 | 62 | | 字段名 | 类型 | 描述 | 63 | | ------- | ------ | ----------------------------------------------------- | 64 | | remark | String | 设备备注名 | 65 | | status | Int | 设备状态 | 66 | | version | String | 版本信息 | 67 | 68 | 该接口返回以下字段: 69 | 70 | | 字段名 | 类型 | 描述 | 71 | | ------ | -------- | ----------- | 72 | | _id | ObjectId | 数据标识 ID | 73 | 74 | 例: 75 | 76 | ```shell 77 | curl http://127.0.0.1:3000/api/device\ 78 | -H "Accept: application/json"\ 79 | -H "Content-type: application/json"\ 80 | -H "Authorization:Bearer "\ 81 | -X POST\ 82 | -d '{"remark": "测试样机", "status": 0, "version": "Unknown"}' 83 | ``` 84 | 85 | 可以得到 86 | 87 | ```json 88 | { 89 | "$oid": "5a9a8e31baf04c21194069d3" 90 | } 91 | ``` 92 | 93 | ## 修改记录:POST `/api/device/` 94 | 95 | 使用方式同新增记录,只提交的 URL 不同,略 96 | 97 | ## 删除记录: DELETE `/api/device/` 98 | 99 | 该接口返回以下字段: 100 | 101 | | 字段名 | 类型 | 描述 | 102 | | ------ | ----- | ------------------------ | 103 | | n | Int | 受影响数据个数,通常为 1 | 104 | | ok | Float | 执行结果 1.0 代表成功 | 105 | 106 | 例: 107 | 108 | ```shell 109 | curl http://127.0.0.1:3000/api/device/5a9a8e31baf04c21194069d3\ 110 | -H "Accept: application/json"\ 111 | -H "Content-type: application/json"\ 112 | -H "Authorization:Bearer "\ 113 | -X DELETE 114 | ``` 115 | 116 | 可以得到 117 | 118 | ```json 119 | { 120 | "n": 1, 121 | "ok": 1.0 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /docs/api/extend.md: -------------------------------------------------------------------------------- 1 | # API 扩展 2 | 3 | 基于 FreeIOT 的轻巧原则,核心库并不包含各类“高级”接口,相关功能均借由 Adapter 实现。 4 | 5 | Adapter 中可以获取到核心库初始化后的 Flask App 实例,可使用该实例向系统中添加接口,参见 [增加新的 API 接口](../adapter/README.md#add-new-endpoint) 6 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # 配置文件详解 2 | 3 | FreeIOT 采用 [dotenv](https://github.com/motdotla/dotenv) 作为配置的读取、管理手段,有关语法不再赘述,请大家阅读 dotenv 的简明文档即可很快了解。下表列出了在 `.env` 配置文件中可使用的配置项名称、描述及默认值(标有 `WIP` 的项目代表实现仍未发布,配置可能无效): 4 | 5 | | 配置项 | 默认值 | 描述 | 6 | | ----------------- | -------------- | ----------------------------------------------------------------------------- | 7 | | APP_HOST | 127.0.0.1 | 核心 API 服务地址 | 8 | | APP_PORT | 3000 | 核心 API 服务端口 | 9 | | APP_DEBUG | true | 调试模式开关(布尔值,全小写) | 10 | | FLASK_CONFIG | development | Flask 配置项,可选production,development,testing | 11 | | MONGO_HOST | localhost | MongoDB 数据库地址 | 12 | | MONGO_PORT | 27017 | MongoDB 数据库端口 | 13 | | MONGO_DBNAME | freeiot | MongoDB 数据库名 | 14 | | ADMIN_PASSWORD | admin | `WIP` 核心 API 鉴权服务 admin 用户密码 | 15 | 16 | 注1:核心库对 MongoDB 支持更多配置,如认证等,可参照 [Flask-PyMongo 文档](https://flask-pymongo.readthedocs.io/en/latest/) 配置 17 | 注2:`.env` 文件应放置在当前运行路径(CWD)的同目录或上层目录 18 | -------------------------------------------------------------------------------- /docs/example.md: -------------------------------------------------------------------------------- 1 | # 实例 2 | 3 | 以下项目使用了 FreeIOT 作为中间件实现的一部分: 4 | 5 | - RB-X 无人驾驶车辆运营实验系统 6 | - 开发者:[Noah Gao](https://github.com/noahziheng) 7 | - 开源地址:闭源项目 8 | - 提交时间:2018-03-03 9 | - 若本巡检机器人 10 | - 开发者:[Noah Gao](https://github.com/noahziheng) 11 | - 开源地址:闭源项目 12 | - 提交时间:2018-03-03 13 | 14 | 如您也使用 FreeIOT 完成了一些实现,欢迎在 [Github 仓库](https://github.com/noahziheng/freeiot) 发布 Issue 提交给我们。 15 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题解答(FAQ) 2 | 3 | 这里提供了您使用FreeIOT中可能遇到问题的解答,如您在本页面内未能找到问题的答案,请按照以下方法反馈给我们: 4 | 5 | - 到 FreeIOT 的 [Github仓库](https://github.com/noahziheng/freeiot) 提交issue。 6 | - 发送邮件到 [noahgaocn@outlook.com](mailto:noahgaocn@outlook.com)。 7 | 8 | ## 核心库常见问题 9 | 10 | 11 | 12 | ### RESTFul API 只能依据数据 ObjectId 单条提取数据吗? 13 | 14 | 15 | 是的,数据信息的 REST API 接口我们仅建议小规模使用,较大规模查询建议使用 `GraphQLAdapter(WIP)` 或开发专门的 Adapter 进行数据分析后提供特定的 API 接口。 16 | 17 | ## Adapter 常见问题 18 | 19 | 20 | 21 | ### MQTTAdapter 能否使用其他的 MQTT Broker? 22 | 23 | 24 | 25 | 符合 MQTT 3 协议的都可以,不过仍然建议使用支持更完善的 MQTT 3.1.1 协议的 Broker。 26 | 27 | ### MQTTAdapter 已上线的设备在 FreeIOT 系统重启后需要重新上线才能正常工作。 28 | 29 | 30 | 31 | 这是正常现象,正在开发的状态更新功能将解决这一问题,通过定时发送状态更新包来维持在线状态。 32 | 33 | ## 系统开发常见问题 34 | 35 | 36 | 37 | ### 我能不能使用 MongoDB 以外的其他数据库? 38 | 39 | 40 | 抱歉,目前版本的核心库只支持 MongoDB,未来将引入数据库驱动机制,是 MySQL, PostgreSQL, Redis 等可作为数据存储系统。 41 | -------------------------------------------------------------------------------- /docs/howtouse/README.md: -------------------------------------------------------------------------------- 1 | # FreeIOT 快速指南 2 | 3 | 4 | 5 | > 以下所提到的可执行命令均以 macOS/Linux 环境为例,Windows 环境请将 `python3/pip3` 替换为 `python/pip` 6 | 7 | ## 安装 8 | 9 | 10 | 11 | > FreeIOT 使用 Python3 开发,您应在安装 FreeIOT 之前先行安装 Python3.5 以上版本。 12 | 13 | 最简单的 FreeIOT 安装方式就是通过 pip 包管理器;当然,我们也提供了基于源码构建安装的方式供选择。 14 | 15 | ### 通过包管理器安装 16 | 17 | ```shell 18 | pip3 install libfreeiot 19 | ``` 20 | 21 | ### 构建安装 22 | 23 | ```shell 24 | git clone https://github.com/noahziheng/freeiot.git 25 | cd freeiot 26 | python3 setup.py install --user 27 | ``` 28 | 29 | ## 基本使用 30 | 31 | 32 | 33 | 一个 FreeIOT 项目需要两个基础文件,一个是 `.env` 配置文件,另一个是你的程序入口,我们使用 `manage.py`。 34 | 35 | FreeIOT 需要使用 MongoDB 数据库,我们在以下的实例中使用在本机 `27017`(默认端口)提供服务的 MongoDB 服务,如您的配置有所不同,参见 [配置文件详解](../config.md) 36 | 37 | 建立 `.env` 文件如下:(可参考项目 Github 仓库中的 [`.env.sample`](https://github.com/noahziheng/freeiot/blob/master/.env.sample) 文件) 38 | 39 | ```dotenv 40 | APP_PORT = 3000 41 | APP_DEBUG = true 42 | FLASK_CONFIG = development 43 | MONGO_HOST = localhost 44 | MONGO_PORT = 27017 45 | MONGO_DBNAME = freeiot 46 | ``` 47 | 48 | 建立 `manage.py` 文件如下:(可参考项目 Github 仓库中的 [`manage.sample.py`](https://github.com/noahziheng/freeiot/blob/master/manage.sample.py) 文件) 49 | 50 | ```python 51 | from libfreeiot import app 52 | 53 | app.run() 54 | ``` 55 | 56 | 现在,我们只需运行 `python3 manage.py` 即可看到: 57 | 58 | ![Result of Basic](../images/howtouse/1.png) 59 | 60 | 现在,FreeIOT 的核心库已经开始工作,可以通过 API 管理 `设备` 和 `数据` 啦~ 61 | 62 | ## 数据 63 | 64 | 65 | 66 | FreeIOT 核心库支持存管各种 [JSON](https://www.json.org) 所支持的数据格式(底层和 HTTP API 基于 JSON,设备侧传输格式由 Adapter 决定,并提供官方 Adapter 存管二进制文件)。 67 | 68 | 每条数据都通过来源设备和主题共同描述其来源,主题为字符串,如可令电池数据主题为 `battery`。 69 | 70 | 数据记录描述格式如下: 71 | 72 | | 字段名 | 类型 | 描述 | 73 | | ------- | -------- | ----------------------------------------------------- | 74 | | _id | ObjectId | 数据标识 ID | 75 | | topic | String | 数据来源主题 | 76 | | flag | String | 数据传输类型标志,默认支持 `u` 代表上传,`d` 代表下放 | 77 | | device | DBRef | 数据来源设备指针引用 | 78 | | content | Any | 数据内容 | 79 | 80 | 其中涉及到的数据类型和数字代指如下: 81 | 82 | ObjectId 类型(MongoDB)用于描述设备或数据的识别 ID,其格式为: 83 | 84 | | 字段名 | 类型 | 描述 | 85 | | ------ | ------ | ------------- | 86 | | $oid | String | ID 字符串形式 | 87 | 88 | DBRef 类型(MongoDB)用于描述关联的其他设备或数据,其格式为: 89 | 90 | | 字段名 | 类型 | 描述 | 91 | | ------ | ------ | ----------------------------------------------- | 92 | | $ref | String | 可能为 `devices` 或 `datas`,分别代指设备或数据 | 93 | | $id | String | 关联到的设备/数据 ID | 94 | 95 | ## 设备 96 | 97 | 98 | 99 | 核心库的主要功能就是提供 `设备` 的信息存管方案,FreeIOT 对一个设备的描述格式如下: 100 | 101 | | 字段名 | 类型 | 描述 | 102 | | -------- | --------------- | --------------------------------------------------------- | 103 | | _id | ObjectId | 设备标识 ID | 104 | | remark | String | 设备备注名 | 105 | | status | Int | 设备状态(参照下表) | 106 | | version | String | 版本信息 | 107 | | lastdata | Object(DataRef) | 最新数据指针引用(使用 Adapter 修改,禁止 REST API POST) | 108 | 109 | 其中涉及到的数据类型和数字代指如下: 110 | 111 | Status 字段可能为: 112 | 113 | | 实际内容 | 代指 | 描述 | 114 | | -------- | ------------------ | -------------------------- | 115 | | -1 | STATUS_BROKEN | 设备损坏 | 116 | | 0 | STATUS_UNKNOWN | 状态未知 | 117 | | 1 | STATUS_WAIT | 等待指示(连接后默认状态) | 118 | | 2 | STATUS_RUNNING | 正在运行 | 119 | | 3 | STATUS_CHARGING | 正在充电 | 120 | | 4 | STATUS_AUTOPROTECT | 设备自我保护 | 121 | 122 | ObjectId 类型(MongoDB)用于描述设备或数据的识别 ID,其格式为: 123 | 124 | | 字段名 | 类型 | 描述 | 125 | | ------ | ------ | ------------- | 126 | | $oid | String | ID 字符串形式 | 127 | 128 | DBRef 类型(MongoDB)用于描述关联的其他设备或数据,其格式为: 129 | 130 | | 字段名 | 类型 | 描述 | 131 | | ------ | ------ | ----------------------------------------------- | 132 | | $ref | String | 可能为 `devices` 或 `datas`,分别代指设备或数据 | 133 | | $id | String | 关联到的设备/数据 ID | 134 | 135 | DataRef 类型(FreeIOT)用于记录某个数据主题的状态,详细参考 [数据](#data) 一节的介绍,其格式为: 136 | 137 | | 字段名 | 类型 | 描述 | 138 | | -------- | ------ | -------------------- | 139 | | content | Any | 数据的实际内容 | 140 | | flag | String | 数据的上传、下载标识 | 141 | | original | DBRef | 对原始数据存档的引用 | 142 | 143 | ## RESTFul API 144 | 145 | 146 | 147 | 由于 FreeIOT 对于 `设备` 和 `数据` 的操作支持集成在核心库内,所以,我们现在已经可以通过 HTTP API 进行读写操作了。 148 | 149 | FreeIOT 采用 RESTFul API 作为主接口,使用 Flask 提供服务,集成 Flask-RESTFul 进行参数处理,并引入 [JWT](https://jwt.io) 作为认证手段。 150 | 151 | 所以,我们首先获取 JWT 凭证,FreeIOT 的默认用户名密码为 `admin/admin`,运行以下命令: 152 | 153 | ```shell 154 | curl http://127.0.0.1:3000/api/auth\ 155 | -H "Accept: application/json"\ 156 | -H "Content-type: application/json"\ 157 | -X POST\ 158 | -d '{"username":"admin", "password": "admin"}' 159 | ``` 160 | 161 | 可以得到 162 | 163 | ```json 164 | { 165 | "jwt": "" 166 | } 167 | ``` 168 | 169 | 再运行: 170 | 171 | ```shell 172 | curl http://127.0.0.1:3000/api/device\ 173 | -H "Accept: application/json"\ 174 | -H "Content-type: application/json"\ 175 | -H "Authorization:Bearer "\ 176 | -X POST\ 177 | -d '{"remark": "测试样机", "status": 0, "version": "Unknown"}' 178 | ``` 179 | 180 | 可以得到 181 | 182 | ```json 183 | { 184 | "remark": "\u6d4b\u8bd5\u6837\u673a", 185 | "_id": { 186 | "$oid": "5a9a8e31baf04c21194069d3" 187 | } 188 | } 189 | ``` 190 | 191 | 已经熟悉 FreeIOT 数据结构和现代 Web 接口的读者已经可以了解到,我们通过该接口建立了一个名为“测试样机”的设备,当前不在线,版本未知。 192 | 193 | 通过 RESTFul API 可以完整操作 FreeIOT 的各项原子,详细用法参见 [RESTFul API](../api) 一章 194 | 195 | ## Adapter 196 | 197 | 198 | 199 | `Adapter` 是使用 FreeIOT 开发物联网系统的最主要形式,可用于建立新的查询接口、建立操作界面、兼容新的数据类型以及最关键的使用“物联网”方式收集、发送数据。 200 | 201 | RESTFul API 方式并不适合在设备侧使用,有 HTTP 资源消耗较大,开发繁琐,接口需要鉴权一类的缺点。而专为物联网而生的 MQTT 协议就完全解决了这些问题。 202 | 203 | 如我们希望在上面建立的系统中加入 MQTT 机制,可以修改 `manage.py` 文件如下: 204 | 205 | ```python 206 | from libfreeiot import app 207 | from libfreeiot.adapters.mqtt import MQTTAdapter 208 | 209 | app.run(adapters = [ MQTTAdapter() ]) 210 | ``` 211 | 212 | 我们可以为 libfreeiot.app 实例的 run 方法中传入 adapters 参数,给出一个包括 Adapter 实例的数组,核心库将使用对应的机制初始化传入的 Adapter。 213 | 214 | 有关机制和开发文档参见 [Adapter](../adapter) 一章。 215 | 216 | MQTTAdpter 是 FreeIOT 官方支持的,包含在 libfreeiot 包内,它的使用请参阅 [MQTTAdapter 说明](../adapter/mqtt) 及 [实例1:MQTT 方案保存温度数据](./ex1.md)。 217 | 218 | 我们还提供了一些其他的 Adapter,它们的介绍与使用说明参见 [Adapter](../adapter) 一章。 219 | 220 | ## 写在最后 221 | 222 | 223 | 224 | 以上我们简单介绍了 FreeIOT 的基本概念、用法,FreeIOT 的设计目标是一个框架,我们对开发接口(Adapter)、核心接口(RESTFul API)的描述将贯穿整个文档,希望大家能利用 FreeIOT提供的接口方案轻松打造出自己的物联网产品。 225 | -------------------------------------------------------------------------------- /docs/howtouse/ex1.md: -------------------------------------------------------------------------------- 1 | # 实例1:MQTT 方案保存温度数据 2 | 3 | 在本文中,我们将带领读者,完成一个小的温度传感器上传温度到 FreeIOT 系统的一个模型。 4 | 5 | ## 准备工作 6 | 7 | 首先,建议读者全文阅读 [FreeIOT 快速指南](./README.md),并对使用 Python3 编程具有一定的基础。 8 | 9 | 其次,我们将用到下列工具: 10 | 11 | - curl 命令行工具,用于操作命令行(习惯图形界面的读者可用 Postman 等 GUI 工具代替) 12 | - MQTTBox Chrome 扩展,用于模拟设备侧行为(熟悉 MQTT 编程的读者亦可自行编写 MQTT 客户端代替) 13 | - 最新版本的 libfreeiot 开发库 14 | - 已安装的 MongoDB 数据库 15 | 16 | 如果您最好对 [MQTT](https://mqtt.org) 有所了解,中文协议推荐阅读 [https://github.com/mcxiaoke/mqtt](https://github.com/mcxiaoke/mqtt)。 17 | 18 | ## MQTT Broker 19 | 20 | 使用 FreeIOT 开发 MQTT 方案的物联网系统,需要借助特有的 Adapter 机制,官方提供支持的 MQTTAdapter 功能为消息监听器,需要单独配置 MQTT Broker,形如下图: 21 | 22 | ![Structure of FreeIOT Middleware](../images/howtouse/2.png) 23 | 24 | MQTTAdapter 建议使用 [Eclipse Mosquitto](https://mosquitto.org/),这是兼容性最好的选择,当然,使用其他符合 MQTT3 协议的 Broker 都可被兼容。 25 | 26 | 我在这里已经配置好了一个 Mosquiito,使用 TCP 协议在 1883 端口提供服务。 27 | 28 | ## 搭建系统 29 | 30 | 我们首先建立 `.env` 配置文件如下: 31 | 32 | ```dotenv 33 | # 全局配置 34 | APP_PORT = 3000 35 | APP_DEBUG = false 36 | FLASK_CONFIG = development 37 | MONGO_HOST = localhost 38 | MONGO_PORT = 27017 39 | MONGO_DBNAME = freeiot 40 | 41 | # MQTT Adapter 配置 42 | MQTT_HOST = localhost 43 | MQTT_PORT = 1883 44 | MQTT_CLIENTID = "mqtt-guide" 45 | MQTT_PARSE_DRIVER = json 46 | TOPICS_NEED = ["tempature"] 47 | ``` 48 | 49 | 再建立一个 `manage.py` 启动脚本: 50 | 51 | ```python 52 | from libfreeiot import app 53 | from libfreeiot.adapters.mqtt import MQTTAdapter 54 | 55 | app.run(adapters = [ MQTTAdapter() ]) 56 | ``` 57 | 58 | 以上我们配置了一个具有 MQTT 功能的 FreeIOT 系统,MQTT Adapter 将在系统启动后介入观察本地 MQTT Broker 中的设备行为,并关注 `tempature` 主题。 59 | 60 | > MQTTAdapter 使用的 Paho 库目前与调试模式存在冲突,使用 MQTT 时请关闭调试模式 `DEBUG`,修改代码后手动重启程序。 61 | 62 | 接下来使用 `python3 manage.py` 即可跑起这个系统。 63 | 64 | ## 建立设备 65 | 66 | 我们首先获取 JWT 凭证,FreeIOT 的默认用户名密码为 `admin/admin`,运行以下命令: 67 | 68 | ```shell 69 | curl http://127.0.0.1:3000/api/auth\ 70 | -H "Accept: application/json"\ 71 | -H "Content-type: application/json"\ 72 | -X POST\ 73 | -d '{"username":"admin", "password": "admin"}' 74 | ``` 75 | 76 | 可以得到 77 | 78 | ```json 79 | { 80 | "jwt": "" 81 | } 82 | ``` 83 | 84 | 再运行: 85 | 86 | ```shell 87 | curl http://127.0.0.1:3000/api/device\ 88 | -H "Accept: application/json"\ 89 | -H "Content-type: application/json"\ 90 | -H "Authorization:Bearer "\ 91 | -X POST\ 92 | -d '{"remark": "测试样机", "status": 0, "version": "Unknown"}' 93 | ``` 94 | 95 | 可以得到 96 | 97 | ```json 98 | { 99 | "remark": "\u6d4b\u8bd5\u6837\u673a", 100 | "_id": { 101 | "$oid": "" 102 | } 103 | } 104 | ``` 105 | 106 | 已经熟悉 FreeIOT 的读者可以了解到,我们通过 RESTFul API 接口建立了一个名为“测试样机”的设备,当前不在线,版本未知。 107 | 108 | ## MQTT 登场 109 | 110 | 好了,我们的配置部分到此就已经结束了,接下来就是模拟设备的行为,我们首先打开 `MQTTBox` 建立一个 MQTT Client,并修改其连接信息如下: 111 | 112 | ![MQTTBox Client Config](../images/howtouse/3.png) 113 | 114 | 我们主要是修改了 ClientId 来保证设备唯一,修改了 MQTT Broker 的连接地址,并配置了一份“遗嘱(will)”来让 FreeIOT 知悉设备的离线。 115 | 116 | 接下来,新建一个 Publisher,用于向 FreeIOT 宣示设备上线。 117 | 118 | ![Online](../images/howtouse/4.png) 119 | 120 | 如果一切正常,在 FreeIOT 的运行窗口中将可以看到: 121 | 122 | ![Online Result](../images/howtouse/6.png) 123 | 124 | 然后,我们来上传数据,新建一个 Publisher,向 `/tempature/u` 这一 topic 推送一个“假”温度(注意此处的温度数据本质上是 JSON,只不过 JSON 规定不加修饰符的就是数字) 125 | 126 | ![Data](../images/howtouse/5.png) 127 | 128 | 最后,我们来检验一下成果,运行: 129 | 130 | ```shell 131 | curl http://127.0.0.1:3000/api/device/\ 132 | -H "Accept: application/json"\ 133 | -H "Content-type: application/json"\ 134 | -H "Authorization:Bearer "\ 135 | ``` 136 | 137 | 可以看到 138 | 139 | ```json 140 | { 141 | "_id": { 142 | "$oid": "5a9a8e31baf04c21194069d3" 143 | }, 144 | "remark": "\u6d4b\u8bd5\u6837\u673a", 145 | "status": 1, 146 | "version": "test version", 147 | "lastdata": { 148 | "tempature": { 149 | "flag": "u", 150 | "content": 20, 151 | "original": { 152 | "$ref": "datas", 153 | "$id": { 154 | "$oid": "5a9b71a4baf04c3898fc9a2c" 155 | } 156 | } 157 | } 158 | } 159 | } 160 | ``` 161 | 162 | 可以清晰地看到,“假”温度已经正常存入了系统,至此,实例完成~ 163 | 164 | 本文只是大概的介绍了 MQTTAdapter 的简单用法和协议格式,深入了解仍需参见 [MQTTAdapter 说明文档](../adapter/mqtt.md)。 165 | -------------------------------------------------------------------------------- /docs/images/howtouse/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/docs/images/howtouse/1.png -------------------------------------------------------------------------------- /docs/images/howtouse/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/docs/images/howtouse/2.png -------------------------------------------------------------------------------- /docs/images/howtouse/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/docs/images/howtouse/3.png -------------------------------------------------------------------------------- /docs/images/howtouse/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/docs/images/howtouse/4.png -------------------------------------------------------------------------------- /docs/images/howtouse/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/docs/images/howtouse/5.png -------------------------------------------------------------------------------- /docs/images/howtouse/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/docs/images/howtouse/6.png -------------------------------------------------------------------------------- /icons/LICENSE/LICENSE.CC_BY_4_0.txt: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | a. Offer from the Licensor -- Licensed Material. Every 169 | recipient of the Licensed Material automatically 170 | receives an offer from the Licensor to exercise the 171 | Licensed Rights under the terms and conditions of this 172 | Public License. 173 | 174 | b. No downstream restrictions. You may not offer or impose 175 | any additional or different terms or conditions on, or 176 | apply any Effective Technological Measures to, the 177 | Licensed Material if doing so restricts exercise of the 178 | Licensed Rights by any recipient of the Licensed 179 | Material. 180 | 181 | 6. No endorsement. Nothing in this Public License constitutes or 182 | may be construed as permission to assert or imply that You 183 | are, or that Your use of the Licensed Material is, connected 184 | with, or sponsored, endorsed, or granted official status by, 185 | the Licensor or others designated to receive attribution as 186 | provided in Section 3(a)(1)(A)(i). 187 | 188 | b. Other rights. 189 | 190 | 1. Moral rights, such as the right of integrity, are not 191 | licensed under this Public License, nor are publicity, 192 | privacy, and/or other similar personality rights; however, to 193 | the extent possible, the Licensor waives and/or agrees not to 194 | assert any such rights held by the Licensor to the limited 195 | extent necessary to allow You to exercise the Licensed 196 | Rights, but not otherwise. 197 | 198 | 2. Patent and trademark rights are not licensed under this 199 | Public License. 200 | 201 | 3. To the extent possible, the Licensor waives any right to 202 | collect royalties from You for the exercise of the Licensed 203 | Rights, whether directly or through a collecting society 204 | under any voluntary or waivable statutory or compulsory 205 | licensing scheme. In all other cases the Licensor expressly 206 | reserves any right to collect such royalties. 207 | 208 | 209 | Section 3 -- License Conditions. 210 | 211 | Your exercise of the Licensed Rights is expressly made subject to the 212 | following conditions. 213 | 214 | a. Attribution. 215 | 216 | 1. If You Share the Licensed Material (including in modified 217 | form), You must: 218 | 219 | a. retain the following if it is supplied by the Licensor 220 | with the Licensed Material: 221 | 222 | i. identification of the creator(s) of the Licensed 223 | Material and any others designated to receive 224 | attribution, in any reasonable manner requested by 225 | the Licensor (including by pseudonym if 226 | designated); 227 | ii. a copyright notice; 228 | 229 | iii. a notice that refers to this Public License; 230 | 231 | iv. a notice that refers to the disclaimer of 232 | warranties; 233 | 234 | v. a URI or hyperlink to the Licensed Material to the 235 | extent reasonably practicable; 236 | 237 | b. indicate if You modified the Licensed Material and 238 | retain an indication of any previous modifications; and 239 | 240 | c. indicate the Licensed Material is licensed under this 241 | Public License, and include the text of, or the URI or 242 | hyperlink to, this Public License. 243 | 244 | 2. You may satisfy the conditions in Section 3(a)(1) in any 245 | reasonable manner based on the medium, means, and context in 246 | which You Share the Licensed Material. For example, it may be 247 | reasonable to satisfy the conditions by providing a URI or 248 | hyperlink to a resource that includes the required 249 | information. 250 | 251 | 3. If requested by the Licensor, You must remove any of the 252 | information required by Section 3(a)(1)(A) to the extent 253 | reasonably practicable. 254 | 255 | 4. If You Share Adapted Material You produce, the Adapter's 256 | License You apply must not prevent recipients of the Adapted 257 | Material from complying with this Public License. 258 | 259 | 260 | Section 4 -- Sui Generis Database Rights. 261 | 262 | Where the Licensed Rights include Sui Generis Database Rights that 263 | apply to Your use of the Licensed Material: 264 | 265 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 266 | to extract, reuse, reproduce, and Share all or a substantial 267 | portion of the contents of the database; 268 | 269 | b. if You include all or a substantial portion of the database 270 | contents in a database in which You have Sui Generis Database 271 | Rights, then the database in which You have Sui Generis Database 272 | Rights (but not its individual contents) is Adapted Material; and 273 | 274 | c. You must comply with the conditions in Section 3(a) if You Share 275 | all or a substantial portion of the contents of the database. 276 | 277 | For the avoidance of doubt, this Section 4 supplements and does not 278 | replace Your obligations under this Public License where the Licensed 279 | Rights include other Copyright and Similar Rights. 280 | 281 | 282 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 283 | 284 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 285 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 286 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 287 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 288 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 289 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 290 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 291 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 292 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 293 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 294 | 295 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 296 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 297 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 298 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 299 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 300 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 301 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 302 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 303 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 304 | 305 | c. The disclaimer of warranties and limitation of liability provided 306 | above shall be interpreted in a manner that, to the extent 307 | possible, most closely approximates an absolute disclaimer and 308 | waiver of all liability. 309 | 310 | 311 | Section 6 -- Term and Termination. 312 | 313 | a. This Public License applies for the term of the Copyright and 314 | Similar Rights licensed here. However, if You fail to comply with 315 | this Public License, then Your rights under this Public License 316 | terminate automatically. 317 | 318 | b. Where Your right to use the Licensed Material has terminated under 319 | Section 6(a), it reinstates: 320 | 321 | 1. automatically as of the date the violation is cured, provided 322 | it is cured within 30 days of Your discovery of the 323 | violation; or 324 | 325 | 2. upon express reinstatement by the Licensor. 326 | 327 | For the avoidance of doubt, this Section 6(b) does not affect any 328 | right the Licensor may have to seek remedies for Your violations 329 | of this Public License. 330 | 331 | c. For the avoidance of doubt, the Licensor may also offer the 332 | Licensed Material under separate terms or conditions or stop 333 | distributing the Licensed Material at any time; however, doing so 334 | will not terminate this Public License. 335 | 336 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 337 | License. 338 | 339 | 340 | Section 7 -- Other Terms and Conditions. 341 | 342 | a. The Licensor shall not be bound by any additional or different 343 | terms or conditions communicated by You unless expressly agreed. 344 | 345 | b. Any arrangements, understandings, or agreements regarding the 346 | Licensed Material not stated herein are separate from and 347 | independent of the terms and conditions of this Public License. 348 | 349 | 350 | Section 8 -- Interpretation. 351 | 352 | a. For the avoidance of doubt, this Public License does not, and 353 | shall not be interpreted to, reduce, limit, restrict, or impose 354 | conditions on any use of the Licensed Material that could lawfully 355 | be made without permission under this Public License. 356 | 357 | b. To the extent possible, if any provision of this Public License is 358 | deemed unenforceable, it shall be automatically reformed to the 359 | minimum extent necessary to make it enforceable. If the provision 360 | cannot be reformed, it shall be severed from this Public License 361 | without affecting the enforceability of the remaining terms and 362 | conditions. 363 | 364 | c. No term or condition of this Public License will be waived and no 365 | failure to comply consented to unless expressly agreed to by the 366 | Licensor. 367 | 368 | d. Nothing in this Public License constitutes or may be interpreted 369 | as a limitation upon, or waiver of, any privileges and immunities 370 | that apply to the Licensor or You, including from the legal 371 | processes of any jurisdiction or authority. 372 | 373 | 374 | ======================================================================= 375 | 376 | Creative Commons is not a party to its public licenses. 377 | Notwithstanding, Creative Commons may elect to apply one of its public 378 | licenses to material it publishes and in those instances will be 379 | considered the "Licensor." Except for the limited purpose of indicating 380 | that material is shared under a Creative Commons public license or as 381 | otherwise permitted by the Creative Commons policies published at 382 | creativecommons.org/policies, Creative Commons does not authorize the 383 | use of the trademark "Creative Commons" or any other trademark or logo 384 | of Creative Commons without its prior written consent including, 385 | without limitation, in connection with any unauthorized modifications 386 | to any of its public licenses or any other arrangements, 387 | understandings, or agreements concerning use of licensed material. For 388 | the avoidance of doubt, this paragraph does not form part of the public 389 | licenses. 390 | 391 | Creative Commons may be contacted at creativecommons.org. 392 | -------------------------------------------------------------------------------- /icons/LICENSE/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Android Material Icon Generator License 2 | ================================ 3 | 4 | Icons generated with the Android Material Icon Generator come with the Creative Common 5 | Attribution 4.0 International License (CC-BY 4.0). You are free to change, 6 | combine and sell any of the icons as you please. Attribution would be great, 7 | but is not strictly required. 8 | 9 | This text only applies to the icons (.zip file) you download from the icon 10 | generator. The software behind the generator has its own license 11 | (https://www.apache.org/licenses/LICENSE-2.0). See the GitHub repository for 12 | details (https://github.com/Maddoc42/Android-Material-Icon-Generator). 13 | 14 | 15 | 16 | Google Material Icons License 17 | ============================= 18 | 19 | (Copied from https://github.com/google/material-design-icons) 20 | We have made these icons available for you to incorporate them into your 21 | products under the Creative Common Attribution 4.0 International License (CC-BY 22 | 4.0, https://creativecommons.org/licenses/by/4.0/). Feel free to remix and 23 | re-share these icons and documentation in your products. We'd love attribution 24 | in your app's *about* screen, but it's not required. The only thing we ask is 25 | that you not re-sell the icons themselves. -------------------------------------------------------------------------------- /icons/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/code.png -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icons/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/playstore/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/icons/playstore/icon.png -------------------------------------------------------------------------------- /libfreeiot/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/libfreeiot/.DS_Store -------------------------------------------------------------------------------- /libfreeiot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/libfreeiot/__init__.py -------------------------------------------------------------------------------- /libfreeiot/adapters/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/libfreeiot/adapters/.DS_Store -------------------------------------------------------------------------------- /libfreeiot/adapters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/libfreeiot/adapters/__init__.py -------------------------------------------------------------------------------- /libfreeiot/adapters/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base Adapter Module 3 | """ 4 | from abc import ABCMeta, abstractmethod 5 | 6 | class BaseAdapter(metaclass=ABCMeta): 7 | """ 8 | Base Adapter Class 9 | """ 10 | 11 | app = None 12 | scope = dict() 13 | 14 | def init(self, app, scope): 15 | """ 16 | Initialize Function for Adapter 17 | """ 18 | self.app = app 19 | self.scope = scope 20 | 21 | @abstractmethod 22 | def run(self): 23 | """ 24 | Main Entry for Adapter 25 | """ 26 | pass 27 | -------------------------------------------------------------------------------- /libfreeiot/adapters/file/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | File Adapter Module 3 | """ 4 | from .main import FileAdapter 5 | -------------------------------------------------------------------------------- /libfreeiot/adapters/file/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | File Adapter Module 3 | """ 4 | import time 5 | import uuid 6 | from flask import request, jsonify 7 | from ..base import BaseAdapter 8 | 9 | class FileAdapter(BaseAdapter): 10 | """ File Adapter Class """ 11 | def create_routes(self, app, mongo): 12 | """ Routes Creator """ 13 | @app.route('/api/upload', methods=['POST']) 14 | def save_upload(): 15 | """ File Upload POST Route """ 16 | filename = uuid.uuid3(uuid.NAMESPACE_DNS, request.files['file'].filename.split('.')[0] + str(time.time())).hex + "." + request.files['file'].filename.split('.')[1] 17 | mongo.save_file(filename, request.files['file'], base = "datas") 18 | return jsonify(filename) 19 | 20 | @app.route('/api/upload/') 21 | def get_upload(filename): 22 | """ File Upload GET Route """ 23 | response = mongo.send_file(filename, base = "datas") 24 | response.cache_control.public = False 25 | response.cache_control.max_age = 0 26 | response.cache_control.no_cache = True 27 | return response 28 | 29 | def run(self): 30 | """ Main Entry for Adapter """ 31 | self.create_routes(self.app, self.scope["mongo"]) 32 | print('Hello from FileAdapter') 33 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/libfreeiot/adapters/mqtt/.DS_Store -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/Constant/Status.py: -------------------------------------------------------------------------------- 1 | """ Constant Module of device status """ 2 | STATUS_BROKEN=-1 3 | STATUS_UNKNOWN=0 4 | STATUS_WAIT=1 5 | STATUS_RUNNING=2 6 | STATUS_CHARGING=3 7 | STATUS_AUTOPROTECT=4 8 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/Constant/__init__.py: -------------------------------------------------------------------------------- 1 | from . import Status 2 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/Parse/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | FreeIOT Parse Package Initial Script 3 | 4 | Author: Noah Gao 5 | Updated at: 2018-02-23 6 | """ 7 | import os 8 | from pymongo import MongoClient 9 | 10 | mongo = MongoClient(host=os.environ.get("MONGO_HOST") or "localhost", port=int(os.environ.get("MONGO_PORT")) or 27017) 11 | mongo.db = mongo[os.environ.get("MONGO_DBNAME") or "test"] 12 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/Parse/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Message Parse Module 3 | Author: Noah Gao 4 | Updated at: 2018-2-2 5 | """ 6 | from datetime import datetime 7 | from bson import DBRef, ObjectId 8 | from .. import Constant 9 | from . import sys, mongo 10 | 11 | def before_check(d_id): 12 | """ Before Check Function """ 13 | if not ObjectId.is_valid(d_id): 14 | print(d_id + " is invalid!") 15 | return 16 | else: 17 | d_id = ObjectId(d_id) 18 | result = mongo.db.devices.find_one({"_id": d_id}) 19 | if result is not None: 20 | if "status" not in dict(result).keys(): 21 | return False 22 | if result.get("status") == Constant.Status.STATUS_UNKNOWN: 23 | r = mongo.db.devices.update_one({"_id": d_id},{ "$set": { "status": Constant.Status.STATUS_WAIT }}) 24 | return result 25 | 26 | def main(client, topic, payload): 27 | """ Parse Engine Main Function """ 28 | print("Parsing ", topic, payload) 29 | if topic[0]=='SYS': 30 | if not before_check(payload["id"]): 31 | return 32 | if topic[1]=='online': 33 | sys.online(client, payload) 34 | elif topic[1]=='will': 35 | sys.offline(client, payload) 36 | else: 37 | if not before_check(topic[0]): 38 | return 39 | res = mongo.db.datas.insert_one({ 40 | "device": DBRef("devices", topic[0]), 41 | "topic": topic[1], 42 | "flag": topic[2], 43 | "content": payload, 44 | "created_at": datetime.utcnow() 45 | }) 46 | mongo.db.devices.update_one({"_id": ObjectId(topic[0])}, 47 | { 48 | "$set": { 49 | "lastdata." + topic[1]: { 50 | "flag": topic[2], 51 | "content": payload, 52 | "original": DBRef("datas", res.inserted_id), 53 | "created_at": datetime.utcnow() 54 | } 55 | } 56 | }) 57 | print(topic, payload, "Created at:", datetime.now()) 58 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/Parse/sys.py: -------------------------------------------------------------------------------- 1 | """ 2 | Message Parse Module SYS topic 3 | Author: Noah Gao 4 | Updated at: 2018-2-2 5 | """ 6 | import os 7 | import json 8 | from bson import ObjectId 9 | from .. import Constant 10 | from . import mongo 11 | 12 | def online(client, device): 13 | """ Register Method of device online """ 14 | d_id = ObjectId(dict(device).get("id")) 15 | mongo.db.devices.update_one({"_id": d_id},{ 16 | "$set": { 17 | "status": Constant.Status.STATUS_WAIT, 18 | "version": dict(device).get("version") 19 | } 20 | }) 21 | client.publish(dict(device).get("id") + "/status/d", Constant.Status.STATUS_WAIT) 22 | if "TOPICS_NEED" in os.environ: 23 | topics_need = json.loads(os.environ.get("TOPICS_NEED")) 24 | for item in topics_need: 25 | client.subscribe(dict(device).get("id") + "/" + item + "/u") 26 | client.subscribe(dict(device).get("id") + "/" + item + "/d") 27 | print("Auto subscribing", dict(device).get("id") + "/" + item + "/(u|d)") 28 | print(dict(device).get("id") + " Online") 29 | 30 | def offline(client, device): 31 | """ Register Method of device offline """ 32 | d_id = dict(device).get("id") 33 | mongo.db.devices.update_one( 34 | {"_id": ObjectId(d_id)}, 35 | { "$set": { "status": Constant.Status.STATUS_UNKNOWN } 36 | }) 37 | client.publish(dict(device).get("id") + "/status/d", Constant.Status.STATUS_UNKNOWN) 38 | if "TOPICS_NEED" in os.environ: 39 | topics_need = json.loads(os.environ.get("TOPICS_NEED")) 40 | print(topics_need) 41 | for item in topics_need: 42 | client.unsubscribe(dict(device).get("id") + "/" + item + "/u") 43 | client.unsubscribe(dict(device).get("id") + "/" + item + "/d") 44 | print(dict(device).get("id") + " Offline") 45 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MQTT Adapter Module 3 | """ 4 | from .main import MQTTAdapter 5 | -------------------------------------------------------------------------------- /libfreeiot/adapters/mqtt/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | MQTT Adapter Module 3 | """ 4 | import os 5 | import json 6 | import paho.mqtt.client as mqtt 7 | from ..base import BaseAdapter 8 | from .Parse import main as Parse 9 | 10 | class MQTTAdapter(BaseAdapter): 11 | """ Base Adapter Class """ 12 | client_id = os.environ.get("MQTT_CLIENTID") or "mqtt-adapter" 13 | server_address = os.environ.get("MQTT_HOST") or "localhost" 14 | server_port = int(os.environ.get("MQTT_PORT")) or 1883 15 | client = mqtt.Client(client_id) 16 | parse_driver = os.environ.get("MQTT_PARSE_DRIVER") or "json" 17 | 18 | def run(self): 19 | """ Main Entry for Adapter """ 20 | self.client.on_connect = self.on_connect 21 | self.client.on_message = self.on_message 22 | self.client.connect(self.server_address, self.server_port, 60) # 连接服务器(TCP) 23 | self.scope["mqttClient"] = self.client # 将 client 代入 Adapter 作用域 24 | self.client.loop_start() 25 | print('Hello from MQTTAdapter') 26 | 27 | def on_connect(self, client, userdata, flags, rc): 28 | """ Callback while conntected """ 29 | print("MQTT Broker connected with result code "+str(rc)) 30 | # Subscribing in on_connect() means that if we lose the connection and 31 | # reconnect then subscriptions will be renewed. 32 | self.client.subscribe("SYS/online") 33 | self.client.subscribe("SYS/will") 34 | 35 | # The callback for when a PUBLISH message is received from the server. 36 | def on_message(self, client, userdata, msg): 37 | """ Callback while received messageA """ 38 | Parse.main(client, msg.topic.split('/'), self.parse_driver_select(msg.payload.decode())) 39 | 40 | def parse_driver_select(self, data): 41 | """ Select a driver to parse data """ 42 | if self.parse_driver == 'msgpack': 43 | raise OSError("Parse driver 'msgpack' under development.") 44 | elif self.parse_driver == 'json': 45 | try: 46 | return json.loads(data) 47 | except json.JSONDecodeError as e: 48 | print("Parsing failed, reason: ", e) 49 | else: 50 | raise OSError("Parse driver '" + self.parse_driver + "' under development.") 51 | -------------------------------------------------------------------------------- /libfreeiot/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main Entry for FreeIOT 3 | 4 | Author: Noah Gao 5 | Updated at: 2018-02-23 6 | """ 7 | 8 | # Load environment 9 | import os 10 | from dotenv import load_dotenv, find_dotenv 11 | from libfreeiot.core import create_app 12 | from libfreeiot import version 13 | 14 | load_dotenv(find_dotenv(usecwd=True), override=True) 15 | 16 | print('FreeIOT Version: %s' % version.__version__) 17 | print('Web Service: %s:%s' % (os.environ.get('APP_HOST'), os.environ.get('APP_PORT'))) 18 | print('MongoDB Service: %s:%s/%s' % (os.environ.get('MONGO_HOST'), os.environ.get('MONGO_PORT'), os.environ.get('MONGO_DBNAME'))) 19 | 20 | scope = dict() 21 | 22 | def create_flask_app(): 23 | """ Function for create flask application instance """ 24 | global scope 25 | return create_app(os.getenv('FLASK_CONFIG') or 'default', scope) 26 | 27 | def run(port = int(os.environ.get("APP_PORT")), 28 | host = os.environ.get('APP_HOST', '127.0.0.1'), 29 | adapters = None, 30 | app = None): 31 | """ Main Method for running application """ 32 | global scope 33 | 34 | if app is None: 35 | app = create_flask_app() # Create Flask Application 36 | 37 | # 代入数据库句柄到 Adapter 作用域 38 | from .core import mongo 39 | scope["mongo"] = mongo 40 | 41 | if adapters is None: 42 | adapters = [] # Create adapter group if not provided 43 | for adapter in adapters: 44 | adapter.init(app, scope) # Initialize all adapters 45 | adapter.run() # Run all adapters 46 | 47 | app.run(debug=os.environ.get("APP_DEBUG") == "true", port=port, host=host, threaded=True) # Start API services 48 | -------------------------------------------------------------------------------- /libfreeiot/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default Configuration for Flask Config 3 | """ 4 | import os 5 | from dotenv import load_dotenv, find_dotenv 6 | 7 | load_dotenv(find_dotenv(usecwd=True), override=True) 8 | 9 | class Config: 10 | """ 11 | Config base class 12 | """ 13 | SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string' 14 | MONGO_HOST = os.environ.get('MONGO_HOST') or 'localhost' 15 | MONGO_PORT = int(os.environ.get('MONGO_PORT') or 27017) 16 | MONGO_DBNAME = os.environ.get('MONGO_DBNAME') or 'test' 17 | DEBUG = os.environ.get('DEBUG') or True 18 | 19 | @staticmethod 20 | def init_app(app): 21 | """ 22 | For Flask Standard 23 | """ 24 | pass 25 | 26 | class DevelopmentConfig(Config): 27 | """ 28 | Config for debug environment 29 | """ 30 | DEBUG = True 31 | 32 | class TestingConfig(Config): 33 | """ 34 | Config for test environment 35 | """ 36 | TESTING = True 37 | 38 | class ProductionConfig(Config): 39 | """ 40 | Config for production environment 41 | """ 42 | PRODUCTION = True 43 | 44 | CONFIG = { 45 | 'development': DevelopmentConfig, 46 | 'testing': TestingConfig, 47 | 'production': ProductionConfig, 48 | 'default': DevelopmentConfig 49 | } 50 | -------------------------------------------------------------------------------- /libfreeiot/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | FreeIOT Core Package Initial Script 3 | 4 | Author: Noah Gao 5 | Updated at: 2018-02-23 6 | """ 7 | from flask import Flask 8 | from flask_pymongo import PyMongo 9 | from libfreeiot.config import CONFIG 10 | 11 | mongo = None 12 | 13 | def create_app(config_name, scope = None): 14 | """ 15 | Function for create Flask App instance 16 | """ 17 | global mongo 18 | if scope is None: 19 | scope = dict() 20 | app = Flask(__name__) # Initialize app 21 | 22 | # Import project config 23 | app.config.from_object(CONFIG[config_name]) 24 | CONFIG[config_name].init_app(app) 25 | 26 | app.config["MONGO_URI"] = "mongodb://" + app.config["MONGO_HOST"] + ":" + str(app.config["MONGO_PORT"]) + "/" + app.config["MONGO_DBNAME"] 27 | print(app.config["MONGO_URI"]) 28 | 29 | # Init MongoDB Ext 30 | mongo = PyMongo(app) 31 | 32 | from libfreeiot.core.routes import create_routes 33 | app, api = create_routes(app, scope) # Initialize api services with Routes definition 34 | return app 35 | -------------------------------------------------------------------------------- /libfreeiot/core/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noahziheng/freeiot/d4aec1a32d9add9e9d2af1670d84c993b4934d69/libfreeiot/core/resources/__init__.py -------------------------------------------------------------------------------- /libfreeiot/core/resources/data.py: -------------------------------------------------------------------------------- 1 | """ 2 | The RESTFul resource of device 3 | 4 | Author: Noah Gao 5 | Updated at: 2018-02-23 6 | """ 7 | import json 8 | from flask import Response 9 | from flask_restful import Resource, reqparse 10 | from flask_jwt_simple import jwt_required 11 | from bson import json_util, ObjectId, DBRef 12 | from libfreeiot.core import mongo 13 | 14 | class Data(Resource): 15 | """ 16 | The RESTFul resource class of data 17 | """ 18 | 19 | def parse_args(self): 20 | """ Message Parse Method """ 21 | parser = reqparse.RequestParser() 22 | parser.add_argument('device', type=str, help='ObjectId(str) of the message\'s original device') 23 | parser.add_argument('topic', type=str, help='The message\'s topic') 24 | parser.add_argument('flag', type=str, help='The message\'s flag') 25 | parser.add_argument('content', type=json.loads, help='The message\'s content of JSON Encode') 26 | args = parser.parse_args() 27 | args["device"] = DBRef("devices", args["device"]) 28 | return args 29 | 30 | @jwt_required 31 | def get(self, data_id=None): 32 | """ 33 | RESTFul GET Method 34 | """ 35 | if data_id!=None: 36 | res = mongo.db.datas.find_one_or_404({'_id': ObjectId(data_id)}) 37 | else: 38 | res = mongo.db.datas.find() 39 | return Response( 40 | json_util.dumps(res), 41 | mimetype='application/json' 42 | ) 43 | 44 | @jwt_required 45 | def post(self): 46 | """ 47 | RESTFul POST Method 48 | """ 49 | args = self.parse_args() 50 | res = mongo.db.datas.insert_one(args) 51 | return Response( 52 | json_util.dumps(res.inserted_id), 53 | mimetype='application/json' 54 | ) 55 | 56 | @jwt_required 57 | def put(self, data_id): 58 | """ 59 | RESTFul PUT Method 60 | """ 61 | args = self.parse_args() 62 | res = mongo.db.datas.update_one({"_id": ObjectId(data_id)}, {"$set": args}) 63 | return Response( 64 | json_util.dumps(res.raw_result), 65 | mimetype='application/json' 66 | ) 67 | 68 | @jwt_required 69 | def delete(self, data_id): 70 | """ 71 | RESTFul DELETE Method 72 | """ 73 | res = mongo.db.datas.delete_one({'_id': ObjectId(data_id)}) 74 | return Response( 75 | json_util.dumps(res.raw_result), 76 | mimetype='application/json' 77 | ) 78 | -------------------------------------------------------------------------------- /libfreeiot/core/resources/device.py: -------------------------------------------------------------------------------- 1 | """ 2 | The RESTFul resource of device 3 | 4 | Author: Noah Gao 5 | Updated at: 2018-02-23 6 | """ 7 | from flask import Response, abort 8 | from flask_restful import Resource, reqparse 9 | from flask_jwt_simple import jwt_required 10 | from bson import json_util, ObjectId 11 | from libfreeiot.core import mongo 12 | from libfreeiot.app import scope 13 | 14 | class Device(Resource): 15 | """ 16 | The RESTFul resource class of device 17 | """ 18 | @jwt_required 19 | def get(self, device_id=None): 20 | """ 21 | RESTFul GET Method 22 | """ 23 | if device_id!=None: 24 | res = mongo.db.devices.find_one_or_404({'_id': ObjectId(device_id)}) 25 | else: 26 | res = mongo.db.devices.find() 27 | return Response( 28 | json_util.dumps(res), 29 | mimetype='application/json' 30 | ) 31 | 32 | @jwt_required 33 | def post(self, device_id=None): 34 | """ 35 | RESTFul POST Method 36 | """ 37 | parser = reqparse.RequestParser() 38 | parser.add_argument('remark', type=str, help='Remark of the device') 39 | parser.add_argument('status', type=int, help='Status of the device') 40 | parser.add_argument('version', type=str, help='Version of the device') 41 | args = parser.parse_args() 42 | if device_id is None: 43 | data = args 44 | res = mongo.db.devices.insert_one(data) 45 | else: 46 | data = {} 47 | if not args["remark"] is None: 48 | data["remark"] = args["remark"] 49 | if not args["status"] is None: 50 | data["status"] = args["status"] 51 | if not args["version"] is None: 52 | data["version"] = args["version"] 53 | if not ObjectId.is_valid(device_id): 54 | return abort(400) 55 | if(len(data) < 1): 56 | return abort(400) 57 | mongo.db.devices.update_one({"_id": ObjectId(device_id)},{ 58 | "$set": data 59 | }) 60 | return Response( 61 | json_util.dumps(data), 62 | mimetype='application/json' 63 | ) 64 | 65 | @jwt_required 66 | def delete(self, device_id): 67 | """ 68 | RESTFul DELETE Method 69 | """ 70 | res = mongo.db.devices.delete_one({'_id': ObjectId(device_id)}) 71 | return Response( 72 | json_util.dumps(res.raw_result), 73 | mimetype='application/json' 74 | ) 75 | -------------------------------------------------------------------------------- /libfreeiot/core/routes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Routes Module 3 | Author: Noah Gao 4 | Updated at: 2018-2-2 5 | """ 6 | import os 7 | import datetime 8 | from bson import json_util 9 | from flask import request, jsonify, Response 10 | from flask_restful import Api 11 | from flask_jwt_simple import JWTManager, create_jwt 12 | from .resources.device import Device 13 | from .resources.data import Data 14 | 15 | JWT_EXPIRES = 7 * 24 * 3600 16 | 17 | def create_routes(app, scope = None): 18 | ''' 19 | Function for create routes 20 | ''' 21 | if scope is None: 22 | scope = dict() 23 | app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this! 24 | app.config['JWT_EXPIRES'] = datetime.timedelta(7) 25 | app.config['UPLOAD_FOLDER'] = os.path.join(os.getcwd(), '/images') 26 | app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 27 | jwt = JWTManager(app) 28 | 29 | @app.route('/hello') 30 | def say_hello(): 31 | ''' 32 | Index Route 33 | ''' 34 | return jsonify({"msg": "Hello World!"}) 35 | 36 | @app.route('/api/auth', methods=['POST']) 37 | def auth(): 38 | ''' 39 | JWT Auth Route 40 | ''' 41 | username = request.json.get('username', None) 42 | password = request.json.get('password', None) 43 | if not username: 44 | return jsonify({"msg": "Missing username parameter"}), 400 45 | if not password: 46 | return jsonify({"msg": "Missing password parameter"}), 400 47 | if 'auth'in scope.keys(): 48 | authr = scope["auth"].login(username, password) 49 | if not authr: 50 | return jsonify({"msg": "Bad username or password"}), 401 51 | else: 52 | if username != 'admin' or password == 'admin': 53 | return jsonify({"msg": "Bad username or password"}), 401 54 | res = authr or {} 55 | res["jwt"] = create_jwt(identity=username) 56 | return Response( 57 | json_util.dumps(res), 58 | mimetype='application/json' 59 | ), 200 60 | 61 | # RESTFul API Routes definition 62 | api = Api(app) 63 | api.add_resource(Device, '/api/device', '/api/device/') 64 | api.add_resource(Data, '/api/data', '/api/data/') 65 | 66 | return (app, api) 67 | -------------------------------------------------------------------------------- /libfreeiot/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Version Info 4 | """ 5 | 6 | script_name = 'libfreeiot' 7 | __version__ = '0.9.18' 8 | -------------------------------------------------------------------------------- /manage.sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Sample script for create a FreeIOT Application 4 | """ 5 | from libfreeiot import app 6 | from libfreeiot.adapters.mqtt import MQTTAdapter 7 | from libfreeiot.adapters.file import FileAdapter 8 | 9 | if __name__ == '__main__': 10 | app.run(adapters = [ MQTTAdapter(), FileAdapter() ]) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==2.0.0 2 | astroid==1.6.1 3 | click==6.7 4 | Flask==1.0 5 | Flask-JWT-Simple==0.0.3 6 | Flask-PyMongo==0.5.1 7 | Flask-RESTful==0.3.6 8 | isort==4.3.4 9 | itsdangerous==0.24 10 | Jinja2==2.10 11 | lazy-object-proxy==1.3.1 12 | MarkupSafe==1.0 13 | mccabe==0.6.1 14 | paho-mqtt==1.3.1 15 | PyJWT==1.5.3 16 | pylint==1.8.2 17 | pymongo==3.6.0 18 | python-dotenv==0.7.1 19 | pytz==2018.3 20 | six==1.11.0 21 | Werkzeug==0.15.3 22 | wrapt==1.10.11 23 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library's Setuptools Script 3 | """ 4 | import imp 5 | import os 6 | from setuptools import setup, find_packages 7 | 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | PACKAGE_NAME = 'libfreeiot' 11 | VERSION = imp.load_source('version', os.path.join(here, '%s/version.py' % PACKAGE_NAME)).__version__ 12 | 13 | setup( 14 | name = "libfreeiot", 15 | version = VERSION, 16 | description = 'A free, open-source IoT Framework', 17 | author = 'Noah Gao', 18 | author_email = 'noahgaocn@outlook.com', 19 | url = 'https://github.com/noahziheng/freeiot', 20 | download_url = 'https://github.com/noahziheng/freeiot/archive/' + VERSION + '.tar.gz', 21 | packages = find_packages(), 22 | install_requires=[ # 依赖列表 23 | 'Flask>=0.12.2', 24 | 'Flask-JWT-Simple>=0.0.3', 25 | 'Flask-PyMongo>=0.5.1', 26 | 'Flask-RESTful>=0.3.6', 27 | 'paho-mqtt>=1.3.1', 28 | 'pylint>=1.8.2', 29 | 'python-dotenv>=0.7.1' 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /supervisor.conf: -------------------------------------------------------------------------------- 1 | ; FreeIOT supervisor config file. 2 | ; 3 | ; For FreeIOT Program, Author Noah Gao 4 | ; Updated at: 2018-02-24 5 | 6 | [unix_http_server] 7 | file=/tmp/supervisor.sock ; the path to the socket file 8 | 9 | [supervisord] 10 | logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log 11 | logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB 12 | logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 13 | loglevel=info ; log level; default info; others: debug,warn,trace 14 | pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid 15 | nodaemon=false ; start in foreground if true; default false 16 | minfds=1024 ; min. avail startup file descriptors; default 1024 17 | minprocs=200 ; min. avail process descriptors;default 200 18 | 19 | [rpcinterface:supervisor] 20 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 21 | 22 | [supervisorctl] 23 | serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket 24 | 25 | [program:freeiot-sample] 26 | command=/data/www/freeiot/venv/bin/python3 ./manage.py ; supervisor启动命令 27 | directory=/data/www/freeiot ; 项目的文件夹路径 28 | startsecs=0 ; 启动时间 29 | stopwaitsecs=0 ; 终止等待时间 30 | autostart=true ; 是否自动启动 31 | autorestart=true ; 是否自动重启 32 | stdout_logfile=/var/server/log/freeiot-sample.log ; log 日志 33 | stderr_logfile=/var/server/log/freeiot-sample.err ; error 日志 34 | --------------------------------------------------------------------------------