machine_access_control 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dm_mac/__init__.py +14 -0
- dm_mac/machine_state.py +73 -0
- dm_mac/py.typed +0 -0
- dm_mac/views/__init__.py +1 -0
- dm_mac/views/api.py +12 -0
- dm_mac/views/machine.py +30 -0
- machine_access_control-0.1.0.dist-info/LICENSE +21 -0
- machine_access_control-0.1.0.dist-info/METADATA +54 -0
- machine_access_control-0.1.0.dist-info/RECORD +10 -0
- machine_access_control-0.1.0.dist-info/WHEEL +4 -0
dm_mac/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Decatur Makers Machine Access Control."""
|
|
2
|
+
|
|
3
|
+
from flask import Flask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_app() -> Flask:
|
|
7
|
+
"""Factory to create the app."""
|
|
8
|
+
app: Flask = Flask("dm_mac")
|
|
9
|
+
|
|
10
|
+
from dm_mac.views.api import api
|
|
11
|
+
|
|
12
|
+
app.register_blueprint(api)
|
|
13
|
+
|
|
14
|
+
return app
|
dm_mac/machine_state.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Classes and functions related to machine state."""
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from logging import getLogger, Logger
|
|
4
|
+
from time import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
logger: Logger = getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
DEFAULT_DISPLAY_TEXT: str = 'Please Insert\nRFID Card'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MachineState:
|
|
13
|
+
"""Object representing frozen state in time of a machine."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, machine_name: str):
|
|
16
|
+
"""Initialize a new MachineState instance."""
|
|
17
|
+
logger.debug('Instantiating new MachineState for %s', machine_name)
|
|
18
|
+
#: The name of the machine
|
|
19
|
+
self.name: str = machine_name
|
|
20
|
+
#: Float timestamp of the machine's last checkin time
|
|
21
|
+
self.last_checkin: Optional[float] = None
|
|
22
|
+
#: Float timestamp of the last time that machine state changed,
|
|
23
|
+
#: not counting `current_amps` or timestamps.
|
|
24
|
+
self.last_update: Optional[float] = None
|
|
25
|
+
#: Value of the RFID card/fob in use, or None if not present.
|
|
26
|
+
self.rfid_value: Optional[str] = None
|
|
27
|
+
#: Float timestamp when `rfid_value` last changed to a non-None value.
|
|
28
|
+
self.rfid_present_since: Optional[float] = None
|
|
29
|
+
#: Whether the output relay is on or not.
|
|
30
|
+
self.relay_is_on: bool = False
|
|
31
|
+
#: Whether the output relay should be on or not.
|
|
32
|
+
self.relay_desired_state: bool = False
|
|
33
|
+
#: Whether the machine's Oops button has been pressed.
|
|
34
|
+
self.is_oopsed: bool = False
|
|
35
|
+
#: Whether the machine is locked out from use.
|
|
36
|
+
self.is_locked_out: bool = False
|
|
37
|
+
#: Whether the machine is force-enabled without RFID present.
|
|
38
|
+
self.is_force_enabled: bool = False
|
|
39
|
+
#: Last reported output ammeter reading (if equipped).
|
|
40
|
+
self.current_amps: float = 0
|
|
41
|
+
#: Text currently displayed on the machine LCD screen
|
|
42
|
+
self.display_text: str = DEFAULT_DISPLAY_TEXT
|
|
43
|
+
self._load_from_cache()
|
|
44
|
+
|
|
45
|
+
def _save_cache(self):
|
|
46
|
+
"""Save machine state cache to disk."""
|
|
47
|
+
raise NotImplementedError()
|
|
48
|
+
|
|
49
|
+
def update_has_changes(
|
|
50
|
+
self, rfid_value: str, relay_state: bool, oops: bool, amps: float
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Return whether or not the update causes changes to significant state values."""
|
|
53
|
+
if (
|
|
54
|
+
rfid_value != self.rfid_value or
|
|
55
|
+
relay_state != self.relay_is_on or
|
|
56
|
+
oops != self.is_oopsed
|
|
57
|
+
):
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def noop_update(self, amps: float):
|
|
62
|
+
"""Just update amps and last_checkin and save cache."""
|
|
63
|
+
self.current_amps = amps
|
|
64
|
+
self.last_checkin = time()
|
|
65
|
+
self._save_cache()
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def machine_response(self) -> dict:
|
|
69
|
+
"""Return the response dict to send to the machine."""
|
|
70
|
+
return {
|
|
71
|
+
'relay': self.relay_desired_state,
|
|
72
|
+
'display': self.display_text
|
|
73
|
+
}
|
dm_mac/py.typed
ADDED
|
File without changes
|
dm_mac/views/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Init for views (empty)."""
|
dm_mac/views/api.py
ADDED
dm_mac/views/machine.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Views related to machine endpoints."""
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
from flask import Blueprint, request, app
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
machine: Blueprint = Blueprint("machine", __name__, url_prefix="/machine")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@machine.route("/update", methods=['POST'])
|
|
10
|
+
def update():
|
|
11
|
+
"""
|
|
12
|
+
API method to update machine state.
|
|
13
|
+
|
|
14
|
+
Accepts POSTed JSON containing the following key/value pairs:
|
|
15
|
+
|
|
16
|
+
- "name" (string) - name of the machine sending the update
|
|
17
|
+
- "rfid_value" (string) - value of the RFID fob/card that is currently
|
|
18
|
+
present in the machine, or empty string if none present
|
|
19
|
+
- "relay_state" (boolean) - the current on/off (true/false) state of the
|
|
20
|
+
relay
|
|
21
|
+
- "oops" (boolean) - whether the oops button is pressed, or has been pressed
|
|
22
|
+
since the last check-in
|
|
23
|
+
- "amps" (float) - amperage value from the current clamp ammeter, if present,
|
|
24
|
+
or 0.0 otherwise.
|
|
25
|
+
"""
|
|
26
|
+
data: Dict[str, Any] = request.json
|
|
27
|
+
machine_name: str = data.pop('name')
|
|
28
|
+
# get the MachineState object for this machine, or else return an error
|
|
29
|
+
# that error should be formatted for display on the device (helper method for this)
|
|
30
|
+
# check if this data would update the state; if not, just call noop_update() and return the same display value
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Jason Antman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: machine_access_control
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Decatur Makers Machine Access Control package
|
|
5
|
+
Home-page: https://github.com/jantman/machine_access_control
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Jason Antman
|
|
8
|
+
Author-email: jason@jasonantman.com
|
|
9
|
+
Requires-Python: >=3.12,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: flask (>=3.0.3,<4.0.0)
|
|
15
|
+
Project-URL: Changelog, https://github.com/jantman/machine_access_control/releases
|
|
16
|
+
Project-URL: Documentation, https://github.com/jantman/machine_access_control
|
|
17
|
+
Project-URL: Repository, https://github.com/jantman/machine_access_control
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Decatur Makers Machine Access Control (dm-mac)
|
|
21
|
+
|
|
22
|
+
[](https://www.repostatus.org/#concept)
|
|
23
|
+
[](https://github.com/jantman/machine_access_control/actions?workflow=Tests)
|
|
24
|
+
|
|
25
|
+
This is a software and hardware project for using RFID cards/fobs to control use of various power tools and equipment in the [Decatur Makers](https://decaturmakers.org/) makerspace. It is made up of custom ESP32-based hardware (machine control units) controlling power to each enabled machine and running ESPHome, and a central access control/management/logging server application written in Python/Flask. Like our [“glue” server](https://github.com/decaturmakers/glue) that powers the RFID-based door access control to the makerspace, dm-mac uses the [Neon CRM](https://www.neoncrm.com/) (or a local flat-file when in development mode) as its backend datastore.
|
|
26
|
+
|
|
27
|
+
## Software Components
|
|
28
|
+
|
|
29
|
+
At a high level, the system is made up of the central control server and the ESPHome configuration for the ESP32’s.
|
|
30
|
+
|
|
31
|
+
### Control Server
|
|
32
|
+
|
|
33
|
+
This is a Python/Flask application that provides authentication and authorization for users via RFID credentials, control of the ESP32-based machine control units;, and logging and monitoring as well as basic management capabilities.
|
|
34
|
+
|
|
35
|
+
**Why not use the Glue server?** First, because the glue server is currently running in a cloud hosting provider. That makes sense for its purpose, but less so for direct control of physical machines in our space. We want the machine access control system to always function, regardless of the state of our Internet connection, with low latency. We also aren’t concerned about reliability through a power outage, as that will also prevent the controlled machines from working. Secondly, having the business logic contained in a central server with relatively “dumb” machine control units on the machines allows for simpler management of the system.
|
|
36
|
+
|
|
37
|
+
### Machine Control Unit Software
|
|
38
|
+
|
|
39
|
+
The machine control units run ESPHome, because it is well-supported with an active community, requires minimal programming (just a YAML configuration), and allows updating and managing many devices wirelessly from a central point. The machine control units (and their ESPHome configuration) are relatively simple - they just react to events (RFID card insertion or removal, button press, or a timer ticking), send their current state to the control server via a HTTP POST, and receive a response with the intended state of their outputs (control relay, LCD screen, LEDs). All of the logic of the system is contained in the central control server.
|
|
40
|
+
|
|
41
|
+
In the event of an extended control server outage, special event, or other exigent circumstance, the machine control unit software is configured with a list of permanently-authorized RFID cards that will enable the machine without requiring authorization from the control server.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
It's recommended to install and run via Docker. Details TBD.
|
|
46
|
+
|
|
47
|
+
## Contributing and Development
|
|
48
|
+
|
|
49
|
+
Contributions are very welcome. To learn more, see the [Contributor Guide](https://github.com/jantman/machine_access_control/blob/main/CONTRIBUTING.md).
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
Distributed under the terms of the [MIT license](https://github.com/jantman/machine_access_control/blob/main/LICENSE), _Machine_Access_Control_ (`dm_mac`) is free and open source software.
|
|
54
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
dm_mac/__init__.py,sha256=_uuRQaFFpWBEakxBpf-SjspfrT7arO1YWQOWGSOO1cU,256
|
|
2
|
+
dm_mac/machine_state.py,sha256=yzIGjqebHa_M-fg4rOff9MggccO91LgsIZUFOZ1jRJI,2810
|
|
3
|
+
dm_mac/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
dm_mac/views/__init__.py,sha256=U79DC2wZ9YQSJJfdwuS5Ben45AIvO-oPHpinq39KNwQ,30
|
|
5
|
+
dm_mac/views/api.py,sha256=InNokuWIV_C7GxsNNN2MkFL_yTpzkR8ouvaQytYpCUA,215
|
|
6
|
+
dm_mac/views/machine.py,sha256=mMUcc1l4kEhKbTC73A7hM3pik9TGThw46wYvigDBSmA,1251
|
|
7
|
+
machine_access_control-0.1.0.dist-info/LICENSE,sha256=TeKo6ThhJh4LJ9xJziWpGNKpBA2Ojnweppy0rzaRbDw,1069
|
|
8
|
+
machine_access_control-0.1.0.dist-info/METADATA,sha256=lN0V-osN4I588WZTPGEu9Q6EH3Ne7Kxnvha2LgbhmQw,4416
|
|
9
|
+
machine_access_control-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
10
|
+
machine_access_control-0.1.0.dist-info/RECORD,,
|