diff --git a/pnpdevice/config.py b/pnpdevice/config.py
deleted file mode 100644
index 4d74568..0000000
--- a/pnpdevice/config.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from starlette.config import Config
-
-config = Config(".env")
-
-DEBUG = config("DEBUG", cast=bool, default=False)
-DATABASE = config("DATABASE_URL", cast=databases.DatabaseURL)
-SECRET_KEY = config("SECRET_KEY", cast=Secret)
-ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=CommaSeparatedStrings)
diff --git a/pnpdevice/relays.py b/pnpdevice/relays.py
index 5b19ac1..0d7ccf8 100644
--- a/pnpdevice/relays.py
+++ b/pnpdevice/relays.py
@@ -9,18 +9,27 @@ import tomli_w
LOGGER = logging.getLogger(__name__)
-@dataclasses.dataclass
+import rgpio
+
+@dataclasses.dataclass(frozen=True)
class Pin:
chip: str
number: int
name: str
+ @property
+ def id(self) -> str:
+ return f"{self.chip}-{self.number}"
-@dataclasses.dataclass
+@dataclasses.dataclass(frozen=True)
class Relay:
pin: Pin
name: str
state: bool
+ @property
+ def id(self) -> str:
+ return f"{self.pin.id}"
+
def _deserialize(data: List[Dict[str, str]]) -> List[Relay]:
"Deserialize a list of relays from the config."
return [Relay(
@@ -29,10 +38,12 @@ def _deserialize(data: List[Dict[str, str]]) -> List[Relay]:
chip=relay["chip"],
number=int(relay["pinnumber"]),
name=relay["pinname"],
- )) for relay in data]
+ ),
+ state=False) for relay in data]
def _serialize(data: List[Relay]) -> List[Dict[str, str]]:
"Serialize a list of relays to the config."
+ LOGGER.info("Serializing %s", data)
return [{
"chip": relay.pin.chip,
"name": relay.name,
@@ -40,71 +51,87 @@ def _serialize(data: List[Relay]) -> List[Dict[str, str]]:
"pinname": relay.pin.name,
} for relay in data]
-class Relays:
+class Manager:
"Class for interacting with relays."
- def __init__(self, configpath: pathlib.Path):
- self.configpath = configpath
+ def __init__(self, configpath: pathlib.Path, has_fakes: bool):
try:
- with open(configpath, "rb") as f:
- content = tomllib.load(f)
- self.config = content
- except Exception as e:
- LOGGER.info("Unable to load config file: %s", e)
+ self.config = _read_config(configpath)
+ except FileNotFoundError:
self.config = {
- "relays": [],
+ "rgpio": {"host": "localhost", "port": 8889},
+ "relays": [{
+ "chip": -1,
+ "pin": "FAKE-P1",
+ "name": "Fake",
+ }],
}
- self.relays = _deserialize(self.config["relays"])
+ # Immediately write out the default config
+ _write_config(configpath, self.config)
+ self.configpath = configpath
+ self.has_fakes = has_fakes
+ # Connection to single-board computer GPIO
+ self.sbc = None
+
+ def __iter__(self):
+ "Provide an iterator for the relays."
+ return iter(self.relays)
+
+ def connect(self) -> None:
+ self.sbc = rgpio.sbc(
+ host=self.config["rgpio"]["host"],
+ port=self.config["rgpio"]["port"],
+ )
def chips_list(self) -> List[str]:
- raise NotImplementedError()
+ if self.has_fakes:
+ return ["fakeA", "fakeB"]
+
+ def get_relay_by_pin_id(self, pin_id: str) -> Relay:
+ for relay in self.relays:
+ if relay.pin.id == pin_id:
+ return relay
+ return None
def pins_list(self) -> List[Pin]:
- raise NotImplementedError()
+ if self.has_fakes:
+ return [
+ Pin(chip="fakeA", number=1, name="CON2-P1"),
+ Pin(chip="fakeA", number=2, name="CON2-P2"),
+ Pin(chip="fakeA", number=3, name="CON2-P10"),
+ Pin(chip="fakeB", number=1, name="CON2-P3"),
+ Pin(chip="fakeB", number=2, name="CON2-P7"),
+ ]
+
def relay_add(self, chip: str, pinnumber: int, name: str) -> None:
- self.config["relays"].append({
- "chip": chip,
- "name": name,
- "pinnumber": pinnumber,
- })
- with open(self.configpath, "wb") as f:
- tomli_w.dump({
- "relays": _serialize(self.config["relays"]),
- }, f)
- LOGGER.info("Wrote config file %s", self.configpath)
+ LOGGER.info("Creating relay %s %s with name %s", chip, pinnumber, name)
+ self.relays.append(Relay(
+ name=name,
+ pin=Pin(
+ chip=chip,
+ number=pinnumber,
+ name=name,
+ ),
+ state=False))
+ self._write_config()
- def relays_list(self) -> List[Relay]:
- return self.relays
-
- def relay_on(self, name: str) -> bool:
- raise NotImplementedError()
+ def relay_set(self, relay: Relay, state: bool) -> None:
+ if not relay.pin.chip.startswith("fake"):
+ pass
- def relay_set(self, name: str, state: bool) -> None:
- raise NotImplementedError()
+ def shutdown(self) -> None:
+ _write_config(self.configpath, self.config)
-class RelaysFake(Relays):
- "Class for fake relays, useful for testing."
- def chips_list(self) -> List[str]:
- return ["chipA", "chipB"]
- def pins_list(self) -> List[Pin]:
- return [
- Pin(chip="chipA", number=1, name="CON2-P1"),
- Pin(chip="chipA", number=2, name="CON2-P2"),
- Pin(chip="chipA", number=3, name="CON2-P10"),
- Pin(chip="chipB", number=1, name="CON2-P3"),
- Pin(chip="chipB", number=2, name="CON2-P7"),
- ]
+def _read_config(configpath: pathlib.Path) -> None:
+ with open(configpath, "rb") as f:
+ content = tomllib.load(f)
+ LOGGER.info("Read config from %s", configpath)
+ return content
- def relays_list(self) -> List[Relay]:
- raise NotImplementedError()
-
- def relay_on(self, name: str) -> bool:
- raise NotImplementedError()
+def _write_config(configpath: pathlib.Path, config) -> None:
+ with open(configpath, "wb") as f:
+ tomli_w.dump(config, f)
+ LOGGER.info("Wrote config file %s", configpath)
- def relay_set(self, name: str, state: bool) -> None:
- raise NotImplementedError()
-
-class RelaysReal(Relays):
- "Class for controlling real relays."
diff --git a/pnpdevice/server.py b/pnpdevice/server.py
index 03b8515..abe41ee 100644
--- a/pnpdevice/server.py
+++ b/pnpdevice/server.py
@@ -1,48 +1,78 @@
-from fastapi import FastAPI, Request
+from contextlib import asynccontextmanager
+import logging
+
+from fastapi import FastAPI, Form, Request
+from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.responses import RedirectResponse
import jinja2
-from pnpdevice.config import config
+from pnpdevice import settings
+from pnpdevice.relays import Manager
import pnpdevice.discovery
import pnpdevice.relays
-app = FastAPI(debug=config.DEBUG)
+LOGGER = logging.getLogger(__name__)
+logging.basicConfig(level=logging.DEBUG)
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ relays.connect()
+ yield
+ relays.shutdown()
+
+app = FastAPI(debug=settings.DEBUG, lifespan=lifespan)
+relays = Manager(settings.CONFIGFILE, settings.RELAYS_FAKE)
templates = Jinja2Templates(directory="templates")
-@app.get("/")
-def index():
- relays = request.app["relays"]
+@app.get("/", response_class=HTMLResponse)
+def index(request: Request):
return templates.TemplateResponse("index.template.html", {
- "relays": relays.relays_list(),
+ "request": request,
+ "relays": relays,
})
@app.get("/relay/create")
def relay_create_get(request: Request):
"Get the form to create a new relay."
- relays = request.app["relays"]
pins = relays.pins_list()
- return templates.TemplateResponse("relay-create.template.html", {"pins": pins})
+ return templates.TemplateResponse("relay-create.template.html", {
+ "request": request,
+ "pins": pins
+ })
-async def relay_create_post(request: Request, chip_and_number: str, name: str):
- "Create a new relay."
- chip, number = chip_and_number.partition("-")
- LOGGER.info("Creating relay %s %s with name %s", chip, number, name)
+@app.post("/relay/create")
+def relay_create_post(
+ request: Request,
+ chip_and_number: str = Form(...),
+ name: str = Form(...),
+ ):
+ "Create a new relay from form POST."
+ chip, _, number = chip_and_number.partition("-")
+ number = int(number)
+ relays.relay_add(chip, number, name)
+ return RedirectResponse(status_code=303, url="/")
+
+@app.get("/relay/{pin_id}")
+def relay_detail_get(request: Request, pin_id: str):
+ "Get details on a single relay."
+ relay = relays.get_relay_by_pin_id(pin_id)
+ return templates.TemplateResponse("relay-detail.template.html", {
+ "request": request,
+ "relay": relay
+ })
+
+@app.post("/relay/create")
+def relay_state_post(
+ request: Request,
+ pin_id: str = Form(...),
+ state: str = Form(...),
+ ):
+ "Change the state of a relay."
+ relay = relays.get_relay_by_pin_id(pin_id)
+ relays.relay_set()
return RedirectResponse(status_code=303, url="/")
async def status(request: Request):
return html("Status")
-
-def run(relays: pnpdevice.relays.Relays):
- "Run the embedded web server"
- app = web.Application()
- app["relays"] = relays
- aiohttp_jinja2.setup(app,
- loader=jinja2.FileSystemLoader("templates"))
- app.on_startup.append(pnpdevice.discovery.handle)
- app.add_routes([web.get("/", index)])
- app.add_routes([web.get("/relay/create", relay_create_get)])
- app.add_routes([web.post("/relay/create", relay_create_post)])
- web.run_app(app)
-
diff --git a/pnpdevice/settings.py b/pnpdevice/settings.py
new file mode 100644
index 0000000..e1e7d2a
--- /dev/null
+++ b/pnpdevice/settings.py
@@ -0,0 +1,11 @@
+from pathlib import Path
+from starlette.config import Config
+from starlette.datastructures import CommaSeparatedStrings
+
+config = Config(".env")
+
+CONFIGFILE = config("CONFIGFILE", cast=Path, default="/etc/pnpdevice.toml")
+DEBUG = config("DEBUG", cast=bool, default=False)
+RELAYS_FAKE = config("RELAYS_FAKE", cast=bool, default=False)
+# SECRET_KEY = config("SECRET_KEY", cast=Secret)
+# ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=CommaSeparatedStrings)
diff --git a/pyproject.toml b/pyproject.toml
index c6ef362..0f8d2e6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,6 +5,7 @@ authors = [
]
dependencies = [
"fastapi",
+ "python-multipart",
"tomli-w",
"uvicorn[standard]",
"zeroconf",
diff --git a/rgpio.py b/rgpio.py
new file mode 100644
index 0000000..07c5bb5
--- /dev/null
+++ b/rgpio.py
@@ -0,0 +1,3806 @@
+"""
+[http://abyz.me.uk/lg/py_rgpio.html]
+
+rgpio is a Python module which allows remote control of the GPIO
+and other functions of Linux SBCs running the rgpiod daemon.
+
+The rgpiod daemon must be running on the SBCs you wish to control.
+
+*Features*
+
+o the rgpio Python module can run on Windows, Macs, or Linux
+o controls one or more SBCs
+o reading and writing GPIO singly and in groups
+o software timed PWM and waves
+o GPIO callbacks
+o pipe notification of GPIO alerts
+o I2C wrapper
+o SPI wrapper
+o serial link wrapper
+o simple file handling
+o creating and running scripts on the rgpiod daemon
+
+*Exceptions*
+
+By default a fatal exception is raised if you pass an invalid
+argument to a rgpio function.
+
+If you wish to handle the returned status yourself you should set
+rgpio.exceptions to False.
+
+You may prefer to check the returned status in only a few parts
+of your code. In that case do the following:
+
+...
+rgpio.exceptions = False
+
+# Code where you want to test the error status.
+
+rgpio.exceptions = True
+...
+
+*Usage*
+
+The rgpiod daemon must be running on the SBCs whose GPIO
+are to be manipulated.
+
+The normal way to start rgpiod is during system start.
+
+rgpiod &
+
+Your Python program must import rgpio and create one or more
+instances of the rgpio.sbc class. This class gives access to
+the specified SBC's GPIO.
+
+...
+sbc1 = rgpio.sbc() # sbc1 accesses the local SBC's GPIO
+sbc2 = rgpio.sbc('tom') # sbc2 accesses tom's GPIO
+sbc3 = rgpio.sbc('dick') # sbc3 accesses dick's GPIO
+...
+
+The later example code snippets assume that sbc is an instance of
+the rgpio.sbc class.
+
+*Licence*
+
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
+
+OVERVIEW
+
+ESSENTIAL
+
+rgpio.sbc Initialise sbc connection
+stop Stop a sbc connection
+
+FILES
+
+file_open Opens a file
+file_close Closes a file
+
+file_read Reads bytes from a file
+file_write Writes bytes to a file
+
+file_seek Seeks to a position within a file
+
+file_list List files which match a pattern
+
+GPIO
+
+gpiochip_open Opens a gpiochip device
+gpiochip_close Closes a gpiochip device
+
+gpio_get_chip_info Get information about a gpiochip
+gpio_get_line_info Get information about a gpiochip line
+
+gpio_get_mode Gets the mode of a GPIO
+
+gpio_claim_input Claims a GPIO for input
+gpio_claim_output Claims a GPIO for output
+gpio_claim_alert Claims a GPIO for alerts
+gpio_free Frees a GPIO
+
+group_claim_input Claims a group of GPIO for inputs
+group_claim_output Claims a group of GPIO for outputs
+group_free Frees a group of GPIO
+
+gpio_read Reads a GPIO
+gpio_write Writes a GPIO
+
+group_read Reads a group of GPIO
+group_write Writes a group of GPIO
+
+tx_pulse Starts pulses on a GPIO
+tx_pwm Starts PWM on a GPIO
+tx_servo Starts servo pulses on a GPIO
+tx_wave Starts a wave on a group of GPIO
+tx_busy See if tx is active on a GPIO or group
+tx_room See if more room for tx on a GPIO or group
+
+gpio_set_debounce_micros Sets the debounce time for a GPIO
+gpio_set_watchdog_micros Sets the watchdog time for a GPIO
+
+callback Starts a GPIO callback
+
+I2C
+
+i2c_open Opens an I2C device
+i2c_close Closes an I2C device
+
+i2c_write_quick SMBus write quick
+
+i2c_read_byte SMBus read byte
+i2c_write_byte SMBus write byte
+
+i2c_read_byte_data SMBus read byte data
+i2c_write_byte_data SMBus write byte data
+
+i2c_read_word_data SMBus read word data
+i2c_write_word_data SMBus write word data
+
+i2c_read_block_data SMBus read block data
+i2c_write_block_data SMBus write block data
+
+i2c_read_i2c_block_data SMBus read I2C block data
+i2c_write_i2c_block_data SMBus write I2C block data
+
+i2c_read_device Reads the raw I2C device
+i2c_write_device Writes the raw I2C device
+
+i2c_process_call SMBus process call
+i2c_block_process_call SMBus block process call
+
+i2c_zip Performs multiple I2C transactions
+
+NOTIFICATIONS
+
+notify_open Request a notification handle
+notify_close Close a notification
+notify_pause Pause notifications
+notify_resume Resume notifications
+
+SCRIPTS
+
+script_store Store a script
+script_run Run a stored script
+script_update Set a scripts parameters
+script_status Get script status and parameters
+script_stop Stop a running script
+script_delete Delete a stored script
+
+SERIAL
+
+serial_open Opens a serial device
+serial_close Closes a serial device
+
+serial_read_byte Reads a byte from a serial device
+serial_write_byte Writes a byte to a serial device
+
+serial_read Reads bytes from a serial device
+serial_write Writes bytes to a serial device
+
+serial_data_available Returns number of bytes ready to be read
+
+SHELL
+
+shell Executes a shell command
+
+SPI
+
+spi_open Opens a SPI device
+spi_close Closes a SPI device
+
+spi_read Reads bytes from a SPI device
+spi_write Writes bytes to a SPI device
+spi_xfer Transfers bytes with a SPI device
+
+UTILITIES
+
+get_sbc_name Get the SBC name
+
+get_internal Get an SBC configuration value
+set_internal Set an SBC configuration value
+
+set_user Set the user (and associated permissions)
+set_share_id Set the share id for a resource
+use_share_id Use this share id when asking for a resource
+
+rgpio.get_module_version Get the rgpio Python module version
+rgpio.error_text Get the error text for an error code
+"""
+
+import sys
+import socket
+import struct
+import time
+import threading
+import os
+import atexit
+import hashlib
+
+RGPIO_PY_VERSION = 0x00020200
+
+exceptions = True
+
+MAGIC=1818715245
+
+# GPIO levels
+
+OFF = 0
+LOW = 0
+CLEAR = 0
+
+ON = 1
+HIGH = 1
+SET = 1
+
+TIMEOUT = 2
+
+GROUP_ALL = 0xffffffffffffffff
+
+# GPIO line flags
+
+SET_ACTIVE_LOW = 4
+SET_OPEN_DRAIN = 8
+SET_OPEN_SOURCE = 16
+SET_PULL_UP = 32
+SET_PULL_DOWN = 64
+SET_PULL_NONE = 128
+
+# GPIO event flags
+
+RISING_EDGE = 1
+FALLING_EDGE = 2
+BOTH_EDGES = 3
+
+# tx constants
+
+TX_PWM = 0
+TX_WAVE = 1
+
+# script run status
+
+SCRIPT_INITING = 0
+SCRIPT_READY = 1
+SCRIPT_RUNNING = 2
+SCRIPT_WAITING = 3
+SCRIPT_ENDED = 4
+SCRIPT_HALTED = 5
+SCRIPT_FAILED = 6
+
+# notification flags
+
+NTFY_FLAGS_ALIVE = (1 << 0)
+
+FILE_READ = 1
+FILE_WRITE = 2
+FILE_RW = 3
+
+FILE_APPEND = 4
+FILE_CREATE = 8
+FILE_TRUNC = 16
+
+FROM_START = 0
+FROM_CURRENT = 1
+FROM_END = 2
+
+SPI_MODE_0 = 0
+SPI_MODE_1 = 1
+SPI_MODE_2 = 2
+SPI_MODE_3 = 3
+
+_SOCK_CMD_LEN = 16
+
+# rgpiod command numbers
+
+_CMD_FO = 1
+_CMD_FC = 2
+_CMD_FR = 3
+_CMD_FW = 4
+_CMD_FS = 5
+_CMD_FL = 6
+_CMD_GO = 10
+_CMD_GC = 11
+_CMD_GSIX = 12
+_CMD_GSOX = 13
+_CMD_GSAX = 14
+_CMD_GSF = 15
+_CMD_GSGIX = 16
+_CMD_GSGOX = 17
+_CMD_GSGF = 18
+_CMD_GR = 19
+_CMD_GW = 20
+_CMD_GGR = 21
+_CMD_GGWX = 22
+_CMD_GPX = 23
+_CMD_PX = 24
+_CMD_SX = 25
+_CMD_GWAVE = 26
+_CMD_GBUSY = 27
+_CMD_GROOM = 28
+_CMD_GDEB = 29
+_CMD_GWDOG = 30
+_CMD_GIC = 31
+_CMD_GIL = 32
+_CMD_GMODE = 33
+_CMD_I2CO = 40
+_CMD_I2CC = 41
+_CMD_I2CRD = 42
+_CMD_I2CWD = 43
+_CMD_I2CWQ = 44
+_CMD_I2CRS = 45
+_CMD_I2CWS = 46
+_CMD_I2CRB = 47
+_CMD_I2CWB = 48
+_CMD_I2CRW = 49
+_CMD_I2CWW = 50
+_CMD_I2CRK = 51
+_CMD_I2CWK = 52
+_CMD_I2CRI = 53
+_CMD_I2CWI = 54
+_CMD_I2CPC = 55
+_CMD_I2CPK = 56
+_CMD_I2CZ = 57
+_CMD_NO = 70
+_CMD_NC = 71
+_CMD_NR = 72
+_CMD_NP = 73
+_CMD_PARSE = 80
+_CMD_PROC = 81
+_CMD_PROCD = 82
+_CMD_PROCP = 83
+_CMD_PROCR = 84
+_CMD_PROCS = 85
+_CMD_PROCU = 86
+_CMD_SERO = 90
+_CMD_SERC = 91
+_CMD_SERRB = 92
+_CMD_SERWB = 93
+_CMD_SERR = 94
+_CMD_SERW = 95
+_CMD_SERDA = 96
+_CMD_SPIO = 100
+_CMD_SPIC = 101
+_CMD_SPIR = 102
+_CMD_SPIW = 103
+_CMD_SPIX = 104
+_CMD_MICS = 113
+_CMD_MILS = 114
+_CMD_CGI = 115
+_CMD_CSI = 116
+_CMD_NOIB = 117
+_CMD_SHELL = 118
+_CMD_SBC = 120
+_CMD_FREE = 121
+_CMD_SHARE = 130
+_CMD_USER = 131
+_CMD_PASSW = 132
+_CMD_LCFG = 133
+_CMD_SHRU = 134
+_CMD_SHRS = 135
+_CMD_PWD = 136
+_CMD_PCD = 137
+_CMD_LGV = 140
+_CMD_TICK = 141
+
+# rgpiod error numbers
+
+OKAY = 0
+INIT_FAILED = -1
+BAD_MICROS = -2
+BAD_PATHNAME = -3
+NO_HANDLE = -4
+BAD_HANDLE = -5
+BAD_SOCKET_PORT = -6
+NOT_PERMITTED = -7
+SOME_PERMITTED = -8
+BAD_SCRIPT = -9
+BAD_TX_TYPE = -10
+GPIO_IN_USE = -11
+BAD_PARAM_NUM = -12
+DUP_TAG = -13
+TOO_MANY_TAGS = -14
+BAD_SCRIPT_CMD = -15
+BAD_VAR_NUM = -16
+NO_SCRIPT_ROOM = -17
+NO_MEMORY = -18
+SOCK_READ_FAILED = -19
+SOCK_WRIT_FAILED = -20
+TOO_MANY_PARAM = -21
+SCRIPT_NOT_READY = -22
+BAD_TAG = -23
+BAD_MICS_DELAY = -24
+BAD_MILS_DELAY = -25
+I2C_OPEN_FAILED = -26
+SERIAL_OPEN_FAILED = -27
+SPI_OPEN_FAILED = -28
+BAD_I2C_BUS = -29
+BAD_I2C_ADDR = -30
+BAD_SPI_CHANNEL = -31
+BAD_I2C_FLAGS = -32
+BAD_SPI_FLAGS = -33
+BAD_SERIAL_FLAGS = -34
+BAD_SPI_SPEED = -35
+BAD_SERIAL_DEVICE = -36
+BAD_SERIAL_SPEED = -37
+BAD_FILE_PARAM = -38
+BAD_I2C_PARAM = -39
+BAD_SERIAL_PARAM = -40
+I2C_WRITE_FAILED = -41
+I2C_READ_FAILED = -42
+BAD_SPI_COUNT = -43
+SERIAL_WRITE_FAILED = -44
+SERIAL_READ_FAILED = -45
+SERIAL_READ_NO_DATA = -46
+UNKNOWN_COMMAND = -47
+SPI_XFER_FAILED = -48
+BAD_POINTER = -49
+MSG_TOOBIG = -50
+BAD_MALLOC_MODE = -51
+TOO_MANY_SEGS = -52
+BAD_I2C_SEG = -53
+BAD_SMBUS_CMD = -54
+BAD_I2C_WLEN = -55
+BAD_I2C_RLEN = -56
+BAD_I2C_CMD = -57
+FILE_OPEN_FAILED = -58
+BAD_FILE_MODE = -59
+BAD_FILE_FLAG = -60
+BAD_FILE_READ = -61
+BAD_FILE_WRITE = -62
+FILE_NOT_ROPEN = -63
+FILE_NOT_WOPEN = -64
+BAD_FILE_SEEK = -65
+NO_FILE_MATCH = -66
+NO_FILE_ACCESS = -67
+FILE_IS_A_DIR = -68
+BAD_SHELL_STATUS = -69
+BAD_SCRIPT_NAME = -70
+CMD_INTERRUPTED = -71
+BAD_EVENT_REQUEST = -72
+BAD_GPIO_NUMBER = -73
+BAD_GROUP_SIZE = -74
+BAD_LINEINFO_IOCTL = -75
+BAD_READ = -76
+BAD_WRITE = -77
+CANNOT_OPEN_CHIP = -78
+GPIO_BUSY = -79
+GPIO_NOT_ALLOCATED = -80
+NOT_A_GPIOCHIP = -81
+NOT_ENOUGH_MEMORY = -82
+POLL_FAILED = -83
+TOO_MANY_GPIOS = -84
+UNEGPECTED_ERROR = -85
+BAD_PWM_MICROS = -86
+NOT_GROUP_LEADER = -87
+SPI_IOCTL_FAILED = -88
+BAD_GPIOCHIP = -89
+BAD_CHIPINFO_IOCTL = -90
+BAD_CONFIG_FILE = -91
+BAD_CONFIG_VALUE = -92
+NO_PERMISSIONS = -93
+BAD_USERNAME = -94
+BAD_SECRET = -95
+TX_QUEUE_FULL = -96
+BAD_CONFIG_ID = -97
+BAD_DEBOUNCE_MICS = -98
+BAD_WATCHDOG_MICS = -99
+BAD_SERVO_FREQ = -100
+BAD_SERVO_WIDTH = -101
+BAD_PWM_FREQ = -102
+BAD_PWM_DUTY = -103
+GPIO_NOT_AN_OUTPUT = -104
+INVALID_GROUP_ALERT = -105
+
+# rgpiod error text
+
+_errors=[
+ [OKAY, "No error"],
+ [INIT_FAILED, "initialisation failed"],
+ [BAD_MICROS, "micros not 0-999999"],
+ [BAD_PATHNAME, "can not open pathname"],
+ [NO_HANDLE, "no handle available"],
+ [BAD_HANDLE, "unknown handle"],
+ [BAD_SOCKET_PORT, "socket port not 1024-32000"],
+ [NOT_PERMITTED, "GPIO operation not permitted"],
+ [SOME_PERMITTED, "one or more GPIO not permitted"],
+ [BAD_SCRIPT, "invalid script"],
+ [BAD_TX_TYPE, "bad tx type for GPIO and group"],
+ [GPIO_IN_USE, "GPIO already in use"],
+ [BAD_PARAM_NUM, "script parameter id not 0-9"],
+ [DUP_TAG, "script has duplicate tag"],
+ [TOO_MANY_TAGS, "script has too many tags"],
+ [BAD_SCRIPT_CMD, "illegal script command"],
+ [BAD_VAR_NUM, "script variable id not 0-149"],
+ [NO_SCRIPT_ROOM, "no more room for scripts"],
+ [NO_MEMORY, "can not allocate temporary memory"],
+ [SOCK_READ_FAILED, "socket read failed"],
+ [SOCK_WRIT_FAILED, "socket write failed"],
+ [TOO_MANY_PARAM, "too many script parameters (> 10)"],
+ [SCRIPT_NOT_READY, "script initialising"],
+ [BAD_TAG, "script has unresolved tag"],
+ [BAD_MICS_DELAY, "bad MICS delay (too large)"],
+ [BAD_MILS_DELAY, "bad MILS delay (too large)"],
+ [I2C_OPEN_FAILED, "can not open I2C device"],
+ [SERIAL_OPEN_FAILED, "can not open serial device"],
+ [SPI_OPEN_FAILED, "can not open SPI device"],
+ [BAD_I2C_BUS, "bad I2C bus"],
+ [BAD_I2C_ADDR, "bad I2C address"],
+ [BAD_SPI_CHANNEL, "bad SPI channel"],
+ [BAD_I2C_FLAGS, "bad I2C open flags"],
+ [BAD_SPI_FLAGS, "bad SPI open flags"],
+ [BAD_SERIAL_FLAGS, "bad serial open flags"],
+ [BAD_SPI_SPEED, "bad SPI speed"],
+ [BAD_SERIAL_DEVICE, "bad serial device name"],
+ [BAD_SERIAL_SPEED, "bad serial baud rate"],
+ [BAD_FILE_PARAM, "bad file parameter"],
+ [BAD_I2C_PARAM, "bad I2C parameter"],
+ [BAD_SERIAL_PARAM, "bad serial parameter"],
+ [I2C_WRITE_FAILED, "i2c write failed"],
+ [I2C_READ_FAILED, "i2c read failed"],
+ [BAD_SPI_COUNT, "bad SPI count"],
+ [SERIAL_WRITE_FAILED, "ser write failed"],
+ [SERIAL_READ_FAILED, "ser read failed"],
+ [SERIAL_READ_NO_DATA, "ser read no data available"],
+ [UNKNOWN_COMMAND, "unknown command"],
+ [SPI_XFER_FAILED, "spi xfer/read/write failed"],
+ [BAD_POINTER, "bad (NULL) pointer"],
+ [MSG_TOOBIG, "socket/pipe message too big"],
+ [BAD_MALLOC_MODE, "bad memory allocation mode"],
+ [TOO_MANY_SEGS, "too many I2C transaction segments"],
+ [BAD_I2C_SEG, "an I2C transaction segment failed"],
+ [BAD_SMBUS_CMD, "SMBus command not supported by driver"],
+ [BAD_I2C_WLEN, "bad I2C write length"],
+ [BAD_I2C_RLEN, "bad I2C read length"],
+ [BAD_I2C_CMD, "bad I2C command"],
+ [FILE_OPEN_FAILED, "file open failed"],
+ [BAD_FILE_MODE, "bad file mode"],
+ [BAD_FILE_FLAG, "bad file flag"],
+ [BAD_FILE_READ, "bad file read"],
+ [BAD_FILE_WRITE, "bad file write"],
+ [FILE_NOT_ROPEN, "file not open for read"],
+ [FILE_NOT_WOPEN, "file not open for write"],
+ [BAD_FILE_SEEK, "bad file seek"],
+ [NO_FILE_MATCH, "no files match pattern"],
+ [NO_FILE_ACCESS, "no permission to access file"],
+ [FILE_IS_A_DIR, "file is a directory"],
+ [BAD_SHELL_STATUS, "bad shell return status"],
+ [BAD_SCRIPT_NAME, "bad script name"],
+ [CMD_INTERRUPTED, "Python socket command interrupted"],
+ [BAD_EVENT_REQUEST, "bad event request"],
+ [BAD_GPIO_NUMBER, "bad GPIO number"],
+ [BAD_GROUP_SIZE, "bad group size"],
+ [BAD_LINEINFO_IOCTL, "bad lineinfo IOCTL"],
+ [BAD_READ, "bad GPIO read"],
+ [BAD_WRITE, "bad GPIO write"],
+ [CANNOT_OPEN_CHIP, "can not open gpiochip"],
+ [GPIO_BUSY, "GPIO busy"],
+ [GPIO_NOT_ALLOCATED, "GPIO not allocated"],
+ [NOT_A_GPIOCHIP, "not a gpiochip"],
+ [NOT_ENOUGH_MEMORY, "not enough memory"],
+ [POLL_FAILED, "GPIO poll failed"],
+ [TOO_MANY_GPIOS, "too many GPIO"],
+ [UNEGPECTED_ERROR, "unexpected error"],
+ [BAD_PWM_MICROS, "bad PWM micros"],
+ [NOT_GROUP_LEADER, "GPIO not the group leader"],
+ [SPI_IOCTL_FAILED, "SPI iOCTL failed"],
+ [BAD_GPIOCHIP, "bad gpiochip"],
+ [BAD_CHIPINFO_IOCTL, "bad chipinfo IOCTL"],
+ [BAD_CONFIG_FILE, "bad configuration file"],
+ [BAD_CONFIG_VALUE, "bad configuration value"],
+ [NO_PERMISSIONS, "no permission to perform action"],
+ [BAD_USERNAME, "bad user name"],
+ [BAD_SECRET, "bad secret for user"],
+ [TX_QUEUE_FULL, "TX queue full"],
+ [BAD_CONFIG_ID, "bad configuration id"],
+ [BAD_DEBOUNCE_MICS, "bad debounce microseconds"],
+ [BAD_WATCHDOG_MICS, "bad watchdog microseconds"],
+ [BAD_SERVO_FREQ, "bad servo frequency"],
+ [BAD_SERVO_WIDTH, "bad servo pulsewidth"],
+ [BAD_PWM_FREQ, "bad PWM frequency"],
+ [BAD_PWM_DUTY, "bad PWM dutycycle"],
+ [GPIO_NOT_AN_OUTPUT, "GPIO not set as an output"],
+ [INVALID_GROUP_ALERT, "can not set a group to alert"],
+]
+
+_except_a = "############################################################\n{}"
+
+_except_z = "############################################################"
+
+_except_1 = """
+Did you start the rgpiod daemon? E.g. rgpiod &
+
+Did you specify the correct host/port in the environment
+variables LG_ADDR and/or LG_PORT?
+E.g. export LG_ADDR=soft, export LG_PORT=8889
+
+Did you specify the correct host/port in the
+rgpio.sbc() function? E.g. rgpio.sbc('soft', 8889)"""
+
+_except_2 = """
+Do you have permission to access the rgpiod daemon?
+Perhaps it was started with rgpiod -nlocalhost"""
+
+_except_3 = """
+Can't create callback thread.
+Perhaps too many simultaneous rgpiod connections."""
+
+class _socklock:
+ """
+ A class to store socket and lock.
+ """
+ def __init__(self):
+ self.s = None
+ self.l = threading.Lock()
+
+class error(Exception):
+ """
+ rgpio module exception
+ """
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class pulse:
+ """
+ A class to store pulse information.
+ """
+
+ def __init__(self, group_bits, group_mask, pulse_delay):
+ """
+ Initialises a pulse.
+
+ group_bits:= the levels to set if the corresponding bit in
+ group_mask is set.
+ group_mask:= a mask indicating the group GPIO to be updated.
+ delay:= the delay in microseconds before the next pulse.
+ """
+ self.group_bits = group_bits
+ self.group_mask = group_mask
+ self.pulse_delay = pulse_delay
+
+
+# A couple of hacks to cope with different string handling
+# between various Python versions
+# 3 != 2.7.8 != 2.7.3
+
+if sys.hexversion < 0x03000000:
+ def _b(x):
+ return x
+else:
+ def _b(x):
+ return x.encode('latin-1')
+
+if sys.hexversion < 0x02070800:
+ def _str(x):
+ return buffer(x)
+else:
+ def _str(x):
+ return x
+
+def u2i(uint32):
+ """
+ Converts a 32 bit unsigned number to signed.
+
+ uint32:= an unsigned 32 bit number
+
+ Returns a signed number.
+
+ ...
+ print(u2i(4294967272))
+ -24
+ print(u2i(37))
+ 37
+ ...
+ """
+ mask = (2 ** 32) - 1
+ if uint32 & (1 << 31):
+ v = uint32 | ~mask
+ else:
+ v = uint32 & mask
+ return v
+
+def _u2i(status):
+ """
+ If the status is negative it indicates an error. On error
+ a rgpio exception will be raised if exceptions is True.
+ """
+ v = u2i(status)
+ if v < 0:
+ if exceptions:
+ raise error(error_text(v))
+ return v
+
+def _u2i_list(lst):
+ """
+ Checks a returned list. The first member is the status.
+ If the status is negative it indicates an error. On error
+ a rgpio exception will be raised if exceptions is True.
+ """
+ lst[0] = u2i(lst[0])
+ if lst[0] < 0:
+ if exceptions:
+ raise error(error_text(lst[0]))
+ return lst
+
+def _lg_command(sl, cmd, Q=0, L=0, H=0):
+ """
+ """
+ status = CMD_INTERRUPTED
+ with sl.l:
+ sl.s.send(struct.pack('IIHHHH', MAGIC, 0, cmd, Q, L, H))
+ status, dummy = struct.unpack('I12s', sl.s.recv(_SOCK_CMD_LEN))
+ return status
+
+def _lg_command_nolock(sl, cmd, Q=0, L=0, H=0):
+ """
+ """
+ status = CMD_INTERRUPTED
+ sl.s.send(struct.pack('IIHHHH', MAGIC, 0, cmd, Q, L, H))
+ status, dummy = struct.unpack('I12s', sl.s.recv(_SOCK_CMD_LEN))
+ return status
+
+def _lg_command_ext(sl, cmd, p3, extents, Q=0, L=0, H=0):
+ """
+ """
+ ext = bytearray(struct.pack('IIHHHH', MAGIC, p3, cmd, Q, L, H))
+ for x in extents:
+ if type(x) == type(""):
+ ext.extend(_b(x))
+ else:
+ ext.extend(x)
+ status = CMD_INTERRUPTED
+ with sl.l:
+ sl.s.sendall(ext)
+ status, dummy = struct.unpack('I12s', sl.s.recv(_SOCK_CMD_LEN))
+ return status
+
+def _lg_command_ext_nolock(sl, cmd, p3, extents, Q=0, L=0, H=0):
+ """
+ """
+ status = CMD_INTERRUPTED
+ ext = bytearray(struct.pack('IIHHHH', MAGIC, p3, cmd, Q, L, H))
+ for x in extents:
+ if type(x) == type(""):
+ ext.extend(_b(x))
+ else:
+ ext.extend(x)
+ sl.s.sendall(ext)
+ status, dummy = struct.unpack('I12s', sl.s.recv(_SOCK_CMD_LEN))
+ return status
+
+class _callback_ADT:
+ """
+ An ADT class to hold callback information.
+ """
+
+ def __init__(self, gpiochip, gpio, edge, func):
+ """
+ Initialises a callback ADT.
+
+ gpiochip:= gpiochip device number.
+ gpio:= gpio number in device.
+ edge:= BOTH_EDGES, RISING_EDGE, or FALLING_EDGE.
+ func:= a user function taking four arguments
+ (gpiochip, gpio, level, tick).
+ """
+ self.chip = gpiochip
+ self.gpio = gpio
+ self.edge = edge
+ self.func = func
+
+class _callback_thread(threading.Thread):
+ """
+ A class to encapsulate rgpio notification callbacks.
+ """
+
+ def __init__(self, control, host, port):
+ """
+ Initialises notifications.
+ """
+ threading.Thread.__init__(self)
+ self.control = control
+ self.sl = _socklock()
+ self.go = False
+ self.daemon = True
+ self.monitor = 0
+ self.callbacks = []
+ self.sl.s = socket.create_connection((host, port), None)
+ self.lastLevel = 0
+ self.handle = _u2i(_lg_command(self.sl, _CMD_NOIB))
+ self.go = True
+ self.start()
+
+ def stop(self):
+ """
+ Stops notifications.
+ """
+ if self.go:
+ self.go = False
+ ext = [struct.pack("I", self.handle)]
+ _lg_command_ext(self.control, _CMD_NC, 4, ext, L=1)
+
+ def append(self, callb):
+ """
+ Adds a callback to the notification thread.
+ """
+ self.callbacks.append(callb)
+
+ def remove(self, callb):
+ """
+ Removes a callback from the notification thread.
+ """
+ if callb in self.callbacks:
+ self.callbacks.remove(callb)
+
+ def run(self):
+ """
+ Runs the notification thread.
+ """
+
+ lastLevel = self.lastLevel
+ RECV_SIZ = 4096
+ MSG_SIZ = 16 # 4 bytes of padding in each message
+
+ buf = bytes()
+ while self.go:
+
+ buf += self.sl.s.recv(RECV_SIZ)
+ offset = 0
+
+ while self.go and (len(buf) - offset) >= MSG_SIZ:
+ msgbuf = buf[offset:offset + MSG_SIZ]
+ offset += MSG_SIZ
+ tick, chip, gpio, level, flags, pad = (
+ struct.unpack('QBBBBI', msgbuf))
+
+ if flags == 0:
+ for cb in self.callbacks:
+ if cb.gpio == gpio:
+ cb.func(chip, gpio, level, tick)
+ else: # no flags currently defined, ignore.
+ pass
+
+ buf = buf[offset:]
+
+ self.sl.s.close()
+
+class _callback:
+ """
+ A class to provide GPIO level change callbacks.
+ """
+
+ def __init__(self, notify, chip, gpio, edge=RISING_EDGE, func=None):
+ """
+ Initialise a callback and adds it to the notification thread.
+ """
+ self._notify = notify
+ self.count=0
+ self._reset = False
+ if func is None:
+ func=self._tally
+ self.callb = _callback_ADT(chip, gpio, edge, func)
+ self._notify.append(self.callb)
+
+ def cancel(self):
+ """
+ Cancels a callback by removing it from the notification thread.
+ """
+ self._notify.remove(self.callb)
+
+ def _tally(self, chip, gpio, level, tick):
+ """
+ Increment the callback called count.
+ """
+ if self._reset:
+ self._reset = False
+ self.count = 0
+ self.count += 1
+
+ def tally(self):
+ """
+ Provides a count of how many times the default tally
+ callback has triggered.
+
+ The count will be zero if the user has supplied their own
+ callback function.
+ """
+ return self.count
+
+ def reset_tally(self):
+ """
+ Resets the tally count to zero.
+ """
+ self._reset = True
+ self.count = 0
+
+def error_text(errnum):
+ """
+ Returns a description of an error number.
+
+ errnum:= <0, the error number
+
+ ...
+ print(rgpio.error_text(-5))
+ level not 0-1
+ ...
+ """
+ for e in _errors:
+ if e[0] == errnum:
+ return e[1]
+ return "unknown error"
+
+def get_module_version():
+ """
+ Returns the version number of the rgpio Python module as a dotted
+ quad.
+
+ A.B.C.D
+
+ A. API major version, changed if breaks previous API
+ B. API minor version, changed when new function added
+ C. bug fix
+ D. documentation changegi
+ """
+ return "rgpio.py_{}.{}.{}.{}".format(
+ (RGPIO_PY_VERSION>>24)&0xff, (RGPIO_PY_VERSION>>16)&0xff,
+ (RGPIO_PY_VERSION>>8)&0xff, RGPIO_PY_VERSION&0xff)
+
+class sbc():
+
+ def _rxbuf(self, count):
+ """
+ Returns count bytes from the command socket.
+ """
+ ext = bytearray(self.sl.s.recv(count))
+ while len(ext) < count:
+ ext.extend(self.sl.s.recv(count - len(ext)))
+ return ext
+
+ def __repr__(self):
+ """
+ Returns details of the sbc connection.
+ """
+ return "".format(self._host, self._port)
+
+ # ESSENTIAL
+
+ def __init__(self,
+ host = os.getenv("LG_ADDR", 'localhost'),
+ port = os.getenv("LG_PORT", 8889),
+ show_errors = True):
+ """
+ Establishes a connection to the rgpiod daemon running on a SBC.
+
+ host:= the host name of the SBC on which the rgpiod daemon is
+ running. The default is localhost unless overridden by
+ the LG_ADDR environment variable.
+ port:= the port number on which the rgpiod daemon is listening.
+ The default is 8889 unless overridden by the LG_PORT
+ environment variable. The rgpiod daemon must have been
+ started with the same port number.
+
+ This connects to the rgpiod daemon and reserves resources
+ to be used for sending commands and receiving notifications.
+
+ An instance attribute [*connected*] may be used to check the
+ success of the connection. If the connection is established
+ successfully [*connected*] will be True, otherwise False.
+
+ If the LG_USER environment variable exists that user will be
+ "logged in" using [*set_user*]. This only has an effect if
+ the rgpiod daemon is running with access control enabled.
+
+ ...
+ sbc = rgpio.sbc() # use defaults
+ sbc = rgpio.sbc('mypi') # specify host, default port
+ sbc = rgpio.sbc('mypi', 7777) # specify host and port
+
+ sbc = rgpio.sbc() # exit script if no connection
+ if not sbc.connected:
+ exit()
+ ...
+ """
+ self.connected = True
+
+ self.sl = _socklock()
+ self._notify = None
+
+ port = int(port)
+
+ if host == '':
+ host = "localhost"
+
+ self._host = host
+ self._port = port
+
+ try:
+ self.sl.s = socket.create_connection((host, port), None)
+
+ # Disable the Nagle algorithm.
+ self.sl.s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+ self._notify = _callback_thread(self.sl, host, port)
+
+ except socket.error:
+ exception = 1
+
+ except struct.error:
+ exception = 2
+
+ except error:
+ # assumed to be no handle available
+ exception = 3
+
+ else:
+ exception = 0
+ atexit.register(self.stop)
+
+ if exception != 0:
+
+ self.connected = False
+
+ if self.sl.s is not None:
+ self.sl.s = None
+
+ if show_errors:
+
+ s = "Can't connect to rgpiod at {}({})".format(host, str(port))
+
+
+ print(_except_a.format(s))
+ if exception == 1:
+ print(_except_1)
+ elif exception == 2:
+ print(_except_2)
+ else:
+ print(_except_3)
+ print(_except_z)
+
+ else: # auto login if LG_USER exists
+
+ user = os.getenv("LG_USER", '')
+
+ if len(user) > 0:
+ self.set_user(user)
+
+ def stop(self):
+ """
+ Disconnects from the rgpiod daemon and releases any used resources.
+
+ ...
+ sbc.stop()
+ ...
+ """
+
+ self.connected = False
+
+ if self._notify is not None:
+ self._notify.stop()
+ self._notify = None
+
+ if self.sl.s is not None:
+ # Free all resources allocated to this connection
+ _lg_command(self.sl, _CMD_FREE)
+ self.sl.s.close()
+ self.sl.s = None
+
+ # FILES
+
+ def file_open(self, file_name, file_mode):
+ """
+ This function returns a handle to a file opened in a specified mode.
+
+ This is a privileged command. See [+Permits+].
+
+ file_name:= the file to open.
+ file_mode:= the file open mode.
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ Mode
+
+ The mode may have the following values:
+
+ Constant @ Value @ Meaning
+ FILE_READ @ 1 @ open file for reading
+ FILE_WRITE @ 2 @ open file for writing
+ FILE_RW @ 3 @ open file for reading and writing
+
+ The following values may be or'd into the mode:
+
+ Name @ Value @ Meaning
+ FILE_APPEND @ 4 @ All writes append data to the end of the file
+ FILE_CREATE @ 8 @ The file is created if it doesn't exist
+ FILE_TRUNC @ 16 @ The file is truncated
+
+ Newly created files are owned by the user who launched the daemon.
+ They will have permissions owner read and write.
+
+ ...
+ #!/usr/bin/env python
+
+ import rgpio
+
+ sbc = rgpio.sbc()
+
+ if not sbc.connected:
+ exit()
+
+ handle = sbc.file_open("/ram/lg.c", rgpio.FILE_READ)
+
+ done = False
+
+ while not done:
+ c, d = sbc.file_read(handle, 60000)
+ if c > 0:
+ print(d)
+ else:
+ done = True
+
+ sbc.file_close(handle)
+
+ sbc.stop()
+ ...
+ """
+ ext = [struct.pack("I", file_mode)] + [file_name]
+ return _u2i(_lg_command_ext(
+ self.sl, _CMD_FO, 4+len(file_name), ext, L=1))
+
+ def file_close(self, handle):
+ """
+ Closes a file.
+
+ handle:= >= 0 (as returned by [*file_open*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.file_close(handle)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_FC, 4, ext, L=1))
+
+ def file_read(self, handle, count):
+ """
+ Reads up to count bytes from the file.
+
+ handle:= >= 0 (as returned by [*file_open*]).
+ count:= >0, the number of bytes to read.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of a negative error code and an
+ empty string.
+
+ ...
+ (b, d) = sbc.file_read(h2, 100)
+ if b > 0:
+ # process read data
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ ext = [struct.pack("II", handle, count)]
+ rdata = ""
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_FR, 8, ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def file_write(self, handle, data):
+ """
+ Writes the data bytes to the file.
+
+ handle:= >= 0 (as returned by [*file_open*]).
+ data:= the bytes to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.file_write(h1, b'\\x02\\x03\\x04')
+
+ sbc.file_write(h2, b'help')
+
+ sbc.file_write(h2, "hello")
+
+ sbc.file_write(h1, [2, 3, 4])
+ ...
+ """
+ ext = [struct.pack("I", handle)] + [data]
+ return _u2i(_lg_command_ext(self.sl, _CMD_FW, 4+len(data), ext, L=1))
+
+ def file_seek(self, handle, seek_offset, seek_from):
+ """
+ Seeks to a position relative to the start, current position,
+ or end of the file. Returns the new position.
+
+ handle:= >= 0 (as returned by [*file_open*]).
+ seek_offset:= byte offset.
+ seek_from:= FROM_START, FROM_CURRENT, or FROM_END.
+
+ If OK returns the new file position.
+
+ On failure returns a negative error code.
+
+ ...
+ new_pos = sbc.file_seek(h, 100, rgpio.FROM_START)
+
+ cur_pos = sbc.file_seek(h, 0, rgpio.FROM_CURRENT)
+
+ file_size = sbc.file_seek(h, 0, rgpio.FROM_END)
+ ...
+ """
+ ext = [struct.pack("IiI", handle, seek_offset, seek_from)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_FS, 12, ext, L=3))
+
+ def file_list(self, fpattern):
+ """
+ Returns a list of files which match a pattern.
+
+ This is a privileged command. See [+Permits+].
+
+ fpattern:= file pattern to match.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the matching filenames (the filenames
+ are separated by newline characters).
+
+ On failure returns a list of a negative error code and an
+ empty string.
+
+ ...
+ #!/usr/bin/env python
+
+ import rgpio
+
+ sbc = rgpio.sbc()
+
+ if not sbc.connected:
+ exit()
+
+ c, d = sbc.file_list("/ram/p*.c")
+ if c > 0:
+ print(d)
+
+ sbc.stop()
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ ext = [struct.pack("I", 60000)] + [fpattern]
+ rdata = ""
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(
+ self.sl, _CMD_FL, 4+len(fpattern), ext, L=1))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ # GPIO
+
+ def gpiochip_open(self, gpiochip):
+ """
+ This returns a handle to a gpiochip device.
+
+ This is a privileged command. See [+permits+].
+
+ gpiochip:= >= 0
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ ...
+ h = gpiochip_open(0) # open /dev/gpiochip0
+ if h >= 0:
+ # open okay
+ else:
+ # open error
+ ...
+ """
+ ext = [struct.pack("I", gpiochip)]
+ handle = u2i(_lg_command_ext(self.sl, _CMD_GO, 4, ext, L=1))
+ if handle >= 0:
+ handle = handle | (gpiochip << 16)
+
+ return _u2i(handle)
+
+ def gpiochip_close(self, handle):
+ """
+ This closes a gpiochip device.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.gpiochip_close(h)
+ ...
+ """
+ ext = [struct.pack("I", handle&0xffff)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GC, 4, ext, L=1))
+
+ def gpio_get_chip_info(self, handle):
+ """
+ This returns summary information of an opened gpiochip.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+
+ If OK returns a list of okay status, number of
+ lines, name, and label.
+
+ On failure returns a negative error code.
+ """
+ bytes = CMD_INTERRUPTED
+ ext = [struct.pack("I", handle&0xffff)]
+ rdata = ""
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_GIC, 4, ext, L=1))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ lines, name, label = struct.unpack("I32s32s", rdata)
+ bytes = OKAY
+ else:
+ lines, name, label = 0, "", ""
+ return _u2i_list([bytes, lines,
+ name.decode().rstrip('\0'), label.decode().rstrip('\0')])
+
+
+ def gpio_get_line_info(self, handle, gpio):
+ """
+ This returns detailed information of a GPIO of
+ an opened gpiochip.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO.
+
+ If OK returns a list of okay status, offset,
+ line flags, name, and user.
+
+ The meaning of the line flags bits are as given for the mode
+ by [*gpio_get_mode*].
+
+ On failure returns a negative error code.
+ """
+ bytes = CMD_INTERRUPTED
+ ext = [struct.pack("II", handle&0xffff, gpio)]
+ rdata = ""
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_GIL, 8, ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ offset, flags, name, user = struct.unpack("II32s32s", rdata)
+ bytes = OKAY
+ else:
+ offset, flags, name, user = 0, 0, "", ""
+ return _u2i_list(
+ [bytes, offset, flags,
+ name.decode().rstrip('\0'), user.decode().rstrip('\0')])
+
+ def gpio_get_mode(self, handle, gpio):
+ """
+ This returns the mode of a GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO.
+
+ If OK returns the mode of the GPIO.
+
+ On failure returns a negative error code.
+
+ Bit @ Value @ Meaning
+ 0 @ 1 @ Kernel: In use by the kernel
+ 1 @ 2 @ Kernel: Output
+ 2 @ 4 @ Kernel: Active low
+ 3 @ 8 @ Kernel: Open drain
+ 4 @ 16 @ Kernel: Open source
+ 5 @ 32 @ Kernel: Pull up set
+ 6 @ 64 @ Kernel: Pull down set
+ 7 @ 128 @ Kernel: Pulls off set
+ 8 @ 256 @ LG: Input
+ 9 @ 512 @ LG: Output
+ 10 @ 1024 @ LG: Alert
+ 11 @ 2048 @ LG: Group
+ 12 @ 4096 @ LG: ---
+ 13 @ 8192 @ LG: ---
+ 14 @ 16384 @ LG: ---
+ 15 @ 32768 @ LG: ---
+ 16 @ 65536 @ Kernel: Input
+ 17 @ 1<<17 @ Kernel: Rising edge alert
+ 18 @ 1<<18 @ Kernel: Falling edge alert
+ 19 @ 1<<19 @ Kernel: Realtime clock alert
+
+ The LG bits are only set if the query was made by the process
+ that owns the GPIO.
+ """
+ ext = [struct.pack("II", handle&0xffff, gpio)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GMODE, 8, ext, L=2))
+
+ def gpio_claim_input(self, handle, gpio, lFlags=0):
+ """
+ This claims a GPIO for input.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be claimed.
+ lFlags:= line flags for the GPIO.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The line flags may be used to set the GPIO
+ as active low, open drain, open source,
+ pull up, pull down, pull off.
+
+ ...
+ sbc.gpio_claim_input(h, 23) # open GPIO 23 for input.
+ ...
+ """
+ ext = [struct.pack("III", handle&0xffff, lFlags, gpio)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GSIX, 12, ext, L=3))
+
+ def gpio_claim_output(self, handle, gpio, level=0, lFlags=0):
+ """
+ This claims a GPIO for output.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be claimed.
+ level:= the initial value for the GPIO.
+ lFlags:= line flags for the GPIO.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The line flags may be used to set the GPIO
+ as active low, open drain, open source,
+ pull up, pull down, pull off.
+
+ If level is zero the GPIO will be initialised low (0). If any other
+ value is used the GPIO will be initialised high (1).
+
+ ...
+ sbc.gpio_claim_output(h, 3) # open GPIO 3 for low output.
+ ...
+ """
+ ext = [struct.pack("IIII", handle&0xffff, lFlags, gpio, level)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GSOX, 16, ext, L=4))
+
+ def gpio_free(self, handle, gpio):
+ """
+ This frees a GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be freed.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The GPIO may now be claimed by another user or for
+ a different purpose.
+ """
+ ext = [struct.pack("II", handle&0xffff, gpio)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GSF, 8, ext, L=2))
+
+ def group_claim_input(self, handle, gpio, lFlags=0):
+ """
+ This claims a group of GPIO for inputs.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpios:= a list of GPIO to be claimed.
+ lFlags:= line flags for the group of GPIO.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The line flags may be used to set the group
+ as active low, open drain, open source,
+ pull up, pull down, pull off.
+
+ gpio is a list of one or more GPIO. The first GPIO in the
+ list is called the group leader and is used to reference the
+ group as a whole.
+
+ """
+ if len(gpio):
+ ext = bytearray()
+ ext.extend(struct.pack("II", handle&0xffff, lFlags))
+ for g in gpio:
+ ext.extend(struct.pack("I", g))
+ return _u2i(_lg_command_ext(
+ self.sl, _CMD_GSGIX, (len(gpio)+2)*4, [ext], L=len(gpio)+2))
+ else:
+ return 0
+
+ def group_claim_output(self, handle, gpio, levels=[0], lFlags=0):
+ """
+ This claims a group of GPIO for outputs.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= a list of GPIO to be claimed.
+ levels:= a list of the initial value for each GPIO.
+ lFlags:= line flags for the group of GPIO.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The line flags may be used to set the group
+ as active low, open drain, open source,
+ pull up, pull down, pull off.
+
+ gpio is a list of one or more GPIO. The first GPIO in the list is
+ called the group leader and is used to reference the group as a whole.
+
+ levels is a list of initialisation values for the GPIO. If a value is
+ zero the corresponding GPIO will be initialised low (0). If any other
+ value is used the corresponding GPIO will be initialised high (1).
+
+ """
+ if len(gpio):
+ diff = len(gpio)-len(levels)
+ if diff > 0:
+ levels = levels + [0]*diff
+ ext = bytearray()
+ ext.extend(struct.pack("II", handle&0xffff, lFlags))
+ for g in gpio:
+ ext.extend(struct.pack("I", g))
+ for v in range(len(gpio)):
+ ext.extend(struct.pack("I", levels[v]))
+ return _u2i(_lg_command_ext(
+ self.sl, _CMD_GSGOX,
+ (2+(len(gpio)*2))*4, [ext], L=2+(len(gpio)*2)))
+ else:
+ return 0
+
+ def group_free(self, handle, gpio):
+ """
+ This frees all the GPIO associated with a group.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the group leader.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The GPIO may now be claimed by another user or for a different purpose.
+
+ """
+ ext = [struct.pack("II", handle&0xffff, gpio)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GSGF, 8, ext, L=2))
+
+ def gpio_read(self, handle, gpio):
+ """
+ This returns the level of a GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be read.
+
+ If OK returns 0 (low) or 1 (high).
+
+ On failure returns a negative error code.
+
+ This command will work for any claimed GPIO (even if a member
+ of a group). For an output GPIO the value returned
+ will be that last written to the GPIO.
+
+ """
+ ext = [struct.pack("II", handle&0xffff, gpio)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GR, 8, ext, L=2))
+
+ def gpio_write(self, handle, gpio, level):
+ """
+ This sets the level of an output GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be written.
+ level:= the value to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ This command will work for any GPIO claimed as an output
+ (even if a member of a group).
+
+ If level is zero the GPIO will be set low (0).
+ If any other value is used the GPIO will be set high (1).
+
+ """
+ ext = [struct.pack("III", handle&0xffff, gpio, level)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GW, 12, ext, L=3))
+
+
+ def group_read(self, handle, gpio):
+ """
+ This returns the levels read from a group.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the group to be read.
+
+ If OK returns a list of group size and levels.
+
+ On failure returns a list of negative error code and a dummy.
+
+ This command will work for an output group as well as an input
+ group. For an output group the value returned
+ will be that last written to the group GPIO.
+
+ Note that this command will also work on an individual GPIO claimed
+ as an input or output as that is treated as a group with one member.
+
+ After a successful read levels is set as follows.
+
+ Bit 0 is the level of the group leader.
+ Bit 1 is the level of the second GPIO in the group.
+ Bit x is the level of GPIO x+1 of the group.
+
+ """
+ ext = [struct.pack("II", handle&0xffff, gpio)]
+ status = CMD_INTERRUPTED
+ levels = 0
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_GGR, 8, ext, L=2))
+ if bytes > 0:
+ data = self._rxbuf(bytes)
+ levels, status = struct.unpack('QI', _str(data))
+ else:
+ status = bytes
+ return _u2i_list([status, levels])
+
+ def group_write(self, handle, gpio, group_bits, group_mask=GROUP_ALL):
+ """
+ This sets the levels of an output group.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the group to be written.
+ group_bits:= the level to set if the corresponding bit in
+ group_mask is set.
+ group_mask:= a mask indicating the group GPIO to be updated.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The values of each GPIO of the group are set according to the bits
+ of group_bits.
+
+ Bit 0 sets the level of the group leader.
+ Bit 1 sets the level of the second GPIO in the group.
+ Bit x sets the level of GPIO x+1 in the group.
+
+ However this may be overridden by the group_mask. A GPIO is only
+ updated if the corresponding bit in the mask is 1.
+
+ """
+ ext = [struct.pack(
+ "QQII", group_bits, group_mask, handle&0xffff, gpio)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GGWX, 24, ext, Q=2, L=2))
+
+
+ def tx_pulse(self, handle, gpio,
+ pulse_on, pulse_off, pulse_offset=0, pulse_cycles=0):
+ """
+ This starts software timed pulses on an output GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be pulsed.
+ pulse_on:= pulse high time in microseconds.
+ pulse_off:= pulse low time in microsseconds.
+ pulse_offset:= offset from nominal pulse start position.
+ pulse_cycles:= the number of cycles to be sent, 0 for infinite.
+
+ If OK returns the number of entries left in the PWM queue for the GPIO.
+
+ On failure returns a negative error code.
+
+ If both pulse_on and pulse_off are zero pulses will be
+ switched off for that GPIO. The active pulse, if any,
+ will be stopped and any queued pulses will be deleted.
+
+ Each successful call to this function consumes one PWM queue entry.
+
+ pulse_cycles cycles are transmitted (0 means infinite). Each
+ cycle consists of pulse_on microseconds of GPIO high followed by
+ pulse_off microseconds of GPIO low.
+
+ PWM is characterised by two values, its frequency (number of cycles
+ per second) and its dutycycle (percentage of high time per cycle).
+
+ The set frequency will be 1000000 / (pulse_on + pulse_off) Hz.
+
+ The set dutycycle will be pulse_on / (pulse_on + pulse_off) * 100 %.
+
+ E.g. if pulse_on is 50 and pulse_off is 100 the frequency will be
+ 6666.67 Hz and the dutycycle will be 33.33 %.
+
+ pulse_offset is a microsecond offset from the natural start of
+ the pulse cycle.
+
+ For instance if the PWM frequency is 10 Hz the natural start of each
+ cycle is at seconds 0, then 0.1, 0.2, 0.3 etc. In this case if
+ the offset is 20000 microseconds the cycle will start at seconds
+ 0.02, 0.12, 0.22, 0.32 etc.
+
+ Another pulse command may be issued to the GPIO before the last
+ has finished.
+
+ If the last pulse had infinite cycles (pulse_cycles of 0) then it
+ will be replaced by the new settings at the end of the current
+ cycle. Otherwise it will be replaced by the new settings at
+ the end of pulse_cycles cycles.
+
+ Multiple pulse settings may be queued in this way.
+
+ """
+ ext = [struct.pack("IIIIII", handle&0xffff, gpio,
+ pulse_on, pulse_off, pulse_offset, pulse_cycles)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GPX, 24, ext, L=6))
+
+
+ def tx_pwm(self, handle, gpio,
+ pwm_frequency, pwm_duty_cycle, pulse_offset=0, pulse_cycles=0):
+ """
+ This starts software timed PWM on an output GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be pulsed.
+ pwm_frequency:= PWM frequency in Hz (0=off, 0.1-10000).
+ pwm_duty_cycle:= PWM duty cycle in % (0-100).
+ pulse_offset:= offset from nominal pulse start position.
+ pulse_cycles:= the number of cycles to be sent, 0 for infinite.
+
+ If OK returns the number of entries left in the PWM queue for the GPIO.
+
+ On failure returns a negative error code.
+
+ Each successful call to this function consumes one PWM queue entry.
+
+ pulse_cycles cycles are transmitted (0 means infinite).
+
+ PWM is characterised by two values, its frequency (number of cycles
+ per second) and its dutycycle (percentage of high time per cycle).
+
+ pulse_offset is a microsecond offset from the natural start of
+ the pulse cycle.
+
+ For instance if the PWM frequency is 10 Hz the natural start of each
+ cycle is at seconds 0, then 0.1, 0.2, 0.3 etc. In this case if
+ the offset is 20000 microseconds the cycle will start at seconds
+ 0.02, 0.12, 0.22, 0.32 etc.
+
+ Another PWM command may be issued to the GPIO before the last
+ has finished.
+
+ If the last pulse had infinite cycles then it will be replaced by
+ the new settings at the end of the current cycle. Otherwise it will
+ be replaced by the new settings when all its cycles are complete.
+
+ Multiple pulse settings may be queued in this way.
+
+ """
+ ext = [struct.pack("IIIIII", handle&0xffff, gpio,
+ int(pwm_frequency*1000), int(pwm_duty_cycle*1000),
+ pulse_offset, pulse_cycles)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_PX, 24, ext, L=6))
+
+
+ def tx_servo(self, handle, gpio, pulse_width,
+ servo_frequency=50, pulse_offset=0, pulse_cycles=0):
+ """
+ This starts software timed servo pulses on an output GPIO.
+
+ I would only use software timed servo pulses for testing
+ purposes. The timing jitter will cause the servo to fidget.
+ This may cause it to overheat and wear out prematurely.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be pulsed.
+ pulse_width:= pulse high time in microseconds (0=off, 500-2500).
+ servo_frequency:= the number of pulses per second (40-500).
+ pulse_offset:= offset from nominal pulse start position.
+ pulse_cycles:= the number of cycles to be sent, 0 for infinite.
+
+ If OK returns the number of entries left in the PWM queue for the GPIO.
+
+ On failure returns a negative error code.
+
+ Each successful call to this function consumes one PWM queue entry.
+
+ pulse_cycles cycles are transmitted (0 means infinite).
+
+ pulse_offset is a microsecond offset from the natural start of
+ the pulse cycle.
+
+ Another servo command may be issued to the GPIO before the last
+ has finished.
+
+ If the last pulse had infinite cycles then it will be replaced by
+ the new settings at the end of the current cycle. Otherwise it will
+ be replaced by the new settings when all its cycles are compete.
+
+ Multiple servo settings may be queued in this way.
+
+ """
+ ext = [struct.pack("IIIIII", handle&0xffff, gpio,
+ pulse_width, servo_frequency, pulse_offset, pulse_cycles)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SX, 24, ext, L=6))
+
+
+ def tx_wave(self, handle, gpio, pulses):
+ """
+ This starts a software timed wave on an output group.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the group to be pulsed.
+ pulses:= the pulses to transmit.
+
+ If OK returns the number of entries left in the wave queue
+ for the group.
+
+ On failure returns a negative error code.
+
+ Each successful call to this function consumes one wave queue entry.
+
+ This command starts a wave of pulses.
+
+ pulses is an array of pulses to be transmitted on the group.
+
+ Each pulse is defined by the following triplet:
+
+ bits: the levels to set for the selected GPIO
+ mask: the GPIO to select
+ delay: the delay in microseconds before the next pulse
+
+ Another wave command may be issued to the group before the
+ last has finished transmission. The new wave will start when
+ the previous wave has competed.
+
+ Multiple waves may be queued in this way.
+
+ """
+ if len(pulses):
+ q = 3 * len(pulses)
+ l = 2
+ size = (q*8) + (l*4)
+ ext1 = bytearray()
+ for p in pulses:
+ ext1.extend(struct.pack(
+ "QQQ", p.group_bits, p.group_mask, p.pulse_delay))
+ ext2 = struct.pack("II", handle&0xffff, gpio)
+ ext = [ext1, ext2]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GWAVE, size, ext, Q=q, L=l))
+ else:
+ return 0
+
+
+ def tx_busy(self, handle, gpio, kind):
+ """
+ This returns true if transmissions of the specified kind
+ are active on the GPIO or group.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO or group to be checked.
+ kind:= TX_PWM or TX_WAVE.
+
+ If OK returns 1 for busy and 0 for not busy.
+
+ On failure returns a negative error code.
+
+ """
+ ext = [struct.pack("III", handle&0xffff, gpio, kind)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GBUSY, 12, ext, L=3))
+
+ def tx_room(self, handle, gpio, kind):
+ """
+ This returns the number of slots there are to queue further
+ transmissions of the specified kind on a GPIO or group.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO or group to be checked.
+ kind:= TX_PWM or TX_WAVE.
+
+ If OK returns the number of free entries (0 for none).
+
+ On failure returns a negative error code.
+
+ """
+ ext = [struct.pack("III", handle&0xffff, gpio, kind)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GROOM, 12, ext, L=3))
+
+ def gpio_set_debounce_micros(self, handle, gpio, debounce_micros):
+ """
+ This sets the debounce time for a GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be configured.
+ debounce_micros:= the value to set.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ This only affects alerts.
+
+ An alert will only be issued if the edge has been stable for
+ at least debounce microseconds.
+
+ Generally this is used to debounce mechanical switches
+ (e.g. contact bounce).
+
+ Suppose that a square wave at 5 Hz is being generated on a GPIO.
+ Each edge will last 100000 microseconds. If a debounce time
+ of 100001 is set no alerts will be generated, If a debounce
+ time of 99999 is set 10 alerts will be generated per second.
+
+ Note that level changes will be timestamped debounce microseconds
+ after the actual level change.
+
+ """
+ ext = [struct.pack("III", handle&0xffff, gpio, debounce_micros)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GDEB, 12, ext, L=3))
+
+
+ def gpio_set_watchdog_micros(self, handle, gpio, watchdog_micros):
+ """
+ This sets the watchdog time for a GPIO.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= the GPIO to be configured.
+ watchdog_micros:= the value to set.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ This only affects alerts.
+
+ A watchdog alert will be sent if no edge alert has been issued
+ for that GPIO in the previous watchdog microseconds.
+
+ Note that only one watchdog alert will be sent per stream of
+ edge alerts. The watchdog is reset by the sending of a new
+ edge alert.
+
+ The level is set to TIMEOUT (2) for a watchdog alert.
+ """
+ ext = [struct.pack("III", handle&0xffff, gpio, watchdog_micros)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GWDOG, 12, ext, L=3))
+
+
+ def gpio_claim_alert(
+ self, handle, gpio, eFlags, lFlags=0, notify_handle=None):
+ """
+ This claims a GPIO to be used as a source of alerts on level changes.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= >= 0, as legal for the gpiochip.
+ eFlags:= event flags for the GPIO.
+ lFlags:= line flags for the GPIO.
+ notifiy_handle: >=0 (as returned by [*notify_open*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ The line flags may be used to set the GPIO
+ as active low, open drain, open source,
+ pull up, pull down, pull off.
+
+ The event flags are used to generate alerts for a rising edge,
+ falling edge, or both edges.
+
+ Use the default notification handle of None unless you plan
+ to read the alerts from a notification pipe you have opened.
+
+ """
+ if notify_handle is None:
+ notify_handle = self._notify.handle
+ ext = [struct.pack(
+ "IIIII", handle&0xffff, lFlags, eFlags, gpio, notify_handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_GSAX, 20, ext, L=5))
+
+
+
+ def callback(self, handle, gpio, edge=RISING_EDGE, func=None):
+ """
+ Calls a user supplied function (a callback) whenever the
+ specified GPIO edge is detected.
+
+ handle:= >= 0 (as returned by [*gpiochip_open*]).
+ gpio:= >= 0, as legal for the gpiochip.
+ edge:= BOTH_EDGES, RISING_EDGE (default), or FALLING_EDGE.
+ func:= user supplied callback function.
+
+ Returns a callback instance.
+
+ The user supplied callback receives four parameters, the chip,
+ the GPIO, the level, and the timestamp.
+
+ The reported level will be one of
+
+ 0: change to low (a falling edge)
+ 1: change to high (a rising edge)
+ 2: no level change (a watchdog timeout)
+
+ Early kernels used to provide a timestamp as the number of nanoseconds
+ since the Epoch (start of 1970). Later kernels use the number of
+ nanoseconds since boot. It's probably best not to make any assumption
+ as to the timestamp origin.
+
+ If a user callback is not specified a default tally callback is
+ provided which simply counts edges. The count may be retrieved
+ by calling the callback instance's tally() method. The count may
+ be reset to zero by calling the callback instance's reset_tally()
+ method.
+
+ The callback may be cancelled by calling the callback
+ instance's cancel() method.
+
+ A GPIO may have multiple callbacks (although I can't think of
+ a reason to do so).
+
+ If you want to track the level of more than one GPIO do so by
+ maintaining the state in the callback. Do not use [*gpio_read*].
+ Remember the alert that triggered the callback may have
+ happened several milliseconds before and the GPIO may have
+ changed level many times since then.
+
+ ...
+ def cbf(chip, gpio, level, timestamp):
+ print(chip, gpio, level, timestamp)
+
+ cb1 = sbc.callback(0, 22, rgpio.BOTH_EDGES, cbf)
+
+ cb2 = sbc.callback(0, 4, rgpio.BOTH_EDGES)
+
+ cb3 = sbc.callback(0, 17)
+
+ print(cb3.tally())
+
+ cb3.reset_tally()
+
+ cb1.cancel() # To cancel callback cb1.
+ ...
+ """
+ return _callback(self._notify, handle>>16, gpio, edge, func)
+
+
+ # I2C
+
+ def i2c_open(self, i2c_bus, i2c_address, i2c_flags=0):
+ """
+ Returns a handle (>= 0) for the device at the I2C bus address.
+
+ This is a privileged command. See [+Permits+].
+
+ i2c_bus:= >= 0.
+ i2c_address:= 0-0x7F.
+ i2c_flags:= 0, no flags are currently defined.
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ For the SMBus commands the low level transactions are shown
+ at the end of the function description. The following
+ abbreviations are used:
+
+ . .
+ S (1 bit) : Start bit
+ P (1 bit) : Stop bit
+ Rd/Wr (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0.
+ A, NA (1 bit) : Accept and not accept bit.
+ Addr (7 bits): I2C 7 bit address.
+ reg (8 bits): Command byte, which often selects a register.
+ Data (8 bits): A data byte.
+ Count (8 bits): A byte defining the length of a block operation.
+
+ [..]: Data sent by the device.
+ . .
+
+ ...
+ h = sbc.i2c_open(1, 0x53) # open device at address 0x53 on bus 1
+ ...
+ """
+ ext = [struct.pack("III", i2c_bus, i2c_address, i2c_flags)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CO, 12, ext, L=3))
+
+ def i2c_close(self, handle):
+ """
+ Closes the I2C device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.i2c_close(h)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CC, 4, ext, L=1))
+
+ def i2c_write_quick(self, handle, bit):
+ """
+ Sends a single bit to the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ bit:= 0 or 1, the value to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.1 - Quick command.
+ . .
+ S Addr bit [A] P
+ . .
+
+ ...
+ sbc.i2c_write_quick(0, 1) # send 1 to handle 0
+ sbc.i2c_write_quick(3, 0) # send 0 to handle 3
+ ...
+ """
+ ext = [struct.pack("II", handle, bit)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWQ, 8, ext, L=2))
+
+ def i2c_write_byte(self, handle, byte_val):
+ """
+ Sends a single byte to the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ byte_val:= 0-255, the value to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.2 - Send byte.
+ . .
+ S Addr Wr [A] byte_val [A] P
+ . .
+
+ ...
+ sbc.i2c_write_byte(1, 17) # send byte 17 to handle 1
+ sbc.i2c_write_byte(2, 0x23) # send byte 0x23 to handle 2
+ ...
+ """
+ ext = [struct.pack("II", handle, byte_val)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWS, 8, ext, L=2))
+
+ def i2c_read_byte(self, handle):
+ """
+ Reads a single byte from the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+
+ If OK returns the read byte (0-255).
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.3 - Receive byte.
+ . .
+ S Addr Rd [A] [Data] NA P
+ . .
+
+ ...
+ b = sbc.i2c_read_byte(2) # read a byte from handle 2
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CRS, 4, ext, L=1))
+
+ def i2c_write_byte_data(self, handle, reg, byte_val):
+ """
+ Writes a single byte to the specified register of the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ byte_val:= 0-255, the value to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.4 - Write byte.
+ . .
+ S Addr Wr [A] reg [A] byte_val [A] P
+ . .
+
+ ...
+ # send byte 0xC5 to reg 2 of handle 1
+ sbc.i2c_write_byte_data(1, 2, 0xC5)
+
+ # send byte 9 to reg 4 of handle 2
+ sbc.i2c_write_byte_data(2, 4, 9)
+ ...
+ """
+ ext = [struct.pack("III", handle, reg, byte_val)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWB, 12, ext, L=3))
+
+ def i2c_write_word_data(self, handle, reg, word_val):
+ """
+ Writes a single 16 bit word to the specified register of the
+ device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ word_val:= 0-65535, the value to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.4 - Write word.
+ . .
+ S Addr Wr [A] reg [A] word_val_Low [A] word_val_High [A] P
+ . .
+
+ ...
+ # send word 0xA0C5 to reg 5 of handle 4
+ sbc.i2c_write_word_data(4, 5, 0xA0C5)
+
+ # send word 2 to reg 2 of handle 5
+ sbc.i2c_write_word_data(5, 2, 23)
+ ...
+ """
+ ext = [struct.pack("III", handle, reg, word_val)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWW, 12, ext, L=3))
+
+ def i2c_read_byte_data(self, handle, reg):
+ """
+ Reads a single byte from the specified register of the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+
+ If OK returns the read byte (0-255).
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.5 - Read byte.
+ . .
+ S Addr Wr [A] reg [A] S Addr Rd [A] [Data] NA P
+ . .
+
+ ...
+ # read byte from reg 17 of handle 2
+ b = sbc.i2c_read_byte_data(2, 17)
+
+ # read byte from reg 1 of handle 0
+ b = sbc.i2c_read_byte_data(0, 1)
+ ...
+ """
+ ext = [struct.pack("II", handle, reg)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CRB, 8, ext, L=2))
+
+ def i2c_read_word_data(self, handle, reg):
+ """
+ Reads a single 16 bit word from the specified register of the
+ device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+
+ If OK returns the read word (0-65535).
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.5 - Read word.
+ . .
+ S Addr Wr [A] reg [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P
+ . .
+
+ ...
+ # read word from reg 2 of handle 3
+ w = sbc.i2c_read_word_data(3, 2)
+
+ # read word from reg 7 of handle 2
+ w = sbc.i2c_read_word_data(2, 7)
+ ...
+ """
+ ext = [struct.pack("II", handle, reg)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CRW, 8, ext, L=2))
+
+ def i2c_process_call(self, handle, reg, word_val):
+ """
+ Writes 16 bits of data to the specified register of the device
+ and reads 16 bits of data in return.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ word_val:= 0-65535, the value to write.
+
+ If OK returns the read word (0-65535).
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.6 - Process call.
+ . .
+ S Addr Wr [A] reg [A] word_val_Low [A] word_val_High [A]
+ S Addr Rd [A] [DataLow] A [DataHigh] NA P
+ . .
+
+ ...
+ r = sbc.i2c_process_call(h, 4, 0x1231)
+ r = sbc.i2c_process_call(h, 6, 0)
+ ...
+ """
+ ext = [struct.pack("III", handle, reg, word_val)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CPC, 12, ext, L=3))
+
+ def i2c_write_block_data(self, handle, reg, data):
+ """
+ Writes up to 32 bytes to the specified register of the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ data:= the bytes to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ SMBus 2.0 5.5.7 - Block write.
+ . .
+ S Addr Wr [A] reg [A] len(data) [A] data0 [A] data1 [A] ... [A]
+ datan [A] P
+ . .
+
+ ...
+ sbc.i2c_write_block_data(4, 5, b'hello')
+
+ sbc.i2c_write_block_data(4, 5, "data bytes")
+
+ sbc.i2c_write_block_data(5, 0, b'\\x00\\x01\\x22')
+
+ sbc.i2c_write_block_data(6, 2, [0, 1, 0x22])
+ ...
+ """
+ ext = [struct.pack("II", handle, reg)] + [data]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWK, 8+len(data), ext, L=2))
+
+ def i2c_read_block_data(self, handle, reg):
+ """
+ Reads a block of up to 32 bytes from the specified register of
+ the device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of a negative error code and
+ a null string.
+
+ SMBus 2.0 5.5.7 - Block read.
+ . .
+ S Addr Wr [A] reg [A]
+ S Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P
+ . .
+
+ The amount of returned data is set by the device.
+
+ ...
+ (b, d) = sbc.i2c_read_block_data(h, 10)
+ if b >= 0:
+ # process data
+ else:
+ # process read failure
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("II", handle, reg)]
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(self.sl, _CMD_I2CRK, 8, ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def i2c_block_process_call(self, handle, reg, data):
+ """
+ Writes data bytes to the specified register of the device
+ and reads a device specified number of bytes of data in return.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ data:= the bytes to write.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of a negative error code and
+ a null string.
+
+ The SMBus 2.0 documentation states that a minimum of 1 byte may
+ be sent and a minimum of 1 byte may be received. The total
+ number of bytes sent/received must be 32 or less.
+
+ SMBus 2.0 5.5.8 - Block write-block read.
+ . .
+ S Addr Wr [A] reg [A] len(data) [A] data0 [A] ... datan [A]
+ S Addr Rd [A] [Count] A [Data] ... A P
+ . .
+
+ ...
+ (b, d) = sbc.i2c_block_process_call(h, 10, b'\\x02\\x05\\x00')
+
+ (b, d) = sbc.i2c_block_process_call(h, 10, b'abcdr')
+
+ (b, d) = sbc.i2c_block_process_call(h, 10, "abracad")
+
+ (b, d) = sbc.i2c_block_process_call(h, 10, [2, 5, 16])
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("II", handle, reg)] + [data]
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(self.sl, _CMD_I2CPK, 8+len(data), ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def i2c_write_i2c_block_data(self, handle, reg, data):
+ """
+ Writes data bytes to the specified register of the device.
+ 1-32 bytes may be written.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ data:= the bytes to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ . .
+ S Addr Wr [A] reg [A] data0 [A] data1 [A] ... [A] datan [NA] P
+ . .
+
+ ...
+ sbc.i2c_write_i2c_block_data(4, 5, 'hello')
+
+ sbc.i2c_write_i2c_block_data(4, 5, b'hello')
+
+ sbc.i2c_write_i2c_block_data(5, 0, b'\\x00\\x01\\x22')
+
+ sbc.i2c_write_i2c_block_data(6, 2, [0, 1, 0x22])
+ ...
+ """
+ ext = [struct.pack("II", handle, reg)] + [data]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWI, 8+len(data), ext, L=2))
+
+ def i2c_read_i2c_block_data(self, handle, reg, count):
+ """
+ Reads count bytes from the specified register of the device.
+ The count may be 1-32.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ reg:= >= 0, the device register.
+ count:= >0, the number of bytes to read.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of a negative error code and
+ a null string.
+
+ . .
+ S Addr Wr [A] reg [A]
+ S Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
+ . .
+
+ ...
+ (b, d) = sbc.i2c_read_i2c_block_data(h, 4, 32)
+ if b >= 0:
+ # process data
+ else:
+ # process read failure
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("III", handle, reg, count)]
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(self.sl, _CMD_I2CRI, 12, ext, L=3))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def i2c_read_device(self, handle, count):
+ """
+ Returns count bytes read from the raw device associated
+ with handle.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ count:= >0, the number of bytes to read.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of a negative error code and
+ a null string.
+
+ . .
+ S Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
+ . .
+
+ ...
+ (count, data) = sbc.i2c_read_device(h, 12)
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("II", handle, count)]
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_I2CRD, 8, ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def i2c_write_device(self, handle, data):
+ """
+ Writes the data bytes to the raw device.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ data:= the bytes to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ . .
+ S Addr Wr [A] data0 [A] data1 [A] ... [A] datan [A] P
+ . .
+
+ ...
+ sbc.i2c_write_device(h, b"\\x12\\x34\\xA8")
+
+ sbc.i2c_write_device(h, b"help")
+
+ sbc.i2c_write_device(h, 'help')
+
+ sbc.i2c_write_device(h, [23, 56, 231])
+ ...
+ """
+ ext = [struct.pack("I", handle)] + [data]
+ return _u2i(_lg_command_ext(self.sl, _CMD_I2CWD, 4+len(data), ext, L=1))
+
+
+ def i2c_zip(self, handle, data):
+ """
+ This function executes a sequence of I2C operations. The
+ operations to be performed are specified by the contents of data
+ which contains the concatenated command codes and associated data.
+
+ handle:= >= 0 (as returned by [*i2c_open*]).
+ data:= the concatenated I2C commands, see below
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of a negative error code and
+ a null string.
+
+ ...
+ (count, data) = sbc.i2c_zip(h, [4, 0x53, 7, 1, 0x32, 6, 6, 0])
+ ...
+
+ The following command codes are supported:
+
+ Name @ Cmd & Data @ Meaning
+ End @ 0 @ No more commands
+ Escape @ 1 @ Next P is two bytes
+ Address @ 2 P @ Set I2C address to P
+ Flags @ 3 lsb msb @ Set I2C flags to lsb + (msb << 8)
+ Read @ 4 P @ Read P bytes of data
+ Write @ 5 P ... @ Write P bytes of data
+
+ The address, read, and write commands take a parameter P.
+ Normally P is one byte (0-255). If the command is preceded by
+ the Escape command then P is two bytes (0-65535, least significant
+ byte first).
+
+ The address defaults to that associated with the handle.
+ The flags default to 0. The address and flags maintain their
+ previous value until updated.
+
+ Any read I2C data is concatenated in the returned bytearray.
+
+ ...
+ Set address 0x53, write 0x32, read 6 bytes
+ Set address 0x1E, write 0x03, read 6 bytes
+ Set address 0x68, write 0x1B, read 8 bytes
+ End
+
+ 2 0x53 5 1 0x32 4 6
+ 2 0x1E 5 1 0x03 4 6
+ 2 0x68 5 1 0x1B 4 8
+ 0
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("I", handle)] + [data]
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(
+ self.sl, _CMD_I2CZ, 4+len(data), ext, L=1))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ # NOTIFICATIONS
+
+ def notify_open(self):
+ """
+ Opens a notification pipe.
+
+ This is a privileged command. See [+Permits+].
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ A notification is a method for being notified of GPIO
+ alerts via a pipe.
+
+ Pipes are only accessible from the local machine so this
+ function serves no purpose if you are using Python from a
+ remote machine. The in-built (socket) notifications
+ provided by [*callback*] should be used instead.
+
+ The pipes are created in the library's working directory.
+
+ Notifications for handle x will be available at the pipe
+ named .lgd-nfyx (where x is the handle number).
+
+ E.g. if the function returns 15 then the notifications must be
+ read from .lgd-nfy15.
+
+ Notifications have the following structure:
+
+ . .
+ Q timestamp
+ B chip
+ B gpio
+ B level
+ B flags
+ . .
+
+ timestamp: the number of nanoseconds since a kernel dependent origin.
+
+ Early kernels used to provide a timestamp as the number of nanoseconds
+ since the Epoch (start of 1970). Later kernels use the number of
+ nanoseconds since boot. It's probably best not to make any assumption
+ as to the timestamp origin.
+
+ chip: the gpiochip device number (NOT the handle).
+
+ gpio: the GPIO.
+
+ level: indicates the level of the GPIO (0=low, 1=high, 2=timeout).
+
+ flags: no flags are currently defined.
+
+ ...
+ h = sbc.notify_open()
+ if h >= 0:
+ sbc.notify_resume(h)
+ ...
+ """
+ return _u2i(_lg_command(self.sl, _CMD_NO))
+
+ def notify_pause(self, handle):
+ """
+ Pauses notifications on a handle.
+
+ handle:= >= 0 (as returned by [*notify_open*])
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ Notifications for the handle are suspended until
+ [*notify_resume*] is called.
+
+ ...
+ h = sbc.notify_open()
+ if h >= 0:
+ sbc.notify_resume(h)
+ ...
+ sbc.notify_pause(h)
+ ...
+ sbc.notify_resume(h)
+ ...
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_NP, 4, ext, L=1))
+
+ def notify_resume(self, handle):
+ """
+ Resumes notifications on a handle.
+
+ handle:= >= 0 (as returned by [*notify_open*])
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ h = sbc.notify_open()
+ if h >= 0:
+ sbc.notify_resume(h)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_NR, 4, ext, L=1))
+
+ def notify_close(self, handle):
+ """
+ Stops notifications on a handle and frees the handle for reuse.
+
+ handle:= >= 0 (as returned by [*notify_open*])
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ h = sbc.notify_open()
+ if h >= 0:
+ sbc.notify_resume(h)
+ ...
+ sbc.notify_close(h)
+ ...
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_NC, 4, ext, L=1))
+
+ # SCRIPTS
+
+ def script_store(self, script):
+ """
+ Store a script for later execution.
+
+ This is a privileged command. See [+Permits+].
+
+ script:= the script text as a series of bytes.
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ ...
+ h = sbc.script_store(
+ b'tag 0 w 22 1 mils 100 w 22 0 mils 100 dcr p0 jp 0')
+ ...
+ """
+ if len(script):
+ return _u2i(_lg_command_ext(
+ self.sl, _CMD_PROC, len(script)+1, [script+'\0']))
+ else:
+ return 0
+
+ def script_run(self, handle, params=None):
+ """
+ Runs a stored script.
+
+ handle:= >=0 (as returned by [*script_store*]).
+ params:= up to 10 parameters required by the script.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ s = sbc.script_run(h, [par1, par2])
+
+ s = sbc.script_run(h)
+
+ s = sbc.script_run(h, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ ...
+ """
+ ext = struct.pack("I", handle)
+ nump = 1
+ if params is not None:
+ for p in params:
+ ext += struct.pack("I", p)
+ nump = 1 + len(params)
+ return _u2i(_lg_command_ext(self.sl, _CMD_PROCR, nump*4, [ext], L=nump))
+
+ def script_update(self, handle, params=None):
+ """
+ Sets the parameters of a script. The script may or
+ may not be running. The parameters of the script are
+ overwritten with the new values.
+
+ handle:= >=0 (as returned by [*script_store*]).
+ params:= up to 10 parameters required by the script.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ s = sbc.script_update(h, [par1, par2])
+
+ s = sbc.script_update(h, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ ...
+ """
+ ext = struct.pack("I", handle)
+ nump = 1
+ if params is not None:
+ for p in params:
+ ext += struct.pack("I", p)
+ nump = 1 + len(params)
+ return _u2i(_lg_command_ext(self.sl, _CMD_PROCU, nump*4, [ext], L=nump))
+
+ def script_status(self, handle):
+ """
+ Returns the run status of a stored script as well as the
+ current values of parameters 0 to 9.
+
+ handle:= >=0 (as returned by [*script_store*]).
+
+ If OK returns a list of the run status and a list of
+ the 10 parameters.
+
+ On failure returns a negative error code and a null list.
+
+ The run status may be
+
+ . .
+ SCRIPT_INITING
+ SCRIPT_READY
+ SCRIPT_RUNNING
+ SCRIPT_WAITING
+ SCRIPT_ENDED
+ SCRIPT_HALTED
+ SCRIPT_FAILED
+ . .
+
+ ...
+ (s, pars) = sbc.script_status(h)
+ ...
+ """
+ status = CMD_INTERRUPTED
+ params = ()
+ ext = [struct.pack("I", handle)]
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_PROCP, 4, ext, L=1))
+ if bytes > 0:
+ data = self._rxbuf(bytes)
+ pars = struct.unpack('11i', _str(data))
+ status = pars[0]
+ params = pars[1:]
+ else:
+ status = bytes
+ return _u2i_list([status, params])
+
+ def script_stop(self, handle):
+ """
+ Stops a running script.
+
+ handle:= >=0 (as returned by [*script_store*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ status = sbc.script_stop(h)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_PROCS, 4, ext, L=1))
+
+ def script_delete(self, handle):
+ """
+ Deletes a stored script.
+
+ handle:= >=0 (as returned by [*script_store*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ status = sbc.script_delete(h)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_PROCD, 4, ext, L=1))
+
+ # SERIAL
+
+ def serial_open(self, tty, baud, ser_flags=0):
+ """
+ Returns a handle for the serial tty device opened
+ at baud bits per second.
+
+ This is a privileged command. See [+Permits+].
+
+ tty:= the serial device to open.
+ baud:= baud rate in bits per second, see below.
+ ser_flags:= 0, no flags are currently defined.
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ The baud rate must be one of 50, 75, 110, 134, 150,
+ 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200,
+ 38400, 57600, 115200, or 230400.
+
+ ...
+ h1 = sbc.serial_open("/dev/ttyAMA0", 300)
+
+ h2 = sbc.serial_open("/dev/ttyUSB1", 19200, 0)
+
+ h3 = sbc.serial_open("/dev/serial0", 9600)
+ ...
+ """
+ ext = [struct.pack("II", baud, ser_flags)] + [tty]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SERO, 8+len(tty), ext, L=2))
+
+ def serial_close(self, handle):
+ """
+ Closes the serial device.
+
+ handle:= >= 0 (as returned by [*serial_open*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.serial_close(h1)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SERC, 4, ext, L=1))
+
+ def serial_read_byte(self, handle):
+ """
+ Returns a single byte from the device.
+
+ handle:= >= 0 (as returned by [*serial_open*]).
+
+ If OK returns the read byte (0-255).
+
+ On failure returns a negative error code.
+
+ ...
+ b = sbc.serial_read_byte(h1)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SERRB, 4, ext, L=1))
+
+ def serial_write_byte(self, handle, byte_val):
+ """
+ Writes a single byte to the device.
+
+ handle:= >= 0 (as returned by [*serial_open*]).
+ byte_val:= 0-255, the value to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.serial_write_byte(h1, 23)
+
+ sbc.serial_write_byte(h1, ord('Z'))
+ ...
+ """
+ ext = [struct.pack("II", handle, byte_val)]
+ return _u2i(
+ _lg_command_ext(self.sl, _CMD_SERWB, 8, ext, L=2))
+
+ def serial_read(self, handle, count=1000):
+ """
+ Reads up to count bytes from the device.
+
+ handle:= >= 0 (as returned by [*serial_open*]).
+ count:= >0, the number of bytes to read (defaults to 1000).
+
+ If OK returns a list of the number of bytes read and
+ a bytearray containing the bytes.
+
+ On failure returns a list of negative error code and
+ a null string.
+
+ If no data is ready a bytes read of zero is returned.
+
+ ...
+ (b, d) = sbc.serial_read(h2, 100)
+ if b > 0:
+ # process read data
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("II", handle, count)]
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_SERR, 8, ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def serial_write(self, handle, data):
+ """
+ Writes the data bytes to the device.
+
+ handle:= >= 0 (as returned by [*serial_open*]).
+ data:= the bytes to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.serial_write(h1, b'\\x02\\x03\\x04')
+
+ sbc.serial_write(h2, b'help')
+
+ sbc.serial_write(h2, "hello")
+
+ sbc.serial_write(h1, [2, 3, 4])
+ ...
+ """
+ ext = [struct.pack("I", handle)] + [data]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SERW, 4+len(data), ext, L=1))
+
+ def serial_data_available(self, handle):
+ """
+ Returns the number of bytes available to be read from the
+ device.
+
+ handle:= >= 0 (as returned by [*serial_open*]).
+
+ If OK returns the count of bytes available (>= 0).
+
+ On failure returns a negative error code.
+
+ ...
+ rdy = sbc.serial_data_available(h1)
+
+ if rdy > 0:
+ (b, d) = sbc.serial_read(h1, rdy)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SERDA, 4, ext, L=1))
+
+
+ # SHELL
+
+ def shell(self, shellscr, pstring=""):
+ """
+ This function uses the system call to execute a shell script
+ with the given string as its parameter.
+
+ This is a privileged command. See [+Permits+].
+
+ shellscr:= the name of the script, only alphanumerics,
+ '-' and '_' are allowed in the name
+ pstring := the parameter string to pass to the script
+
+ If OK returns the exit status of the system call.
+
+ On failure returns a negative error code.
+
+ shellscr must exist in a directory named cgi in the daemon's
+ configuration directory and must be executable.
+
+ The returned exit status is normally 256 times that set by
+ the shell script exit function. If the script can't be
+ found 32512 will be returned.
+
+ The following table gives some example returned statuses:
+
+ Script exit status @ Returned system call status
+ 1 @ 256
+ 5 @ 1280
+ 10 @ 2560
+ 200 @ 51200
+ script not found @ 32512
+
+ ...
+ // pass two parameters, hello and world
+ status = sbc.shell("scr1", "hello world");
+
+ // pass three parameters, hello, string with spaces, and world
+ status = sbc.shell("scr1", "hello 'string with spaces' world");
+
+ // pass one parameter, hello string with spaces world
+ status = sbc.shell("scr1", "\\"hello string with spaces world\\"");
+ ...
+ """
+ ls = len(shellscr)+1
+ lp = len(pstring)+1
+ ext = [struct.pack("I", ls)] + [shellscr+'\x00'+pstring+'\x00']
+ return _u2i(_lg_command_ext(self.sl, _CMD_SHELL, 4+ls+lp, ext, L=1))
+
+ # SPI
+
+ def spi_open(self, spi_device, spi_channel, baud, spi_flags=0):
+ """
+ Returns a handle for the SPI device on the channel. Data
+ will be transferred at baud bits per second. The flags
+ may be used to modify the default behaviour.
+
+ This is a privileged command. See [+Permits+].
+
+ spi_device:= >= 0.
+ spi_channel:= >= 0.
+ baud:= speed to use.
+ spi_flags:= see below.
+
+ If OK returns a handle (>= 0).
+
+ On failure returns a negative error code.
+
+ spi_flags consists of the least significant 2 bits.
+
+ . .
+ 1 0
+ m m
+ . .
+
+ mm defines the SPI mode.
+
+ . .
+ Mode POL PHA
+ 0 0 0
+ 1 0 1
+ 2 1 0
+ 3 1 1
+ . .
+
+
+ The other bits in flags should be set to zero.
+
+ ...
+ # open SPI device on channel 1 in mode 3 at 50000 bits per second
+
+ h = sbc.spi_open(1, 50000, 3)
+ ...
+ """
+ ext = [struct.pack("IIII", spi_device, spi_channel, baud, spi_flags)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SPIO, 16, ext, L=4))
+
+ def spi_close(self, handle):
+ """
+ Closes the SPI device.
+
+ handle:= >= 0 (as returned by [*spi_open*]).
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.spi_close(h)
+ ...
+ """
+ ext = [struct.pack("I", handle)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SPIC, 4, ext, L=1))
+
+ def spi_read(self, handle, count):
+ """
+ Reads count bytes from the SPI device.
+
+ handle:= >= 0 (as returned by [*spi_open*]).
+ count:= >0, the number of bytes to read.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of negative error code and
+ a null string.
+
+ ...
+ (b, d) = sbc.spi_read(h, 60) # read 60 bytes from handle h
+ if b == 60:
+ # process read data
+ else:
+ # error path
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("II", handle, count)]
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(self.sl, _CMD_SPIR, 8, ext, L=2))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+ def spi_write(self, handle, data):
+ """
+ Writes the data bytes to the SPI device.
+
+ handle:= >= 0 (as returned by [*spi_open*]).
+ data:= the bytes to write.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.spi_write(0, b'\\x02\\xc0\\x80') # write 3 bytes to handle 0
+
+ sbc.spi_write(0, b'defgh') # write 5 bytes to handle 0
+
+ sbc.spi_write(0, "def") # write 3 bytes to handle 0
+
+ sbc.spi_write(1, [2, 192, 128]) # write 3 bytes to handle 1
+ ...
+ """
+ # I p1 handle
+ # I p2 0
+ # I p3 len
+ ## extension ##
+ # s len data bytes
+ ext = [struct.pack("I", handle)] + [data]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SPIW, 4+len(data), ext, L=1))
+
+ def spi_xfer(self, handle, data):
+ """
+ Writes the data bytes to the SPI device,
+ returning the data bytes read from the device.
+
+ handle:= >= 0 (as returned by [*spi_open*]).
+ data:= the bytes to write.
+
+ If OK returns a list of the number of bytes read and a
+ bytearray containing the bytes.
+
+ On failure returns a list of negative error code and
+ a null string.
+
+ ...
+ (count, rx_data) = sbc.spi_xfer(h, b'\\x01\\x80\\x00')
+
+ (count, rx_data) = sbc.spi_xfer(h, [1, 128, 0])
+
+ (count, rx_data) = sbc.spi_xfer(h, b"hello")
+
+ (count, rx_data) = sbc.spi_xfer(h, "hello")
+ ...
+ """
+ # I p1 handle
+ # I p2 0
+ # I p3 len
+ ## extension ##
+ # s len data bytes
+
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ ext = [struct.pack("I", handle)] + [data]
+ with self.sl.l:
+ bytes = u2i(_lg_command_ext_nolock(
+ self.sl, _CMD_SPIX, 4+len(data), ext, L=1))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ return _u2i_list([bytes, rdata])
+
+
+ # UTILITIES
+
+ def get_sbc_name(self):
+ """
+ Returns the name of the sbc running the rgpiod daemon.
+
+ If OK returns the SBC host name.
+
+ On failure returns a null string.
+
+ ...
+ server = sbc.get_sbc_name()
+ print(server)
+ ...
+ """
+ bytes = CMD_INTERRUPTED
+ rdata = ""
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_nolock(self.sl, _CMD_SBC))
+ if bytes > 0:
+ rdata = self._rxbuf(bytes)
+ else:
+ rdata = ""
+ return rdata
+
+ def set_user(self, user="default",
+ secretsFile=os.path.expanduser("~/.lg_secret")):
+ """
+ Sets the rgpiod daemon user. The user then has the
+ associated permissions.
+
+ user:= the user to set (defaults to the default user).
+ secretsFile:= the path to the shared secret file (defaults to
+ .lg_secret in the users home directory).
+
+ If OK returns True if the user was set, False if not.
+
+ On failure returns a negative error code.
+
+ The returned value is True if permission is granted.
+
+ ...
+ if sbc.set_user("gpio"):
+ print("using user gpio permissions")
+ else:
+ print("using default permissions")
+ ...
+ """
+
+ user = user.strip()
+
+ if user == "":
+ user = "default"
+
+ secret = bytearray()
+
+ with open(secretsFile) as f:
+ for line in f:
+ l = line.split("=")
+ if len(l) == 2:
+ if l[0].strip() == user:
+ secret = bytearray(l[1].strip().encode('utf-8'))
+ break
+
+ bytes = CMD_INTERRUPTED
+
+ with self.sl.l:
+
+ salt1 = "{:015x}".format((int(time.time()*1e7))&0xfffffffffffffff)
+ ext = salt1 + '.' + user
+ bytes = u2i(_lg_command_ext_nolock(
+ self.sl, _CMD_USER, len(ext), [ext]))
+
+ if bytes < 0:
+ return bytes
+
+ salt1 = bytearray(salt1.encode('utf-8'))
+ salt2 = self._rxbuf(bytes)[:15]
+
+ pwd=""
+
+ h = hashlib.md5()
+ h.update(salt1 + secret + salt2)
+ pwd = h.hexdigest()
+
+ res = u2i(_lg_command_ext_nolock(
+ self.sl, _CMD_PASSW, len(pwd), [pwd]))
+
+ return res
+
+ def set_share_id(self, handle, share_id):
+ """
+ Starts or stops sharing of an object.
+
+ handle:= >=0
+ share_id:= >= 0, 0 stops sharing.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ Normally objects associated with a handle are only accessible
+ to the Python script which created them (and are automatically
+ deleted when the script ends).
+
+ If a non-zero share is set the object is accessible to any
+ software which knows the share (and are not automatically
+ deleted when the script ends).
+
+ ...
+ sbc.share_set(h, 23)
+ ...
+ """
+ ext = [struct.pack("II", handle, share_id)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SHRS, 8, ext, L=2))
+
+ def use_share_id(self, share_id):
+ """
+ Starts or stops sharing of an object.
+
+ share_id:= >= 0, 0 stops sharing.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ Normally objects associated with a handle are only accessible
+ to the Python script which created them (and are automatically
+ deleted when the script ends).
+
+ If a non-zero share is set the object is accessible to any
+ software which knows the share and the object handle.
+
+ ...
+ sbc.share_use(23)
+ ...
+ """
+ ext = [struct.pack("I", share_id)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_SHRU, 4, ext, L=1))
+
+ def get_internal(self, config_id):
+ """
+ Returns the value of a configuration item.
+
+ This is a privileged command. See [+Permits+].
+
+ If OK returns a list of 0 (OK) and the item's value.
+
+ On failure returns a list of negative error code and None.
+
+ config_id:= the configuration item.
+
+ ...
+ cfg = sbc.get_internal(0)
+ print(cfg)
+ ...
+ """
+ ext = [struct.pack("I", config_id)]
+ status = CMD_INTERRUPTED
+ config_value = None
+ with self.sl.l:
+ bytes = u2i(
+ _lg_command_ext_nolock(self.sl, _CMD_CGI, 4, ext, L=1))
+ if bytes > 0:
+ data = self._rxbuf(bytes)
+ config_value = struct.unpack('Q', _str(data))[0]
+ status = OKAY
+ else:
+ status = bytes
+ return _u2i_list([status, config_value])
+
+ def set_internal(self, config_id, config_value):
+ """
+ Sets the value of a sbc internal.
+
+ This is a privileged command. See [+Permits+].
+
+ config_id:= the configuration item.
+ config_value:= the value to set.
+
+ If OK returns 0.
+
+ On failure returns a negative error code.
+
+ ...
+ sbc.set_internal(0, 255)
+ cfg = sbc.get_internal()
+ print(cfg)
+ ...
+ """
+ ext = [struct.pack("QI", config_value, config_id)]
+ return _u2i(_lg_command_ext(self.sl, _CMD_CSI, 12, ext, Q=1, L=1))
+
+
+def xref():
+ """
+ baud:
+ The speed of serial communication (I2C, SPI, serial link)
+ in bits per second.
+
+ bit: 0-1
+ A value of 0 or 1.
+
+ byte_val: 0-255
+ A whole number.
+
+ config_id:
+ A number identifying a configuration item.
+
+ . .
+ CFG_ID_DEBUG_LEVEL 0
+ CFG_ID_MIN_DELAY 1
+ . .
+
+ config_value:
+ The value of a configurtion item.
+
+
+ connected:
+ True if a connection was established, False otherwise.
+
+ count:
+ The number of bytes of data to be transferred.
+
+ data:
+ Data to be transmitted, a series of bytes.
+
+ debounce_micros:
+ The debounce time in microseconds.
+
+ edge:
+ . .
+ RISING_EDGE
+ FALLING_EDGE
+ BOTH_EDGES
+ . .
+
+ eFlags:
+ Alert request flags for the GPIO.
+
+ The following values may be or'd to form the value.
+
+ . .
+ RISING_EDGE
+ FALLING_EDGE
+ BOTH_EDGES
+ . .
+
+ errnum: <0
+ Indicates an error. Use [*rgpio.error_text*] for the error text.
+
+ file_mode:
+ The mode may have the following values
+
+ . .
+ FILE_READ 1
+ FILE_WRITE 2
+ FILE_RW 3
+ . .
+
+ The following values can be or'd into the file open mode
+
+ . .
+ FILE_APPEND 4
+ FILE_CREATE 8
+ FILE_TRUNC 16
+ . .
+
+ file_name:
+ A full file path. To be accessible the path must match
+ an entry in the [files] section of the permits file.
+
+ fpattern:
+ A file path which may contain wildcards. To be accessible the path
+ must match an entry in the [files] section of the permits file.
+
+ func:
+ A user supplied callback function.
+
+ gpio:
+ The 0 based offset of a GPIO from the start of a gpiochip.
+
+ gpiochip: >= 0
+ The number of a gpiochip device.
+
+ group_bits:
+ A 64-bit value used to set the levels of a group.
+
+ Set bit x to set GPIO x of the group high.
+
+ Clear bit x to set GPIO x of the group low.
+
+ group_mask:
+ A 64-bit value used to determine which members of a group
+ should be updated.
+
+ Set bit x to update GPIO x of the group.
+
+ Clear bit x to leave GPIO x of the group unaltered.
+
+ handle: >= 0
+ A number referencing an object opened by one of the following
+
+ [*file_open*]
+ [*gpiochip_open*]
+ [*i2c_open*]
+ [*notify_open*]
+ [*serial_open*]
+ [*script_store*]
+ [*spi_open*]
+
+ host:
+ The name or IP address of the sbc running the rgpiod daemon.
+
+ i2c_address: 0-0x7F
+ The address of a device on the I2C bus.
+
+ i2c_bus: >= 0
+ An I2C bus number.
+
+ i2c_flags: 0
+ No I2C flags are currently defined.
+
+ kind: TX_PWM or TX_WAVE
+ A type of transmission.
+
+ level: 0 or 1
+ A GPIO level.
+
+ levels:
+ A list of GPIO levels.
+
+ lFlags:
+ Line modifiers for the GPIO.
+
+ The following values may be or'd to form the value.
+
+ . .
+ SET_ACTIVE_LOW
+ SET_OPEN_DRAIN
+ SET_OPEN_SOURCE
+ SET_PULL_UP
+ SET_PULL_DOWN
+ SET_PULL_NONE
+ . .
+
+ notify_handle:
+ This associates a notification with a GPIO alert.
+
+ params: 32 bit number
+ When scripts are started they can receive up to 10 parameters
+ to define their operation.
+
+ port:
+ The port used by the rgpiod daemon, defaults to 8889.
+
+ pstring:
+ The string to be passed to a [*shell*] script to be executed.
+
+ pulse_cycles: >= 0
+ The number of pulses to generate. A value of 0 means infinite.
+
+ pulse_delay:
+ The delay in microseconds before the next wave pulse.
+
+ pulse_off: >= 0
+ The off period for a pulse in microseconds.
+
+ pulse_offset: >= 0
+ The offset in microseconds from the nominal pulse start.
+
+ pulse_on: >= 0
+ The on period for a pulse in microseconds.
+
+ pulse_width: 0, 500-2500 microseconds
+ Servo pulse width
+
+ pulses:
+ pulses is a list of pulse objects. A pulse object is a container
+ class with the following members.
+
+ group_bits - the levels to set if the corresponding bit in
+ group_mask is set.
+ group_mask - a mask indicating the group GPIO to be updated.
+ pulse_delay - the delay in microseconds before the next pulse.
+
+ pwm_duty_cycle: 0-100 %
+ PWM duty cycle %
+
+ pwm_frequency: 0.1-10000 Hz
+ PWM frequency
+
+ reg: 0-255
+ An I2C device register. The usable registers depend on the
+ actual device.
+
+ script:
+ The text of a script to store on the rgpiod daemon.
+
+ secretsFile:
+ The file containing the shared secret for a user. If the shared
+ secret for a user matches that known by the rgpiod daemon the user can
+ "log in" to the daemon.
+
+ seek_from: 0-2
+ Direction to seek for [*file_seek*].
+
+ . .
+ FROM_START=0
+ FROM_CURRENT=1
+ FROM_END=2
+ . .
+
+ seek_offset:
+ The number of bytes to move forward (positive) or backwards
+ (negative) from the seek position (start, current, or end of file).
+
+ ser_flags: 32 bit
+ No serial flags are currently defined.
+
+ servo_frequency:: 40-500 Hz
+ Servo pulse frequency
+
+ share_id:
+ Objects created with a non-zero share_id are persistent and may be
+ used by other software which knows the share_id.
+
+ shellscr:
+ The name of a shell script. The script must exist
+ in the cgi directory of the rgpiod daemon's configuration
+ directory and must be executable.
+
+ show_errors:
+ Controls the display of rgpiod daemon connection failures.
+ The default of True prints the probable failure reasons to
+ standard output.
+
+ spi_channel: >= 0
+ A SPI channel.
+
+ spi_device: >= 0
+ A SPI device.
+
+ spi_flags: 32 bit
+ See [*spi_open*].
+
+ tty:
+ A serial device, e.g. /dev/ttyAMA0, /dev/ttyUSB0
+
+ uint32:
+ An unsigned 32 bit number.
+
+ user:
+ A name known by the rgpiod daemon and associated with a set of user
+ permissions.
+
+ watchdog_micros:
+ The watchdog time in microseconds.
+
+ word_val: 0-65535
+ A whole number.
+ """
+ pass
+
diff --git a/templates/index.template.html b/templates/index.template.html
index cbb0d9d..43bd495 100644
--- a/templates/index.template.html
+++ b/templates/index.template.html
@@ -6,7 +6,7 @@
Relays
{% for relay in relays %}
- - {{ relay.name }}
+ - {{ relay.name }} - {{ relay.state }}
{% endfor %}
{% endif %}
diff --git a/templates/relay-create.template.html b/templates/relay-create.template.html
index 5fa4ddf..fe8ec23 100644
--- a/templates/relay-create.template.html
+++ b/templates/relay-create.template.html
@@ -2,7 +2,7 @@
Relay Creation