This commit is contained in:
langzihan 2024-10-24 09:23:39 +08:00
parent 641c34b1b3
commit 16c005556f
36 changed files with 7780 additions and 0 deletions

24
SCLP/Pipfile Normal file
View File

@ -0,0 +1,24 @@
[[source]]
url = "https://mirrors.ustc.edu.cn/pypi/simple"
verify_ssl = false
name = "pip_conf_index_global"
[packages]
fastapi = "*"
uvicorn = "*"
argparse = "*"
pillow = "*"
paho-mqtt = "*"
requests = "*"
pandas = "*"
openpyxl = "*"
xlrd = "*"
aiofiles = "*"
psutil = "*"
python-multipart = "*"
[dev-packages]
[requires]
python_version = "3.10"
python_full_version = "3.10.14"

685
SCLP/Pipfile.lock generated Normal file
View File

@ -0,0 +1,685 @@
{
"_meta": {
"hash": {
"sha256": "44345757f24b73dd5c0974ed045d2479e1773441b6d789525398bd83e837bfb9"
},
"pipfile-spec": 6,
"requires": {
"python_full_version": "3.10.14",
"python_version": "3.10"
},
"sources": [
{
"name": "pip_conf_index_global",
"url": "https://mirrors.ustc.edu.cn/pypi/simple",
"verify_ssl": false
}
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c",
"sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==24.1.0"
},
"annotated-types": {
"hashes": [
"sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53",
"sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"
],
"markers": "python_version >= '3.8'",
"version": "==0.7.0"
},
"anyio": {
"hashes": [
"sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94",
"sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"
],
"markers": "python_version >= '3.8'",
"version": "==4.4.0"
},
"argparse": {
"hashes": [
"sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4",
"sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"
],
"index": "pip_conf_index_global",
"version": "==1.4.0"
},
"certifi": {
"hashes": [
"sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
"sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
],
"markers": "python_version >= '3.6'",
"version": "==2024.8.30"
},
"charset-normalizer": {
"hashes": [
"sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
"sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
"sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
"sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
"sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
"sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
"sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
"sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
"sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
"sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
"sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
"sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
"sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
"sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
"sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
"sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
"sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
"sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
"sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
"sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
"sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
"sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
"sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
"sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
"sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
"sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
"sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
"sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
"sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
"sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
"sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
"sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
"sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
"sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
"sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
"sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
"sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
"sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
"sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
"sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
"sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
"sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
"sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
"sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
"sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
"sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
"sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
"sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
"sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
"sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
"sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
"sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
"sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
"sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
"sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
"sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
"sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
"sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
"sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
"sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
"sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
"sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
"sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
"sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
"sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
"sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
"sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
"sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
"sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
"sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
"sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
"sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
"sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
"sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
"sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
"sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
"sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
"sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
"sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
"sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
"sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
"sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
"sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
"sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
"sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
"sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
"sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
"sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
"sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
"sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==3.3.2"
},
"click": {
"hashes": [
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.7"
},
"et-xmlfile": {
"hashes": [
"sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c",
"sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"
],
"markers": "python_version >= '3.6'",
"version": "==1.1.0"
},
"exceptiongroup": {
"hashes": [
"sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",
"sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"
],
"markers": "python_version < '3.11'",
"version": "==1.2.2"
},
"fastapi": {
"hashes": [
"sha256:3d4729c038414d5193840706907a41839d839523da6ed0c2811f1168cac1798c",
"sha256:db84b470bd0e2b1075942231e90e3577e12a903c4dc8696f0d206a7904a7af1c"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==0.112.2"
},
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"idna": {
"hashes": [
"sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac",
"sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"
],
"markers": "python_version >= '3.6'",
"version": "==3.8"
},
"numpy": {
"hashes": [
"sha256:06156c55771da4952a2432aa457cd96159675dcab4336f5307bff042535cb6ea",
"sha256:06d14d20b7e98c8c06bb62e56f2b64747dd10c422bb8adbf1e6dd82cd8442e12",
"sha256:0816fd52956e14551d8d71319d4b4fcfa1bcb21641f2c603f4eb64c65b1e1009",
"sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b",
"sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911",
"sha256:0a5a25ab780b8c29e443824abefc6ca79047ceeb889a6f76d7b1953649498e93",
"sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977",
"sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84",
"sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b",
"sha256:0c489f6c47bbed44918c9c8036a679614920da2a45f481d0eca2ad168ca5327f",
"sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33",
"sha256:12b38b0f3ddc1342863a6849f4fcb3f506e1d21179ebd34b7aa55a30cb50899f",
"sha256:140c5ce21f1eccb254e550c8431825cb716eb76e896202cffa7a0d2a843506da",
"sha256:14dea4f0d62ddd1a7f9d7b0003b35a537ac41a2b6205deec8c9c25a8e01748b4",
"sha256:15c6bde88f242747258cfee803f3161b7a2c1ffead0817e409d95444a79b4029",
"sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b",
"sha256:17581a2080012afe603c43005c9d050570e54683dde0d395e3edb4fa9c25f328",
"sha256:1a4f960e2e5c1084cf6b1d15482a5556ecc122855d631a2395063ab703d62fdd",
"sha256:1d9e0ddfb33a7a78fe92d49aaa2992a78ed5aff4cef7a21d8b1057cca075cc85",
"sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d",
"sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111",
"sha256:2b0e379a15c6b8eb69bb8170d10cfbb8a0dc9126b5402ee8860a2646f4111c3d",
"sha256:2d3d1e61191e408a11658a64e9f9bb61741ad28c160576c95dac9df6f74713b4",
"sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673",
"sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06",
"sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36",
"sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f",
"sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd",
"sha256:3b534c62b1887b4bfa80f633485f2a9338f5d46d720b6cc695d2ba8b38d98987",
"sha256:3e9276bff9a57100b53e5f9c44469baca1e58ec612e5143db568d69ec27b65ea",
"sha256:3f79d241e4833a2a570b6e6639d2114d497011e48a351798bba81fda51560ab7",
"sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e",
"sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62",
"sha256:47f11bf152d8707217feb46e9662a8b1aa3554a8ee56b64d2aa99c3e9914f101",
"sha256:48a724dbfad6f4933e2c8a22239980e1b5bc16868df3450cc4ebeb9522b7902f",
"sha256:4c33387be8eadc07d0834e0b9e2ead53117fe76ab2dadd37ee80d1df80be4c05",
"sha256:4e08e733600647242a9046b6aff888e72fe8a846b00855e5136e7641b08d25d8",
"sha256:4f9317da3aa64d0ee93950d3f319b3fe0169500e25c18223715cba39e89808bd",
"sha256:50b3dab872001b87052532bd4da3879fda856a2cf6c9418c19bfc94dc290e259",
"sha256:524b5311d21741f0b3f48efcdd3442546be3b38507a4e3b0f5138e4340f5dee0",
"sha256:53979581e6acdd75b7ce94e6d3b70994f9f8cf1021316d388a159f7f388bdc7f",
"sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb",
"sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300",
"sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b",
"sha256:5821c9831fad20cd1a8621a9ed483322ca97a9da9832690a4050ffedcb3e766b",
"sha256:58a07f2947aa06ca03d922a86ac30e403ce8282cd15904606bac852bf29ea2ad",
"sha256:590acae9e4b0baa895850c0edab988c329a196bacc7326f3249fa5fe7b94e5a8",
"sha256:61cf71f62033987ed49b78a19465f40fcbf6f7e94674eda21096ebde6935c2e0",
"sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb",
"sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8",
"sha256:64e8de086d2e4dac41fa286412321469b4535677184e78cc78e5061b44f0e4bf",
"sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195",
"sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2",
"sha256:713cb46d266514db773de52af677aa931cc896a4f5e52f494449c4ff53ce6051",
"sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce",
"sha256:77fa9826cbc7273e4bc3b7aa289b86936c942fe2c91bc35617c2417e14421592",
"sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6",
"sha256:7bcb4f360dc9e29b4f5f9fb36b35b429e731373843ccf39a22105bd809ef3138",
"sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2",
"sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33",
"sha256:86cc61c5479ed3b324011aa69484cae8f491b7f58dc0e54acf0894bdb4fae879",
"sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b",
"sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667",
"sha256:8ee3ab33c02a0bd7d219a184c9bc43811de373551529981035673ca2a1ba7b93",
"sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1",
"sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a",
"sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e",
"sha256:98a1486861fa3c603a5a3ccd56fc45b9756372bb30f6fb559b898fc2fad82e0d",
"sha256:9a6bdc19830703eee91e7eb2d671b165febefbf5eec6a4f163d1833d23be17af",
"sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745",
"sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc",
"sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324",
"sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0",
"sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8",
"sha256:be3ddd26a22d032914cfca5ef7db74f31adbd6c9d88a6f4e21ebd8e057d9474c",
"sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02",
"sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574",
"sha256:c8458becc562ee35b30b5e53173933414cf42e56b3f4f3d80997bf0dda7308d1",
"sha256:ca195cd9d1d84b3498532968237774a6e06e2a4afe706b87172f1d033b95e230",
"sha256:ccc68ee27362f8d3516deecffa124d1488ae20347628e357264e7e66dbdaba08",
"sha256:d3d59479b98cc364b8a08ddd854c7817b5c578a521b56af5a96b3a9db18cc9b7",
"sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd",
"sha256:dc2af0135139bbb26b1ea5bdc430e049edb745ae643cb898afb32549ce4801de",
"sha256:dc7ce867d277aa74555c67b93ef2a6f78bd7bd73e6c2bbafeb96f8bccd05b9d9",
"sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1",
"sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5",
"sha256:e5a64ac6016839fd906b3d7cc1f7ecb145c7d44a310234b6843f3b23b8ec0651",
"sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d",
"sha256:e74dc488a27b90f31ab307b4cf3f07a45bb78a0e91cfb36d69c6eced4f36089b",
"sha256:e82b8e0b88d493d4e882f18de30f679bf1197c82d8c799acb5fdb4068cadb945",
"sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883",
"sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575",
"sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529",
"sha256:f38fabd7b8d14fb7d63fbb2d07971d6edd518d2a43542c63c29164c901d2a758",
"sha256:f412923d4ce1ec29aa3cf7752598e5eb154f549cfbf62d7c6f3cc76cb25b32e0",
"sha256:f4e07df8476545da7cf23f75811f4fc334b06fc50d8e945e897cfc00c8f89690",
"sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa",
"sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3",
"sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211",
"sha256:f73e4fcf7455d3b734e6ecbafdbc12d3c1dd8f2146fd186e003ae1c8f00e5eed",
"sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1",
"sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736",
"sha256:fea6d6939d9bf098d96c6d22bb3e4ff39f8eb3f0f26b52c8c69ba06845490095",
"sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"
],
"markers": "python_version < '3.11'",
"version": "==2.1.0"
},
"openpyxl": {
"hashes": [
"sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2",
"sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==3.1.5"
},
"paho-mqtt": {
"hashes": [
"sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834",
"sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.7'",
"version": "==2.1.0"
},
"pandas": {
"hashes": [
"sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863",
"sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2",
"sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1",
"sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad",
"sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db",
"sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76",
"sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51",
"sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32",
"sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08",
"sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b",
"sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4",
"sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921",
"sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288",
"sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee",
"sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0",
"sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24",
"sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99",
"sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151",
"sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd",
"sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce",
"sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57",
"sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef",
"sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54",
"sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a",
"sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238",
"sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23",
"sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772",
"sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce",
"sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.9'",
"version": "==2.2.2"
},
"pillow": {
"hashes": [
"sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885",
"sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea",
"sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df",
"sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5",
"sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c",
"sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d",
"sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd",
"sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06",
"sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908",
"sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a",
"sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be",
"sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0",
"sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b",
"sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80",
"sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a",
"sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e",
"sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9",
"sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696",
"sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b",
"sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309",
"sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e",
"sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab",
"sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d",
"sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060",
"sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d",
"sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d",
"sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4",
"sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3",
"sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6",
"sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb",
"sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94",
"sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b",
"sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496",
"sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0",
"sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319",
"sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b",
"sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856",
"sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef",
"sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680",
"sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b",
"sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42",
"sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e",
"sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597",
"sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a",
"sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8",
"sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3",
"sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736",
"sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da",
"sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126",
"sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd",
"sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5",
"sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b",
"sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026",
"sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b",
"sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc",
"sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46",
"sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2",
"sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c",
"sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe",
"sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984",
"sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a",
"sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70",
"sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca",
"sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b",
"sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91",
"sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3",
"sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84",
"sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1",
"sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5",
"sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be",
"sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f",
"sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc",
"sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9",
"sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e",
"sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141",
"sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef",
"sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22",
"sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27",
"sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e",
"sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==10.4.0"
},
"psutil": {
"hashes": [
"sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35",
"sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0",
"sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c",
"sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1",
"sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3",
"sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c",
"sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd",
"sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3",
"sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0",
"sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2",
"sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6",
"sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d",
"sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c",
"sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0",
"sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132",
"sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14",
"sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==6.0.0"
},
"pydantic": {
"hashes": [
"sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a",
"sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"
],
"markers": "python_version >= '3.8'",
"version": "==2.8.2"
},
"pydantic-core": {
"hashes": [
"sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d",
"sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f",
"sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686",
"sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482",
"sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006",
"sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83",
"sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6",
"sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88",
"sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86",
"sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a",
"sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6",
"sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a",
"sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6",
"sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6",
"sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43",
"sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c",
"sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4",
"sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e",
"sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203",
"sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd",
"sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1",
"sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24",
"sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc",
"sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc",
"sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3",
"sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598",
"sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98",
"sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331",
"sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2",
"sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a",
"sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6",
"sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688",
"sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91",
"sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa",
"sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b",
"sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0",
"sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840",
"sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c",
"sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd",
"sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3",
"sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231",
"sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1",
"sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953",
"sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250",
"sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a",
"sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2",
"sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20",
"sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434",
"sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab",
"sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703",
"sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a",
"sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2",
"sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac",
"sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611",
"sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121",
"sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e",
"sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b",
"sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09",
"sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906",
"sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9",
"sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7",
"sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b",
"sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987",
"sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c",
"sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b",
"sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e",
"sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237",
"sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1",
"sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19",
"sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b",
"sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad",
"sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0",
"sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94",
"sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312",
"sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f",
"sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669",
"sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1",
"sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe",
"sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99",
"sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a",
"sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a",
"sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52",
"sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c",
"sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad",
"sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1",
"sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a",
"sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f",
"sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a",
"sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"
],
"markers": "python_version >= '3.8'",
"version": "==2.20.1"
},
"python-dateutil": {
"hashes": [
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.9.0.post0"
},
"python-multipart": {
"hashes": [
"sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026",
"sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==0.0.9"
},
"pytz": {
"hashes": [
"sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812",
"sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"
],
"version": "==2024.1"
},
"requests": {
"hashes": [
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
"sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==2.32.3"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.1"
},
"starlette": {
"hashes": [
"sha256:526f53a77f0e43b85f583438aee1a940fd84f8fd610353e8b0c1a77ad8a87e76",
"sha256:53a7439060304a208fea17ed407e998f46da5e5d9b1addfea3040094512a6379"
],
"markers": "python_version >= '3.8'",
"version": "==0.38.4"
},
"typing-extensions": {
"hashes": [
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
],
"markers": "python_version < '3.11'",
"version": "==4.12.2"
},
"tzdata": {
"hashes": [
"sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd",
"sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"
],
"markers": "python_version >= '2'",
"version": "==2024.1"
},
"urllib3": {
"hashes": [
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
"sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"
],
"markers": "python_version >= '3.8'",
"version": "==2.2.2"
},
"uvicorn": {
"hashes": [
"sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788",
"sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '3.8'",
"version": "==0.30.6"
},
"xlrd": {
"hashes": [
"sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd",
"sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"
],
"index": "pip_conf_index_global",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.0.1"
}
},
"develop": {}
}

99
SCLP/app.py Normal file
View File

@ -0,0 +1,99 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : api后端服务程序启动入口
"""
import argparse
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
import uvicorn
from config import VERSION, PREFIX_PATH
from utils import logger, database, misc
from models import sessions, houses, devices, products, householders, subsystem, parkinglots
from routers import (login, houses_manage, devices_manage, space_manage, householder_manage,
access_devices_0, access_devices_1,
parkinglot_0, parkinglot_1)
class MainServer:
def __init__(self, app_args):
self.port = app_args.port
self.app = None
self.prepare()
self.server_main()
def prepare(self):
"""准备处理,一些适配和配置加载工作"""
# 数据库初始化
logger.Logger.init("数据库执行初始化校验 ...")
th = database.get_table_handler()
sessions.SessionsTable.check(th)
houses.HousesTable.check(th)
products.ProductsTable.check(th)
devices.DevicesTable.check(th)
subsystem.SubsystemTable.check(th)
householders.HouseholdersTable.check(th)
parkinglots.ParkinglotsTable.check(th)
logger.Logger.init("数据库完成初始化校验 ✅")
self.app = FastAPI(
title="Smart Community Local Platform",
description="智慧社区本地化平台后端服务",
version=VERSION,
docs_url=f"{PREFIX_PATH}/docs" # 设为None则会关闭
)
# 注册路由模块中的路由
self.app.include_router(login.router, prefix=PREFIX_PATH, tags=["登录界面"])
self.app.include_router(houses_manage.router, prefix=PREFIX_PATH, tags=["内置信息"])
self.app.include_router(householder_manage.router, prefix=PREFIX_PATH, tags=["住户管理界面"])
self.app.include_router(access_devices_0.router, prefix=PREFIX_PATH, tags=["通行设备界面"])
self.app.include_router(access_devices_1.router, prefix=PREFIX_PATH, tags=["通行授权界面"])
self.app.include_router(parkinglot_0.router, prefix=PREFIX_PATH, tags=["车场管理界面"])
self.app.include_router(parkinglot_1.router, prefix=PREFIX_PATH, tags=["车场授权界面"])
self.app.include_router(devices_manage.router, prefix=PREFIX_PATH, tags=["设备管理界面"])
self.app.include_router(space_manage.router, prefix=PREFIX_PATH, tags=["通行空间映射界面"])
@self.app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
logger.Logger.error(f"{request.url.path} 请求参数错误 {exc.errors()} - {request.client.host}")
return JSONResponse(
status_code=422,
content={"status": False,
"message": f"请求参数错误,请检查输入"
f"{',错误类型:' + exc.errors()[0]['type'] if len(exc.errors()) > 0 else ''}"}
)
@self.app.exception_handler(misc.InvalidException)
async def invalid_target_exception_handler(request: Request, exc: misc.InvalidException):
logger.Logger.error(f"{request.url.path} {exc.message} - {request.client.host}")
return JSONResponse(
status_code=400,
content={"status": False, "message": f"{exc.message}"}
)
def server_main(self):
"""主服务程序"""
logger.Logger.debug(f"当前已开启 DEBUG 模式")
uvicorn.run(self.app, host="0.0.0.0", port=self.port, access_log=False)
def main(port: str = 9680):
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"-p",
"--port",
type=int,
default=port,
help=f"Port of server, default {logger.new_dc(port)}",
)
app_args = parser.parse_args()
MainServer(app_args)
if __name__ == "__main__":
main()

51
SCLP/auto_callback.py Normal file
View File

@ -0,0 +1,51 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 用于自动回复mqtt用于测试模拟设备下发请求成功
"""
import json
import time
import paho.mqtt.client as mqtt
from utils import logger
from config import BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWD
def on_connect(client, userdata, flags, rc):
logger.Logger.debug(f"\033[1;32mAUTO CALLBACK\033[0m 🔗 Mqtt connection! {{rc: {rc}}} 🔗")
client.subscribe("/jmlink/+/tml/service/call")
def on_disconnect(client, userdata, rc):
logger.Logger.debug(f"\033[1;31mAUTO CALLBACK\033[0m 🔌 Break mqtt connection! {{rc: {rc}}} 🔌")
def on_message(client, userdata, message):
logger.Logger.debug("\033[1;36mAUTO CALLBACK\033[0m <- 💡 接收到新数据,执行 on_message 中 ...")
msg = json.loads(message.payload.decode().replace("\x00", ""))
print(f"msg: {msg}")
if "messageId" in msg.keys():
topic = f"{message.topic}_resp"
print(topic)
auto_callback = {
"messageId": msg["messageId"],
"code": 0,
"data": {
"code": 0
}
}
client.publish(topic, json.dumps(auto_callback))
print(auto_callback)
client = mqtt.Client(client_id="auto_callback@python")
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.username_pw_set(BROKER_USERNAME, BROKER_PASSWD)
client.connect(BROKER_HOST, BROKER_PORT)
client.loop_start()
while True:
time.sleep(86400)

38
SCLP/backend.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : mqtt后端服务程序启动入口
"""
import time
from config import BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWD
from utils.misc import create_mqtt_client, UserData, on_connect, on_disconnect, on_publish
from device.services import on_message
def main_mqtt():
client_dict = {}
userdata = UserData()
userdata.set_topics([
"/jmlink/+/comm/register",
"/jmlink/+/comm/sub/register",
"/jmlink/+/comm/online",
"/jmlink/+/comm/offline",
"/jmlink/+/comm/post",
"/jmlink/+/tml/event/post",
"/jmlink/+/tml/property/post"
])
userdata.set_clients(client_dict)
client = create_mqtt_client(BROKER_HOST, BROKER_PORT,
userdata, on_message, on_publish, on_connect, on_disconnect,
"backend@python", username=BROKER_USERNAME, password=BROKER_PASSWD)
client.loop_start()
client_dict["center"] = [client, userdata]
while True:
time.sleep(86400)
if __name__ == "__main__":
main_mqtt()

50
SCLP/config.py Normal file
View File

@ -0,0 +1,50 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 根据实际情况动态变化的配置
"""
import os
from utils import logger
from utils.misc import get_ip_address
VERSION = "0.0.0.1"
PREFIX_PATH = "/api/v1"
DB_PATH = "./data/SCLP.db"
FACES_DIR_PATH = "data/FaceImages"
SUB_PATH = "faces"
ENV_FLAG = os.getenv("ENV_FLAG", "DEPLOY")
if_name = "WLAN" if os.name != "posix" else "enp1s0" if ENV_FLAG != "WJ14P" else "wlp2s0"
LAN_IP, _ = get_ip_address(if_name)
if ENV_FLAG == "DEPLOY":
if "10.59.246" in LAN_IP:
ENV_FLAG = "LAB"
if "192.168.1" in LAN_IP:
ENV_FLAG = "X13"
SLEEP_TIME = 0.03 # mqtt publish后的循环等待返回时间30ms检测一次
TIMEOUT_SECOND = 30 # 等待超时时间3s
LOGGER_DEBUG = os.environ.get("LOGGER_DEBUG", "True")
logger.DEBUG = False if LOGGER_DEBUG.lower() == "false" else True
logger.LOGGER_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "run.log")
LOCAL_IP = "127.0.0.1"
ENV_CONFIG = {
"B760M": {"HOST": "b760m.langzihan.top", "PORT": 9680, "BROKER_HOST": LOCAL_IP, "BROKER_PORT": 1883},
"M5": {"HOST": "sclp.langzihan.top", "PORT": 80, "BROKER_HOST": LOCAL_IP, "BROKER_PORT": 1883},
"X13": {"HOST": LOCAL_IP, "PORT": 9680, "BROKER_HOST": "intranet.b760m.langzihan.top", "BROKER_PORT": 1883},
"WJ14P": {"HOST": LOCAL_IP, "PORT": 9680, "BROKER_HOST": "intranet.b760m.langzihan.top", "BROKER_PORT": 1883},
"LAB": {"HOST": "10.59.246.170", "PORT": 9680, "BROKER_HOST": "10.59.246.161", "BROKER_PORT": 31883},
"DEPLOY": {"HOST": "10.59.246.170", "PORT": 9680, "BROKER_HOST": "10.59.246.161", "BROKER_PORT": 31883},
}
APP_HOST = ENV_CONFIG[ENV_FLAG]["HOST"]
APP_PORT = ENV_CONFIG[ENV_FLAG]["PORT"]
IMAGE_SERVER_PORT = APP_PORT + 1
BROKER_HOST = ENV_CONFIG[ENV_FLAG]["BROKER_HOST"]
BROKER_PORT = ENV_CONFIG[ENV_FLAG]["BROKER_PORT"]
BROKER_USERNAME = "test@sclp"
BROKER_PASSWD = "test@sclp"
AUTO_CALLBACK = False if ENV_FLAG == "LAB" else True

BIN
SCLP/data/mvboli.ttf Normal file

Binary file not shown.

323
SCLP/device/call.py Normal file
View File

@ -0,0 +1,323 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 由服务主动调用的一些方法实现调用后自动连接mqtt进行任务下发和回馈处理解耦服务端与mqtt
"""
import json
import time
import traceback
from typing import Optional
from pydantic import BaseModel, field_validator
from config import (BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWD, PREFIX_PATH, SUB_PATH,
SLEEP_TIME, TIMEOUT_SECOND)
from config import APP_HOST as HOST_IP
from config import IMAGE_SERVER_PORT as PORT
from utils import logger
from utils.misc import generate_captcha_text, UserData, create_mqtt_client, on_publish, on_disconnect, now_tz_datetime
class CallMessage:
def __init__(self, service_code, params):
self.messageId = generate_captcha_text(16)
self.version = "1.0"
self.time = int(time.time() * 1000)
self.fromUid = "system"
self.serviceCode = service_code
self.params = params
def dict(self):
_dict = self.__dict__.copy()
for key, value in self.__dict__.items():
if value is None:
_dict.pop(key)
_dict["from"] = "backend"
return _dict
def __call__(self, *args, **kwargs):
return json.dumps(self.dict())
class AddFaceItem(BaseModel):
name: str
user_id: str | int
device_ids: str
start_date: str = now_tz_datetime()
phone_number: str = ""
face_url: str
expire_date: Optional[str] = "2099-01-01T01:01:01.001Z"
@field_validator("user_id")
def check_user_id(cls, value):
if isinstance(value, int):
return str(value)
return value
class DelFaceItem(BaseModel):
user_id: str | int
device_ids: str
@field_validator("user_id")
def check_user_id(cls, value):
if isinstance(value, int):
return str(value)
return value
ACTION = {
"insert": "新增",
"update": "更新",
"delete": "删除"
}
class VehicleRegistrationItem(BaseModel):
car_plate_no: str # 车牌
registration_id: str
owner_id: str
owner_name: str
registration_time: str
begin_time: str
end_time: str = "2099-01-01T01:01:01.001Z"
action: str # insert, update, delete
registration_type: str # 0: 业主1: 访客
class UpdateFixedCardItem(BaseModel):
vehicle_owner_id: str
vehicle_owner_name: str
vehicle_owner_phone: str
plate_number: str # 车牌
effective_date_begin: str
effective_date_end: str
action: str # insert, update, delete
card_id: str # 在新增时进行生成
def on_connect(client, userdata, _flags, rc):
logger.Logger.debug(f"🔗 Mqtt connection! {{rc: {rc}}} 🔗")
if userdata.topics:
_topics = [(topic, 0) for topic in userdata.topics]
client.subscribe(_topics)
logger.Logger.debug(f"subscribe topics: {userdata.topics}")
def on_message(_client, userdata: UserData, message):
try:
logger.Logger.debug("💡 接收到新数据,执行 on_message 中 ...", log_path=None)
msg = json.loads(message.payload.decode().replace("\x00", ""))
logger.Logger.info(f"{message.topic}: {msg}")
userdata.set_topic(message.topic)
if userdata.token:
if "messageId" in msg.keys() and userdata.token == msg["messageId"]: # 兼容aiot平台
userdata.set_status_add("status", True)
userdata.set_status_add("response", msg)
except Exception as e:
logger.Logger.error(f"{type(e).__name__}, {e}")
if logger.DEBUG:
traceback.print_exc()
class ServicesCall:
def __init__(self):
# 创建mqtt连接
self.userdata = UserData()
self.client = create_mqtt_client(BROKER_HOST, BROKER_PORT,
self.userdata, on_message, on_publish, on_connect, on_disconnect,
username=BROKER_USERNAME, password=BROKER_PASSWD)
self.client.loop_start()
def __del__(self):
self.client.loop_stop()
def add_face(self, device_id, obj: AddFaceItem) -> tuple[bool, int, str]:
if obj.face_url.startswith("http"):
face_filename = obj.face_url.split('/')[-1]
else:
face_filename = obj.face_url
obj.face_url = f"http://{HOST_IP}:{PORT}{PREFIX_PATH}/{SUB_PATH}/{face_filename}"
topic = f"/jmlink/{device_id}/tml/service/call"
self.userdata.set_topic(topic)
logger.Logger.debug(f"{'=' * 10} 🚩 AddFace 🚩 {'=' * 10}")
logger.Logger.debug(f"{obj.device_ids} 添加用户 {obj.user_id} 的人脸授权")
cm = CallMessage("add_face", obj.__dict__)
self.userdata.set_message(cm.dict())
# 等待回传
self.userdata.set_token(cm.messageId)
self.userdata.set_status_add("status", False)
self.userdata.set_status_add("start_timestamp", time.time())
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
self.client.subscribe(topic_resp)
self.client.publish(topic, cm())
try:
while True:
if self.userdata.status["status"]:
if "code" in self.userdata.status["response"].keys():
if self.userdata.status["response"]["code"] != 0:
error_code = self.userdata.status['response']['code']
logger.Logger.error(
f"用户 - {obj.user_id} 在设备 - {device_id} 上人脸下置失败,错误码 {error_code}")
return False, error_code, self.userdata.status['response']['message']
else:
logger.Logger.info(f"设备 - {obj.device_ids} 用户({obj.user_id}) 人脸完成授权")
return True, 0, ""
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
logger.Logger.error("等待回复超时")
return False, -1, "等待回复超时"
time.sleep(SLEEP_TIME)
finally:
self.client.unsubscribe(topic_resp)
logger.Logger.debug(f"移除订阅: {topic_resp}")
self.userdata.set_token(None)
self.userdata.set_status_remove("response")
logger.Logger.debug(f"{'=' * 10} 🏁 AddFace 🏁 {'=' * 10}")
def del_face(self, device_id, obj: DelFaceItem) -> tuple[bool, int, str]:
topic = f"/jmlink/{device_id}/tml/service/call"
self.userdata.set_topic(topic)
logger.Logger.debug(f"{'=' * 10} 🚩 DelFace 🚩 {'=' * 10}")
logger.Logger.debug(f"{obj.device_ids} 移除用户 {obj.user_id} 的人脸授权")
cm = CallMessage("del_face", obj.__dict__)
self.userdata.set_message(cm.dict())
# 等待回传
self.userdata.set_token(cm.messageId)
self.userdata.set_status_add("status", False)
self.userdata.set_status_add("start_timestamp", time.time())
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
self.client.subscribe(topic_resp)
self.client.publish(topic, cm())
try:
while True:
if self.userdata.status["status"]:
if "code" in self.userdata.status["response"].keys():
if self.userdata.status["response"]["code"] != 0:
error_code = self.userdata.status['response']['code']
logger.Logger.error(
f"用户 - {obj.user_id} 在设备 - {device_id} 上人脸移除失败,错误码 {error_code}")
return False, error_code, self.userdata.status['response']['message']
else:
logger.Logger.info(f"设备 - {obj.device_ids} 用户({obj.user_id}) 人脸完成移除")
return True, 0, ""
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
logger.Logger.error("等待回复超时")
return False, -1, "等待回复超时"
time.sleep(SLEEP_TIME)
finally:
self.client.unsubscribe(topic_resp)
logger.Logger.debug(f"移除订阅: {topic_resp}")
self.userdata.set_token(None)
self.userdata.set_status_remove("response")
logger.Logger.debug(f"{'=' * 10} 🏁 DelFace 🏁 {'=' * 10}")
def vehicle_registration(self, device_id, obj: VehicleRegistrationItem) -> tuple[bool, int, str]:
topic = f"/jmlink/{device_id}/tml/service/call"
self.userdata.set_topic(topic)
logger.Logger.debug(f"{'=' * 10} 🚩 VehicleRegistration 🚩 {'=' * 10}")
logger.Logger.debug(f"{device_id} {ACTION[obj.action]}车辆 {obj.car_plate_no} 信息")
cm = CallMessage("vehicle_registration", obj.__dict__)
self.userdata.set_message(cm.dict())
# 等待回传
self.userdata.set_token(cm.messageId)
self.userdata.set_status_add("status", False)
self.userdata.set_status_add("start_timestamp", time.time())
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
self.client.subscribe(topic_resp)
self.client.publish(topic, cm())
try:
while True:
if self.userdata.status["status"]:
if "code" in self.userdata.status["response"].keys():
if self.userdata.status["response"]["code"] != 0:
error_code = self.userdata.status['response']['code']
logger.Logger.error(f"{topic_resp} 返回错误码: {error_code}")
return False, error_code, self.userdata.status['response']['message']
else:
logger.Logger.info(f"车辆({obj.car_plate_no}) 信息完成{ACTION[obj.action]}")
return True, 0, ""
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
logger.Logger.error("等待回复超时")
return False, -1, "等待回复超时"
time.sleep(SLEEP_TIME)
finally:
self.client.unsubscribe(topic_resp)
logger.Logger.debug(f"移除订阅: {topic_resp}")
self.userdata.set_token(None)
self.userdata.set_status_remove("response")
logger.Logger.debug(f"{'=' * 10} 🏁 VehicleRegistration 🏁 {'=' * 10}")
def update_fixed_card(self, device_id, obj: UpdateFixedCardItem) -> tuple[bool, int, str]:
logger.Logger.debug(f"{'=' * 10} 🚩 UpdateFixedCard 🚩 {'=' * 10}")
topic = f"/jmlink/{device_id}/tml/service/call"
self.userdata.set_topic(topic)
logger.Logger.debug(f"{device_id} {ACTION[obj.action]}用户 {obj.plate_number} 的月卡信息")
cm = CallMessage("update_fixed_card", obj.__dict__)
self.userdata.set_message(cm.dict())
# 等待回传
self.userdata.set_token(cm.messageId)
self.userdata.set_status_add("status", False)
self.userdata.set_status_add("start_timestamp", time.time())
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
self.client.subscribe(topic_resp)
self.client.publish(topic, cm())
try:
while True:
if self.userdata.status["status"]:
if "code" in self.userdata.status["response"].keys():
if self.userdata.status["response"]["code"] != 0:
error_code = self.userdata.status['response']['code']
logger.Logger.error(f"{topic_resp} 返回错误码: {error_code}")
return False, error_code, self.userdata.status['response']['message']
else:
logger.Logger.info(f"车辆({obj.plate_number}) 月卡信息完成修改")
return True, 0, ""
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
logger.Logger.error("等待回复超时")
return False, -1, "等待回复超时"
time.sleep(SLEEP_TIME)
finally:
self.client.unsubscribe(topic_resp)
logger.Logger.debug(f"移除订阅: {topic_resp}")
self.userdata.set_token(None)
self.userdata.set_status_remove("response")
logger.Logger.debug(f"{'=' * 10} 🏁 UpdateFixedCard 🏁 {'=' * 10}")
if __name__ == '__main__':
sc = ServicesCall()
sc.del_face(2, DelFaceItem(
user_id="1234",
device_ids=str([2])
))
sc.add_face(2, AddFaceItem(
name="测试",
user_id="ceshi",
face_url="http://ip:port/api/v1/faces/person_demo.jpg",
device_ids=str([2, 321]),
start_date="2024-01-01T18:00:00.000Z"
))
sc.vehicle_registration(2, VehicleRegistrationItem(
car_plate_no="鲁G95339",
registration_id="5a12265cf73f261148770c12328bb195",
owner_id="1244668310399733760",
owner_name="测试业主",
registration_time="2024-05-27T16:12:26.895Z",
begin_time="2024-05-27T16:12:26.895Z",
end_time="2034-05-27T16:12:26.895Z",
action="insert",
registration_type='0'
))
sc.update_fixed_card(2, UpdateFixedCardItem(
vehicle_owner_id="1244668310399733760",
vehicle_owner_name="测试业主",
vehicle_owner_phone="1244668310399733760",
action="insert",
card_id=str(int(time.time() * 1000)),
effective_date_begin=now_tz_datetime(),
effective_date_end=now_tz_datetime(365 * 10),
plate_number="鲁G95339"
))

100
SCLP/device/message.py Normal file
View File

@ -0,0 +1,100 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : None
"""
import json
import time
class BaseResp:
def __init__(self, message_id, data):
self.message_id = message_id
self.time = int(time.time() * 1000)
self.data = data
def dict(self):
if not isinstance(self.data, list):
if isinstance(self.data, dict):
data = self.data
else:
data = self.data()
else:
data = [i if isinstance(i, dict) else i() for i in self.data]
return {
"messageId": self.message_id,
"time": self.time,
"data": data
}
def __call__(self):
return json.dumps(self.dict())
class SimpleResp:
def __init__(self, message_id, code):
self.message_id = message_id
self.time = int(time.time() * 1000)
self.code = code
def dict(self):
return {
"messageId": self.message_id,
"time": self.time,
"code": self.code
}
def __call__(self, *args, **kwargs):
return json.dumps(self.dict())
class ErrorResp:
def __init__(self, message_id, code):
self.message_id = message_id
self.time = int(time.time() * 1000)
self.code = code
def dict(self):
return {
"messageId": self.message_id,
"time": self.time,
"data": {"code": self.code}
}
def __call__(self, *args, **kwargs):
return json.dumps(self.dict())
class DRRespItem:
"""设备注册返回数据体"""
def __init__(self, device_id: str, device_name: str, device_secret: str, code: int):
self.device_id = device_id
self.device_name = device_name
self.device_secret = device_secret
self.code = code
def __call__(self, *args, **kwargs):
return {
"deviceId": self.device_id,
"deviceName": self.device_name,
"deviceSecret": self.device_secret,
"code": self.code
}
class SDRRespItem:
"""设备注册返回数据体"""
def __init__(self, device_id: str, device_name: str, code: int):
self.device_id = device_id
self.device_name = device_name
self.code = code
def __call__(self, *args, **kwargs):
return {
"deviceId": self.device_id,
"deviceName": self.device_name,
"code": self.code
}

520
SCLP/device/services.py Normal file
View File

@ -0,0 +1,520 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : None
"""
import json
import traceback
import time
from config import DB_PATH
from device.message import ErrorResp, DRRespItem, BaseResp, SDRRespItem, SimpleResp
from models.devices import DevicesTable, DeviceRegister
from models.products import ProductsTable
from utils.misc import generate_captcha_text, now_datetime_second
from utils import logger
from utils.database import SQLiteDatabaseEngine, BaseTable
from utils.misc import UserData
def on_message(_client, userdata: UserData, message):
try:
logger.Logger.debug("💡 接收到新数据,执行 on_message 中 ...", log_path=None)
msg = json.loads(message.payload.decode().replace("\x00", ""))
logger.Logger.info(f"{message.topic}: {msg}")
userdata.set_topic(message.topic)
if userdata.table_handler is None:
_db = SQLiteDatabaseEngine(db_path=DB_PATH)
userdata.set_table_handler(BaseTable(_db.connection, _db.cursor))
if userdata.token:
if "messageId" in msg.keys() and userdata.token == msg["messageId"]: # 兼容aiot平台
userdata.set_status_add("status", True)
userdata.set_status_add("response", msg)
else:
auto_service(msg, userdata)
except Exception as e:
logger.Logger.error(f"{type(e).__name__}, {e}")
if logger.DEBUG:
traceback.print_exc()
class BaseService:
def handle(self, **kwargs):
"""must be `override`"""
pass
class Services:
"""业务逻辑入口"""
class DeviceRegisterService(BaseService):
"""直连设备、网关设备注册"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("设备注册请求已接收,正在执行注册 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
# 获取产品信息
params = msg["params"]
product = ProductsTable.get_product_info(userdata.table_handler, params["productId"])
if product is None:
logger.Logger.error("DeviceRegisterService -> 产品ID不存在")
message = ErrorResp(message_id, 601)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if product.node_type not in ["网关设备", "直连设备"]:
logger.Logger.error("DeviceRegisterService -> 节点类型错误")
message = ErrorResp(message_id, 503)
userdata.set_message(message.dict())
client.publish(topic, message())
return
sct = generate_captcha_text(32)
dr = DeviceRegister(
device_mac=params["deviceName"],
device_name=params["displayName"] if params["displayName"] else "",
device_desc=params["desc"] if params["desc"] else "",
third_local_device_id=params["thirdLocalDeviceId"] if params["thirdLocalDeviceId"] else "",
device_sct=sct,
gateway_id=None,
gateway_name=None,
product_name=product.product_name,
node_type=product.node_type
)
device_id = DevicesTable.insert_by_device_register(userdata.table_handler, dr)
resp = BaseResp(message_id, DRRespItem(
str(device_id),
params["displayName"],
sct,
0
))
userdata.set_message(resp.dict())
client.publish(topic, resp())
except (KeyError, TypeError):
logger.Logger.error("DeviceRegisterService -> 关键参数缺失")
message = ErrorResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class SubDeviceRegisterService(BaseService):
"""子设备注册"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("子设备注册请求已接收,正在执行注册 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
gateway_id = int(userdata.topic.split("/")[2])
gateway_info = DevicesTable.get_device_info(userdata.table_handler, gateway_id)
if gateway_info is None: # 确保当前主题中的设备ID存在
logger.Logger.error("SubDeviceRegisterService -> 并无对应上级设备")
message = ErrorResp(message_id, 601)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if gateway_info.node_type != "网关设备": # 确保当前主题是网关设备
logger.Logger.error("SubDeviceRegisterService -> 节点类型错误")
message = ErrorResp(message_id, 503)
userdata.set_message(message.dict())
client.publish(topic, message())
return
data = []
for params in msg["params"]:
product_info = ProductsTable.get_product_info(userdata.table_handler, params["productId"])
if product_info is None:
logger.Logger.error("SubDeviceRegisterService -> 产品ID不存在")
data.append({"code": 601})
continue
if product_info.node_type != "网关子设备": # 确保注册的产品是网关子设备
logger.Logger.error("SubDeviceRegisterService -> 产品ID节点类型错误")
data.append({"code": 503})
continue
dr = DeviceRegister(
device_mac=params["deviceName"],
device_name=params["displayName"] if params["displayName"] else "",
device_desc=params["desc"] if params["desc"] else "",
third_local_device_id=params["thirdLocalDeviceId"] if params["thirdLocalDeviceId"] else "",
device_sct=None,
gateway_id=gateway_id,
gateway_name=gateway_info.device_name,
product_name=product_info.product_name,
node_type=product_info.node_type
)
device_id = DevicesTable.insert_by_device_register(userdata.table_handler, dr)
data.append(SDRRespItem(
str(device_id),
params["displayName"] if params["displayName"] else "",
0
))
message = BaseResp(message_id, data)
userdata.set_message(message.dict())
client.publish(topic, message())
except (KeyError, TypeError) as e:
logger.Logger.error("DeviceRegisterService -> 关键参数缺失")
logger.Logger.error(f"{type(e).__name__}, {e}")
if logger.DEBUG:
traceback.print_exc()
message = ErrorResp(message_id, 401)
userdata.set_message(message)
client.publish(topic,
{"messageId": message_id, "time": int(time.time() * 1000), "data": [{"code": 401}]})
class UpdateDeviceStatusService(BaseService):
"""设备在线和离线状态的变更"""
def handle(self, msg: dict, userdata: UserData):
message_type = userdata.topic.split("/")[-1]
if message_type == "online":
device_status = "在线"
else:
device_status = "离线"
logger.Logger.debug(f"设备状态更新请求已接收,正在执行{device_status} ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
device_id = int(userdata.topic.split("/")[2])
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
if device_info is None: # 确保当前主题中的设备ID存在
logger.Logger.error("UpdateDeviceStatusService -> 并无对应设备")
message = ErrorResp(message_id, 40014)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if message_type == "offline" and device_info.node_type == "网关设备":
DevicesTable.offline_gateway(userdata.table_handler, device_id)
else:
DevicesTable.update_device_status(userdata.table_handler, device_id, device_status)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
except (KeyError, TypeError):
logger.Logger.error("UpdateDeviceStatusService -> 关键参数缺失")
message = SimpleResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class PropertyService(BaseService):
"""设备属性上报事件"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("设备属性上报事件已接收,正在处理 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
device_id = int(userdata.topic.split("/")[2])
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
if device_info is None: # 确保当前主题中的设备ID存在
logger.Logger.info("PropertyService -> 并无对应设备")
logger.Logger.debug("忽略的事件上报请求已接收,即将给予成功回馈 ...", log_path=None)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if "停车" not in device_info.product_name: # 只处理停车场设备的信息上报
logger.Logger.debug("忽略的事件上报请求已接收,即将给予成功回馈 ...", log_path=None)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
return
data = msg["values"]
nt = now_datetime_second()
# 判断是否存在对应停车场若不存在进行新建若存在则更新除ID之外的信息
userdata.table_handler.execute(
"""
INSERT INTO parkinglots
(id, number, name, type, update_datetime)
VALUES (?, ?, ?, 'parkinglot', ?)
ON CONFLICT (id, type)
DO UPDATE SET number = ?, name = ?, update_datetime = ?
""", (data["parkinglot_no"], device_id, data["parkinglot_name"], nt,
device_id, data["parkinglot_name"], nt)
)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
except (KeyError, TypeError):
logger.Logger.error("PropertyService -> 关键参数缺失")
message = SimpleResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class AreaService(BaseService):
"""车场区域信息变化同步事件"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("车场区域信息变化同步事件已接收,正在处理 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
device_id = int(userdata.topic.split("/")[2])
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
if device_info is None: # 确保当前主题中的设备ID存在
logger.Logger.error("AreaService -> 并无对应设备")
message = SimpleResp(message_id, 40014)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if "停车" not in device_info.product_name: # 确保当前主题是停车场设备
logger.Logger.error("AreaService -> 节点类型错误")
message = SimpleResp(message_id, 503)
userdata.set_message(message.dict())
client.publish(topic, message())
return
params = msg["params"]
# ["区域ID","父区域ID","区域编码","区域名称","车位总数"]
# {"area_name":"地面","area_no":"1","place_numbers":"60","area_id":"1","parent_area_id":"1"}
if params["parent_area_id"] == params["area_id"]:
params["parent_area_id"] = None
nt = now_datetime_second()
userdata.table_handler.execute(
"SELECT id FROM parkinglots WHERE number = ? AND type = 'parkinglot'", (device_id,))
res = userdata.table_handler.cursor.fetchall()
if res:
parkinglot_number = res[0][0]
# 判断是否存在对应区域若不存在进行新建若存在则更新除ID之外的信息
userdata.table_handler.execute(
"""
INSERT OR REPLACE INTO parkinglots
(id, number, name, type, area_id, parkinglot_id, update_datetime)
VALUES (?, ?, ?, 'area', ?, ?, ?)
""", (params["area_id"], params["area_no"], params["area_name"], params["parent_area_id"],
parkinglot_number, nt)
)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
else:
message = SimpleResp(message_id, 40014) # 对应设备属性没有上报,数据库中未记载设备编码
userdata.set_message(message.dict())
client.publish(topic, message())
return
except (KeyError, TypeError):
logger.Logger.error("AreaService -> 关键参数缺失")
message = SimpleResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class ChannelService(BaseService):
"""车场通道信息变化同步事件"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("车场通道信息变化同步事件已接收,正在执行处理 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
device_id = int(userdata.topic.split("/")[2])
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
if device_info is None: # 确保当前主题中的设备ID存在
logger.Logger.error("ChannelService -> 并无对应设备")
message = SimpleResp(message_id, 40014)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if "停车" not in device_info.product_name: # 确保当前主题是停车场设备
logger.Logger.error("ChannelService -> 节点类型错误")
message = SimpleResp(message_id, 503)
userdata.set_message(message.dict())
client.publish(topic, message())
return
params = msg["params"]
# ["区域ID","通道ID","通道编码","通道名称","通道类型"]
# {"channel_name":"测试出口","channel_no":"13","area_id":"1","channel_type":"1","channel_id":"13"}
channel_type = int(params["channel_type"])
if channel_type == 0:
params["channel_type"] = "入口"
elif channel_type == 1:
params["channel_type"] = "出口"
else:
params["channel_type"] = "出入口"
nt = now_datetime_second()
userdata.table_handler.execute(
"SELECT id FROM parkinglots WHERE number = ? AND type = 'parkinglot'", (device_id,))
res = userdata.table_handler.cursor.fetchall()
if res:
parkinglot_number = res[0][0]
# 判断是否存在对应区域若不存在进行新建若存在则更新除ID之外的信息
userdata.table_handler.execute(
"""
INSERT OR REPLACE INTO parkinglots
(id, number, name, type, area_id, parkinglot_id, channel_type, update_datetime)
VALUES (?, ?, ?, 'channel', ?, ?, ?, ?)
""", (params["channel_id"], params["channel_no"], params["channel_name"],
params["area_id"], parkinglot_number, params["channel_type"], nt)
)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
else:
message = SimpleResp(message_id, 40014) # 对应设备属性没有上报,数据库中未记载设备编码
userdata.set_message(message.dict())
client.publish(topic, message())
return
except (KeyError, TypeError):
logger.Logger.error("ChannelService -> 关键参数缺失")
message = SimpleResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class GateService(BaseService):
"""道闸信息同步"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("道闸信息同步事件已接收,正在执行处理 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
device_id = int(userdata.topic.split("/")[2])
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
if device_info is None: # 确保当前主题中的设备ID存在
logger.Logger.error("ChanGateService -> 并无对应设备")
message = SimpleResp(message_id, 40014)
userdata.set_message(message.dict())
client.publish(topic, message())
return
if "停车" not in device_info.product_name: # 确保当前主题是停车场设备
logger.Logger.error("GateService -> 节点类型错误")
message = SimpleResp(message_id, 503)
userdata.set_message(message.dict())
client.publish(topic, message())
return
params = msg["params"]
# ["区域ID","通道ID","道闸编码","道闸ID","道闸名称","道闸品牌","道闸状态","运行状态","是否在线"]
# {"gate_id":"13","gate_name":"测试出口","is_online":"0","gate_no":"13","area_id":"1","channel_id":"13"}
if "is_online" in params.keys():
is_online = int(params["is_online"])
if is_online == 0:
params["is_online"] = "下线"
else:
params["is_online"] = "上线"
else:
params["is_online"] = ""
if "gate_status" in params.keys():
gate_status = int(params["gate_status"])
if gate_status == 0:
params["gate_status"] = "关闭"
elif gate_status == 1:
params["gate_status"] = "开启"
elif gate_status == 2:
params["gate_status"] = "常开"
else:
params["gate_status"] = "常闭"
else:
params["gate_status"] = ""
if "running_status" in params.keys():
running_status = int(params["running_status"])
if running_status == 0:
params["running_status"] = "正常"
elif running_status == 1:
params["running_status"] = "故障"
else:
params["running_status"] = ""
nt = now_datetime_second()
userdata.table_handler.execute(
"SELECT id FROM parkinglots WHERE number = ? AND type = 'parkinglot'", (device_id,))
res = userdata.table_handler.cursor.fetchall()
if res:
parkinglot_number = res[0][0]
# 判断是否存在对应区域若不存在进行新建若存在则更新除ID之外的信息
userdata.table_handler.execute(
"""
INSERT OR REPLACE INTO parkinglots
(id, number, name, type, area_id, parkinglot_id, channel_id,
is_online, gate_status, running_status, update_datetime)
VALUES (?, ?, ?, 'gate', ?, ?, ?, ?, ?, ?, ?)
""", (params["gate_id"], params["gate_no"], params["gate_name"],
params.get("area_id", ""), parkinglot_number, params.get("channel_id", ""),
params["is_online"], params["gate_status"], params["running_status"], nt)
)
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
else:
message = SimpleResp(message_id, 40014) # 对应设备属性没有上报,数据库中未记载设备编码
userdata.set_message(message.dict())
client.publish(topic, message())
return
except (KeyError, TypeError):
logger.Logger.error("GateService -> 关键参数缺失")
message = SimpleResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class NoHandlePostService(BaseService):
"""忽略的事件上报,直接返回成功的反馈"""
def handle(self, msg: dict, userdata: UserData):
logger.Logger.debug("忽略的事件上报请求已接收,即将给予成功回馈 ...", log_path=None)
client = userdata.clients["center"][0]
topic = f"{userdata.topic}_resp"
userdata.set_topic(topic)
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
try:
device_id = int(userdata.topic.split("/")[2])
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
if device_info is None: # 确保当前主题中的设备ID存在
logger.Logger.error("SubDeviceRegisterService -> 并无对应设备")
message = SimpleResp(message_id, 601)
userdata.set_message(message.dict())
client.publish(topic, message())
return
message = SimpleResp(message_id, 0)
userdata.set_message(message.dict())
client.publish(topic, message())
except (KeyError, TypeError):
logger.Logger.error("UpdateDeviceStatusService -> 关键参数缺失")
message = SimpleResp(message_id, 401)
userdata.set_message(message.dict())
client.publish(topic, message())
class UpdateFixedCardService(BaseService):
def handle(self, **kwargs):
pass
class VehicleRegistration(BaseService):
def handle(self, **kwargs):
pass
def auto_service(msg: dict, userdata: UserData):
"""自动化加载不同的服务层"""
message_type = userdata.topic.split("/")[-1]
servicer = None
if message_type == "register": # 设备注册
if userdata.topic.split("/")[-2] == "sub":
servicer = Services.SubDeviceRegisterService()
else:
servicer = Services.DeviceRegisterService()
elif message_type in ["online", "offline"]: # 设备上下线
servicer = Services.UpdateDeviceStatusService()
elif message_type == "post": # 事件上报,一般是通行事件,不做处理,直接返回成功
if userdata.topic.split("/")[-2] == "property":
servicer = Services.PropertyService()
elif "eventCode" in msg.keys() and msg["eventCode"] in ["area", "channel", "gate"]:
if msg["eventCode"] == "area":
servicer = Services.AreaService()
elif msg["eventCode"] == "channel":
servicer = Services.ChannelService()
elif msg["eventCode"] == "gate":
servicer = Services.GateService()
else:
servicer = Services.NoHandlePostService()
# 构建服务层实体并进行调用
if servicer is not None:
servicer.handle(msg, userdata)

80
SCLP/image_server.py Normal file
View File

@ -0,0 +1,80 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : api后端服务程序启动入口
"""
import argparse
import os
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse
import aiofiles
import uvicorn
from config import VERSION, PREFIX_PATH, FACES_DIR_PATH, IMAGE_SERVER_PORT
from utils import logger
from routers import edge_simulation_api
class ImageServer:
def __init__(self, app_args):
self.port = app_args.port
self.app = None
self.prepare()
self.server_main()
def prepare(self):
"""准备处理,一些适配和配置加载工作"""
# 清理图片库,移除不存在数据库中的图片
logger.Logger.init("人脸图片库执行初始化校验 ...")
for filename in os.listdir(FACES_DIR_PATH):
if filename.startswith("_"):
os.remove(os.path.join(FACES_DIR_PATH, filename))
logger.Logger.init("人脸图片库完成初始化校验 ✅")
self.app = FastAPI(
title="Image Server",
description="图片后端服务",
version=VERSION,
docs_url=f"{PREFIX_PATH}/docs" # 设为None则会关闭
)
# self.app.mount(f"{PREFIX_PATH}/faces", StaticFiles(directory=FACES_DIR_PATH), name="static")
self.app.include_router(edge_simulation_api.router, tags=["边缘端请求接口"])
@self.app.get(f"{PREFIX_PATH}/faces/{{image_filename}}", summary="返回指定图片")
async def get_image_data(request: Request, image_filename: str):
"""返回指定图片"""
image_path = f"{FACES_DIR_PATH}/{image_filename}"
if not os.path.exists(image_path):
raise HTTPException(status_code=404, detail="Image not found")
async def image_streamer(file_path: str):
async with aiofiles.open(file_path, mode='rb') as f:
while chunk := await f.read(1024): # 每次读取 1024 字节
yield chunk
logger.Logger.info(f"{PREFIX_PATH}/faces -> {image_path}")
return StreamingResponse(image_streamer(image_path), media_type="image/jpeg")
def server_main(self):
"""主服务程序"""
uvicorn.run(self.app, host="0.0.0.0", port=self.port, access_log=False)
def main(port: str = IMAGE_SERVER_PORT):
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"-p",
"--port",
type=int,
default=port,
help=f"Port of server, default {logger.new_dc(port)}",
)
app_args = parser.parse_args()
ImageServer(app_args)
if __name__ == "__main__":
main()

41
SCLP/main.py Normal file
View File

@ -0,0 +1,41 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 主程序运行入口
"""
import multiprocessing
import subprocess
from config import AUTO_CALLBACK, ENV_FLAG, LAN_IP
from utils import logger
def run_script(script_path):
# 使用 subprocess 运行脚本
subprocess.run(['python', script_path])
if __name__ == "__main__":
logger.Logger.init(f"当前程序环境 ENV_FLAG: {ENV_FLAG}")
logger.Logger.init(f"当前环境IP: {LAN_IP}")
# 创建两个进程
p1 = multiprocessing.Process(target=run_script, args=('backend.py',))
p2 = multiprocessing.Process(target=run_script, args=('app.py',))
p3 = multiprocessing.Process(target=run_script, args=('image_server.py',))
p4 = multiprocessing.Process(target=run_script, args=('auto_callback.py',))
# 启动进程
p1.start()
p2.start()
p3.start()
if AUTO_CALLBACK:
p4.start()
# 等待进程完成
p1.join()
p2.join()
p3.join()
if AUTO_CALLBACK:
p4.join()

54
SCLP/models/brands.py Normal file
View File

@ -0,0 +1,54 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 可视对讲厂家信息表 &
"""
from pydantic import BaseModel, field_validator
from utils.database import BaseTable, get_table_handler
from utils.misc import InvalidException
class UpdateBrand(BaseModel):
name: str
@field_validator("name")
def check_name(cls, value):
th = get_table_handler()
if BrandsTable.exists(th, value):
return value
else:
raise InvalidException("请提供正确的可视对讲厂家名")
class BrandsTable(BaseTable):
@staticmethod
def get_checked_factory(table_handler: BaseTable):
"""获取当前启用的可视对讲厂家"""
table_handler.query("SELECT brand_name FROM brands WHERE status = '开启'")
res = table_handler.cursor.fetchall()
if res:
return {"name": res[0][0]}
else:
return {"name": None}
@staticmethod
def update_checked_factory(table_handler: BaseTable, brand_name: str):
"""更新启用的可视对讲厂家"""
sqls = [
"UPDATE brands SET status = '关闭' WHERE status = '开启'",
"UPDATE brands SET status = '开启' WHERE brand_name = ?"
]
table_handler.execute(sqls, [(), (brand_name,)])
return {"status": True}
@staticmethod
def exists(table_handler: BaseTable, brand_name: str):
"""可视对讲厂家是否存在"""
table_handler.query("SELECT brand_name FROM brands WHERE brand_name = ?", (brand_name,))
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False

793
SCLP/models/devices.py Normal file
View File

@ -0,0 +1,793 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 设备信息表 &&
"""
import csv
import os
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, field_validator
from device.call import ServicesCall, DelFaceItem, AddFaceItem
from models.houses import HousesTable
from utils import logger
from utils.database import BaseTable, get_table_handler
from utils.misc import InvalidException, now_datetime_second, decrypt_number
class DeviceRegister(BaseModel):
device_mac: str
device_name: str
device_desc: str
third_local_device_id: str
device_status: str = "离线"
device_sct: Optional[str] = None
# 以下需要查询已注册设备信息获取
gateway_id: Optional[int] = None
gateway_name: Optional[str] = None
# 以下需要查询产品信息表获取
product_name: str
node_type: str
class Device(BaseModel):
device_id: int
device_name: str
node_type: str
product_name: str
class AccessDevice(BaseModel):
device_id: int
building_ids: list[str]
@field_validator("device_id")
def check_device_id(cls, value):
th = get_table_handler()
if DevicesTable.bind_exits(th, value):
raise InvalidException(f"设备:{value} 已被添加过关联")
if not DevicesTable.exits(th, value):
raise InvalidException(f"设备:{value} 不存在")
return value
@field_validator("building_ids")
def check_authorized_scope(cls, value):
th = get_table_handler()
for i in value:
if not HousesTable.building_exists(th, i):
raise InvalidException(f"楼栋:{i} 不存在")
return value
class AccessDevicesScope(BaseModel):
device_type: str
info: List[AccessDevice]
@field_validator("device_type")
def check_device_type(cls, value):
if value in ["大门闸机", "大门门禁"]:
return value
else:
raise InvalidException("请提供正确的设备类型:[大门闸机, 大门门禁]")
class BuildingDevice(BaseModel):
device_id: int
bind_unit_id: str
@field_validator("device_id")
def check_device_id(cls, value):
th = get_table_handler()
if DevicesTable.bind_exits(th, value):
raise InvalidException(f"设备:{value} 已被添加过关联")
if not DevicesTable.exits(th, value):
raise InvalidException(f"设备:{value} 不存在")
return value
@field_validator("bind_unit_id")
def check_bind_unit_id(cls, value):
th = get_table_handler()
if not HousesTable.unit_exists(th, value):
raise InvalidException(f"单元:{value} 不存在")
return value
class BuildingDevicesScope(BaseModel):
info: List[BuildingDevice]
class SearchDevicesInfo(BaseModel):
search_type: Optional[str] = None
search_key: Optional[str] = None
@field_validator("search_type")
def check_search_type(cls, value):
types = {
"设备名称": "device_name",
"MAC地址": "device_mac",
"设备ID": "device_id"
}
if value in types:
return types[value]
else:
raise InvalidException(f"请提供正确的类型:{list(types.keys())}")
class DevicesTable(BaseTable):
@staticmethod
def check(table_handler: BaseTable):
"""检测是否存在当前表"""
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE devices (
device_id INTEGER PRIMARY KEY AUTOINCREMENT,
device_mac TEXT UNIQUE,
device_name TEXT,
device_desc TEXT,
third_local_device_id TEXT,
device_status TEXT,
device_sct TEXT,
gateway_id INTEGER,
gateway_name TEXT,
product_name TEXT,
node_type TEXT,
last_online_datetime TEXT,
register_datetime TEXT
)
"""
)
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/devices.csv")
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
data = []
if len(head) == 12:
for row in csvreader:
device_mac = row[0].strip()
device_name = row[1].strip()
device_desc = row[2].strip() if row[2].strip() else None
third_local_device_id = row[3].strip() if row[3].strip() else None
device_status = row[4].strip() if row[4].strip() else "离线"
device_sct = row[5].strip() if row[5].strip() else None
gateway_id = int(row[6].strip()) if row[6].strip() else None
gateway_name = row[7].strip() if row[7].strip() else None
product_name = row[8].strip()
node_type = row[9].strip()
last_online_datetime = row[10].strip() if row[10].strip() else None
register_datetime = row[11].strip() if row[11].strip() else None
data.append((device_mac, device_name, device_desc, third_local_device_id, device_status,
device_sct, gateway_id, gateway_name, product_name, node_type,
last_online_datetime, register_datetime))
table_handler.executemany(
f"""
INSERT INTO devices
(device_mac, device_name, device_desc, third_local_device_id, device_status,
device_sct, gateway_id, gateway_name, product_name, node_type,
last_online_datetime, register_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (device_mac) DO NOTHING
""",
data
)
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices_scope'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE devices_scope (
device_id INT,
device_type TEXT,
bind_unit_id TEXT,
UNIQUE (device_id, bind_unit_id)
)
"""
)
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")),
"data/InitialData/devices_scope.csv")
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
data = []
if len(head) == 3:
for row in csvreader:
device_id = row[0].strip()
device_type = row[1].strip()
bind_unit_id = row[2].strip()
data.append((device_id, device_type, bind_unit_id))
table_handler.executemany(
f"""
INSERT INTO devices_scope
(device_id, device_type, bind_unit_id)
VALUES (?, ?, ?)
ON CONFLICT (device_id, bind_unit_id) DO NOTHING
""", data
)
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices_auth'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE devices_auth (
device_id INT,
_id TEXT,
record_type TEXT,
start_date TEXT,
expire_date TEXT,
add_datetime TEXT,
update_datetime TEXT,
UNIQUE (device_id, _id, record_type)
)
"""
)
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")),
"data/InitialData/devices_auth.csv")
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
data = []
if len(head) == 5:
for row in csvreader:
device_id = row[0].strip()
_id = row[1].strip()
record_type = row[2].strip()
start_date = row[3].strip()
expire_date = row[4].strip()
data.append((device_id, _id, record_type, start_date, expire_date,
now_datetime_second(), now_datetime_second()))
table_handler.executemany(
f"""
INSERT INTO devices_auth
(device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (device_id, _id, record_type) DO NOTHING
""", data
)
@staticmethod
def insert_by_device_register(table_handler: BaseTable, obj: DeviceRegister):
register_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
table_handler.execute(
"""
INSERT INTO devices
(device_mac, device_name, device_desc, third_local_device_id, device_status, device_sct,
gateway_id, gateway_name, product_name, node_type, register_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (device_mac) DO UPDATE SET
device_name=?, device_desc=?, third_local_device_id=?,
device_status=?, device_sct=?, gateway_id=?, gateway_name=?,
product_name=?, node_type=?, register_datetime=?
""",
(obj.device_mac, obj.device_name, obj.device_desc, obj.third_local_device_id,
obj.device_status, obj.device_sct, obj.gateway_id, obj.gateway_name,
obj.product_name, obj.node_type, register_datetime,
obj.device_name, obj.device_desc, obj.third_local_device_id,
obj.device_status, obj.device_sct, obj.gateway_id, obj.gateway_name,
obj.product_name, obj.node_type, register_datetime)
)
table_handler.query(
"""
SELECT device_id
FROM devices
WHERE device_mac = ?
""",
(obj.device_mac,)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
return None
@staticmethod
def insert_device_auth_record(table_handler: BaseTable, device_id: int, _id: str,
start_date: str, expire_date: str, record_type: str = '人行'):
table_handler.execute(
"""
INSERT INTO devices_auth
(device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (device_id, _id, record_type) DO UPDATE SET
start_date=?, expire_date=?, update_datetime=?
""",
(device_id, _id, record_type, start_date, expire_date,
now_datetime_second(), now_datetime_second(), start_date, expire_date, now_datetime_second())
)
@staticmethod
def get_devices_info(table_handler: BaseTable):
"""获取对应设备的基本信息"""
table_handler.query(
"""
SELECT device_name, third_local_device_id, device_id, device_status,
gateway_name, product_name, node_type, last_online_datetime, register_datetime
FROM devices
WHERE node_type != '网关设备'
"""
)
res = table_handler.cursor.fetchall()
if res:
devices_info = []
for item in res:
devices_info.append({
"device_name": item[0],
"third_local_device_id": item[1],
"device_id": item[2],
"device_status": item[3],
"gateway_name": item[4] if item[4] else "",
"product_name": item[5],
"node_type": item[6],
"last_online_datetime": item[7] if item[7] else "",
"register_datetime": item[8]
})
return devices_info
return None
@staticmethod
def get_device_info(table_handler: BaseTable, device_id: int):
"""获取对应设备的基本信息"""
table_handler.query(
"""
SELECT device_name, node_type, product_name
FROM devices
WHERE device_id = ?
""",
(device_id,)
)
res = table_handler.cursor.fetchall()
if res:
return Device(device_id=device_id, device_name=res[0][0], node_type=res[0][1], product_name=res[0][2])
return None
@staticmethod
def get_device_name(table_handler: BaseTable, device_id: int):
"""获取对应设备名"""
table_handler.query(
"""
SELECT device_name
FROM devices
WHERE device_id = ?
""",
(device_id,)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
return ""
@staticmethod
def get_device_scope_type(table_handler: BaseTable, device_id: int):
"""获取对应设备门禁类型"""
table_handler.query(
"""
SELECT device_type
FROM devices_scope
WHERE device_id = ?
""",
(device_id,)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
return ""
@staticmethod
def get_device_ids_by_unit_id(table_handler: BaseTable, unit_id: str) -> list:
"""查询对应单元权限内的设备ID"""
table_handler.query(
"""
SELECT GROUP_CONCAT(device_id) AS device_ids
FROM devices_scope
WHERE bind_unit_id = ?
""", (unit_id,)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0].split(',')
else:
return []
@staticmethod
def get_device_ids(table_handler: BaseTable, filter_name: Optional[str] = None, filter_online: bool = True):
"""获取相关的设备ID"""
sub_sql_list = []
if filter_online:
sub_sql_list.append("device_status = '在线'")
if filter_name is not None:
sub_sql_list.append(f"product_name like '%{filter_name}%'")
if len(sub_sql_list) > 0:
sub_sql = "WHERE " + " AND ".join(sub_sql_list)
else:
sub_sql = ""
table_handler.query(f"SELECT GROUP_CONCAT(device_id) device_ids FROM devices {sub_sql}")
res = table_handler.cursor.fetchall()
if res:
return [i for i in res[0][0].split(',')]
else:
return []
@staticmethod
def get_auth_interval(table_handler: BaseTable, _id: str, device_id: int, record_type: str = '人行'):
"""获取对应ID授权在设备上的有效期"""
table_handler.query(
"""
SELECT start_date, expire_date
FROM devices_auth
WHERE device_id = ? and _id = ? and record_type = ?
""",
(device_id, _id, record_type)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0], res[0][1]
return "", ""
@staticmethod
def update_device_status(table_handler: BaseTable, device_id: int, device_status: str):
"""更新设备在线状态"""
if device_status == "在线":
last_online_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
set_sql = f"device_status='在线', last_online_datetime='{last_online_datetime}' "
else:
set_sql = f"device_status='离线' "
table_handler.execute(
f"""
UPDATE devices SET {set_sql}
WHERE device_id={device_id}
"""
)
@staticmethod
def offline_gateway(table_handler: BaseTable, device_id: int):
"""用于在网关下线时同时下线网关下的所有设备"""
table_handler.execute(
f"""
UPDATE devices SET device_status='离线'
WHERE device_id={device_id} or gateway_id={device_id}
"""
)
@staticmethod
def get_access_devices_info(table_handler: BaseTable,
is_associated: bool,
device_name: str = None,
product_name: str = None,
device_mac: str = None,
device_id: int = None):
if is_associated:
sub_sql = "WHERE d.node_type != '网关设备' AND product_name not like '%停车场%' AND device_type is not null "
else:
sub_sql = "WHERE d.node_type != '网关设备' AND product_name not like '%停车场%' AND device_type is null "
if device_name:
sub_sql += f"AND device_name = '{device_name}'"
elif product_name:
sub_sql += f"AND product_name = '{product_name}'"
elif device_mac:
sub_sql += f"AND device_mac = '{device_mac}'"
elif device_id:
sub_sql += f"AND d.device_id = {device_id}"
table_handler.query(
f"""
SELECT
d.device_id,
device_name,
device_mac,
device_status,
ds.device_type,
GROUP_CONCAT(t.building_name || '-' || t.unit_name) as scopes,
product_name
FROM devices d
LEFT JOIN devices_scope ds ON ds.device_id = d.device_id
LEFT JOIN (SELECT DISTINCT unit_id, unit_name, building_id, building_name FROM houses) t
ON t.unit_id = ds.bind_unit_id
{sub_sql}
GROUP BY d.device_id, device_name, device_mac, device_status, ds.device_type, product_name
"""
)
res = table_handler.cursor.fetchall()
if res:
devices_info = []
for i in res:
devices_info.append({
"device_id": i[0],
"device_name": i[1],
"device_mac": i[2],
"device_status": i[3],
"device_type": i[4] if i[4] else "",
"authorized_scope": i[5].split(",") if i[5] else [],
"product_name": i[6]
})
return {"devices": devices_info}
return {"devices": []}
@staticmethod
def get_associated_access_devices_info(table_handler: BaseTable,
search_type: Optional[str] = None,
search_key: Optional[str] = None):
if search_type:
if search_type == "device_id":
sub_sql = f"WHERE ds.device_id = {search_key}"
elif search_type == "device_name":
sub_sql = f"WHERE d.device_name like '%{search_key}%'"
else:
sub_sql = f"WHERE d.{search_type} = '{search_key}'"
else:
sub_sql = ""
table_handler.query(
f"""
SELECT ds.device_id as _id,
d.device_name as _name,
d.device_mac as _mac,
d.device_status as _status
FROM (SELECT DISTINCT device_id FROM devices_scope) ds
LEFT JOIN devices d ON ds.device_id = d.device_id
{sub_sql}
"""
)
res = table_handler.cursor.fetchall()
if res:
devices_info = []
for i in res:
devices_info.append({
"device_id": i[0],
"device_name": i[1],
"device_mac": i[2],
"device_status": i[3]
})
return {"devices": devices_info}
return {"devices": []}
@staticmethod
def auto_auth_by_unit_id(table_handler: BaseTable, device_id: int, unit_id: str):
"""查询对应单元下相关住户,对具备人脸的住户授权"""
table_handler.query(
"""
SELECT h.householder_id, name, phone, face_url
FROM householders h
LEFT JOIN householders_type ht ON ht.householder_id=h.householder_id
LEFT JOIN houses ON ht.room_id=houses.room_id
WHERE h.type = '住户' AND (face_url != '' or face_url is not null ) AND houses.unit_id = ?
""", (unit_id,)
)
res = table_handler.cursor.fetchall()
if res:
# 人员人脸下放
for i in res:
sc = ServicesCall()
face_item = AddFaceItem(
user_id=i[0],
name=i[1],
phone_number=decrypt_number(i[2]),
face_url=i[3],
device_ids=str([device_id])
)
_callback, code, msg = sc.add_face(device_id, face_item)
if _callback:
DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id,
face_item.start_date, face_item.expire_date)
@staticmethod
def add_access_devices(table_handler: BaseTable, device_type: str, objs: List[AccessDevice]):
data = []
for obj in objs:
for bind_building_id in obj.building_ids:
unit_ids = HousesTable.get_unit_ids(table_handler, bind_building_id)
for unit_id in unit_ids:
data.append((obj.device_id, device_type, unit_id, device_type))
DevicesTable.auto_auth_by_unit_id(table_handler, obj.device_id, unit_id)
try:
table_handler.executemany(
"""
INSERT INTO devices_scope
(device_id, device_type, bind_unit_id)
VALUES (?, ?, ?)
ON CONFLICT (device_id, bind_unit_id) DO UPDATE
SET device_type = ?
""", data
)
return True
except Exception as e:
logger.Logger.error(f"DevicesTable.add_access_devices: {type(e).__name__}, {e}")
raise InvalidException(f"DevicesTable.add_access_devices: {type(e).__name__}, {e}")
@staticmethod
def add_building_devices(table_handler: BaseTable, device_type: str, objs: List[BuildingDevice]):
data = []
for obj in objs:
data.append((obj.device_id, device_type, obj.bind_unit_id))
DevicesTable.auto_auth_by_unit_id(table_handler, obj.device_id, obj.bind_unit_id)
try:
table_handler.executemany(
"""
INSERT INTO devices_scope
(device_id, device_type, bind_unit_id)
VALUES (?, ?, ?)
ON CONFLICT (device_id, bind_unit_id) DO NOTHING
""", data
)
return True
except Exception as e:
logger.Logger.error(f"DevicesTable.add_building_devices: {type(e).__name__}, {e}")
return False
@staticmethod
def add_update_device_auth(table_handler: BaseTable, device_id, _ids: list[str], record_type: str,
start_date: str, expire_date: str):
"""对应设备批量授权记录增加"""
data = []
for _id in _ids:
_datetime = now_datetime_second()
data.append((device_id, _id, record_type, start_date, expire_date, _datetime, _datetime,
start_date, expire_date, _datetime))
try:
table_handler.executemany(
"""
INSERT INTO devices_auth
(device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (device_id, _id, record_type)
DO UPDATE SET start_date = ?, expire_date = ?, update_datetime = ?
""", data
)
return True
except Exception as e:
logger.Logger.error(f"DevicesTable.add_device_auth: {type(e).__name__}, {e}")
return False
@staticmethod
def get_auth_device_ids(table_handler: BaseTable, _id: str, record_type: str = '人行'):
"""获取ID的所有授权设备"""
table_handler.query(
"""
SELECT device_id
FROM devices_auth
WHERE _id = ? AND record_type = ?
""", (_id, record_type)
)
res = table_handler.cursor.fetchall()
if res:
return [i[0] for i in res]
else:
return []
@staticmethod
def get_auth_device_info(table_handler: BaseTable, _id: str, record_type: str = '人行'):
"""获取ID当前所有授权设备基础信息"""
table_handler.query(
"""
SELECT devices_auth.device_id, device_name, device_mac, device_status
FROM devices_auth
LEFT JOIN devices ON devices.device_id = devices_auth.device_id
WHERE _id = ? AND record_type = ?
""", (_id, record_type)
)
res = table_handler.cursor.fetchall()
if res:
return [{"device_id": i[0], "device_name": i[1], "device_mac": i[2], "device_status": i[3]} for i in res]
else:
return []
@staticmethod
def get_auth_householders_info(table_handler: BaseTable, device_id: int):
"""获取关联授权下的所有完成下放的人员信息"""
table_handler.query(
"""
SELECT da._id, name, phone, face_url, da.start_date, da.expire_date
FROM devices_auth da
LEFT JOIN householders h ON da._id = h.householder_id
WHERE device_id = ? AND record_type = '人行'
""", (device_id,)
)
res = table_handler.cursor.fetchall()
if res:
return [{"id": i[0], "name": i[1], "phone": i[2], "url": i[3], "sdate": i[4], "edate": i[5]} for i in res]
else:
return []
@staticmethod
def delete_householder_all_auth(table_handler: BaseTable, _id: str):
table_handler.execute(
"""
DELETE FROM devices_auth
WHERE _id = ? AND record_type = '人行'
""", (_id,)
)
@staticmethod
def delete_invalid_auth_record(table_handler: BaseTable, device_id: int, _id: str, record_type: str = '人行'):
table_handler.execute(
"""
DELETE FROM devices_auth
WHERE device_id = ? AND _id = ? AND record_type = ?
""",
(device_id, _id, record_type)
)
@staticmethod
def delete_access_device_info(table_handler: BaseTable, device_id: int):
# 1. 确认设备ID有效
if not DevicesTable.exits(table_handler, device_id):
raise InvalidException(f"设备:{device_id} 不存在")
# 2. 获取关联授权下的所有完成下放的人员信息
householder_infos = DevicesTable.get_auth_householders_info(table_handler, device_id)
if len(householder_infos) > 0:
# 3. 移除对应设备中所有的人员信息
sc = ServicesCall()
success_ids = []
for index, householder_info in enumerate(householder_infos):
_callback, code, _ = sc.del_face(device_id,
DelFaceItem(user_id=str(householder_info["id"]),
device_ids=str([device_id])))
if not _callback:
# 存在异常时,回滚已删除掉的人脸
failed = []
msgs = []
for success_id in success_ids:
_back, code, msg = sc.add_face(device_id, AddFaceItem(
name=householder_infos[success_id]["name"],
user_id=str(householder_infos[success_id]["id"]),
phone_number=householder_infos[success_id]["phone"],
face_url=householder_infos[success_id]["url"],
device_ids=str([device_id]),
start_date=householder_infos[success_id]["sdate"],
expire_date=householder_infos[success_id]["edate"]
))
if not _back:
failed.append(householder_infos[success_id]["id"])
msgs.append(msg)
if len(failed) == 0:
raise InvalidException(
f"住户 - {householder_info['id']} 人脸授权移除失败,错误码{code}, {msgs}, 已完成回滚")
else:
raise InvalidException(
f"住户 - {householder_info['id']} 人脸授权移除失败,错误码{code}, {msgs}, 回滚失败")
success_ids.append(index)
# 4. 移除设备的关联记录
table_handler.execute(
"""
DELETE FROM devices_auth
WHERE device_id = ? AND record_type = '人行'
""", (device_id,)
)
table_handler.execute(
"""
DELETE FROM devices_scope
WHERE device_id = ?
""", (device_id,)
)
return {"status": True}
@staticmethod
def exits(table_handler: BaseTable, device_id: int):
table_handler.query(
"""
SELECT device_id FROM devices
WHERE device_id = ?
""", (device_id,)
)
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False
@staticmethod
def bind_exits(table_handler: BaseTable, device_id: int):
table_handler.query(
"""
SELECT bind_unit_id
FROM devices_scope
WHERE device_id = ?
""", (device_id,)
)
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False

1369
SCLP/models/householders.py Normal file

File diff suppressed because it is too large Load Diff

303
SCLP/models/houses.py Normal file
View File

@ -0,0 +1,303 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 房产信息表查&
"""
import csv
import json
import os
import traceback
from utils import logger
from utils.database import BaseTable
from utils.misc import now_datetime_second, extract_fixed_length_number
class HousesTable(BaseTable):
@staticmethod
def check(table_handler: BaseTable):
"""检测是否存在当前表"""
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='houses'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE houses (
room_id TEXT UNIQUE,
house_name TEXT UNIQUE,
project_id TEXT,
project_name TEXT,
area_id TEXT,
area_name TEXT,
building_id TEXT,
building_name TEXT,
unit_id TEXT,
unit_name TEXT,
floor_id TEXT,
floor_name TEXT,
room_name TEXT,
householder_count INTEGER,
update_datetime TEXT
)
"""
)
# 根据导出数据生成内置数据
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/houses.csv")
csv_file = open(init_config_path, "w", encoding="utf8")
csv_file.write(
"房屋ID, 房屋编号, 项目ID, 所属项目, 区域ID, 所属区域, 楼栋ID, 所属楼栋, 单元ID, 所属单元, 楼层ID, 所属楼层, 房间号, 住户数量\n")
room_info_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/ExportData/room.txt")
if os.path.exists(room_info_path):
with open(room_info_path, "r", encoding="utf8") as f:
file_content = f.read()
file_content = file_content.replace("ISODate(", "").replace(")", "").replace("'", "\"")
room_info = json.loads(file_content)
for item in room_info:
room_id = item.get("_id", "")
project_id = item["project_id"]
project = item["project_name"]
area_id = item.get("area_id", "")
area = item.get("area_name", "")
building_id = item.get("build_id", "")
building = item.get("building_name", "")
unit_id = item.get("unit_id", "")
unit = item.get("unit_name", "")
floor_id = item.get("floor_id", "")
floor = item.get("floor_name", "")
room = item.get("num", "")
wait_write = [room_id,
'-'.join([i for i in [project, area, building, unit, floor, room] if i != '']),
project_id, project, area_id, area, building_id, building,
unit_id, unit, floor_id, floor, room, str(len(item.get("households", [])))]
csv_file.write(', '.join(wait_write) + "\n")
csv_file.flush()
csv_file.close()
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
data = []
if len(head) == 14:
for row in csvreader:
room_id = row[0].strip()
house_name = row[1].strip()
project_id = row[2].strip()
project_name = row[3].strip()
area_id = row[4].strip() if row[4].strip() else None
area_name = row[5].strip() if row[5].strip() else None
building_id = row[6].strip() if row[6].strip() else None
building_name = row[7].strip() if row[7].strip() else None
unit_id = row[8].strip() if row[8].strip() else None
unit_name = row[9].strip() if row[9].strip() else None
floor_id = row[10].strip() if row[10].strip() else None
floor_name = row[11].strip() if row[11].strip() else None
room_name = row[12].strip()
householder_count = int(row[13].strip())
update_datetime = now_datetime_second()
data.append((room_id, house_name, project_id, project_name, area_id, area_name,
building_id, building_name, unit_id, unit_name, floor_id, floor_name,
room_name, householder_count, update_datetime))
table_handler.executemany(
f"""
INSERT INTO houses
(room_id, house_name, project_id, project_name, area_id, area_name,
building_id, building_name, unit_id, unit_name, floor_id, floor_name,
room_name, householder_count, update_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (room_id) DO NOTHING
""",
data
)
@staticmethod
def get_house_detail_info(table_handler: BaseTable, room_id):
"""根据房屋ID获取房产详细信息"""
table_handler.query(
"""
SELECT room_id, project_id, room_name, project_name
FROM houses
WHERE room_id = ?
""", (room_id,)
)
res = table_handler.cursor.fetchall()
if res:
return {
"id": res[0][0],
"project_id": res[0][1],
"name": res[0][2],
"type": 1,
"subtype": 0,
"project_name": res[0][3]
}
else:
return None
@staticmethod
def get_unit_id_by_room_id(table_handler: BaseTable, room_id):
"""根据房屋ID获取房产详细信息"""
table_handler.query(
"""
SELECT unit_id
FROM houses
WHERE room_id = ?
""", (room_id,)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
else:
return None
@staticmethod
def get_house_ty_info(table_handler: BaseTable, project_id: str, room_id: str) -> dict | None:
"""查询房产真实信息"""
table_handler.query(
"""
SELECT houses.room_id, house_name, project_id, corp_id,
building_name, unit_name, floor_name, room_name
FROM houses
LEFT JOIN subsystem ON houses.room_id = subsystem.room_id
WHERE project_id = ? AND houses.room_id = ?
""", (project_id, room_id)
)
res = table_handler.cursor.fetchall()
if res:
building = extract_fixed_length_number(res[0][4])
unit = extract_fixed_length_number(res[0][5])
floor = extract_fixed_length_number(res[0][6])
room = extract_fixed_length_number(res[0][7])
ty_house_id = f"{building}{unit}{floor}{room}"
return {
"id": res[0][0],
"house_name": res[0][1],
"ty_house_id": ty_house_id,
"project_id": res[0][2],
"corp_id": res[0][3]
}
else:
return None
@staticmethod
def get_items_by_dynamic_item(table_handler: BaseTable, query_target: str, query_item: dict):
sub_sql_list = []
for key, value in query_item.items():
if isinstance(value, int):
sub_sql_list.append(f"{key}_id = {value}")
else:
sub_sql_list.append(f"{key}_id = '{value}'")
sub_sql = "" if len(sub_sql_list) == 0 else "WHERE " + " and ".join(sub_sql_list)
try:
table_handler.query(
f"""
SELECT DISTINCT {query_target}_id, {query_target}_name
FROM houses {sub_sql}
"""
)
res = table_handler.cursor.fetchall()
if res:
item_list = [{"_id": i[0], "_name": i[1]} for i in res if i[0] is not None]
return item_list
else:
return []
except Exception as e:
logger.Logger.error(f"{type(e).__name__}, {e}")
if logger.DEBUG:
traceback.print_exc()
return []
@staticmethod
def get_householder_count(table_handler: BaseTable, room_id: str):
table_handler.query(
"""
SELECT householder_count
FROM houses
WHERE room_id = ?
""",
(room_id,),
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
else:
return None
@staticmethod
def update_householder_count(table_handler: BaseTable, room_id: str, householder_count: int):
table_handler.execute(
"""
UPDATE houses SET
householder_count=?,
update_datetime=?
WHERE room_id=?
""",
(
householder_count,
now_datetime_second(),
room_id
),
)
return True
@staticmethod
def get_unit_ids(table_handler: BaseTable, building_id: str):
table_handler.execute(
"""
SELECT DISTINCT unit_id
FROM houses
WHERE building_id = ?
""", (building_id,)
)
res = table_handler.cursor.fetchall()
if res:
return [i[0] for i in res]
else:
return []
@staticmethod
def exists(table_handler: BaseTable, room_id: str):
table_handler.query(
"""
SELECT house_name
FROM houses
WHERE room_id = ?
""",
(room_id,)
)
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False
@staticmethod
def building_exists(table_handler: BaseTable, building_id: str):
table_handler.query(
"""
SELECT DISTINCT building_id
FROM houses
WHERE building_id = ?
""",
(building_id,)
)
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False
@staticmethod
def unit_exists(table_handler: BaseTable, unit_id: str):
table_handler.query(
"""
SELECT DISTINCT unit_id
FROM houses
WHERE unit_id = ?
""",
(unit_id,)
)
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False

1019
SCLP/models/parkinglots.py Normal file

File diff suppressed because it is too large Load Diff

113
SCLP/models/products.py Normal file
View File

@ -0,0 +1,113 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 内置的产品信息表在初始化时会初始化csv进行覆盖
"""
import csv
import os
from datetime import datetime
from pydantic import BaseModel
from utils import logger
from utils.database import BaseTable
from utils.misc import now_datetime_second
class Product(BaseModel):
product_id: str
product_name: str
node_type: str
class ProductsTable(BaseTable):
@staticmethod
def check(table_handler: BaseTable):
"""检测是否存在当前表"""
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/products.csv")
if os.path.exists(init_config_path):
table_handler.execute("DROP TABLE IF EXISTS products;")
table_handler.execute(
f"""
CREATE TABLE products (
product_id TEXT,
product_name TEXT,
node_type TEXT,
update_datetime TEXT,
PRIMARY KEY (product_id)
)
"""
)
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
data = []
if len(head) == 3:
for row in csvreader:
product_id = row[0].strip()
product_name = row[1].strip()
node_type = row[2].strip()
update_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
data.append((product_id, product_name, node_type, update_datetime))
table_handler.executemany(
f"""
INSERT INTO products
(product_id, product_name, node_type, update_datetime)
VALUES (?, ?, ?, ?)
""",
data
)
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='brands'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE brands (
brand_id INT,
brand_name TEXT,
status TEXT,
add_datetime TEXT,
update_datetime TEXT,
PRIMARY KEY (brand_id)
)
"""
)
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/brands.csv")
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
data = []
if len(head) == 3:
for row in csvreader:
brand_id = row[0].strip()
brand_name = row[1].strip()
status = row[2].strip()
data.append((brand_id, brand_name, status, now_datetime_second(), now_datetime_second()))
table_handler.executemany(
f"""
INSERT INTO brands
(brand_id, brand_name, status, add_datetime, update_datetime)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT (brand_id) DO NOTHING
""",
data
)
@staticmethod
def get_product_info(table_handler: BaseTable, product_id: str):
table_handler.query(
"""
SELECT product_name, node_type
FROM products
WHERE product_id = ?
""",
(product_id,)
)
res = table_handler.cursor.fetchall()
if res:
return Product(product_id=product_id, product_name=res[0][0], node_type=res[0][1])
else:
return None

113
SCLP/models/sessions.py Normal file
View File

@ -0,0 +1,113 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 记录一些会话数据
"""
import time
from utils.database import BaseTable
class SessionsTable(BaseTable):
@staticmethod
def check(table_handler: BaseTable):
"""检测是否存在当前表"""
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE sessions (
session_id TEXT,
username TEXT,
token TEXT,
captcha TEXT,
last_timestamp TEXT,
PRIMARY KEY (session_id)
)
"""
)
table_handler.execute(
f"""
CREATE INDEX idx_sessions_token ON sessions(token);
"""
)
# 去除冗余的验证会话信息
table_handler.execute(
f"""
DELETE FROM sessions WHERE last_timestamp < {int(time.time() * 1000) - 3 * 24 * 60 * 60 * 1000}
"""
)
@staticmethod
def get_captcha(table_handler: BaseTable, session_id: str):
table_handler.query(
"""
SELECT captcha
FROM sessions
WHERE session_id = ?
""",
(session_id,),
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
else:
return None
@staticmethod
def insert(table_handler: BaseTable, session_id, captcha):
timestamp = str(int(time.time() * 1000))
table_handler.execute(
"""
INSERT INTO sessions
(session_id, captcha, last_timestamp)
VALUES (?, ?, ?)
ON CONFLICT (session_id)
DO UPDATE SET captcha=?, last_timestamp=?
""",
(
session_id,
captcha,
timestamp,
captcha,
timestamp,
),
)
return True
@staticmethod
def update(table_handler: BaseTable, session_id: str, username: str, token: str):
timestamp = str(int(time.time() * 1000))
table_handler.execute(
"""
UPDATE sessions SET
username=?,
token=?,
last_timestamp=?
WHERE session_id=?
""",
(
username,
token,
timestamp,
session_id
),
)
return True
@staticmethod
def check_token(table_handler: BaseTable, timestamp: str, token: str):
table_handler.query(
"""
SELECT captcha
FROM sessions
WHERE last_timestamp > ? and token = ?
""",
(timestamp, token),
)
res = table_handler.cursor.fetchall()
if res:
return True
else:
return False

95
SCLP/models/spaces.py Normal file
View File

@ -0,0 +1,95 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 子系统映射表 &&
"""
import csv
import os
from models.houses import HousesTable
from utils.database import BaseTable
from utils.misc import now_datetime_second, sql_export_xls
class SpacesTable(BaseTable):
@staticmethod
def get_sub_system_name_by_room_id(table_handler: BaseTable, room_id: str):
table_handler.query(
"""
SELECT sub_space_name
FROM subsystem
WHERE room_id = ?
""", (room_id,)
)
res = table_handler.cursor.fetchall()
if res:
return res[0][0]
else:
return None
@staticmethod
def get_space_info_by_sub_space_name(table_handler: BaseTable, sub_space_name: str):
"""根据第三方ID获取对应的房产信息"""
table_handler.query(
"""
SELECT subsystem.room_id, house_name, project_id, sub_system_id
FROM subsystem
LEFT JOIN houses ON subsystem.room_id = houses.room_id
WHERE sub_space_name = ?
""", (sub_space_name,)
)
res = table_handler.cursor.fetchall()
if res:
return {
"room_id": res[0][0],
"house_name": res[0][1],
"project_id": res[0][2],
"sub_system_id": res[0][3]
}
else:
return None
@staticmethod
def get_aiot_platform_data(table_handler: BaseTable):
query = """
SELECT project_id, houses.room_id, house_name, COALESCE(subsystem_id, ''), COALESCE(sub_space_name, '')
FROM houses
LEFT JOIN subsystem ON subsystem.room_id = houses.room_id
"""
file_path = os.path.join(f"data/AIOT平台空间数据{now_datetime_second().replace(':', '_')}.xls")
sql_export_xls(query, table_handler.connection, file_path,
"房屋子系统映射数据表",
["项目id", "房屋id", "房屋名称", "子系统id", "子系统空间名称"])
return file_path
@staticmethod
def get_sub_system_data(table_handler: BaseTable):
query = "SELECT subsystem_id, sub_space_name FROM subsystem"
file_path = os.path.join(f"data/子系统空间数据{now_datetime_second().replace(':', '_')}.xls")
sql_export_xls(query, table_handler.connection, file_path,
"子系统数据表",
["子系统id", "子系统空间名称"])
return file_path
@staticmethod
def update(table_handler: BaseTable, objs: list):
data = []
for obj in objs:
data.append((obj[0], now_datetime_second(), obj[1], obj[2]))
table_handler.executemany(
f"""
UPDATE subsystem
SET room_id = ?, update_datetime = ?
WHERE subsystem_id = ? and sub_space_name = ?
""", data
)
@staticmethod
def exits_room_id(table_handler: BaseTable, room_id: str):
table_handler.query("SELECT room_id FROM spaces WHERE room_id = ?", (room_id,))
if table_handler.cursor.fetchone() is not None:
return True
else:
return False

200
SCLP/models/subsystem.py Normal file
View File

@ -0,0 +1,200 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 子系统房产信息表
"""
import csv
import json
import os
import time
from typing import Optional
from pydantic import BaseModel, Field, field_validator
from models.spaces import SpacesTable
from utils.database import BaseTable, get_table_handler
from utils.misc import now_datetime_second, InvalidException, generate_captcha_text
class HouseDetailInfo(BaseModel):
project_id: str
subsystem_id: str
subsystem_name: str
subsystem_data: str
createby_id: str
subsystem_extend: dict
create_time: Optional[int]
corp_id: Optional[str]
message_id: Optional[str] = Field(alias="_id")
action: str = "insert"
house_id: Optional[str] = None
house_name: Optional[str] = None
@field_validator("subsystem_extend")
def check_subsystem_extend(cls, value):
if value.get("label", None) is None:
raise InvalidException("subsystem_extend 中 label 值缺失")
return value
# @field_validator("house_id")
# def check_house_id(cls, value, values):
# th = get_table_handler()
# if value is None or value == "":
# label = values.data.get("subsystem_extend")["label"]
# space_info = SpacesTable.get_space_info_by_sub_space_name(th, label)
# if space_info is None:
# raise InvalidException(f"subsystem_extend - label:{label} 不在映射表中")
# if SubsystemTable.exits_room_id(th, space_info["room_id"]):
# raise InvalidException(f"house_id:{space_info['room_id']} 记录已存在,禁止插入操作")
# return space_info["room_id"]
# if not SpacesTable.exits_room_id(th, value):
# raise InvalidException("映射表中不存在对应的 house_id")
# else:
# return value
# @field_validator("house_name")
# def check_house_name(cls, value, values):
# th = get_table_handler()
# if value is None:
# label = values.data.get("subsystem_extend")["label"]
# space_info = SpacesTable.get_space_info_by_sub_space_name(th, label)
# if space_info is None:
# raise InvalidException(f"subsystem_extend - label:{label} 不在映射表中")
# if SubsystemTable.exits_room_id(th, space_info["room_id"]):
# raise InvalidException(f"house_id:{space_info['room_id']} 记录已存在,禁止插入操作")
# return space_info["house_name"]
# return value
class SubsystemTable(BaseTable):
@staticmethod
def check(table_handler: BaseTable):
"""检测是否存在当前表"""
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='subsystem'")
if table_handler.cursor.fetchone() is None:
table_handler.execute(
f"""
CREATE TABLE subsystem (
room_id TEXT UNIQUE,
subsystem_id TEXT,
subsystem_project_id TEXT,
sub_space_name TEXT,
subsystem_name TEXT,
subsystem_data TEXT,
subsystem_extend TEXT,
createby_id TEXT,
corp_id TEXT,
_id TEXT,
create_time INTEGER,
update_time INTEGER,
update_datetime TEXT,
PRIMARY KEY (subsystem_id, subsystem_project_id, sub_space_name)
)
"""
)
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/subsystem.csv")
if os.path.exists(init_config_path):
with open(init_config_path, newline='', encoding='utf8') as csvfile:
csvreader = csv.reader(csvfile)
head = next(csvreader)
if len(head) == 10:
data = []
for row in csvreader:
room_id = row[0].strip()
subsystem_project_id = row[1].strip()
sub_space_name = row[2].strip()
subsystem_name = row[3].strip()
subsystem_data = row[4].strip()
subsystem_extend = row[5].strip()
createby_id = row[6].strip()
corp_id = row[7].strip()
_id = row[8].strip()
create_time = int(row[9].strip())
update_datetime = now_datetime_second()
data.append((room_id, subsystem_project_id, sub_space_name, subsystem_name, subsystem_data, subsystem_extend,
createby_id, corp_id, _id, create_time, int(time.time() * 1000), update_datetime))
table_handler.executemany(
f"""
INSERT INTO subsystem
(room_id, subsystem_project_id, sub_space_name, subsystem_name, subsystem_data, subsystem_extend,
createby_id, corp_id, _id, create_time, update_time, update_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (room_id) DO NOTHING
""", data
)
@staticmethod
def get_house_detail_info_by_project_id(table_handler: BaseTable, project_id: str) -> list:
"""根据项目ID获取查找子系统空间结构中的房间信息"""
table_handler.query(
"""
SELECT subsystem.room_id, sub_space_name, house_name, subsystem_project_id,
subsystem_id, subsystem_name, subsystem_data, subsystem_extend,
createby_id, corp_id, _id, create_time, update_time
FROM subsystem
LEFT JOIN houses ON subsystem.room_id = houses.room_id
WHERE subsystem_project_id = ?
ORDER BY create_time DESC
""", (project_id,)
)
res = table_handler.cursor.fetchall()
house_detail_info_list = []
if res:
for info in res:
subsystem_extend = json.loads(info[7].replace("'", '"').replace("None", "null"))
subsystem_extend["label"] = info[1]
house_detail_info_list.append({
"house_id": info[0] if info[0] else "",
"house_name": info[2] if info[0] else "",
"project_id": info[3],
"subsystem_id": info[4],
"subsystem_name": info[5],
"subsystem_data": info[6],
"subsystem_extend": subsystem_extend,
"createby_id": info[8],
"corp_id": info[9],
"_id": generate_captcha_text(16).lower(),
"create_time": info[11],
"update_time": info[12],
"is_stop": False,
"valid": True,
"ids": None
})
return house_detail_info_list
@staticmethod
def add_sub_system_house_info(table_handler: BaseTable, obj: HouseDetailInfo):
"""添加房产信息"""
nt = now_datetime_second()
if obj.action == "insert":
table_handler.execute(
"""
INSERT OR IGNORE INTO subsystem
(subsystem_id, subsystem_project_id, sub_space_name, subsystem_name, subsystem_data, subsystem_extend,
createby_id, corp_id, _id, create_time, update_time, update_datetime)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (obj.subsystem_id, obj.project_id, obj.subsystem_extend["label"], obj.subsystem_name, obj.subsystem_data, str(obj.subsystem_extend),
obj.createby_id, obj.corp_id, obj.message_id, obj.create_time, int(time.time() * 1000), nt)
)
else:
table_handler.execute(
"""
UPDATE subsystem
SET subsystem_id = ?, subsystem_project_id = ?, sub_space_name = ?, subsystem_name = ?,
subsystem_data = ?, subsystem_extend = ?,
createby_id = ?, corp_id = ?, _id = ?, create_time = ?, update_time = ?, update_datetime = ?
WHERE room_id = ?
""", (obj.subsystem_id, obj.project_id, obj.subsystem_extend["label"], obj.subsystem_name,
obj.subsystem_data, str(obj.subsystem_extend),
obj.createby_id, obj.corp_id, obj.message_id,
obj.create_time, int(time.time() * 1000), nt, obj.house_id)
)
@staticmethod
def exits_room_id(table_handler: BaseTable, room_id: str):
table_handler.query("SELECT room_id FROM subsystem WHERE room_id = ?", (room_id,))
if table_handler.cursor.fetchone() is not None:
return True
else:
return False

34
SCLP/readme.md Normal file
View File

@ -0,0 +1,34 @@
# 项目简介
> 智慧社区本地化平台 Smart Community Localization Platform基于python实现的平台后端
# 功能模块
* 内置信息导入
* 设备管理
* 住户管理
* 通行管理
* 智慧停车
# 数据表
* **brands** 可视对讲厂商信息表
- 初始化后只会进行更新不会进行增删
* **devices** 设备信息表
- 基本aiot设备信息接受边缘端注册、上线过程中保存的信息
* **devices_auth** 设备授权信息表
- 记录设备授权过程中的信息
* **devices_scope** 单元ID与实际设备关系表
- 在设备配置关联信息时插入,记录每一个单元权限下的所有设备
- 移除设备关联信息时,在完成设备端授权清除后会清除该表中的关系
* **parkinglots** 停车场信息表
- 在一张表中记录停车场、区域、通道、道闸的信息通过type字段进行区分
- 停车场id(id就是编号)\number(aiot设备ID)\name\factory_name\running_status(是否为null控制页面显示)
- 区域id\number\name\parkinglot_id\area_id(如果存在父区域的话为父区域ID)
- 通道id\number\name\parkinglot_id\area_id\channel_type
- 道闸id\number\name\parkinglot_id\area_id\channel_id\is_online\gate_status\running_status(工作状态)

View File

@ -0,0 +1,77 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 通行设备界面控制逻辑
"""
from fastapi import APIRouter, Request, Header, Query
from models.devices import DevicesTable, AccessDevicesScope, BuildingDevicesScope
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException
router = APIRouter()
@router.get("/getAccessDevicesInfo", summary="获取通行设备信息")
async def get_access_devices_info(request: Request,
is_associated: bool = Query(...),
device_name: str = Query(None),
product_name: str = Query(None),
device_mac: str = Query(None),
device_id: int = Query(None),
token: str = Header(...)):
"""获取通行设备信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
if device_name:
resp = DevicesTable.get_access_devices_info(th, is_associated, device_name=device_name)
elif product_name:
resp = DevicesTable.get_access_devices_info(th, is_associated, product_name=product_name)
elif device_mac:
resp = DevicesTable.get_access_devices_info(th, is_associated, device_mac=device_mac)
elif device_id:
resp = DevicesTable.get_access_devices_info(th, is_associated, device_id=device_id)
else:
resp = DevicesTable.get_access_devices_info(th, is_associated)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/addAccessDevicesAssociateInfo", summary="批量增加大门门禁/大门闸机关联信息")
async def add_access_devices_associate_info(request: Request, item: AccessDevicesScope, token: str = Header(...)):
"""批量增加大门门禁/大门闸机关联信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
_callback = DevicesTable.add_access_devices(th, item.device_type, item.info)
resp = {"status": _callback}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/addBuildingDevicesAssociateInfo", summary="批量增加楼栋门禁关联信息")
async def add_building_devices_associate_info(request: Request, item: BuildingDevicesScope, token: str = Header(...)):
"""批量增加楼栋门禁关联信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
_callback = DevicesTable.add_building_devices(th, "楼栋门禁", item.info)
resp = {"status": _callback}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.delete("/deleteAssociatedAccessDeviceInfo", summary="移除完成关联的设备及其已授权的人员信息")
async def delete_access_device_info(request: Request, device_id: int = Query(alias="id"), token: str = Header(...)):
"""移除完成关联的设备及其已授权的人员信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = DevicesTable.delete_access_device_info(th, device_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

View File

@ -0,0 +1,193 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 通行授权界面控制逻辑
"""
import os
import time
from fastapi import APIRouter, Request, Header, File, UploadFile, Query
from config import PREFIX_PATH, SUB_PATH
from config import APP_HOST as HOST_IP
from config import IMAGE_SERVER_PORT as PORT
from models.devices import SearchDevicesInfo, DevicesTable
from models.householders import HouseholdersTable, GetAuthHouseholdersInfo, GetAuthUsersInfo, \
UpdateHouseholderFace, AddAuthUserInfo, UpdateAuthUserInfo
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException
router = APIRouter()
@router.get("/getHouseholdersInfo", summary="授权住户查询")
async def get_householders_info(request: Request,
page: int = Query(..., gt=0),
limit: int = Query(..., gt=0),
token: str = Header(...)):
"""获取已授权的住户信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_auth_householder_info(th, None, None, None, None,
page, limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/getHouseholdersInfo", summary="授权住户条件查询")
async def get_householders_info_by_condition(request: Request,
item: GetAuthHouseholdersInfo,
token: str = Header(...)):
"""根据条件获取已授权的住户信息查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
logger.Logger.debug(f"{request.url.path} <- {item.__dict__}")
resp = HouseholdersTable.get_auth_householder_info(th, item.search_type, item.search_key,
item.space_type, item.space_id,
item.page, item.limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getHouseholderDetailInfo", summary="住户授权详细信息查询")
async def get_householder_detail_info(request: Request, householder_id: int = Query(...), token: str = Header(...)):
"""获取指定住户授权详细信息查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_auth_householder_detail_info(th, householder_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getUsersInfo", summary="通用授权人员信息查询")
async def get_users_info(request: Request,
page: int = Query(..., gt=0),
limit: int = Query(..., gt=0),
token: str = Header(...)):
"""获取已授权的通用人员信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_auth_users_info(th, None, None, None, page, limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/getUsersInfo", summary="通用授权人员信息条件查询")
async def get_users_info_by_condition(request: Request,
item: GetAuthUsersInfo,
token: str = Header(...)):
"""根据条件获取已授权的通用人员信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_auth_users_info(th, item.search_type, item.search_key,
item.user_type, item.page, item.limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getUserDetailInfo", summary="通用授权人员详细信息查询")
async def get_user_detail_info(request: Request,
user_id: int = Query(alias="id"),
token: str = Header(...)):
"""获取指定通用人员的详细信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_auth_user_info(th, user_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.delete("/deleteUserInfo", summary="删除通用授权人员信息及关联设备授权")
async def get_users_info(request: Request, user_id: int = Query(...), token: str = Header(...)):
"""通用授权人员删除 & 移除关联设备授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.delete_auth_user_info(th, user_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/addUserInfo", summary="新增通用授权人员信息及关联设备授权")
async def add_user_info(request: Request, item: AddAuthUserInfo, token: str = Header(...)):
"""新增通用授权人员 & 人脸图像绑定 & 添加关联设备授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.add_auth_user_info(th, item)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/updateUserInfo", summary="更新通用授权人员信息及关联设备授权")
async def update_user_info(request: Request, item: UpdateAuthUserInfo, token: str = Header(...)):
"""更新通用授权人员信息 & 人脸图像绑定 & 判断是否需要刷新关联设备授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.update_auth_user_info(th, item)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/uploadHouseholderFace", summary="人脸图片上传")
async def upload_householder_face(request: Request,
face_file: UploadFile = File(...),
token: str = Header(...)):
"""接收人脸图片上传并保存"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
# 检查文件类型是否为图像
if not face_file.content_type.startswith("image"):
resp = {"status": False, "message": "仅支持上传图片"}
else:
file_content = await face_file.read()
max_size_mb = 10 # 设置最大允许的文件大小为 10MB
if face_file.file.seek(0, os.SEEK_END) > max_size_mb * 1024 * 1024:
resp = {"status": False, "message": f"文件大小不得超过 {max_size_mb}MB"}
else:
save_path = f"./data/FaceImages"
filename = f"_{int(time.time() * 1000)}"
if not os.path.exists(save_path):
os.mkdir(save_path)
file_path = f"{save_path}/{filename}.jpg"
with open(file_path, "wb") as f:
f.write(file_content)
# 返回成功消息及人脸照片的 URL
face_url = f"http://{HOST_IP}:{PORT}{PREFIX_PATH}/{SUB_PATH}/{filename}.jpg"
resp = {"face_url": face_url}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/updateHouseholderFace", summary="更新住户人脸信息")
async def get_users_info(request: Request, item: UpdateHouseholderFace, token: str = Header(...)):
"""人脸图像与住户ID绑定 & 刷新关联设备授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.update_householder_face(th, item.id, item.face_url)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/getAssociatedAccessDevicesInfo", summary="通用授权-门禁设备条件查询")
async def get_associated_access_devices_info(request: Request, item: SearchDevicesInfo, token: str = Header(...)):
"""已具备关联空间信息的门禁设备条件查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = DevicesTable.get_associated_access_devices_info(th, item.search_type, item.search_key)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

View File

@ -0,0 +1,30 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Created :
@Updated :
@Contact : xuxingchen@sinochem.com
@Desc : 设备管理界面控制逻辑
"""
from fastapi import APIRouter, Request, Header
from models.devices import DevicesTable
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException
router = APIRouter()
@router.get("/getAllDevicesInfo", summary="获取全量的设备信息")
async def get_all_devices_info(request: Request, token: str = Header(...)):
"""获取全量的设备信息列表"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
devices_info = DevicesTable.get_devices_info(th)
resp = {"devices": devices_info}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

View File

@ -0,0 +1,163 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 模拟边缘对接所需的平台api接口
"""
from fastapi import APIRouter, Request, Query
from models.householders import HouseholdersTable
from models.houses import HousesTable
from models.spaces import SpacesTable
from models.subsystem import SubsystemTable, HouseDetailInfo
from routers.login import authenticate_token
from utils import logger
from utils.database import get_table_handler
from utils.misc import InvalidException, snake2camel_list_dict
router = APIRouter()
@router.post("/v2/accesskey_auth", summary="回传一个无效的随机字符串")
async def access_key_auth(_item: dict):
return {"access_token": "Q0E5QjU4QUNDMDA4MTY4RDNFNkRGRjNGNzQ4NDAzMTQ5OTVBMDQwODMyNjBFMjBCQkIwQjA2QzlCNUQ3MUY3NA=="}
@router.get("/v3/realty-master-data/owners/{user_id}", summary="根据用户 id 获取房产信息")
async def get_property_info_by_user_id(request: Request, user_id: int):
logger.Logger.debug(f"{request.url.path} <- {user_id}")
th = get_table_handler()
house_ids = HouseholdersTable.get_room_ids(th, user_id)
resp = {
"status": 200,
"msg": "ok",
"data": {
"house_ids": house_ids
},
"code": 200,
"det": 0
}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/v3/realty-master-data/houses", summary="根据房产 id 获取房产详细信息")
async def get_house_detail_info(request: Request, item: dict):
try:
logger.Logger.debug(f"{request.url.path} <- {item}")
house_ids = item["query"]["id"]["$in"]
house_detail_list = []
th = get_table_handler()
for house_id in house_ids:
house_detail_info = HousesTable.get_house_detail_info(th, house_id)
if house_detail_info is not None:
house_detail_list.append(house_detail_info)
resp = {
"status": 200,
"msg": "ok",
"data": {
"total_count": len(house_detail_list),
"total_page": 0,
"list": house_detail_list
},
"code": 200,
"det": 0
}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
except (KeyError, TypeError):
raise InvalidException("请求参数结构异常")
@router.post("/v2/realty-master-data/SubsystemSpaceMap/list", summary="查找空间结构中的房间信息")
async def get_subsystem_house_detail_info(request: Request, item: dict):
try:
logger.Logger.debug(f"{request.url.path} <- {item}")
project_id = item["query"]["project_id"]["$eq"]
th = get_table_handler()
data_list = SubsystemTable.get_house_detail_info_by_project_id(th, project_id)
resp = {
"status": 200,
"msg": "ok",
"data": {
"total_count": len(data_list),
"total_page": 0,
"list": data_list
},
"code": 200,
"det": 0
}
logger.Logger.debug(f"{request.url.path} -> {resp}")
return resp
except (KeyError, TypeError):
raise InvalidException("请求参数结构异常")
@router.post("/v2/realty-master-data/SubsystemSpaceMap/added", summary="添加房产信息")
async def add_subsystem_house_info(request: Request, item: HouseDetailInfo):
try:
logger.Logger.debug(f"{request.url.path} <- {item}")
th = get_table_handler()
SubsystemTable.add_sub_system_house_info(th, item)
resp = {
"status": 200,
"msg": "ok",
"data": item.message_id,
"code": 200,
"det": 0
}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
except (KeyError, TypeError):
raise InvalidException("请求参数结构异常")
@router.post("/v2/realty-master-data/SubsystemSpaceMap/update", summary="更新房产信息")
async def update_subsystem_house_info(request: Request, item: HouseDetailInfo):
try:
logger.Logger.debug(f"{request.url.path} <- {item}")
th = get_table_handler()
item.action = "update"
SubsystemTable.add_sub_system_house_info(th, item)
resp = {
"status": 200,
"msg": "ok",
"data": item.message_id,
"code": 200,
"det": 0
}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
except (KeyError, TypeError):
raise InvalidException("请求参数结构异常")
@router.post("/v2/smart-access/house/house_ty_maps", summary="查询房产的真实信息")
async def get_house_ty_info(request: Request, item: dict):
try:
logger.Logger.debug(f"{request.url.path} <- {item}")
project_id = item["query"]["project_id"]["$eq"]
room_id = item["query"]["_id"]["$eq"]
th = get_table_handler()
house_ty_info = HousesTable.get_house_ty_info(th, project_id, room_id)
if house_ty_info is None:
house_ty_infos = []
else:
house_ty_infos = [house_ty_info]
resp = {
"status": 200,
"msg": "ok",
"data": {
"count": 1,
"list": house_ty_infos,
"query_res": {
"count": 1,
"list": snake2camel_list_dict(house_ty_infos)
}
},
"det": 0
}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
except (KeyError, TypeError):
raise InvalidException("请求参数结构异常")

View File

@ -0,0 +1,84 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 住户管理界面控制逻辑
"""
from fastapi import APIRouter, Request, Header, Query
from models.householders import HouseholdersTable, GetHouseholdersInfo, AddHouseholderInfo, UpdateHouseholderInfo
from models.houses import HousesTable
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException
router = APIRouter()
@router.post("/addHouseholderInfo", summary="新增住户信息")
async def add_householder_info(request: Request, item: AddHouseholderInfo, token: str = Header(...)):
"""新增住户信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
# 进行住户信息插入
householder_id = HouseholdersTable.insert(th, item.name, item.sex, item.phone, "住户")
if householder_id:
for room_id, _type in item.property_info:
# 房产关联表信息插入
HouseholdersTable.insert_type(th, room_id, householder_id, _type)
# 更新房产中的住户数量
old_count = HousesTable.get_householder_count(th, room_id)
HousesTable.update_householder_count(th, room_id, old_count + 1)
resp = {"status": True}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/getHouseholderInfo", summary="获取住户信息")
async def get_householder_info(request: Request, item: GetHouseholdersInfo, token: str = Header(...)):
"""获取住户信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_householder_info(th, item.search_type, item.search_key, item.role_type,
item.page, item.limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getHouseholderInfo", summary="查询指定住户信息")
async def get_householder_detail_info(request: Request,
householder_id: int = Query(alias="id"),
token: str = Header(...)):
"""查询指定住户信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.get_householder_info_by_id(th, householder_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/updateHouseholderInfo", summary="更新指定住户信息")
async def update_householder_info(request: Request, item: UpdateHouseholderInfo, token: str = Header(...)):
"""更新指定住户信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.update_householder_info(th, item.householder_id, item.property_info)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.delete("/deleteHouseholderInfo", summary="删除指定住户信息及移除其关联设备授权")
async def delete_householder_info(request: Request, householder_id: int = Query(alias="id"), token: str = Header(...)):
"""删除指定住户的信息及其关联授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = HouseholdersTable.delete_householder_info(th, householder_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

View File

@ -0,0 +1,86 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 房产信息控制逻辑
"""
from typing import Optional
from fastapi import APIRouter, Header, Request, Query
from pydantic import BaseModel, field_validator, Field
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from models.houses import HousesTable
from utils.misc import InvalidException
router = APIRouter()
class ListsReq(BaseModel):
project: str = Field(description="必填项,用于限定项目编号")
area: Optional[str] = Field(None, description="可选项,用于限定区域")
building: Optional[str] = Field(None, description="可选项,用于限定楼栋")
unit: Optional[str] = Field(None, description="可选项,用于限定单元")
floor: Optional[str] = Field(None, description="可选项,用于限定楼层")
target: str = Field(description="必填项用于控制输出结果取值范围area, building, unit, floor, room")
def dict(self, *args, **kwargs):
_dict = self.__dict__.copy()
for key, value in self.__dict__.items():
if value is None:
_dict.pop(key)
_dict.pop("target")
return _dict
@field_validator("target")
def validate_target(cls, value):
if value not in ["area", "building", "unit", "floor", "room"]:
raise InvalidException("请提供正确的请求target [area, building, unit, floor, room]")
else:
return value
@router.get("/projects", summary="获取所有项目列表")
async def get_projects_list(request: Request, token: str = Header(...)):
"""获取项目列表"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
projects_list = HousesTable.get_items_by_dynamic_item(th, "project", {})
resp = {"projects": projects_list}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/project/lists", summary="动态获取房产信息")
async def get_items(request: Request,
item: ListsReq,
token: str = Header(...)):
"""根据请求获取列表"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
item_list = HousesTable.get_items_by_dynamic_item(th, item.target, item.dict())
resp = {f"{item.target.split('_')[0]}s": item_list}
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getHouseholderCount", summary="获取房产住户数量")
async def get_householder_count(request: Request,
room_id: str = Query(...),
token: str = Header(...)):
"""根据请求获取对应房屋中当前住户数量"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
count = HousesTable.get_householder_count(th, room_id)
if count is not None:
resp = {"count": count}
else:
raise InvalidException("room_id 不存在")
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

140
SCLP/routers/login.py Normal file
View File

@ -0,0 +1,140 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 登陆界面控制逻辑
"""
import hashlib
import os.path
import random
import io
import time
from typing import Optional
from starlette.responses import StreamingResponse
from pydantic import BaseModel
from fastapi import APIRouter, Query, Request
from PIL import Image, ImageDraw, ImageFont
from utils.database import get_table_handler
from utils import logger
from utils.misc import generate_captcha_text
from models.sessions import SessionsTable
router = APIRouter()
class LoginItem(BaseModel):
username: str
password: str
session_id: str
captcha: str
class LoginResp(BaseModel):
status: bool
message: str
token: Optional[str] = None
def generate_captcha_image(text, size=(120, 40), font_path='./data/mvboli.ttf', font_size=24):
"""生成验证码图片"""
image = Image.new('RGB', size, (73, 109, 137)) # 设置背景色
d = ImageDraw.Draw(image)
assert os.path.exists(font_path), "字体文件不存在"
font = ImageFont.truetype(font_path, font_size)
# d.text((5, 5), text, font=font, fill=(255, 255, 0)) # 设置字体颜色和位置
font_box = font.getbbox(text)
text_width, text_height = font_box[2] - font_box[0], font_box[3] - font_box[1]
text_x = (size[0] - text_width) // 2
text_y = (size[1] - text_height) // 2
d.text((text_x, text_y - 8), text, font=font, fill=(255, 255, 0))
# 添加噪点
for _ in range(100): # 可以根据需要调整噪点数量
x = random.randint(0, size[0] - 1)
y = random.randint(0, size[1] - 1)
image.putpixel((x, y), (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
# 添加线条
for _ in range(3): # 可以根据需要调整线条数量
x1, y1 = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1)
x2, y2 = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1)
d.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
width=2)
# 将图片保存为字节流
bytes_io = io.BytesIO()
image.save(bytes_io, format='PNG')
# image.save("./data/tmp.png", format='PNG')
bytes_io.seek(0)
return bytes_io
def hash_password(password):
"""对密码进行 SHA-256 哈希加密"""
password_bytes = password.encode('utf-8')
sha256_hash = hashlib.sha256()
sha256_hash.update(password_bytes)
return sha256_hash.hexdigest()
def verify_password(password, hashed_password):
"""比对密码和哈希值"""
password_hash = hash_password(password)
return password_hash.lower() == hashed_password.lower()
def authenticate_token(token: str):
th = get_table_handler()
timeout_timestamp = str(int(time.time() * 1000 - 4 * 60 * 60 * 1000))
return SessionsTable.check_token(th, timeout_timestamp, token)
@router.get("/generateCaptcha", summary="生成验证码")
async def generate_captcha_endpoint(request: Request,
session_id: str = Query(None, description="Session ID")):
"""生成验证码图片并返回"""
if session_id:
captcha_text = generate_captcha_text()
logger.Logger.debug(f"{request.url.path} 验证码生成:{captcha_text}")
# session_id 入库
th = get_table_handler()
SessionsTable.insert(th, session_id, captcha_text)
captcha_image = generate_captcha_image(captcha_text)
# 设置HTTP响应的头部指定内容类型为PNG图片
headers = {"Content-Type": "image/png"}
return StreamingResponse(captcha_image, headers=headers)
else:
logger.Logger.error(f"{request.url.path} 请求参数缺少 session_id - {request.client.host}")
return {"status": False, "message": "请求参数缺少 session_id"}
@router.post("/login", response_model=LoginResp, response_model_exclude_none=True, summary="登录请求")
async def login(item: LoginItem):
"""简单的账户登录逻辑"""
status = False
token = None
th = get_table_handler()
# 验证码校验
right_captcha = SessionsTable.get_captcha(th, item.session_id)
if right_captcha is not None:
if item.captcha.lower() == right_captcha.lower():
# 账户密码校验
if item.username == "admin":
if verify_password(item.password, "B12AD23C7E230E2CA365508DD16635DD3D7214BCD9BEA27457A356FD5C15F8BF"):
status = True
message = "校验通过"
token = generate_captcha_text(16)
SessionsTable.update(th, item.session_id, item.username, token)
else:
message = "密码错误"
else:
message = "账户不存在"
else:
message = "验证码错误"
else:
message = "无效 session_id"
resp = LoginResp(status=status, message=message, token=token)
logger.Logger.debug("/login " + str(resp.__dict__))
return resp

View File

@ -0,0 +1,85 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 车场管理界面控制逻辑
"""
from fastapi import APIRouter, Request, Header, Query
from models.parkinglots import ParkinglotsTable, AddParkingLot
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException
router = APIRouter()
@router.get("/getParkingLotsInfo", summary="车场信息查询")
async def get_parkinglots_info(request: Request, token: str = Header(...)):
"""获取车场信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_parkinglots_info(th)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/addParkingLot", summary="添加车场")
async def add_parkinglot(request: Request, item: AddParkingLot, token: str = Header(...)):
"""添加显示信息的车场 & 更新车场编号对应的名字和供应商"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
factory_name = item.factory_name if item.factory_name else ''
resp = ParkinglotsTable.update_parkinglot_show(th, item.parkinglot_number, item.parkinglot_name, factory_name, True)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.delete("/deleteParkingLot", summary="移除车场")
async def delete_parkinglot(request: Request,
parkinglot_number: str = Query(alias="number", description="车场编号"),
token: str = Header(...)):
"""移除显示信息的车场"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.update_parkinglot_show(th, parkinglot_number, None, None, False)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getParkingLotsAreaInfo", summary="区域信息查询")
async def get_parkinglots_area_info(request: Request, token: str = Header(...)):
"""区域信息查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_showed_parkinglot_area_info(th)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getParkingLotsChannelInfo", summary="通道信息查询")
async def get_parkinglots_channel_info(request: Request, token: str = Header(...)):
"""通道信息查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_showed_parkinglot_channel_info(th)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getParkingLotsGateInfo", summary="道闸信息查询")
async def get_parkinglots_channel_info(request: Request, token: str = Header(...)):
"""道闸信息查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_showed_parkinglot_gate_info(th)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

View File

@ -0,0 +1,108 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 车辆授权界面控制逻辑
"""
from fastapi import APIRouter, Query, Request, Header
from models.parkinglots import ParkinglotsTable, GetCarsInfo, AddCarsCard, UpdateCarsCard
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException
router = APIRouter()
@router.get("/getCarsInfo", summary="授权车辆信息查询")
async def get_cars_info(request: Request,
page: int = Query(None),
limit: int = Query(None),
token: str = Header(...)):
"""授权车辆信息全量查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_cars_info(th, None, None, page, limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/getCarsInfo", summary="授权车辆信息条件查询")
async def get_cars_info(request: Request,
item: GetCarsInfo,
token: str = Header(...)):
"""授权车辆信息全量查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_cars_info(th, item.search_type, item.search_key, item.page, item.limit)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getCarDetailInfo", summary="详细授权信息查询")
async def get_car_detail_info(request: Request,
auth_id: int = Query(...),
token: str = Header(...)):
"""授权车辆信息全量查询"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_auth_info(th, auth_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getCarIdAuthStatus", summary="获取车牌号授权状态")
async def get_car_id_auth_status(request: Request,
car_id: str = Query(...),
token: str = Header(...)):
"""获取车牌号授权状态"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.get_car_id_auth_status(th, car_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.delete("/deleteCarAuthId", summary="删除车辆授权")
async def delete_car_auth_id(request: Request,
auth_id: int = Query(...),
token: str = Header(...)):
"""删除车辆月卡授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.delete_car_auth_id(th, auth_id)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/addCarAuthInfo", summary="新增车辆授权")
async def add_car_card_auth(request: Request,
item: AddCarsCard,
token: str = Header(...)):
"""新增车辆月卡授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.add_car_card_auth(th, item)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.post("/updateCarInfo", summary="更新车辆授权")
async def add_car_card_auth(request: Request,
item: UpdateCarsCard,
token: str = Header(...)):
"""更新车辆月卡授权"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = ParkinglotsTable.update_car_card_auth(th, item)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

View File

@ -0,0 +1,101 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 通行空间映射界面控制逻辑
"""
import os.path
from io import BytesIO
from fastapi import APIRouter, Request, Header, BackgroundTasks, UploadFile, File
from fastapi.responses import FileResponse
from models.brands import BrandsTable, UpdateBrand
from models.spaces import SpacesTable
from routers.login import authenticate_token
from utils import logger
from utils.logger import TOKEN_ERROR
from utils.database import get_table_handler
from utils.misc import InvalidException, valid_xls
router = APIRouter()
@router.post("/updateSubSystemPlatformName", summary="更新当前使用的可视对讲厂家")
async def update_sub_system_platform_name(request: Request, item: UpdateBrand, token: str = Header(...)):
"""更新当前使用的可视对讲厂家"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = BrandsTable.update_checked_factory(th, item.name)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/getSubSystemPlatformName", summary="查询当前使用的可视对讲厂家")
async def get_sub_system_platform_name(request: Request, token: str = Header(...)):
"""获取当前启用的可视对讲厂家"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
resp = BrandsTable.get_checked_factory(th)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp
@router.get("/exportAiotPlatformData", summary="导出AIOT平台空间数据")
async def export_aiot_platform_data(request: Request, background_tasks: BackgroundTasks, token: str = Header(...)):
"""导出AIOT平台空间数据"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
file_path = SpacesTable.get_aiot_platform_data(th)
background_tasks.add_task(os.remove, file_path)
logger.Logger.debug(f"{request.url.path} 完成AIOT平台空间数据导出")
return FileResponse(path=file_path,
filename=os.path.basename(file_path),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@router.get("/exportSubSystemData", summary="导出子系统空间结构数据表")
async def export_aiot_platform_data(request: Request, background_tasks: BackgroundTasks, token: str = Header(...)):
"""导出子系统空间结构数据表"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
th = get_table_handler()
file_path = SpacesTable.get_sub_system_data(th)
background_tasks.add_task(os.remove, file_path)
logger.Logger.debug(f"{request.url.path} 完成子系统空间数据导出")
return FileResponse(path=file_path,
filename=os.path.basename(file_path),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@router.post("/importIdMapData", summary="导入空间映射表")
async def import_id_map_data(request: Request,
xls_file: UploadFile = File(...),
token: str = Header(...)):
"""接收空间映射表xls表格上传、校验并更新对应映射信息"""
if not authenticate_token(token):
raise InvalidException(TOKEN_ERROR)
# 检查文件类型是否为xls
if xls_file.content_type not in ["application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]:
resp = {"status": False, "message": "仅支持上传xls文件"}
else:
file_content = await xls_file.read()
max_size_mb = 10 # 设置最大允许的文件大小为 10MB
if xls_file.file.seek(0, os.SEEK_END) > max_size_mb * 1024 * 1024:
resp = {"status": False, "message": f"文件大小不得超过 {max_size_mb}MB"}
else:
# 解析xls校验数据格式是否符合标准
required_columns = ["房屋id", "子系统id", "子系统空间名称"]
_callback, message, data = valid_xls(BytesIO(file_content), required_columns)
if _callback:
th = get_table_handler()
SpacesTable.update(th, data)
resp = {"status": True}
else:
raise InvalidException(message)
logger.Logger.debug(f"{request.url.path} {resp}")
return resp

1
SCLP/run.sh Normal file
View File

@ -0,0 +1 @@
nohup python main.py >> nohup.out 2>&1 &

85
SCLP/utils/database.py Normal file
View File

@ -0,0 +1,85 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 数据库处理基础方法
"""
import sqlite3
from config import DB_PATH
from utils.logger import Logger, new_dc
class SQLiteDatabaseEngine:
def __init__(self, db_path: str = "demo.db") -> None:
self.sqlite3 = sqlite3
self.db_path = db_path
self.connection = None
self.cursor = None
self.connect()
def connect(self):
"""连接SQLite 数据库(如果数据库不存在则会自动创建)"""
self.connection = self.sqlite3.connect(self.db_path)
self.cursor = self.connection.cursor()
Logger.init(new_dc(f"🔗 SQLite - {self.db_path} has connect successfully! 🔗", "[1;32m"))
def disconnect(self):
try:
self.cursor.close()
self.connection.close()
except Exception as e:
if type(e).__name__ != "ProgrammingError":
Logger.error(f"{type(e).__name__}, {e}")
Logger.info(new_dc(f"🔌 Disconnect from SQLite - {self.db_path}! 🔌", "[1m"))
def exist(self, table_name):
self.cursor.execute(
f"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
(table_name,),
)
result = self.cursor.fetchone()
if result:
return True
else:
return False
class BaseTable:
def __init__(self, connection=None, cursor=None):
self.connection = connection
self.cursor = cursor
def set(self, connection, cursor):
self.connection = connection
self.cursor = cursor
def execute(self, sql: str | list, params: tuple | list = ()):
if isinstance(sql, list):
for i, s in enumerate(sql):
self.cursor.execute(s, params[i])
else:
self.cursor.execute(sql, params)
self.connection.commit()
def executemany(self, sql: str, params: list[tuple]):
self.cursor.executemany(sql, params)
self.connection.commit()
def query(self, sql: str, params: tuple = ()):
self.cursor.execute(sql, params)
class TableHandlerSingleton:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
db = SQLiteDatabaseEngine(db_path=DB_PATH)
cls._instance = BaseTable(db.connection, db.cursor)
return cls._instance
def get_table_handler():
return TableHandlerSingleton.get_instance()

163
SCLP/utils/logger.py Normal file
View File

@ -0,0 +1,163 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 日志模块
"""
import sys
import io
import time
DEBUG = None
LOGGER_PATH = None
TOKEN_ERROR = "token无效"
def log(text: str, log_path: str = None):
"""打印日志"""
log_path = log_path if log_path else LOGGER_PATH
log_line = '[{}] {}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), text)
if log_path:
log_file = open(log_path, 'a', encoding='utf8')
log_file.write("{}\n".format(log_line))
log_file.close()
print(log_line)
def log_plus(text: str, log_path: str = None, prefix_text: str = None, suffix_text: str = None):
"""加强版打印日志,预置了不同文字颜色"""
log_path = log_path if log_path else LOGGER_PATH
if prefix_text:
log_line_start = f"[{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}] {prefix_text}"
else:
log_line_start = f"[{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}]"
if suffix_text:
log_line = f"{log_line_start} {text} {suffix_text}"
else:
log_line = f"{log_line_start} {text}"
if log_path:
log_file = open(log_path, 'a', encoding='utf8')
log_file.write("{}\n".format(log_line))
log_file.close()
print(log_line)
def format_text(text_list: list, align_list: list = None) -> str:
"""格式化一些文本信息, 可以根据 align_list 调整每列文本的距离"""
if not align_list:
align_list = [(30, '<'), (60, '<')]
formatted_text = []
for txt, (int_align, flag) in zip(text_list, align_list):
formatted_text.append(f'{txt:{flag}{int_align}}')
return ' '.join(formatted_text)
def new_dc(msgs: str | float | int, fore_color: str = "", back_color: str = "") -> str:
"""给文本上色
fore_color格式如下
[{显示方式};{前景色};{背景色}m
显示方式
0默认值1高亮22非粗体4下划线24非下划线 5闪烁25非闪烁7反显27非反显
前景色
30黑色31红色32绿色 33黄色34蓝色35 36青色37白色
背景色
40黑色41红色42绿色 43黄色44蓝色45 46青色47白色
高亮绿色红色背景 [1;32;41m
默认-绿字体 [32m
"""
if fore_color == "":
fore_color = '[32m' # 默认绿色
return "\033{}{}{}\033[0m".format(back_color, fore_color, str(msgs))
def console_mute(func):
"""采用装饰器的方式提供函数的控制台标准输出临时置空,净化控制台环境"""
def wrapper(*args, **kwargs):
original_stdout = sys.stdout
try:
sys.stdout = io.StringIO()
return func(*args, **kwargs)
finally:
# 恢复原始的 sys.stdout 和 sys.stderr
sys.stdout = original_stdout
return wrapper
def log_speed_ms(start_time: float, suffix: str = "", prefix: str = "", number_color: str = "", decimal: int = 4):
"""控制台打印消耗的毫秒时间"""
log(f"{suffix}用时:{new_dc(str(round((time.time() - start_time) * 1000, decimal)), number_color)}ms{prefix}")
def speed_ms(start_time: float, decimal: int = 4):
"""消耗的毫秒时间"""
return round((time.time() - start_time) * 1000, decimal)
class Logger:
"""对控制台日志输出的二次封装,对部分常用日志进行预置"""
@staticmethod
def debug(text: str, log_path: str = None):
"""预置的debug形式的log"""
log_path = log_path if log_path else LOGGER_PATH
if isinstance(DEBUG, bool) and DEBUG:
log_plus(text, log_path, f"[{new_dc('INFO-DEBUG', '[34m')}]")
@staticmethod
def info(text: str, log_path: str = None):
"""预置的info形式的log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(text, log_path, f"[{new_dc('INFO', '[34m')}]")
@staticmethod
def error(text: str, log_path: str = None):
"""预置的error形式的log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(text, log_path, f"[{new_dc('ERROR', '[1;31m')}]")
@staticmethod
def warn(text: str, log_path: str = None):
"""预置的warn形式的log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(text, log_path, f"[{new_dc('WARN', '[1;33m')}]")
@staticmethod
def init(text: str, log_path: str = None):
"""预置的error形式的log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(text, log_path, f"[{new_dc('INIT', '[35m')}]")
@staticmethod
def title(text: str, log_path: str = None):
"""预置title形式的显目log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(new_dc(text, '[1m'), log_path, "🚀", "🚀")
@staticmethod
def complete(text: str, log_path: str = None):
"""预置complete形式的显目log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(new_dc(text, '[1;32m'), log_path, "", "")
@staticmethod
def remove(text: str, log_path: str = None):
"""预置remove形式的显目log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(new_dc(text, '[1m'), log_path, "🚮", "🚮")
@staticmethod
def connect(text: str, log_path: str = None):
"""预置connect形式的显目log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(new_dc(text, '[1;32m'), log_path, "🔗", "🔗")
@staticmethod
def disconnect(text: str, log_path: str = None):
"""预置disconnect形式的显目log"""
log_path = log_path if log_path else LOGGER_PATH
log_plus(new_dc(text, '[1m'), log_path, "🚫", "🚫")

360
SCLP/utils/misc.py Normal file
View File

@ -0,0 +1,360 @@
# -*- coding:utf-8 -*-
"""
@Author : xuxingchen
@Contact : xuxingchen@sinochem.com
@Desc : 杂项
"""
import base64
import hashlib
import os
import time
import warnings
from io import BytesIO
import re
import random
import string
import threading
from datetime import datetime, timedelta
import socket
import psutil
from typing import Optional
import numpy as np
import pandas as pd
import pytz
import requests
from PIL import Image
import paho.mqtt.client as mqtt
from openpyxl.styles import PatternFill, Font, Border, Side
from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.workbook import Workbook
from pydantic import BaseModel
from utils import logger
def get_ip_address(interface_name: str) -> tuple[str, str]:
interfaces = psutil.net_if_addrs()
interface = interfaces.get(interface_name, None)
ipv4_address, ipv6_address = "", ""
if interface:
for address in interface:
if address.family == socket.AF_INET:
ipv4_address = address.address
elif address.family == socket.AF_INET6 and not address.address.startswith("fe80"):
ipv6_address = address.address
return ipv4_address, ipv6_address
def extract_fixed_length_number(number_str: str, fixed_length: int = 2) -> str:
"""提取一串存在数值的字符串中的第一个数值对其从右往左取值定值不足补0"""
pattern = re.compile(r'[0-9]+')
number = pattern.search(number_str)
if number is None:
return "0" * fixed_length
else:
if len(number[0]) < fixed_length:
return "0" * (fixed_length - len(number[0])) + number[0]
else:
return number[0][-fixed_length:]
def snake2camel(key: str) -> str:
"""snake命名风格转成camel命名风格"""
parts = key.split('_')
return parts[0] + ''.join(word.capitalize() for word in parts[1:])
def snake2camel_list_dict(snake_list: list[dict]) -> list[dict]:
"""将list[dict]中所有的snake格式的key转换成camel格式的key命名风格"""
camel_list = []
for snake_dict in snake_list:
camel_dict = {}
for key, value in snake_dict.items():
camel_dict[snake2camel(key)] = value
camel_list.append(camel_dict)
return camel_list
def clear_log_file(log_path: str, day: int = 7):
"""每7天清除一次日志文件"""
creation_time = os.path.getctime(log_path)
days_since_creation = (time.time() - creation_time) / (60 * 60 * 24)
if os.path.exists(log_path) and days_since_creation >= day:
try:
f0 = open(log_path, "r", encoding="utf8")
f1 = open(f"{log_path}.old", "w", encoding="utf8")
f1.write(f0.read())
f1.close()
f0.close()
os.remove(log_path)
print(f"日志文件 {logger.LOGGER_PATH} 完成重置")
except Exception as e:
print(f"日志文件 {logger.LOGGER_PATH} 重置失败: {e}")
def generate_captcha_text(characters: int = 6) -> str:
"""生成指定长度的随机文本"""
letters_and_digits = string.ascii_letters + string.digits
return ''.join(random.choice(letters_and_digits) for _ in range(characters))
def encrypt_number(phone_number: str, key: bytes = b'7A') -> str:
"""将电话号码加密为字符串"""
phone_bytes = phone_number.encode('utf-8') # 将字符串转换为字节
encrypted_bytes = bytes(
[byte ^ key[i % len(key)] for i, byte in enumerate(phone_bytes)]
)
# 使用Base64编码加密后的字节
encrypted_number = base64.b64encode(encrypted_bytes).decode('utf-8')
return encrypted_number
def decrypt_number(encrypted_number: str, key: bytes = b'7A') -> str:
"""将字符串解密为电话号码"""
# 使用Base64解码加密后的字符串
encrypted_bytes = base64.b64decode(encrypted_number)
decrypted_bytes = bytes(
[byte ^ key[i % len(key)] for i, byte in enumerate(encrypted_bytes)]
)
decrypted_number = decrypted_bytes.decode('utf-8')
return decrypted_number
def now_tz_datetime(days: int = 0) -> str:
future_time = datetime.now() + timedelta(days=days)
return future_time.strftime("%Y-%m-%dT%H:%M:%S.") + f"{future_time.microsecond // 1000:03d}Z"
def now_datetime_nanosecond(days: int = 0) -> str:
future_time = datetime.now() + timedelta(days=days)
return future_time.strftime("%Y-%m-%d %H:%M:%S.%f")
def now_datetime_second(days: int = 0) -> str:
future_time = datetime.now() + timedelta(days=days)
return future_time.strftime("%Y-%m-%d %H:%M:%S")
def millisecond_timestamp2tz(timestamp_13: str):
timestamp = int(timestamp_13) / 1000
dt_utc = datetime.fromtimestamp(timestamp, tz=pytz.UTC)
# 转换为所需的时区这里以北京时间China Standard Time为例
china_tz = pytz.timezone('Asia/Shanghai')
return dt_utc.astimezone(china_tz).strftime("%Y-%m-%dT%H:%M:%S.") + f"{dt_utc.microsecond // 1000:03d}Z"
def is_image_url_valid(url: str) -> bool:
try:
# 发送请求获取URL内容
response = requests.get(url)
response.raise_for_status() # 如果状态码不是200会抛出异常
# 将内容加载为图片
image = Image.open(BytesIO(response.content))
image.verify() # 验证图像文件是否可读
# 如果上面的代码没有抛出异常,说明图片存在且格式可读
return True
except (requests.RequestException, IOError):
# 如果有任何异常,说明图片不可用或格式不可读
return False
def is_image_valid(path: str) -> bool:
try:
# 将内容加载为图片
image = Image.open(open(path, 'rb'))
image.verify() # 验证图像文件是否可读
# 如果上面的代码没有抛出异常,说明图片存在且格式可读
return True
except (requests.RequestException, IOError):
# 如果有任何异常,说明图片不可用或格式不可读
return False
def get_file_md5(file_path):
content = open(file_path, 'rb')
md5hash = hashlib.md5(content.read())
return md5hash.hexdigest()
def sql_export_xls(query,
db_connection,
save_file_path,
sheet_title,
sheet_header: Optional[list] = None,
header_background_color: str = "808080",
header_font_color: str = "ffffff"):
df = pd.read_sql_query(query, db_connection)
wb = Workbook()
ws = wb.active
ws.title = sheet_title
for i, r in enumerate(dataframe_to_rows(df, index=False, header=True)):
if sheet_header is not None and i == 0:
ws.append(sheet_header)
continue
ws.append(r)
# 表头样式
header_fill = PatternFill(start_color=header_background_color, fill_type="solid")
header_font = Font(color=header_font_color, bold=True)
for cell in ws[1]:
cell.fill = header_fill
cell.font = header_font
# 边框样式 表头无边框-数据无顶实线框
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
bottom=Side(style='thin')
)
for i, row in enumerate(ws.iter_rows()):
if i == 0:
continue
for cell in row:
cell.border = thin_border
# 单元格宽度自适应调整
for column in ws.columns:
max_length = 0
column = list(column)
for cell in column:
if cell.value is not None:
cell_length = len(str(cell.value))
if re.search(r'[\u4e00-\u9fff]', str(cell.value)):
cell_length += len(re.findall(r'[\u4e00-\u9fff]', str(cell.value)))
if cell_length > max_length:
max_length = cell_length
adjusted_width = (max_length + 2)
ws.column_dimensions[column[0].column_letter].width = adjusted_width
wb.save(save_file_path)
def valid_xls(file: BytesIO, required_columns: Optional[list]) -> tuple[bool, str, Optional[list]]:
"""如果校验通过返回list结构的数据如果检验不通过返回None"""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
try:
df = pd.read_excel(file)
df = df.replace(np.nan, None)
if all(col in df.columns for col in required_columns):
return True, "", df[required_columns].values.tolist()
else:
missing_cols = [col for col in required_columns if col not in df.columns]
return False, f"缺少必要的列: {', '.join(missing_cols)}", None
except Exception as e:
return False, f"文件解析失败 {type(e).__name__}, {e}", None
class BasicCallback(BaseModel):
status: bool
message: Optional[str]
class InvalidException(Exception):
def __init__(self, message: str):
self.message = message
class UserData:
def __init__(self):
self.table_handle = None
self.topic: Optional[str] = None
self.topics: list = []
self.table_handler = None
self.message = None
self.token = None
self.status: dict = {}
self.clients: dict = {}
self.lock = threading.Lock() # 添加一个锁用于线程同步
def set_table_handle(self, value):
with self.lock:
self.table_handle = value
def set_topic(self, value: str):
with self.lock:
self.topic = value
def set_topics(self, value: list):
with self.lock:
self.topics = value
def set_table_handler(self, value):
with self.lock:
self.table_handler = value
def set_message(self, value):
with self.lock:
self.message = value
def set_token(self, value):
with self.lock:
self.token = value
def set_status(self, value: dict):
with self.lock:
self.status = value
def set_status_add(self, key, value):
with self.lock:
self.status[key] = value
def set_status_remove(self, key):
with self.lock:
if self.status and key in self.status.keys():
self.status.pop(key)
def get_status(self, key):
if self.status and key in self.status.keys():
return self.status[key]
def set_clients(self, value: dict):
with self.lock:
self.clients = value
def set_client_add(self, key, value):
with self.lock:
self.clients[key] = value
def create_mqtt_client(broker_host,
broker_port,
userdata: UserData,
on_message=None,
on_publish=None,
on_connect=None,
on_disconnect=None,
client_id: str = "",
username: str = "",
password: str = ""):
if client_id != "":
client = mqtt.Client(client_id=client_id)
else:
client = mqtt.Client()
client.user_data_set(userdata)
if on_connect:
client.on_connect = on_connect
if on_disconnect:
client.on_disconnect = on_disconnect
if on_message:
client.on_message = on_message
if on_publish:
client.on_publish = on_publish
client.username_pw_set(username, password)
client.connect(broker_host, broker_port)
return client
def on_connect(client, userdata, flags, rc):
logger.Logger.init(logger.new_dc(f"🔗 Mqtt connection! {{rc: {rc}}} 🔗", '[1;32m'))
if userdata.topics:
_topics = [(topic, 0) for topic in userdata.topics]
client.subscribe(_topics)
logger.Logger.debug(f"subscribe topics: {userdata.topics}")
def on_disconnect(client, userdata, rc):
logger.Logger.info(logger.new_dc(f"🔌 Break mqtt connection! {{rc: {rc}}} 🔌", "[1m"))
def on_publish(client, userdata, rc):
logger.Logger.debug(f"{userdata.topic} <- {userdata.message}")