+++ /dev/null
-#!/usr/bin/env python3
-# Copyright (C) 2025 Bdale Garbee <bdale@gag.com>. GPLv3+
-
-import os
-import cherrypy
-import plotly.express as px
-import gpiod
-import iio
-import signal
-import sys
-import time
-
-# configuration for each channel on ADS8688
-OFFSET = "0"
-SCALE = "0.078127104"
-VOLTAGES = ['voltage0', 'voltage1', 'voltage2', 'voltage3', 'voltage4', 'voltage5', 'voltage6', 'voltage7']
-
-file_path = os.getcwd()
-from gpiod.line import Direction, Value
-
-def set_line_values(chip_path, line_values):
- value_str = {Value.ACTIVE: "Active", Value.INACTIVE: "Inactive"}
-
- request = gpiod.request_lines(
- chip_path,
- consumer=sys.argv[0],
- config={
- tuple(line_values.keys()): gpiod.LineSettings(direction=Direction.OUTPUT)
- },
- )
- request.set_values(line_values)
-
-def handler(signum, frame):
- set_line_values(
- "/dev/gpiochip0",
- { 4: Value.INACTIVE, # indicate 'not ready' to LPC
- 16: Value.INACTIVE, # pyro off
- 17: Value.INACTIVE, # alarm b off
- 20: Value.INACTIVE, # turn continuity LED off
- 21: Value.INACTIVE, # turn armed LED off
- 27: Value.INACTIVE # alarm a off
- }
- )
- sys.exit(0)
-
-# having systemd use SIGINT to avoid CherryPy consuming the kill signal
-signal.signal(signal.SIGINT, handler)
-
-class Root(object):
- @cherrypy.expose
-
- # define what happens on default (index) page
- def index(self):
- cherrypy.log("index")
-
- # some sort of demo data
- df = px.data.gapminder().query("country=='Canada'")
-
- # constrain height to 600 to avoid having to scroll for other info?
- fig = px.line(df, x="year", y="lifeExp", \
- title='Life expectancy in Canada', height=600)
-
- # fig.to_html gives us the entire interactive graph as a python string!
- plotly_data = fig.to_html(full_html=False)
-
- # for now, just concatenate a header and a footer
- html_data = '<html> <body> <h1>QuantiMotor</h1>' + \
- plotly_data + \
- "<h2>Thats all folks!</h2></body> </html>"
-
- # give the complete html to the client
- return html_data
-
-if __name__ == '__main__':
- ctx = iio.LocalContext()
- ctrl = ctx.find_device('ads8688')
-
- # initialize hardware
- set_line_values(
- "/dev/gpiochip0",
- {25: Value.ACTIVE, # take ADS8688 out of reset
- 4: Value.ACTIVE, # indicate 'ready' to LPC
- 16: Value.INACTIVE, # pyro off
- 17: Value.INACTIVE, # alarm b off
- 20: Value.INACTIVE, # turn continuity LED off
- 21: Value.INACTIVE, # turn armed LED off
- 27: Value.INACTIVE # alarm a off
- }
- )
-
- # configure ADC channels
- for id in VOLTAGES:
- chan = ctrl.find_channel(id)
- # must set scale before offset, so offset 0 is valid!
- chan.attrs['scale'].value = SCALE
- chan.attrs['offset'].value = OFFSET
-
- config = {
- 'global': {
- 'server.socket_host': '0.0.0.0',
- 'server.socket_port': 8080,
- },
- }
-
- cherrypy.quickstart(Root(), '/', config)
-
--- /dev/null
+#!/usr/bin/env python3
+# Copyright (C) 2025 Bdale Garbee <bdale@gag.com>. GPLv3+
+
+import os
+import cherrypy
+import plotly.express as px
+import gpiod
+import iio
+import signal
+import sys
+import time
+
+# configuration for each channel on ADS8688
+OFFSET = "0"
+SCALE = "0.078127104"
+VOLTAGES = ['voltage0', 'voltage1', 'voltage2', 'voltage3', 'voltage4', 'voltage5', 'voltage6', 'voltage7']
+
+file_path = os.getcwd()
+from gpiod.line import Direction, Value
+
+def set_line_values(chip_path, line_values):
+ value_str = {Value.ACTIVE: "Active", Value.INACTIVE: "Inactive"}
+
+ request = gpiod.request_lines(
+ chip_path,
+ consumer=sys.argv[0],
+ config={
+ tuple(line_values.keys()): gpiod.LineSettings(direction=Direction.OUTPUT)
+ },
+ )
+ request.set_values(line_values)
+
+def handler(signum, frame):
+ set_line_values(
+ "/dev/gpiochip0",
+ { 4: Value.INACTIVE, # indicate 'not ready' to LPC
+ 16: Value.INACTIVE, # pyro off
+ 17: Value.INACTIVE, # alarm b off
+ 20: Value.INACTIVE, # turn continuity LED off
+ 21: Value.INACTIVE, # turn armed LED off
+ 27: Value.INACTIVE # alarm a off
+ }
+ )
+ sys.exit(0)
+
+# having systemd use SIGINT to avoid CherryPy consuming the kill signal
+signal.signal(signal.SIGINT, handler)
+
+class Root(object):
+ @cherrypy.expose
+
+ # define what happens on default (index) page
+ def index(self):
+ cherrypy.log("index")
+
+ # some sort of demo data
+ df = px.data.gapminder().query("country=='Canada'")
+
+ # constrain height to 600 to avoid having to scroll for other info?
+ fig = px.line(df, x="year", y="lifeExp", \
+ title='Life expectancy in Canada', height=600)
+
+ # fig.to_html gives us the entire interactive graph as a python string!
+ plotly_data = fig.to_html(full_html=False)
+
+ # for now, just concatenate a header and a footer
+ html_data = '<html> <body> <h1>QuantiMotor</h1>' + \
+ plotly_data + \
+ "<h2>Thats all folks!</h2></body> </html>"
+
+ # give the complete html to the client
+ return html_data
+
+if __name__ == '__main__':
+ ctx = iio.LocalContext()
+ ctrl = ctx.find_device('ads8688')
+
+ # initialize hardware
+ set_line_values(
+ "/dev/gpiochip0",
+ {25: Value.ACTIVE, # take ADS8688 out of reset
+ 4: Value.ACTIVE, # indicate 'ready' to LPC
+ 16: Value.INACTIVE, # pyro off
+ 17: Value.INACTIVE, # alarm b off
+ 20: Value.INACTIVE, # turn continuity LED off
+ 21: Value.INACTIVE, # turn armed LED off
+ 27: Value.INACTIVE # alarm a off
+ }
+ )
+
+ # configure ADC channels
+ for id in VOLTAGES:
+ chan = ctrl.find_channel(id)
+ # must set scale before offset, so offset 0 is valid!
+ chan.attrs['scale'].value = SCALE
+ chan.attrs['offset'].value = OFFSET
+
+ config = {
+ 'global': {
+ 'server.socket_host': '0.0.0.0',
+ 'server.socket_port': 8080,
+ },
+ }
+
+ cherrypy.quickstart(Root(), '/', config)
+
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright (C) 2025 Bdale Garbee <bdale@gag.com>. GPLv3+
-
-import gpiod
-import iio
-import signal
-import sys
-import time
-
-ctx = iio.LocalContext()
-ctrl = ctx.find_device('ads8688')
-# configuration for each channel on ADS8688
-OFFSET = "0"
-SCALE = "0.078127104"
-#VOLTAGES = ['voltage0', 'voltage1', 'voltage2', 'voltage3', 'voltage4', 'voltage5', 'voltage6', 'voltage7']
-VOLTAGES = ['voltage0', 'voltage1']
-
-from gpiod.line import Direction, Value
-
-def handler(signum, frame):
- print('Ctrl+C pressed, shutting down cleanly.')
- set_line_values(
- "/dev/gpiochip0",
- { 4: Value.INACTIVE, # indicate 'not ready' to LPC
- 16: Value.INACTIVE, # pyro off
- 17: Value.INACTIVE, # alarm b off
- 20: Value.INACTIVE, # turn continuity LED off
- 21: Value.INACTIVE, # turn armed LED off
- 25: Value.INACTIVE, # put ADS8688 in reset
- 27: Value.INACTIVE # alarm a off
- }
- )
- exit(1)
-
-signal.signal(signal.SIGINT, handler)
-
-def set_line_values(chip_path, line_values):
- value_str = {Value.ACTIVE: "Active", Value.INACTIVE: "Inactive"}
-
- request = gpiod.request_lines(
- chip_path,
- consumer=sys.argv[0],
- config={
- tuple(line_values.keys()): gpiod.LineSettings(direction=Direction.OUTPUT)
- },
- )
- request.set_values(line_values)
-
-if __name__ == "__main__":
- try:
- # initialize hardware
- set_line_values(
- "/dev/gpiochip0",
- {25: Value.ACTIVE, # take ADS8688 out of reset
- 4: Value.ACTIVE, # indicate 'ready' to LPC
- 16: Value.ACTIVE, # pyro on
- 17: Value.ACTIVE, # alarm b on
- 20: Value.ACTIVE, # turn continuity LED on
- 21: Value.INACTIVE, # turn armed LED off
- 27: Value.ACTIVE # alarm a on
- }
- )
-
- # configure ADC channels
- for id in VOLTAGES:
- chan = ctrl.find_channel(id)
- chan.attrs['offset'].value = OFFSET
- chan.attrs['scale'].value = SCALE
-
- # Iterate until ctrl/c
- while True:
- for id in VOLTAGES:
- chan = ctrl.find_channel(id)
- rawstring = chan.attrs['raw'].value
- voltage = (float(rawstring) + float(OFFSET)) * float(SCALE) / 1000
- print("{0}: {1:0.3f}, ".format( chan.id, voltage), end="")
- print()
- time.sleep(1)
-
- except OSError as ex:
- print(ex, "\nD'oh!")
-
-application.py usr/share/quantimotor
+application/* usr/share/quantimotor/ui
bcm2837-rpi-zero-2-w.dtb usr/share/quantimotor
enable_ads.py usr/share/quantimotor
quantimotor.conf etc/modules-load.d
#!/bin/sh
+# QuantiMotor application start-up script
+# Copyright 2025 Bdale Garbee <bdale@gag.com>, GPLv3
# set up gpio lines, enabling ADC and setting channel parameters
/usr/share/quantimotor/enable_ads.py
+# set up sampling trigger based on kernel high resolution timer
mkdir /sys/kernel/config/iio/triggers/hrtimer/instance1
echo instance1 > /sys/bus/iio/devices/iio\:device0/trigger/current_trigger
-
echo 1000 > /sys/bus/iio/devices/trigger0/sampling_frequency
+
+# enable all 8 ADS8688 channels for sampling on each trigger
echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage0_en
echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage1_en
echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage2_en
echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage5_en
echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage6_en
echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage7_en
+
+# enable triggered buffer sampling
echo 1 > /sys/bus/iio/devices/iio\:device0/buffer/enable
echo "quantimotor startup script ran" > /tmp/status
-/usr/share/quantimotor/application.py
+# launch the user interface
+/usr/share/quantimotor/ui/app.py
+