To create a micropython-native framework for IoT, with no services or accounts to sign up for to use. It's a library/framework; not a business model.
To provide a more decentralized IoT framework option. Allow devices to exist/operate on their own without needing to define a configuration for them off-device. This allows for additional framework components that can be added/removed based on a project's needs (more components will be introduced in the future, including external storage, monitoring, analysis, and more).
Recommended cli tool specific to this project: msf-cli. It can help with setting up an initial project and running a local MQTT broker for testing the application:
pipx install msf-cliThe msf framework package itself can be downloaded with (skip this if using the msf-cli tool):
mpremote mip install github:surdouski/micropython-sniffs-framework Provides a communication mechanism for sensor values over the network and between hardware components.
- Communication of
LocalSensorvalues passed to any other hardware currently running msf that defines aRemoteSensorwith the same name. - Decorators to trigger on sensor updates. (
@remote_sensor_foo.on_update()decorated on a function calls that function with the new value whenever that sensor value is updated).
RemoteSensor on one hardware device:
from msf.sensor import RemoteSensor
remote_sensor_foo = RemoteSensor(name="foo_sensor")
@remote_sensor_foo.on_update()
def on_update_foo(value):
print(value)LocalSensor on another hardware device:
from msf.sensor import LocalSensor
local_sensor_foo = LocalSensor(name="foo_sensor")
await local_sensor_foo.update(42)When local_sensor_foo.update(42) is called, the other hardware device with remote_sensor_foo defined will print 42.
Note: The default MQTT topic for sensors is test/sensors. This can be changed by updating MQTT_SENSORS_TOPIC in settings.py.
To access a LocalSensor through the registry:
from msf.sensor import LocalSensorsRegistry
local_sensors_registry = LocalSensorsRegistry()
foo_sensor = local_sensors_registry.get("local_sensor_foo") # only works if the firmware on device already defined thisTo access a RemoteSensor through the registry:
from msf.sensor import RemoteSensorsRegistry
remote_sensors_registry = RemoteSensorsRegistry()
foo_sensor = remote_sensors_registry.get("remote_sensor_foo") # only works if the firmware on device already defined thisNote: Registries will not retrieve across hardware devices boundaries. They are for static retrieval of locally defined objects.
Provides stateful configuration management for firmware. Can be managemed remotely and persists through hardware resets.
- Live setting changes are maintained through hardware reboots.
- Decorators to trigger on setting updates. (
@setting_foo.on_update()decorated on a function calls that function with the new value whenever that setting is updated).
To define a device and settings, create Setting objects and pass them to Device.
from msf.device import Device, Setting
setting_1 = Setting(name="setting_1", value=4.0, description="The number 4, as a float.")
setting_2 = Setting(name="setting_2", value=3, description="The number 3, as an int.")
device = Device(device_name="water_pump", settings=[setting_1,setting_2])This will either create a new saved state in the device settings, or for any settings that had previously been defined, it will pull the value from the state instead.
For instance, if a device was saved with the above settings, but had updated the setting_1 to 5.0, the setting_1.value would be 5.0 after the Device instantiation:
# saved state for setting_1.value is currently "5.0"
setting_1 = Setting(name="setting_1", value=4.0, description="The number 4, as a float.")
print(setting_1.value) # before Device instantiation: 4.0
device = Device(device_name="water_pump", settings=[setting_1])
print(setting_1.value) # after Device instantiation: 5.0Loading the state in this way ensures changed/updated settings are not lost because of a hardware reboot.
Note: The default MQTT topic for sensors is test/devices. This can be changed by updating MQTT_DEVICES_TOPIC in settings.py.
When a setting is updated (except for the Device instantiation), this decorated function is called with the new setting value.
An example of how this might be used in practice:
from msf.device import Device, Setting
from machine import PWM
duty_u16 = Setting(name="duty_u16", value=8192, description="For use in PWM of pump.")
water_pump = Device(device_name="water_pump", settings=[duty_u16])
pwm = PWM(pin, freq=50, duty_u16=duty_u16.value)
@duty_u16.on_update()
def pump_change_duty_cycle(new_value):
pwm.duty_u16(new_value)Whenever the duty_u16 setting was updated, it would trigger the pump_change_duty_cycle function and update the PWM to use the new_value. The setting could be updated non-locally through the msf-cli tool, with the following command:
msf devices water_pump duty_u16 --set 4000More information on using the msf-cli tool can be found here.
from msf.device import Setting, Device
setting_1 = Setting(name="setting_1", value=42, description="The answer to the question.")
my_device = Device(device_name="my_device", settings=[setting_1])
foo_ref_to_setting_1 = my_device.settings.get("setting_1")
bar_ref_to_setting_1 = my_device.settings["setting_1"] # alternatively, but can raise KeyErrorImport Device or Setting objects into other modules:
foo.py
from msf.device import Setting, Device
setting_1 = Setting(name="setting_1", value=42, description="The answer to the question.")
my_device = Device(device_name="my_device", settings=[setting_1])bar.py
from .foo import setting_1 # relative imports are actually evil, but this is just an example so it's fine
print(setting_1.value) # 42, or whichever value was loaded from the saved stateAlternatively, use the singleton DevicesRegistry for access:
foo.py
from msf.device import Setting, Device
setting_1 = Setting(name="setting_1", value=42, description="The answer to the question.")
my_device = Device(device_name="my_device", settings=[setting_1])bar.py
from msf.device import DevicesRegistry
my_device = DevicesRegistry().devices.get("my_device")
setting_1 = my_device.settings.get("setting_1")Devices settings save to the DEVICES_SETTINGS_PATH defined in settings.py. The default value is "/.settings/devices.json".
Example:
/.settings/devices.json
{
"water_pump": {
"setting_1": {
"value": "4.0",
"type": "float",
"description": "The description of setting 1."
},
"setting_2": {
"value": "3",
"type": "int",
"description": "The description of setting 2."
}
},
"another_device": {
"other_device_setting_1": {
"value": "some string",
"type": "str",
"description": "The description of other device setting 1."
}
}
}The JSON adheres to the following rules:
- Important: No keys can contain
.character. - Every setting has a value, type, and description.
- The value, type, and description must all be strings. The value will be converted to and from a string using the type definition.
- Currently supported types are
(float/int/str).